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 pattern-matching in top-level scripts #13999

Merged
merged 13 commits into from
Sep 28, 2016
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs
Original file line number Diff line number Diff line change
Expand Up @@ -725,7 +725,7 @@ private BoundExpression BindDeconstructionDeclarationVariable(
}

// Is this a field?
SourceMemberFieldSymbolFromDesignation field = LookupDeclaredField(designation);
GlobalExpressionVariable field = LookupDeclaredField(designation);

if ((object)field == null)
{
Expand Down
22 changes: 16 additions & 6 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2130,7 +2130,7 @@ private BoundExpression BindOutVariableArgument(DeclarationExpressionSyntax decl
}

// Is this a field?
SourceMemberFieldSymbolFromDesignation expressionVariableField = LookupDeclaredField(declarationExpression.VariableDesignation());
GlobalExpressionVariable expressionVariableField = LookupDeclaredField(declarationExpression.VariableDesignation());

if ((object)expressionVariableField == null)
{
Expand Down Expand Up @@ -2158,14 +2158,24 @@ private BoundExpression BindOutVariableArgument(DeclarationExpressionSyntax decl
expressionVariableField, null, LookupResultKind.Viable, fieldType);
}

internal SourceMemberFieldSymbolFromDesignation LookupDeclaredField(SingleVariableDesignationSyntax variableDesignator)
internal GlobalExpressionVariable LookupDeclaredField(SingleVariableDesignationSyntax variableDesignator)
{
foreach (Symbol member in ContainingType?.GetMembers(variableDesignator.Identifier.ValueText) ?? ImmutableArray<Symbol>.Empty)
return LookupDeclaredField(variableDesignator, variableDesignator.Identifier.ValueText);
}

internal GlobalExpressionVariable LookupDeclaredField(DeclarationPatternSyntax variable)
{
return LookupDeclaredField(variable, variable.Identifier.ValueText);
}

internal GlobalExpressionVariable LookupDeclaredField(SyntaxNode node, string identifier)
{
foreach (Symbol member in ContainingType?.GetMembers(identifier) ?? ImmutableArray<Symbol>.Empty)
{
SourceMemberFieldSymbolFromDesignation field;
GlobalExpressionVariable field;
if (member.Kind == SymbolKind.Field &&
(field = member as SourceMemberFieldSymbolFromDesignation)?.SyntaxTree == variableDesignator.SyntaxTree &&
field.SyntaxNode == variableDesignator)
(field = member as GlobalExpressionVariable)?.SyntaxTree == node.SyntaxTree &&
field.SyntaxNode == node)
{
return field;
}
Expand Down
59 changes: 31 additions & 28 deletions src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,11 +249,6 @@ private BoundPattern BindDeclarationPattern(
{
Debug.Assert(operand != null && operandType != (object)null);

if (InConstructorInitializer || InFieldInitializer)
{
Error(diagnostics, ErrorCode.ERR_ExpressionVariableInConstructorOrFieldInitializer, node);
}

var typeSyntax = node.Type;
var identifier = node.Identifier;

Expand All @@ -279,39 +274,47 @@ private BoundPattern BindDeclarationPattern(
else
{
hasErrors |= CheckValidPatternType(typeSyntax, operand, operandType, declType,
isVar: isVar, patternTypeWasInSource: true, diagnostics: diagnostics);
isVar: isVar, patternTypeWasInSource: true, diagnostics: diagnostics);
}

SourceLocalSymbol localSymbol = this.LookupLocal(identifier);

// In error scenarios with misplaced code, it is possible we can't bind the local declaration.
// This occurs through the semantic model. In that case concoct a plausible result.
if (localSymbol == (object)null)
if (localSymbol != (object)null)
{
localSymbol = SourceLocalSymbol.MakeLocal(
ContainingMemberOrLambda,
this,
false, // do not allow ref
typeSyntax,
identifier,
LocalDeclarationKind.PatternVariable);
}
if (InConstructorInitializer || InFieldInitializer)
{
Error(diagnostics, ErrorCode.ERR_ExpressionVariableInConstructorOrFieldInitializer, node);
}

localSymbol.SetType(declType);
localSymbol.SetType(declType);

// Check for variable declaration errors.
hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);
// Check for variable declaration errors.
hasErrors |= localSymbol.ScopeBinder.ValidateDeclarationNameConflictsInScope(localSymbol, diagnostics);

if (this.ContainingMemberOrLambda.Kind == SymbolKind.Method
&& ((MethodSymbol)this.ContainingMemberOrLambda).IsAsync
&& declType.IsRestrictedType()
&& !hasErrors)
if (this.ContainingMemberOrLambda.Kind == SymbolKind.Method
&& ((MethodSymbol)this.ContainingMemberOrLambda).IsAsync
&& declType.IsRestrictedType()
&& !hasErrors)
{
Error(diagnostics, ErrorCode.ERR_BadSpecialByRefLocal, typeSyntax, declType);
hasErrors = true;
}

return new BoundDeclarationPattern(node, localSymbol, boundDeclType, isVar, hasErrors);
}
else
{
Error(diagnostics, ErrorCode.ERR_BadSpecialByRefLocal, typeSyntax, declType);
hasErrors = true;
// We should have the right binder in the chain for a script or interactive, so we use the field for the pattern.
Debug.Assert(node.SyntaxTree.Options.Kind != SourceCodeKind.Regular);
GlobalExpressionVariable expressionVariableField = LookupDeclaredField(node);
DiagnosticBag tempDiagnostics = DiagnosticBag.GetInstance();
expressionVariableField.SetType(declType, tempDiagnostics);
tempDiagnostics.Free();
BoundExpression receiver = SynthesizeReceiver(node, expressionVariableField, diagnostics);
var variableAccess = new BoundFieldAccess(node, receiver, expressionVariableField, null, hasErrors);
return new BoundDeclarationPattern(node, expressionVariableField, variableAccess, boundDeclType, isVar, hasErrors);
}

return new BoundDeclarationPattern(node, localSymbol, boundDeclType, isVar, hasErrors);
}

}
}
28 changes: 24 additions & 4 deletions src/Compilers/CSharp/Portable/Binder/ExpressionVariableFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,12 @@ public override void VisitSwitchStatement(SwitchStatementSyntax node)

public override void VisitDeclarationPattern(DeclarationPatternSyntax node)
{
_localsBuilder.Add(MakePatternVariable(node, _nodeToBind));
var variable = MakePatternVariable(node, _nodeToBind);
if ((object)variable != null)
{
_localsBuilder.Add(variable);
}

base.VisitDeclarationPattern(node);
}

Expand Down Expand Up @@ -307,6 +312,15 @@ internal static void FindExpressionVariables(

protected override LocalSymbol MakePatternVariable(DeclarationPatternSyntax node, SyntaxNode nodeToBind)
{
NamedTypeSymbol container = _scopeBinder.ContainingType;

if ((object)container != null && container.IsScriptClass &&
(object)_scopeBinder.LookupDeclaredField(node) != null)
{
// This is a field declaration
return null;
}

return SourceLocalSymbol.MakeLocalSymbolWithEnclosingContext(
_scopeBinder.ContainingMemberOrLambda,
scopeBinder: _scopeBinder,
Expand Down Expand Up @@ -382,13 +396,19 @@ internal static void FindExpressionVariables(

protected override Symbol MakePatternVariable(DeclarationPatternSyntax node, SyntaxNode nodeToBind)
{
throw ExceptionUtilities.Unreachable;
return GlobalExpressionVariable.Create(
_containingType, _modifiers, node.Type,
node.Identifier.ValueText, node, node.Identifier.GetLocation(),
_containingFieldOpt, nodeToBind);
}

protected override Symbol MakeOutVariable(DeclarationExpressionSyntax node, BaseArgumentListSyntax argumentListSyntax, SyntaxNode nodeToBind)
{
return SourceMemberFieldSymbolFromDesignation.Create(_containingType, node.VariableDesignation(), node.Type(),
_modifiers, _containingFieldOpt, nodeToBind);
var designation = node.VariableDesignation();
return GlobalExpressionVariable.Create(
_containingType, _modifiers, node.Type(),
designation.Identifier.ValueText, designation, designation.Identifier.GetLocation(),
_containingFieldOpt, nodeToBind);
}

#region pool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1227,28 +1227,16 @@ private BetterResult BetterFunctionMember<TMember>(
var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2);

bool okToDowngradeToNeither;
BetterResult r;

if (argumentKind == BoundKind.OutVariablePendingInference || argumentKind == BoundKind.OutDeconstructVarPendingInference)
{
// If argument is an out variable that needs type inference,
// neither candidate is better in this argument.
r = BetterResult.Neither;
okToDowngradeToNeither = false;
}
else
{
r = BetterConversionFromExpression(arguments[i],
type1,
m1.Result.ConversionForArg(i),
refKind1,
type2,
m2.Result.ConversionForArg(i),
refKind2,
considerRefKinds,
ref useSiteDiagnostics,
out okToDowngradeToNeither);
}
var r = BetterConversionFromExpression(arguments[i],
type1,
m1.Result.ConversionForArg(i),
refKind1,
type2,
m2.Result.ConversionForArg(i),
refKind2,
considerRefKinds,
ref useSiteDiagnostics,
out okToDowngradeToNeither);

var type1Normalized = type1.NormalizeTaskTypes(Compilation);
var type2Normalized = type2.NormalizeTaskTypes(Compilation);
Expand Down Expand Up @@ -1748,11 +1736,13 @@ private BetterResult BetterConversionFromExpression(
{
okToDowngradeToNeither = false;

if (considerRefKinds)
var argumentKind = node.Kind;
if (considerRefKinds || argumentKind == BoundKind.OutVariablePendingInference || argumentKind == BoundKind.OutDeconstructVarPendingInference)
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 27, 2016

Choose a reason for hiding this comment

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

if (considerRefKinds || argumentKind == BoundKind.OutVariablePendingInference || argumentKind == BoundKind.OutDeconstructVarPendingInference) [](start = 12, length = 141)

I believe merging handling of BoundKind.OutVariablePendingInference and BoundKind.OutDeconstructVarPendingInference into this if complicates the code significantly. The behavior we want for these nodes is very simple and it is better to handle them separately, so that we can quote the spec and have an easy way to match implementation to the spec. It is nearly impossible to match the current implementation to the spec. #Closed

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 behavior we want arises from the specification for exactly matching expression. I will implement it that way, now that we have that tentative spec language.


In reply to: 80791006 [](ancestors = 80791006)

Copy link
Contributor

Choose a reason for hiding this comment

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

The behavior we want arises from the specification for exactly matching expression.

Please quote what you think the tentative specification is. I am not able to verify correctness of the change otherwise.


In reply to: 80793171 [](ancestors = 80793171,80791006)

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 will include the quote in a comment.


In reply to: 80794118 [](ancestors = 80794118,80793171,80791006)

Copy link
Member Author

Choose a reason for hiding this comment

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

There is an open issue for the spec/implementation misalignment in #14177 and an open discussion in the LDM. When it is settled I will implement and document the new spec.


In reply to: 80795408 [](ancestors = 80795408,80794118,80793171,80791006)

Copy link
Contributor

Choose a reason for hiding this comment

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

Please revert change to this file then.


In reply to: 80800555 [](ancestors = 80800555,80795408,80794118,80793171,80791006)

Copy link
Member Author

Choose a reason for hiding this comment

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

Closed the issue, as it does not occur in the ECMA draft for our next spec. I've updated the spec and the implementation to align as close as the shape of our implementation allows.


In reply to: 80800555 [](ancestors = 80800555,80795408,80794118,80793171,80791006)

{
// We may need to consider the ref kinds of the parameters while determining the better conversion from the given expression to the respective parameter types.
// This is needed for the omit ref feature for COM interop: We can pass arguments by value for ref parameters if we are calling a method within a COM imported type.
// We can reach here only if we had at least one ref omitted argument for the given call, which must be a call to a method within a COM imported type.
// It is also needed for 'out var' arguments.

// Algorithm for determining the better conversion from expression when ref kinds need to be considered is NOT provided in the C# language specification,
// see section 7.5.3.3 'Better Conversion From Expression'.
Expand All @@ -1766,9 +1756,6 @@ private BetterResult BetterConversionFromExpression(
// NOTE: gets considered while classifying conversions between parameter types when computing better conversion target in the native compiler.
// NOTE: Roslyn correctly follows the specification and ref kinds are not considered while classifying conversions between types, see method BetterConversionTarget.

Debug.Assert(refKind1 == RefKind.None || refKind1 == RefKind.Ref);
Debug.Assert(refKind2 == RefKind.None || refKind2 == RefKind.Ref);

if (refKind1 != refKind2)
{
if (refKind1 == RefKind.None)
Expand All @@ -1780,7 +1767,7 @@ private BetterResult BetterConversionFromExpression(
return conv2.Kind == ConversionKind.Identity ? BetterResult.Right : BetterResult.Neither;
}
}
else if (refKind1 == RefKind.Ref)
else if (refKind1 != RefKind.None)
{
return BetterResult.Neither;
}
Expand Down
12 changes: 8 additions & 4 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1562,12 +1562,16 @@
</AbstractNode>

<Node Name="BoundDeclarationPattern" Base="BoundPattern">
<Field Name="LocalSymbol" Type="LocalSymbol" Null="disallow"/>
<!-- Variable is a local symbol, or in the case of top-level code in scripts and interactive,
a field that is a member of the script class. -->
<Field Name="Variable" Type="Symbol" Null="disallow"/>
Copy link
Contributor

@AlekseyTs AlekseyTs Sep 27, 2016

Choose a reason for hiding this comment

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

[](start = 4, length = 54)

Please add a comment what kind of symbols could end up here. #Closed

<!-- VariableAccess is an access to the declared symbol, suitable for use
in the lowered form in either an lvalue or rvalue position. We maintain it separately
from the LocalSymbol to facilitate lowerings in which the variable is no longer a simple
local access (e.g. the expression evaluator). It is expected to be logically side-effect
free. -->
from the Symbol to facilitate lowerings in which the variable is no longer a simple
variable access (e.g. in the expression evaluator). It is expected to be logically side-effect
free. The necessity of this member is a consequence of a design issue documented in
https://github.com/dotnet/roslyn/issues/13960 . When that is fixed this field can be
removed. -->
<Field Name="VariableAccess" Type="BoundExpression" Null="disallow"/>
<Field Name="DeclaredType" Type="BoundTypeExpression" Null="allow"/>
<Field Name="IsVar" Type="bool"/>
Expand Down
10 changes: 5 additions & 5 deletions src/Compilers/CSharp/Portable/BoundTree/DecisionTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ internal DecisionTree ComputeDecisionTree()

if (defaultLabel != null)
{
Add(result, (e, t) => new DecisionTree.Guarded(_switchStatement.Expression, _switchStatement.Expression.Type, default(ImmutableArray<KeyValuePair<BoundExpression, LocalSymbol>>), defaultSection, null, defaultLabel));
Add(result, (e, t) => new DecisionTree.Guarded(_switchStatement.Expression, _switchStatement.Expression.Type, default(ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>>), defaultSection, null, defaultLabel));
}

return result;
Expand All @@ -230,13 +230,13 @@ private void AddToDecisionTree(DecisionTree decisionTree, BoundPatternSwitchLabe
case BoundKind.ConstantPattern:
{
var constantPattern = (BoundConstantPattern)pattern;
AddByValue(decisionTree, constantPattern.Value, (e, t) => new DecisionTree.Guarded(e, t, default(ImmutableArray<KeyValuePair<BoundExpression, LocalSymbol>>), _section, guard, label));
AddByValue(decisionTree, constantPattern.Value, (e, t) => new DecisionTree.Guarded(e, t, default(ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>>), _section, guard, label));
break;
}
case BoundKind.DeclarationPattern:
{
var declarationPattern = (BoundDeclarationPattern)pattern;
DecisionMaker maker = (e, t) => new DecisionTree.Guarded(e, t, ImmutableArray.Create(new KeyValuePair<BoundExpression, LocalSymbol>(e, declarationPattern.LocalSymbol)), _section, guard, label);
DecisionMaker maker = (e, t) => new DecisionTree.Guarded(e, t, ImmutableArray.Create(new KeyValuePair<BoundExpression, BoundExpression>(e, declarationPattern.VariableAccess)), _section, guard, label);
if (declarationPattern.IsVar)
{
Add(decisionTree, maker);
Expand Down Expand Up @@ -1098,7 +1098,7 @@ public class Guarded : DecisionTree
{
// A sequence of bindings to be assigned before evaluation of the guard or jump to the label.
// Each one contains the source of the assignment and the destination of the assignment, in that order.
public readonly ImmutableArray<KeyValuePair<BoundExpression, LocalSymbol>> Bindings;
public readonly ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>> Bindings;
public readonly BoundPatternSwitchSection Section;
public readonly BoundExpression Guard;
public readonly BoundPatternSwitchLabel Label;
Expand All @@ -1107,7 +1107,7 @@ public class Guarded : DecisionTree
public Guarded(
BoundExpression expression,
TypeSymbol type,
ImmutableArray<KeyValuePair<BoundExpression, LocalSymbol>> bindings,
ImmutableArray<KeyValuePair<BoundExpression, BoundExpression>> bindings,
BoundPatternSwitchSection section,
BoundExpression guard,
BoundPatternSwitchLabel label)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public BoundExpression SetInferredType(TypeSymbol type, Binder binderOpt, Diagno
return new BoundLocal(this.Syntax, local, constantValueOpt: null, type: type, hasErrors: this.HasErrors || inferenceFailed);

case SymbolKind.Field:
var field = (SourceMemberFieldSymbolFromDesignation)this.VariableSymbol;
var field = (GlobalExpressionVariable)this.VariableSymbol;
var inferenceDiagnostics = DiagnosticBag.GetInstance();
if (inferenceFailed)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private BoundExpression SetInferredType(TypeSymbol type, Binder binderOpt, Diagn
return new BoundLocal(this.Syntax, localSymbol, constantValueOpt: null, type: type, hasErrors: this.HasErrors || inferenceFailed);

case SymbolKind.Field:
var fieldSymbol = (SourceMemberFieldSymbolFromDesignation)this.VariableSymbol;
var fieldSymbol = (GlobalExpressionVariable)this.VariableSymbol;
var inferenceDiagnostics = DiagnosticBag.GetInstance();

if (inferenceFailed)
Expand Down
Loading