Skip to content
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

Merged
merged 12 commits into from
May 17, 2021

Conversation

jcouv
Copy link
Member

@jcouv jcouv commented May 8, 2021

  • Changes in this PR: completion (recommend symbols in extended pattern chain, don't recommend "not" and such after a .) and formatting
  • Added tests for FAR. Manually validated QuickInfo, GoToDef, Rename (which share much of the same infrastructure)
  • Not sure if we want GenerateVariable to work on extended property patterns, so added a note to test plan.

A 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 as GetTypeInfo which works to get the type of the member access in a fully-typed extended property pattern is { Property.X: ... }) to get the types of the properties in the chain.

FYI @alrz

Test plan: #52468

@jcouv jcouv self-assigned this May 8, 2021

static IEnumerable<ISymbol> GetCandidatePropertiesAndFields(Document document, int position, SemanticModel semanticModel, ITypeSymbol type)
{
IEnumerable<ISymbol> members = semanticModel.LookupSymbols(position, type);
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 8, 2021

Choose a reason for hiding this comment

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

Suggested change
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();
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 8, 2021

Choose a reason for hiding this comment

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

Suggested change
var matches = members.Where(m => m.Name == name).ToArray();
var matches = members.WhereAsArray(m => m.Name == name);
``` #Resolved

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member Author

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

Copy link
Member

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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 8, 2021

Choose a reason for hiding this comment

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

Suggested change
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

Comment on lines 126 to 132
type = matches[0] switch
{
IPropertySymbol property => property.Type,
IFieldSymbol field => field.Type,
_ => null
};
return type;
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 8, 2021

Choose a reason for hiding this comment

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

Suggested change
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

@jcouv jcouv marked this pull request as ready for review May 10, 2021 05:05
@jcouv jcouv requested review from a team as code owners May 10, 2021 05:05
@jcouv jcouv requested a review from CyrusNajmabadi May 10, 2021 20:54
@jcouv
Copy link
Member Author

jcouv commented May 10, 2021

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@jcouv
Copy link
Member Author

jcouv commented May 10, 2021

Test plan: #51199

@jcouv jcouv added this to the C# 10 milestone May 11, 2021
Await state.AssertNoCompletionSession()
Assert.Contains("is { CProperty.IntProperty: 2, CProperty.ShortProperty: 3", state.GetLineTextFromCaretPosition(), StringComparison.Ordinal)
End Using
End Function
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Added CompletionOnExtendedPropertyPattern_AlreadyTestedByNestedPattern

Copy link
Member

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);
}
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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

Copy link
Member Author

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.

Copy link
Member Author

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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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

Copy link
Member Author

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);
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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 &&
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
return members.Where(m => m.CanBeReferencedByName &&
return members.WhereAsArray(m => m.CanBeReferencedByName &&
``` #Resolved

Copy link
Member

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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
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
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
_ => 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)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
if (matches.Length is 0 or > 1)
if (matches.Length != 1)
``` #Resolved


return default;

(PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) makeResult(SyntaxToken token)
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
(PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) makeResult(SyntaxToken token)
(PropertyPatternClauseSyntax, MemberAccessExpressionSyntax) MakeResult(SyntaxToken token)
``` #Resolved

{
return default;
return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default;
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

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;
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
return token.Parent.IsKind(SyntaxKind.PropertyPatternClause) ? makeResult(token) : default;
return token.Parent is { Kind: (int)SyntaxKind.PropertyPatternClause, Parent: PatternSyntax } propertyPattern ? (propertyPattern, null) : default;
``` #Resolved

Comment on lines 187 to 195
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);
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

Suggested change
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;
}
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 12, 2021

Choose a reason for hiding this comment

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

doc? #Resolved

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi left a 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")]
Copy link
Member

@CyrusNajmabadi CyrusNajmabadi May 13, 2021

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

Copy link
Member Author

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.

@jcouv jcouv requested a review from CyrusNajmabadi May 16, 2021 18:02
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.$$ }`.
Copy link
Member

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. }`
Copy link
Member

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?

Copy link
Member Author

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));
Copy link
Member

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.

Copy link
Member Author

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).

Copy link
Member

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.

Copy link
Member Author

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) :-)

Copy link
Member

@CyrusNajmabadi CyrusNajmabadi left a 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.

@jcouv jcouv merged commit 2436749 into dotnet:features/extended-property-patterns May 17, 2021
@jcouv jcouv deleted the property-ide branch May 17, 2021 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants