KEMBAR78
Fix `TypeName.GetReflectionType()` to work when the `TypeName` instance represents a generic type definition within a `GenericTypeName` by daxian-dbw · Pull Request #24985 · PowerShell/PowerShell · GitHub
Skip to content

Conversation

@daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Feb 11, 2025

PR Summary

Fix #24982

When a generic type expression is parsed for the second time, the generic type can be found in cache, and hence the symbol resolver won't go through the code path to resolve genericTypeName.TypeName, which represents the generic type definition. Therefore, unlike the first-time parsing, the cached type for genericTypeName.TypeName won't be set during the symbol resolving process for the second parsing.

However, the type resolution in TypeName.GetReflectionType() doesn't work for a generic type definition in most cases, for example, when the generic type expression is [System.Collections.Generic.List[string]], genericTypeName.TypeName will be a TypeName instance with the full name System.Collections.Generic.List which will fail to be resolved. That's why the second time you parse the same generic type expression, genericTypeName.TypeName.GetReflectionType() will return null.

Why genericTypeName.TypeName can be resolved in the first-time resolution of genericTypeName, but doesn't work when directly calling genericTypeName.TypeName.GetReflectionType()? That's because for the former, within VisitGenericTypeName(...), it uses a TypeResolutionState instance as the resolution context, which is constructed with the information from the genericTypeName (see code here). But when directly calling genericTypeName.TypeName.GetReflectionType(), it doesn't have that information and thus cannot use the same/equivalent TypeResolutionState instance as the context for type resolution.

The fix is to pass the generic argument count to TypeName when constructing a GenericTypeName instance, so that the TypeName instance can try resolving with an alternate name (i.e. typename`<n>) when the original name fails to resolve.

The changes also fix another issue:

Before: assembly-qualified name [System.Collections.Generic.List[string], System.Private.CoreLib] cannot be resolved.
After: [System.Collections.Generic.List[string], System.Private.CoreLib] can be resolved as expected.

PR Checklist

@daxian-dbw daxian-dbw requested a review from Copilot February 11, 2025 01:18
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot reviewed 1 out of 2 changed files in this pull request and generated no comments.

Files not reviewed (1)
  • test/powershell/Language/Parser/Parsing.Tests.ps1: Language not supported
Comments suppressed due to low confidence (1)

src/System.Management.Automation/engine/parser/SymbolResolver.cs:717

  • Ensure that the new behavior of setting the cached type for 'genericTypeName.TypeName' when 'GetReflectionType()' returns null is covered by tests.
((ISupportsTypeCaching)genericDefinition).CachedType = foundType.GetGenericTypeDefinition();

@daxian-dbw
Copy link
Member Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@daxian-dbw daxian-dbw added the CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log label Feb 12, 2025
@microsoft-github-policy-service microsoft-github-policy-service bot added the Review - Needed The PR is being reviewed label Feb 19, 2025
@daxian-dbw
Copy link
Member Author

@MartinGC94 Can you please also review this PR? Thank you!

@microsoft-github-policy-service microsoft-github-policy-service bot removed the Review - Needed The PR is being reviewed label Feb 20, 2025
@MartinGC94
Copy link
Contributor

It fixes my real world scenario and the fix itself seems reasonable. Thanks for the quick fix :)

@microsoft-github-policy-service microsoft-github-policy-service bot added the Review - Needed The PR is being reviewed label Mar 1, 2025
@daxian-dbw daxian-dbw changed the title Set the cached type for GenericTypeName.TypeName as needed when the generic type is found in cache Fix TypeName.GetReflectionType() to work when the TypeName instance represents a generic type definition within a GenericTypeName Mar 14, 2025
@microsoft-github-policy-service microsoft-github-policy-service bot removed the Review - Needed The PR is being reviewed label Mar 14, 2025
if (genericArgumentCount > 0 && !_name.Contains('`'))
{
_genericArgumentCount = genericArgumentCount;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, we expect a constructor to throw an exception if the argument is unexpected. But here we perform a correction of the argument. It's amazing. I'm afraid this may provoke accidental misuse of this constructor in the future. At the very least, it would be good to add a comment with examples of the scenarios we address and explaining why this correction is needed in this place.
Alternatively, we could perform a check at the call location and pass the correct arguments.
It could be even better to use a template like private static TypeName TypeName.CreateAlternativeGenericTypeName() with a comprehensive description.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This "try alternate name" thing happens in multiple places, which is common when dealing with generic type resolution. Comments were added in the GetReflectionType() to explain why we only fall back to alternate name even if genericArgumentCount is set.

As for the scenario for using this constructor, the XML comment calls it out and it's also easy to understand by looking at where it's used. So, I don't think more comments are needed.

I can add a check to throw when genericArgumentCount is a negative value.

@iSazonov iSazonov added CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log and removed CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log labels Mar 14, 2025
Copy link
Collaborator

@SeeminglyScience SeeminglyScience left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@daxian-dbw daxian-dbw merged commit ef64132 into PowerShell:master Mar 17, 2025
34 checks passed
@daxian-dbw daxian-dbw deleted the getreflectiontype branch March 17, 2025 22:40
@microsoft-github-policy-service
Copy link
Contributor

microsoft-github-policy-service bot commented Mar 17, 2025

📣 Hey @daxian-dbw, how did we do? We would love to hear your feedback with the link below! 🗣️

🔗 https://aka.ms/PSRepoFeedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CL-General Indicates that a PR should be marked as a general cmdlet change in the Change Log

Projects

Development

Successfully merging this pull request may close these issues.

GetReflectionType only returns open generic types once per session

4 participants