Extended property patterns: main IDE scenarios#53280
Extended property patterns: main IDE scenarios#53280jcouv merged 12 commits intodotnet:features/extended-property-patternsfrom
Conversation
There was a problem hiding this comment.
| IEnumerable<ISymbol> members = semanticModel.LookupSymbols(position, type); | |
| var members = semanticModel.LookupSymbols(position, type); | |
| ``` #Resolved |
There was a problem hiding this comment.
| var matches = members.Where(m => m.Name == name).ToArray(); | |
| var matches = members.WhereAsArray(m => m.Name == name); | |
| ``` #Resolved |
There was a problem hiding this comment.
why not pass in 'name' into GetCandidatePropertiesAndFields?
There was a problem hiding this comment.
why not pass in 'name' into GetCandidatePropertiesAndFields?
This local function is used twice. One of the usages doesn't have a name to filter on.
There was a problem hiding this comment.
WhereAsArray
I don't think there's such extension on IEnumerable
There was a problem hiding this comment.
this can use ImmutableArray from bottom to top :)
There was a problem hiding this comment.
| static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, int position, SemanticModel semanticModel, ITypeSymbol type) | |
| static ImmutableArray<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) | |
| ``` #Resolved |
There was a problem hiding this comment.
| type = matches[0] switch | |
| { | |
| IPropertySymbol property => property.Type, | |
| IFieldSymbol field => field.Type, | |
| _ => null | |
| }; | |
| return type; | |
| return matches[0] switch | |
| { | |
| IPropertySymbol property => property.Type, | |
| IFieldSymbol field => field.Type, | |
| _ => null | |
| }; | |
| ``` #Resolved |
…rns' into property-ide
|
/azp run |
|
Azure Pipelines successfully started running 3 pipeline(s). |
|
Test plan: #51199 |
| Await state.AssertNoCompletionSession() | ||
| Assert.Contains("is { CProperty.IntProperty: 2, CProperty.ShortProperty: 3", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal) | ||
| End Using | ||
| End Function |
There was a problem hiding this comment.
can we have a test where the user is writing a new proeprty like this above an existing pattern property? #Resolved
There was a problem hiding this comment.
Added CompletionOnExtendedPropertyPattern_AlreadyTestedByNestedPattern
There was a problem hiding this comment.
that adds a new property after an existing property. i'd like a test where we'er adding a prop-pattern before An existing property. basically like this:
_ = c is { $$CProperty: { IntProperty: 2 }
i want to make sure the following namr (or dotted name) doesn't throw off completion before this.
also a test of just this:
`_ = c is { $$CProperty
i.e. you're trying to write a dotted name before a name that is not part of a prop pattern.
| // `c is { X. }` | ||
|
|
||
| type = GetMemberAccessType(type, memberNameAccess.Expression, document, semanticModel, position); | ||
| } |
There was a problem hiding this comment.
i found this code confusing. esp passing in the type to return a type. could we just compute the type in whatever way is appropriate given the presence of a memberNameAccess or not? #Resolved
There was a problem hiding this comment.
I didn't understand the suggestion.
In the example of c is { X. } we start with the type of c (given by GetTypeInfo), then we're going to access members (just X in this case) and we'll end up with the type of c.X as the starting point for completion.
There was a problem hiding this comment.
I've attemped a change, but I'm not sure it's what you had in mind.
| var type = semanticModel.GetTypeInfo(pattern, cancellationToken).ConvertedType; | ||
| if (type == null) | ||
|
|
||
| if (memberNameAccess is not null) |
There was a problem hiding this comment.
this is a weir dname. should it be memberAccessName? #Resolved
| var alreadyTestedMembers = new HashSet<string>(propertyPatternClause.Subpatterns.Select( | ||
| // PROTOTYPE(extended-property-patterns) ExpressionColon | ||
| p => p.NameColon?.Name.Identifier.ValueText).Where(s => !string.IsNullOrEmpty(s))); | ||
| if (propertyPatternClause is not null) |
There was a problem hiding this comment.
this test seems unnecessary (you checked it for null above). #Resolved
There was a problem hiding this comment.
Thanks. It was the wrong test
| return null; | ||
| } | ||
|
|
||
| return GetMemberType(type, name, document, semanticModel, position); |
There was a problem hiding this comment.
i'm pretty confused with teh data flow here :) any way to make thsi simpler by just separating out the 'is dotted name' and 'is identifer name' cases? #Resolved
| static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) | ||
| { | ||
| var members = semanticModel.LookupSymbols(position, type); | ||
| return members.Where(m => m.CanBeReferencedByName && |
There was a problem hiding this comment.
| return members.Where(m => m.CanBeReferencedByName && | |
| return members.WhereAsArray(m => m.CanBeReferencedByName && | |
| ``` #Resolved |
There was a problem hiding this comment.
acn just be WhereAsArray :)
| }; | ||
| } | ||
|
|
||
| static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) |
There was a problem hiding this comment.
| static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) | |
| static ImmutableArray<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) | |
| ``` #Resolved |
| { | ||
| IPropertySymbol property => property.Type, | ||
| IFieldSymbol field => field.Type, | ||
| _ => null |
There was a problem hiding this comment.
| _ => null | |
| _ => throw ExceptionUtilities.Unreachable; | |
| ``` #Resolved |
| { | ||
| var members = GetCandidatePropertiesAndFields(document, semanticModel, position, type); | ||
| var matches = members.Where(m => m.Name == name).ToArray(); | ||
| if (matches.Length is 0 or > 1) |
There was a problem hiding this comment.
| if (matches.Length is 0 or > 1) | |
| if (matches.Length != 1) | |
| ``` #Resolved |
|
|
||
| return default; | ||
|
|
||
| (PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) makeResult(SyntaxToken token) |
There was a problem hiding this comment.
| (PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) makeResult(SyntaxToken token) | |
| (PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) MakeResult(SyntaxToken token) | |
| ``` #Resolved |
| if (token.IsKind(SyntaxKind.CommaToken, SyntaxKind.OpenBraceToken)) | ||
| { | ||
| return default; | ||
| return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default; |
There was a problem hiding this comment.
this is a bit weird. yo uahve the local functino. but it's called in one place. #Resolved
| if (token.IsKind(SyntaxKind.CommaToken, SyntaxKind.OpenBraceToken)) | ||
| { | ||
| return default; | ||
| return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default; |
There was a problem hiding this comment.
| return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default; | |
| return token.Parent is { Kind: (int)SyntaxKind.PropertyPatternClause, Parent: PatternSyntax } propertyPattern ? (propertyPattern, null) : default; | |
| ``` #Resolved |
| var memberNameAccess = token.Parent; | ||
| if (!memberNameAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) | ||
| || !memberNameAccess.Parent.Parent.IsKind(SyntaxKind.Subpattern) | ||
| || !memberNameAccess.Parent.Parent.Parent.IsKind(SyntaxKind.PropertyPatternClause)) | ||
| { | ||
| return default; | ||
| } | ||
|
|
||
| return ((PropertyPatternClauseSyntax)memberNameAccess.Parent.Parent.Parent, (MemberAccessExpressionSyntax)memberNameAccess); |
There was a problem hiding this comment.
| var memberNameAccess = token.Parent; | |
| if (!memberNameAccess.IsKind(SyntaxKind.SimpleMemberAccessExpression) | |
| || !memberNameAccess.Parent.Parent.IsKind(SyntaxKind.Subpattern) | |
| || !memberNameAccess.Parent.Parent.Parent.IsKind(SyntaxKind.PropertyPatternClause)) | |
| { | |
| return default; | |
| } | |
| return ((PropertyPatternClauseSyntax)memberNameAccess.Parent.Parent.Parent, (MemberAccessExpressionSyntax)memberNameAccess); | |
| var memberNameAccess = token.Parent; | |
| if (memberNameAccess is { Kind: (int)SyntaxKind.SimpleMemberAccessExpression), Parent: { Parent: SubpatternSyntax { Parent: PropertyPatternClauseSyntax propertyPatternClause } } } memberAccess)) | |
| { | |
| return (propertyPatternClause, memberAccess); | |
| } | |
| ``` #Resolved |
| if (leftToken.IsKind(SyntaxKind.DotToken)) | ||
| { | ||
| return false; | ||
| } |
CyrusNajmabadi
left a comment
There was a problem hiding this comment.
overall looking good. just some cleanup/doc requests.
| TestNormalizeStatement(actual, expected); | ||
| } | ||
|
|
||
| [Fact(Skip = "PROTOTYPE")] |
There was a problem hiding this comment.
can we link this to an actual tracking bug? #Resolved
There was a problem hiding this comment.
No need. This is going into a feature branch and all PROTOTYPE markers need to be addressed (or converted to issues) before merging the feature.
| var propertyPatternType = semanticModel.GetTypeInfo((PatternSyntax)propertyPatternClause.Parent, cancellationToken).ConvertedType; | ||
| // For simple property patterns, the type we want is the "input type" of the property pattern, ie the type of `c` in `c is { $$ }`. | ||
| // For extended property patterns, we get the type by following the chain of members that we have so far, ie | ||
| // the type of `c.Property` for `c is { Property.$$ }` and the type of `c.Property1.Property2` for `c is { Property1.Property2.$$ }`. |
|
|
||
| // We have to figure out the type of the extended property ourselves, because | ||
| // the semantic model could not provide the answer we want in incomplete syntax: | ||
| // `c is { X. }` |
There was a problem hiding this comment.
where are we landing with collection patterns? do we have tests that this shows the right thing when 'X' binds to something in scope?
There was a problem hiding this comment.
List patterns are in progress in another feature branch (Ali is currently working on binding/lowering for those in PR #53299).
I did my best here to assume the two features will eventually meet, so hopefully merging should be smooth.
| IsFieldOrReadableProperty(m) && | ||
| !m.IsImplicitlyDeclared && | ||
| !m.IsStatic && | ||
| m.IsEditorBrowsable(document.ShouldHideAdvancedMembers(), semanticModel.Compilation)); |
There was a problem hiding this comment.
i dont' think IsEditorBrowsable is right in this method (it should be at the top level). Wit hthis approach, if you had:
x.y.z. you wouldn't show anything if y was EB(never). Instead, we want to still lookup the members properly, but just not show the EB properties/fields at the end when determining the final list of members to show.
There was a problem hiding this comment.
Good catch.
Fixed but FYI I wasn't able to test (attribute is ignored in source).
There was a problem hiding this comment.
in general we just do a cross language test. write the type in VB, consume in C#. But i'm ok if you dont' want to write such a test here.
There was a problem hiding this comment.
Thanks, that worked (added test that fails before fix but passes now) :-)
CyrusNajmabadi
left a comment
There was a problem hiding this comment.
LGTM, but we should fix the EB issue.
.) and formattingA note on completion:
We parse
is { Property.$$ }as a member access expression (Property.) in a constant pattern, inside a property pattern.This is correct and will remain that way due to list-patterns. But the binding/lookup rules for
Propertyin a list-pattern are different than those in an extended-property-pattern. That means that we can't directly rely on a compiler API (such asGetTypeInfowhich works to get the type of the member access in a fully-typed extended property patternis { Property.X: ... }) to get the types of the properties in the chain.FYI @alrz
Test plan: #52468