Skip to content

Commit

Permalink
Implement pattern-matching in top-level scripts (#13999)
Browse files Browse the repository at this point in the history
* Implement pattern-matching in top-level scripts in which pattern variables become fields.
  Fixes #10603
* Move computation of better conversion from expression into `BetterConversionFromExpression`
* Update the out var specification to align with the implementation, and vice versa.
  • Loading branch information
gafter committed Sep 28, 2016
1 parent 94ef59b commit 2891628
Show file tree
Hide file tree
Showing 32 changed files with 19,561 additions and 13,160 deletions.
29 changes: 21 additions & 8 deletions docs/features/outvar.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ You may use the contextual keyword `var` for the variable's type.
The scope will be the same as for a *pattern-variable* introduced via pattern-matching.

According to Language Specification (section 7.6.7 Element access)
The argument-list of an element-access is not allowed to contain ref or out arguments.
However, due to backward compatibility, compiler overlooks this restriction during parsing
and even ignores out/ref modifiers in element access during binding.
We will enforce that language rule for out variables declarations at the syntax level.
the argument-list of an element-access (indexing expression)
does not contain ref or out arguments.
However, they are permitted by the compiler for various scenarios, for example indexers
declared in metadata that accept `out`.

Within the scope of a local variable introduced by a local-variable-declaration,
it is a compile-time error to refer to that local variable in a textual position
Expand All @@ -27,10 +27,23 @@ that precedes its declaration.
It is also an error to reference implicitly-typed (§8.5.1) out variable in the same argument list that immediately
contains its declaration.

For the purposes of overload resolution (see sections 7.5.3.2 Better function member and 7.5.3.3 Better conversion from expression),
neither conversion is considered better when corresponding argument is an implicitly-typed out variable declaration.
Once overload resolution succeeds, the type of implicitly-typed out variable is set to be equal to the type of the
corresponding parameter in the signature of the method.
Overload resolution is modified as follows:

We add a new conversion:

> There is a *conversion from expression* from an implicitly-typed out variable declaration to every type.
Also

> The type of an explicitly-typed out variable argument is the declared type.
and

> An implicitly-typed out variable argument has no type.
Neither conversion from expression is better when the argument is an implicitly-typed out variable declaration. (this needs to be woven into the form of the specification)

The type of an implicitly-typed out variable is the type of the corresponding parameter in the signature of the method.

The new syntax node `DeclarationExpressionSyntax` is added to represent the declaration in an out var argument.

Expand Down
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 @@ -1229,26 +1229,16 @@ private BetterResult BetterFunctionMember<TMember>(
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);
}
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 @@ -1802,6 +1792,14 @@ private BetterResult BetterConversionFromExpression(BoundExpression node, TypeSy

var lambdaOpt = node as UnboundLambda;

var nodeKind = node.Kind;
if (nodeKind == BoundKind.OutVariablePendingInference || nodeKind == BoundKind.OutDeconstructVarPendingInference)
{
// Neither conversion from expression is better when the argument is an implicitly-typed out variable declaration.
okToDowngradeToNeither = false;
return BetterResult.Neither;
}

// Given an implicit conversion C1 that converts from an expression E to a type T1,
// and an implicit conversion C2 that converts from an expression E to a type T2,
// C1 is a better conversion than C2 if E does not exactly match T2 and one of the following holds:
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"/>
<!-- 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
Loading

0 comments on commit 2891628

Please sign in to comment.