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

Implement SymbolInfo for the property name of a property pattern #9233

Merged
merged 2 commits into from
Feb 28, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 38 additions & 21 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ private BoundPattern BindRecursivePattern(RecursivePatternSyntax node, BoundExpr
// use a dedicated bound node for that form.
var properties = correspondingMembers;
var boundPatterns = BindRecursiveSubPropertyPatterns(node, properties, type, diagnostics);
return new BoundPropertyPattern(node, type, boundPatterns, properties, hasErrors: hasErrors);
var builder = ArrayBuilder<BoundSubPropertyPattern>.GetInstance(properties.Length);
for (int i = 0; i < properties.Length; i++)
{
builder.Add(new BoundSubPropertyPattern(node.PatternList.SubPatterns[i], properties[i].GetTypeOrReturnType(), properties[i], LookupResultKind.Empty, boundPatterns[i], hasErrors));
}

return new BoundPropertyPattern(node, type, builder.ToImmutableAndFree(), hasErrors: hasErrors);
}

private ImmutableArray<BoundPattern> BindRecursiveSubPropertyPatterns(RecursivePatternSyntax node, ImmutableArray<Symbol> properties, NamedTypeSymbol type, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -162,21 +168,20 @@ private BoundPattern BindPropertyPattern(PropertyPatternSyntax node, BoundExpres
{
var type = (NamedTypeSymbol)this.BindType(node.Type, diagnostics);
hasErrors = hasErrors || CheckValidPatternType(node.Type, operand, operandType, type, false, diagnostics);
var properties = ArrayBuilder<Symbol>.GetInstance();
var boundPatterns = BindSubPropertyPatterns(node, properties, type, diagnostics);
hasErrors |= properties.Count != boundPatterns.Length;
return new BoundPropertyPattern(node, type, boundPatterns, properties.ToImmutableAndFree(), hasErrors: hasErrors);
var boundPatterns = BindSubPropertyPatterns(node, type, diagnostics);
return new BoundPropertyPattern(node, type, boundPatterns, hasErrors: hasErrors);
}

private ImmutableArray<BoundPattern> BindSubPropertyPatterns(PropertyPatternSyntax node, ArrayBuilder<Symbol> properties, TypeSymbol type, DiagnosticBag diagnostics)
private ImmutableArray<BoundSubPropertyPattern> BindSubPropertyPatterns(PropertyPatternSyntax node, TypeSymbol type, DiagnosticBag diagnostics)
{
var boundPatternsBuilder = ArrayBuilder<BoundPattern>.GetInstance();
var result = ArrayBuilder<BoundSubPropertyPattern>.GetInstance();
foreach (var syntax in node.PatternList.SubPatterns)
{
var propName = syntax.Left;
BoundPattern pattern;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Symbol property = FindPropertyByName(type, propName, ref useSiteDiagnostics);
LookupResultKind resultKind;
Symbol property = FindPropertyOrFieldByName(type, propName, out resultKind, ref useSiteDiagnostics);
if ((object)property != null)
{
bool hasErrors = false;
Expand All @@ -190,39 +195,51 @@ private ImmutableArray<BoundPattern> BindSubPropertyPatterns(PropertyPatternSynt
diagnostics.Add(node, useSiteDiagnostics);
}

properties.Add(property);
pattern = this.BindPattern(syntax.Pattern, null, property.GetTypeOrReturnType(), hasErrors, diagnostics);
var propertyType = property.GetTypeOrReturnType();
pattern = this.BindPattern(syntax.Pattern, null, propertyType, hasErrors, diagnostics);
result.Add(new BoundSubPropertyPattern(syntax, propertyType, property, resultKind, pattern, hasErrors));
}
else
{
Error(diagnostics, ErrorCode.ERR_NoSuchMember, propName, type, propName.Identifier.ValueText);
Error(diagnostics, ErrorCode.ERR_NoSuchMember, propName, type, propName.ValueText);
pattern = new BoundWildcardPattern(node, hasErrors: true);
result.Add(new BoundSubPropertyPattern(syntax, CreateErrorType(), null, resultKind, pattern, true));
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not use UnknownResultType singleton for the error type 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.

The binder never does so. This is the pattern currently used throughout the binder. We can consider changing that design, but I'd rather be consistent until we make a decision to do things differently.

Copy link
Contributor

Choose a reason for hiding this comment

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

I see no point in allocating a new object every time when it doesn't carry any additional information (I am referring to the empty argument list).

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 agree that it would be valuable to review this design issue in the context of the binder as a whole. I do not want to start that review by making a change in this PR that is inconsistent with the existing design.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough

}

boundPatternsBuilder.Add(pattern);
}

return boundPatternsBuilder.ToImmutableAndFree();
return result.ToImmutableAndFree();
}

private Symbol FindPropertyByName(TypeSymbol type, IdentifierNameSyntax name, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
private Symbol FindPropertyOrFieldByName(TypeSymbol type, SyntaxToken name, out LookupResultKind resultKind, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
var symbols = ArrayBuilder<Symbol>.GetInstance();
var result = LookupResult.GetInstance();
this.LookupMembersWithFallback(result, type, name.Identifier.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics);
var lookupResult = LookupResult.GetInstance();
this.LookupMembersWithFallback(lookupResult, type, name.ValueText, arity: 0, useSiteDiagnostics: ref useSiteDiagnostics);
resultKind = lookupResult.Kind;
Symbol result = null;

if (result.IsMultiViable)
if (lookupResult.IsMultiViable)
{
foreach (var symbol in result.Symbols)
foreach (var symbol in lookupResult.Symbols)
Copy link
Contributor

Choose a reason for hiding this comment

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

A property can be overloaded, it is not clear why it is a right thing to grab the first one. Perhaps we should grab the first one that doesn't have parameters?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can even have more than one with no parameters due to multiple interface inheritance. This code should probably defer to lookup code elsewhere to do the disambiguation, and then give an error if the result isn't as expected.

Filed a language issue as #9255.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, reworked this code to report ambiguous if there is more than one property.

{
if (symbol.Kind == SymbolKind.Property || symbol.Kind == SymbolKind.Field)
{
return symbol;
if (result != null && symbol != result)
{
resultKind = LookupResultKind.Ambiguous;
result = null;
break;
}
else
{
result = symbol;
}
}
}
}

return null;
lookupResult.Free();
Copy link
Contributor

Choose a reason for hiding this comment

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

It looks like we are leaking lookupResult when we are returning from inside of the loop above.

Copy link
Member Author

Choose a reason for hiding this comment

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

True. Should probably either be freed there too or done in a try-finally.

return result;
}

private BoundPattern BindConstantPattern(ConstantPatternSyntax node, BoundExpression operand, TypeSymbol operandType, bool hasErrors, DiagnosticBag diagnostics)
Expand Down
12 changes: 9 additions & 3 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1446,10 +1446,16 @@

<Node Name="BoundPropertyPattern" Base="BoundPattern">
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
<Field Name="Patterns" Type="ImmutableArray&lt;BoundPattern&gt;" Null="disallow"/>
<Field Name="Properties" Type="ImmutableArray&lt;Symbol&gt;" Null="disallow"/>
<Field Name="Subpatterns" Type="ImmutableArray&lt;BoundSubPropertyPattern&gt;" Null="disallow"/>
</Node>


<Node Name="BoundSubPropertyPattern" Base="BoundNode">
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
<Field Name="Property" Type="Symbol" Null="allow"/>
<Field Name="ResultKind" Type="LookupResultKind"/>
<Field Name="Pattern" Type="BoundPattern" Null="disallow"/>
</Node>

<Node Name="BoundRecursivePattern" Base="BoundPattern">
<Field Name="Type" Type="TypeSymbol" Null="disallow"/>
<Field Name="IsOperator" Type="MethodSymbol" Null="allow"/>
Expand Down
16 changes: 16 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,22 @@ public static Conversion ClassifyConversion(this Compilation compilation, ITypeS
}
}

/// <summary>
/// Gets the symbol information for the property of a sub-property pattern.
/// </summary>
public static SymbolInfo GetSymbolInfo(this SemanticModel semanticModel, SubPropertyPatternSyntax node, CancellationToken cancellationToken = default(CancellationToken))
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure it is appropriate to support GetSymbolInfo for SubPropertyPatternSyntax. The syntax node doesn't represent a reference to a symbol, its child node does.

Copy link
Member Author

Choose a reason for hiding this comment

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

That appears to be the current pattern we use in SemanticModel (e.g. it is done this way for constructor initializer and attribute). You can also get the symbol from the Left child node.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see a similarity, ConstructorInitializerSyntax actually represents a constructor reference. Also, I believe SymbolInfo cannot be requested for a token and base/this are tokens. So, there is only one node that can be used to get information about the constructor used. Similar with an attribute, it represents a constructor reference.
I would say that SubPropertyPatternSyntax node is somewhat similar to a member initialization withing object initializer, which is represented by an assignment expression node. I would be surprised if GetSymbolInfo returned the target property given the assignment node. One actually have to pass the Left to get this information.

Copy link
Contributor

Choose a reason for hiding this comment

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

To clarify my position, I think that, given SubPropertyPatternSyntax node, one should pass SubPropertyPatternSyntax.Left to get SymbolInfo for the target property or a field. If SubPropertyPatternSyntax node itself is passed, SymbolInfo.None should be returned.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK, I'm changing SubPropertyPatternSyntax.Left to a SyntaxToken.

{
var csmodel = semanticModel as CSharpSemanticModel;
if (csmodel != null)
{
return csmodel.GetSymbolInfo(node, cancellationToken);
}
else
{
return SymbolInfo.None;
}
}

/// <summary>
/// Returns what symbol(s), if any, the given expression syntax bound to in the program.
///
Expand Down
11 changes: 11 additions & 0 deletions src/Compilers/CSharp/Portable/Compilation/CSharpSemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,11 @@ internal virtual IOperation GetOperationWorker(CSharpSyntaxNode node, GetOperati
/// </summary>
public abstract SymbolInfo GetSymbolInfo(SelectOrGroupClauseSyntax node, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Gets the symbol information for the property of a sub-property pattern.
/// </summary>
public abstract SymbolInfo GetSymbolInfo(SubPropertyPatternSyntax node, CancellationToken cancellationToken = default(CancellationToken));

/// <summary>
/// Returns what symbol(s), if any, the given expression syntax bound to in the program.
///
Expand Down Expand Up @@ -4346,6 +4351,12 @@ private SymbolInfo GetSymbolInfoFromNode(SyntaxNode node, CancellationToken canc
return this.GetSymbolInfo(orderingSyntax, cancellationToken);
}

var subPropertyPattern = node as SubPropertyPatternSyntax;
if (subPropertyPattern != null)
{
return this.GetSymbolInfo(subPropertyPattern, cancellationToken);
}

return SymbolInfo.None;
}

Expand Down
20 changes: 19 additions & 1 deletion src/Compilers/CSharp/Portable/Compilation/MemberSemanticModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,23 @@ internal override Optional<object> GetConstantValueWorker(CSharpSyntaxNode node,
return GetTypeInfoForQuery(bound);
}

/// <summary>
/// Gets the symbol information for the property of a sub-property pattern.
/// </summary>
public override SymbolInfo GetSymbolInfo(SubPropertyPatternSyntax node, CancellationToken cancellationToken)
{
var boundNode = GetLowerBoundNode(node) as BoundSubPropertyPattern;
if (boundNode != null)
{
var property = boundNode.Property;
return new SymbolInfo(property, boundNode.ResultKind == LookupResultKind.Viable ? CandidateReason.None : boundNode.ResultKind.ToCandidateReason());
}
else
{
return default(SymbolInfo);
Copy link
Contributor

Choose a reason for hiding this comment

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

SymbolInfo.None ?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are not the same. I'd prefer to use the same pattern as is currently used elsewhere in MemberSemanticModel.cs.

Copy link
Contributor

Choose a reason for hiding this comment

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

See line 919 in this file.

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'm aware of the existing inconsistency - there is that one place where we do it differently. I'm not intending to address that inconsistency now, nor do I want to add to it.

}
}

private void GetBoundNodes(CSharpSyntaxNode node, out CSharpSyntaxNode bindableNode, out BoundNode lowestBoundNode, out BoundNode highestBoundNode, out BoundNode boundParent)
{
bindableNode = this.GetBindableSyntaxNode(node);
Expand Down Expand Up @@ -1609,7 +1626,8 @@ internal protected virtual CSharpSyntaxNode GetBindableSyntaxNode(CSharpSyntaxNo
!(node is OrderingSyntax) &&
!(node is JoinIntoClauseSyntax) &&
!(node is QueryContinuationSyntax) &&
!(node is ArrowExpressionClauseSyntax))
!(node is ArrowExpressionClauseSyntax) &&
!(node is SubPropertyPatternSyntax))
{
return GetBindableSyntaxNode(parent);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,13 @@ internal override Optional<object> GetConstantValueWorker(CSharpSyntaxNode node,
return (model == null) ? default(TypeInfo) : model.GetTypeInfo(node, cancellationToken);
}

public override SymbolInfo GetSymbolInfo(SubPropertyPatternSyntax node, CancellationToken cancellationToken)
{
CheckSyntaxNode(node);
var model = this.GetMemberModel(node);
return (model == null) ? default(SymbolInfo) : model.GetSymbolInfo(node, cancellationToken);
Copy link
Contributor

Choose a reason for hiding this comment

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

SymbolInfo.None instead of default?

Copy link
Member Author

Choose a reason for hiding this comment

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

That would be different from all the other places this pattern appears in the code. I'd rather be consistent and review a possible change all at once.

}

public override IPropertySymbol GetDeclaredSymbol(AnonymousObjectMemberDeclaratorSyntax declaratorSyntax, CancellationToken cancellationToken = default(CancellationToken))
{
CheckSyntaxNode(declaratorSyntax);
Expand Down
8 changes: 4 additions & 4 deletions src/Compilers/CSharp/Portable/FlowAnalysis/DataFlowPass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1332,9 +1332,9 @@ private void AssignPatternVariables(BoundPattern pattern)
case BoundKind.PropertyPattern:
{
var pat = (BoundPropertyPattern)pattern;
foreach (var prop in pat.Patterns)
foreach (var prop in pat.Subpatterns)
{
AssignPatternVariables(prop);
AssignPatternVariables(prop.Pattern);
}
break;
}
Expand Down Expand Up @@ -1407,9 +1407,9 @@ private void CreateSlots(BoundPattern pattern)
case BoundKind.PropertyPattern:
{
var pat = (BoundPropertyPattern)pattern;
foreach (var prop in pat.Patterns)
foreach (var prop in pat.Subpatterns)
{
CreateSlots(prop);
CreateSlots(prop.Pattern);
}
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,12 +270,11 @@ public virtual void VisitPattern(BoundExpression expression, BoundPattern patter
{
// so far so good: the expression is known to match the *type* of the property pattern.
// Now check if each subpattern is irrefutable.
Debug.Assert(propPattern.Properties.Length == propPattern.Patterns.Length);
int n = propPattern.Properties.Length;
int n = propPattern.Subpatterns.Length;
for (int i = 0; i < n; i++)
{
var prop = propPattern.Properties[i];
var pat = propPattern.Patterns[i];
var prop = propPattern.Subpatterns[i].Property;
var pat = propPattern.Subpatterns[i].Pattern;
BoundExpression subExpr;
switch (prop.Kind)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ BoundExpression TranslatePattern(BoundExpression input, BoundPattern pattern)
var temp = _factory.SynthesizedLocal(pat.Type);
var matched = DeclPattern(syntax, input, temp);
input = _factory.Local(temp);
for (int i = 0; i < pat.Patterns.Length; i++)
for (int i = 0; i < pat.Subpatterns.Length; i++)
{
var subProperty = pat.Properties[i];
var subPattern = pat.Patterns[i];
var subProperty = pat.Subpatterns[i].Property;
var subPattern = pat.Subpatterns[i].Pattern;
var subExpression =
subProperty.Kind == SymbolKind.Field
? (BoundExpression)_factory.Field(input, (FieldSymbol)subProperty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ private bool IsPossibleSubPropertyPattern()

private SubPropertyPatternSyntax ParseSubPropertyPattern()
{
var name = this.ParseIdentifierName();
var name = this.EatToken(SyntaxKind.IdentifierToken);
var operandToken = this.EatToken(SyntaxKind.IsKeyword);

PatternSyntax pattern = this.CurrentToken.Kind == SyntaxKind.CommaToken ?
Expand Down
Loading