-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extended property patterns: main IDE scenarios #53280
Extended property patterns: main IDE scenarios #53280
Conversation
|
||
static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, int position, SemanticModel semanticModel, ITypeSymbol type) | ||
{ | ||
IEnumerable<ISymbol> members = semanticModel.LookupSymbols(position, type); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IEnumerable<ISymbol> members = semanticModel.LookupSymbols(position, type); | |
var members = semanticModel.LookupSymbols(position, type); | |
``` #Resolved |
static ITypeSymbol GetMemberType(ITypeSymbol type, string name, Document document, SemanticModel semanticModel, int position) | ||
{ | ||
var members = GetCandidatePropertiesAndFields(document, position, semanticModel, type); | ||
var matches = members.Where(m => m.Name == name).ToArray(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not pass in 'name' into GetCandidatePropertiesAndFields?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WhereAsArray
I don't think there's such extension on IEnumerable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can use ImmutableArray from bottom to top :)
return type; | ||
} | ||
|
||
static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, int position, SemanticModel semanticModel, ITypeSymbol type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 |
type = matches[0] switch | ||
{ | ||
IPropertySymbol property => property.Type, | ||
IFieldSymbol field => field.Type, | ||
_ => null | ||
}; | ||
return type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added CompletionOnExtendedPropertyPattern_AlreadyTestedByNestedPattern
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this test seems unnecessary (you checked it for null above). #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. It was the wrong test
return null; | ||
} | ||
|
||
return GetMemberType(type, name, document, semanticModel, position); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return members.Where(m => m.CanBeReferencedByName && | |
return members.WhereAsArray(m => m.CanBeReferencedByName && | |
``` #Resolved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
acn just be WhereAsArray :)
}; | ||
} | ||
|
||
static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, SemanticModel semanticModel, int position, ITypeSymbol type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_ => 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) makeResult(SyntaxToken token) | |
(PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) MakeResult(SyntaxToken token) | |
``` #Resolved |
{ | ||
return default; | ||
return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is a bit weird. yo uahve the local functino. but it's called in one place. #Resolved
{ | ||
return default; | ||
return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doc? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
overall looking good. just some cleanup/doc requests.
@@ -942,6 +942,16 @@ public void TestNormalizeBlockAnonymousFunctions(string actual, string expected) | |||
TestNormalizeStatement(actual, expected); | |||
} | |||
|
|||
[Fact(Skip = "PROTOTYPE")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we link this to an actual tracking bug? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
...tures/CSharp/Portable/Completion/CompletionProviders/PropertySubPatternCompletionProvider.cs
Show resolved
Hide resolved
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.$$ }`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
|
||
// 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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch.
Fixed but FYI I wasn't able to test (attribute is ignored in source).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that worked (added test that fails before fix but passes now) :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
Property
in 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 asGetTypeInfo
which 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