Skip to content

Commit

Permalink
Merge 'dotnet/features/ExpressionVariables' into 'dotnet/dev15.7.x'
Browse files Browse the repository at this point in the history
 Merge 'dotnet/features/ExpressionVariables' into 'dotnet/dev15.7.x'
  • Loading branch information
AlekseyTs committed Mar 16, 2018
2 parents b5b30da + ac0f2f3 commit 515d928
Show file tree
Hide file tree
Showing 100 changed files with 7,129 additions and 2,386 deletions.
17 changes: 17 additions & 0 deletions docs/features/ExpressionVariables.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Expression Variables
=========================

The *expression variables* feature extends the features introduced in C# 7 to permit expressions
containing expression variables (out variable declarations and declaration patterns) in field
initializers, property initializers, ctor-initializers, and query clauses.

See https://github.com/dotnet/csharplang/issues/32 and
https://github.com/dotnet/csharplang/blob/master/proposals/expression-variables-in-initializers.md
for more information.

Current state of the feature:

[X] Permit in field initializers
[X] Permit in property initializers
[ ] Permit in ctor-initializers
[X] Permit in query clauses
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@ public override Binder VisitDestructorDeclaration(DestructorDeclarationSyntax pa
// Destructors have neither parameters nor type parameters, so there's nothing special to do here.
resultBinder = VisitCore(parent.Parent);

SourceMemberMethodSymbol method = GetMethodSymbol(parent, resultBinder);
resultBinder = new InMethodBinder(method, resultBinder);

resultBinder = resultBinder.WithUnsafeRegionIfNecessary(parent.Modifiers);

binderCache.TryAdd(key, resultBinder);
Expand Down
88 changes: 61 additions & 27 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -236,35 +236,43 @@ internal BoundExpression BindValueAllowArgList(ExpressionSyntax node, Diagnostic
return CheckValue(result, valueKind, diagnostics);
}

internal BoundExpression BindVariableOrAutoPropInitializer(
internal BoundFieldEqualsValue BindFieldInitializer(
FieldSymbol field,
EqualsValueClauseSyntax initializerOpt,
RefKind refKind,
TypeSymbol varType,
DiagnosticBag diagnostics)
{
Debug.Assert((object)this.ContainingMemberOrLambda == field);

if (initializerOpt == null)
{
return null;
}

SymbolKind containingMemberOrLambdaKind = this.ContainingMemberOrLambda.Kind;
Debug.Assert(containingMemberOrLambdaKind == SymbolKind.Field || containingMemberOrLambdaKind == SymbolKind.Method);
bool isMemberInitializer = containingMemberOrLambdaKind == SymbolKind.Field;
Binder initializerBinder = isMemberInitializer ? this.GetBinder(initializerOpt) : this;

Binder initializerBinder = this.GetBinder(initializerOpt);
Debug.Assert(initializerBinder != null);

BindValueKind valueKind;
ExpressionSyntax value;
IsInitializerRefKindValid(initializerOpt, initializerOpt, refKind, diagnostics, out valueKind, out value);
var initializer = initializerBinder.BindPossibleArrayInitializer(value, varType, valueKind, diagnostics);
initializer = initializerBinder.GenerateConversionForAssignment(varType, initializer, diagnostics);
BoundExpression result = initializerBinder.BindVariableOrAutoPropInitializerValue(initializerOpt, RefKind.None,
field.GetFieldType(initializerBinder.FieldsBeingBound), diagnostics);

if (isMemberInitializer)
return new BoundFieldEqualsValue(initializerOpt, field, initializerBinder.GetDeclaredLocalsForScope(initializerOpt), result);
}

internal BoundExpression BindVariableOrAutoPropInitializerValue(
EqualsValueClauseSyntax initializerOpt,
RefKind refKind,
TypeSymbol varType,
DiagnosticBag diagnostics)
{
if (initializerOpt == null)
{
initializer = initializerBinder.WrapWithVariablesIfAny(initializerOpt, initializer);
return null;
}

BindValueKind valueKind;
ExpressionSyntax value;
IsInitializerRefKindValid(initializerOpt, initializerOpt, refKind, diagnostics, out valueKind, out value);
BoundExpression initializer = BindPossibleArrayInitializer(value, varType, valueKind, diagnostics);
initializer = GenerateConversionForAssignment(varType, initializer, diagnostics);
return initializer;
}

Expand All @@ -278,9 +286,9 @@ internal Binder CreateBinderForParameterDefaultValue(
binder);
}

internal BoundExpression BindParameterDefaultValue(
internal BoundParameterEqualsValue BindParameterDefaultValue(
EqualsValueClauseSyntax defaultValueSyntax,
TypeSymbol parameterType,
ParameterSymbol parameter,
DiagnosticBag diagnostics,
out BoundExpression valueBeforeConversion)
{
Expand All @@ -295,10 +303,11 @@ internal BoundExpression BindParameterDefaultValue(

// Always generate the conversion, even if the expression is not convertible to the given type.
// We want the erroneous conversion in the tree.
return defaultValueBinder.WrapWithVariablesIfAny(defaultValueSyntax, defaultValueBinder.GenerateConversionForAssignment(parameterType, valueBeforeConversion, diagnostics, isDefaultParameter: true));
return new BoundParameterEqualsValue(defaultValueSyntax, parameter, defaultValueBinder.GetDeclaredLocalsForScope(defaultValueSyntax),
defaultValueBinder.GenerateConversionForAssignment(parameter.Type, valueBeforeConversion, diagnostics, isDefaultParameter: true));
}

internal BoundExpression BindEnumConstantInitializer(
internal BoundFieldEqualsValue BindEnumConstantInitializer(
SourceEnumConstantSymbol symbol,
EqualsValueClauseSyntax equalsValueSyntax,
DiagnosticBag diagnostics)
Expand All @@ -308,7 +317,7 @@ internal BoundExpression BindEnumConstantInitializer(

var initializer = initializerBinder.BindValue(equalsValueSyntax.Value, diagnostics, BindValueKind.RValue);
initializer = initializerBinder.GenerateConversionForAssignment(symbol.ContainingType.EnumUnderlyingType, initializer, diagnostics);
return initializerBinder.WrapWithVariablesIfAny(equalsValueSyntax, initializer);
return new BoundFieldEqualsValue(equalsValueSyntax, symbol, initializerBinder.GetDeclaredLocalsForScope(equalsValueSyntax), initializer);
}

public BoundExpression BindExpression(ExpressionSyntax node, DiagnosticBag diagnostics)
Expand Down Expand Up @@ -2311,7 +2320,7 @@ private BoundExpression BindOutVariableDeclarationArgument(
Debug.Assert(localSymbol.DeclarationKind == LocalDeclarationKind.OutVariable);
if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType)
{
Error(diagnostics, ErrorCode.ERR_ExpressionVariableInConstructorOrFieldInitializer, declarationExpression);
CheckFeatureAvailability(declarationExpression, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics);
}

bool isConst = false;
Expand Down Expand Up @@ -3318,14 +3327,20 @@ internal BoundExpression BindConstructorInitializer(
MethodSymbol constructor,
DiagnosticBag diagnostics)
{
Binder initializerBinder = initializerArgumentListOpt == null ? this : this.GetBinder(initializerArgumentListOpt);
Debug.Assert(initializerBinder != null);

var result = initializerBinder.BindConstructorInitializerCore(initializerArgumentListOpt, constructor, diagnostics);
Binder argumentListBinder = null;

if (initializerArgumentListOpt != null)
{
result = initializerBinder.WrapWithVariablesIfAny(initializerArgumentListOpt, result);
argumentListBinder = this.GetBinder(initializerArgumentListOpt);
}

var result = (argumentListBinder ?? this).BindConstructorInitializerCore(initializerArgumentListOpt, constructor, diagnostics);

if (argumentListBinder != null)
{
// This code is reachable only for speculative SemanticModel.
Debug.Assert(argumentListBinder.IsSemanticModelBinder);
result = argumentListBinder.WrapWithVariablesIfAny(initializerArgumentListOpt, result);
}

return result;
Expand All @@ -3336,7 +3351,26 @@ private BoundExpression BindConstructorInitializerCore(
MethodSymbol constructor,
DiagnosticBag diagnostics)
{
Debug.Assert(initializerArgumentListOpt == null || this.SkipSemanticModelBinder() == this.GetBinder(initializerArgumentListOpt).SkipSemanticModelBinder());
// Either our base type is not object, or we have an initializer syntax, or both. We're going to
// need to do overload resolution on the set of constructors of the base type, either on
// the provided initializer syntax, or on an implicit ": base()" syntax.

// SPEC ERROR: The specification states that if you have the situation
// SPEC ERROR: class B { ... } class D1 : B {} then the default constructor
// SPEC ERROR: generated for D1 must call an accessible *parameterless* constructor
// SPEC ERROR: in B. However, it also states that if you have
// SPEC ERROR: class B { ... } class D2 : B { D2() {} } or
// SPEC ERROR: class B { ... } class D3 : B { D3() : base() {} } then
// SPEC ERROR: the compiler performs *overload resolution* to determine
// SPEC ERROR: which accessible constructor of B is called. Since B might have
// SPEC ERROR: a ctor with all optional parameters, overload resolution might
// SPEC ERROR: succeed even if there is no parameterless constructor. This
// SPEC ERROR: is unintentionally inconsistent, and the native compiler does not
// SPEC ERROR: implement this behavior. Rather, we should say in the spec that
// SPEC ERROR: if there is no ctor in D1, then a ctor is created for you exactly
// SPEC ERROR: as though you'd said "D1() : base() {}".
// SPEC ERROR: This is what we now do in Roslyn.

Debug.Assert((object)constructor != null);
Debug.Assert(constructor.MethodKind == MethodKind.Constructor ||
constructor.MethodKind == MethodKind.StaticConstructor); // error scenario: constructor initializer on static constructor
Expand Down
11 changes: 4 additions & 7 deletions src/Compilers/CSharp/Portable/Binder/Binder_Initializers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal static void BindRegularCSharpFieldInitializers(

parentBinder = new LocalScopeBinder(parentBinder).WithAdditionalFlagsAndContainingMemberOrLambda(BinderFlags.FieldInitializer, fieldSymbol);

BoundFieldInitializer boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics);
BoundFieldEqualsValue boundInitializer = BindFieldInitializer(parentBinder, fieldSymbol, initializerNode, diagnostics);
boundInitializers.Add(boundInitializer);
}
}
Expand Down Expand Up @@ -246,7 +246,7 @@ private static BoundInitializer BindGlobalStatement(
return new BoundGlobalStatementInitializer(statementNode, statement);
}

private static BoundFieldInitializer BindFieldInitializer(Binder binder, FieldSymbol fieldSymbol, EqualsValueClauseSyntax equalsValueClauseNode,
private static BoundFieldEqualsValue BindFieldInitializer(Binder binder, FieldSymbol fieldSymbol, EqualsValueClauseSyntax equalsValueClauseNode,
DiagnosticBag diagnostics)
{
Debug.Assert(!fieldSymbol.IsMetadataConstant);
Expand All @@ -269,17 +269,14 @@ private static BoundFieldInitializer BindFieldInitializer(Binder binder, FieldSy
}

binder = new ExecutableCodeBinder(equalsValueClauseNode, fieldSymbol, new LocalScopeBinder(binder));
var boundInitValue = binder.BindVariableOrAutoPropInitializer(equalsValueClauseNode, RefKind.None, fieldSymbol.GetFieldType(fieldsBeingBound), initializerDiagnostics);
BoundFieldEqualsValue boundInitValue = binder.BindFieldInitializer(fieldSymbol, equalsValueClauseNode, initializerDiagnostics);

if (isImplicitlyTypedField)
{
initializerDiagnostics.Free();
}

return new BoundFieldInitializer(
equalsValueClauseNode.Value, //we want the attached sequence point to indicate the value node
fieldSymbol,
boundInitValue);
return boundInitValue;
}
}
}
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ private BoundPattern BindDeclarationPattern(
{
if ((InConstructorInitializer || InFieldInitializer) && ContainingMemberOrLambda.ContainingSymbol.Kind == SymbolKind.NamedType)
{
Error(diagnostics, ErrorCode.ERR_ExpressionVariableInConstructorOrFieldInitializer, node);
CheckFeatureAvailability(node, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics);
}

localSymbol.SetType(declType);
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ private BoundBlock CreateLambdaBlockForQueryClause(ExpressionSyntax expression,
var locals = this.GetDeclaredLocalsForScope(expression);
if (locals.Any())
{
diagnostics.Add(ErrorCode.ERR_ExpressionVariableInQueryClause, locals[0].Locations[0]);
CheckFeatureAvailability(expression, MessageID.IDS_FeatureExpressionVariablesInQueriesAndInitializers, diagnostics, locals[0].Locations[0]);
}

return this.CreateBlockFromExpression(expression, locals, RefKind.None, result, expression, diagnostics);
Expand Down
74 changes: 72 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
Expand Down Expand Up @@ -520,7 +521,7 @@ private BoundStatement BindLocalFunctionStatement(LocalFunctionStatementSyntax n
{
if (ImplicitReturnIsOkay(localSymbol))
{
block = FlowAnalysisPass.AppendImplicitReturn(block, localSymbol, node.Body);
block = FlowAnalysisPass.AppendImplicitReturn(block, localSymbol);
}
else
{
Expand Down Expand Up @@ -2855,7 +2856,7 @@ private static bool IsValidExpressionBody(SyntaxNode expressionSyntax, BoundExpr
/// <summary>
/// Binds an expression-bodied member with expression e as either { return e;} or { e; }.
/// </summary>
internal BoundBlock BindExpressionBodyAsBlock(ArrowExpressionClauseSyntax expressionBody,
internal virtual BoundBlock BindExpressionBodyAsBlock(ArrowExpressionClauseSyntax expressionBody,
DiagnosticBag diagnostics)
{
Binder bodyBinder = this.GetBinder(expressionBody);
Expand Down Expand Up @@ -2901,6 +2902,75 @@ private BindValueKind GetRequiredReturnValueKind(RefKind refKind)
return requiredValueKind;
}

public virtual BoundNode BindMethodBody(CSharpSyntaxNode syntax, DiagnosticBag diagnostics)
{
switch (syntax)
{
case BaseMethodDeclarationSyntax method:
if (method.Kind() == SyntaxKind.ConstructorDeclaration)
{
return BindConstructorBody((ConstructorDeclarationSyntax)method, diagnostics);
}

return BindMethodBody(method, method.Body, method.ExpressionBody, diagnostics);

case AccessorDeclarationSyntax accessor:
return BindMethodBody(accessor, accessor.Body, accessor.ExpressionBody, diagnostics);

case ArrowExpressionClauseSyntax arrowExpression:
return BindExpressionBodyAsBlock(arrowExpression, diagnostics);

default:
throw ExceptionUtilities.UnexpectedValue(syntax.Kind());
}
}

private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, DiagnosticBag diagnostics)
{
if (constructor.Initializer == null && constructor.Body == null && constructor.ExpressionBody == null)
{
return null;
}

Binder bodyBinder = this.GetBinder(constructor);
Debug.Assert(bodyBinder != null);

// Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel
return new BoundConstructorMethodBody(constructor,
bodyBinder.GetDeclaredLocalsForScope(constructor),
constructor.Initializer == null ? null : bodyBinder.BindConstructorInitializer(constructor.Initializer, diagnostics),
constructor.Body == null ? null : (BoundBlock)bodyBinder.BindStatement(constructor.Body, diagnostics),
constructor.ExpressionBody == null ?
null :
bodyBinder.BindExpressionBodyAsBlock(constructor.ExpressionBody,
constructor.Body == null ? diagnostics : new DiagnosticBag()));
}

internal virtual BoundExpressionStatement BindConstructorInitializer(ConstructorInitializerSyntax initializer, DiagnosticBag diagnostics)
{
BoundExpression initializerInvocation = GetBinder(initializer).BindConstructorInitializer(initializer.ArgumentList, (MethodSymbol)this.ContainingMember(), diagnostics);
// Base WasCompilerGenerated state off of whether constructor is implicitly declared, this will ensure proper instrumentation.
Debug.Assert(!this.ContainingMember().IsImplicitlyDeclared);
var constructorInitializer = new BoundExpressionStatement(initializer, initializerInvocation);
Debug.Assert(initializerInvocation.HasAnyErrors || constructorInitializer.IsConstructorInitializer(), "Please keep this bound node in sync with BoundNodeExtensions.IsConstructorInitializer.");
return constructorInitializer;
}

private BoundNode BindMethodBody(CSharpSyntaxNode declaration, BlockSyntax blockBody, ArrowExpressionClauseSyntax expressionBody, DiagnosticBag diagnostics)
{
if (blockBody == null && expressionBody == null)
{
return null;
}

// Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel
return new BoundNonConstructorMethodBody(declaration,
blockBody == null ? null : (BoundBlock)BindStatement(blockBody, diagnostics),
expressionBody == null ?
null :
BindExpressionBodyAsBlock(expressionBody,
blockBody == null ? diagnostics : new DiagnosticBag()));
}

internal virtual ImmutableArray<LocalSymbol> Locals
{
Expand Down
Loading

0 comments on commit 515d928

Please sign in to comment.