|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer;
namespace Microsoft.CodeAnalysis.CSharp
{
/// <summary>
/// The LocalBinderFactory is used to build up the map of all Binders within a method body, and the associated
/// CSharpSyntaxNode. To do so it traverses all the statements, handling blocks and other
/// statements that create scopes. For efficiency reasons, it does not traverse into all
/// expressions. This means that blocks within lambdas and queries are not created.
/// Blocks within lambdas are bound by their own LocalBinderFactory when they are
/// analyzed.
///
/// For reasons of lifetime management, this type is distinct from the BinderFactory
/// which also creates a map from CSharpSyntaxNode to Binder. That type owns its binders
/// and that type's lifetime is that of the compilation. Therefore we do not store
/// binders local to method bodies in that type's cache.
/// </summary>
internal sealed class LocalBinderFactory : CSharpSyntaxWalker
{
private readonly SmallDictionary<SyntaxNode, Binder> _map;
private Symbol _containingMemberOrLambda;
private Binder _enclosing;
private readonly SyntaxNode _root;
private void Visit(CSharpSyntaxNode syntax, Binder enclosing)
{
if (_enclosing == enclosing)
{
this.Visit(syntax);
}
else
{
Binder oldEnclosing = _enclosing;
_enclosing = enclosing;
this.Visit(syntax);
_enclosing = oldEnclosing;
}
}
private void VisitRankSpecifiers(TypeSyntax type, Binder enclosing)
{
type.VisitRankSpecifiers((rankSpecifier, args) =>
{
foreach (var size in rankSpecifier.Sizes)
{
if (size.Kind() != SyntaxKind.OmittedArraySizeExpression)
{
args.localBinderFactory.Visit(size, args.binder);
}
}
}, (localBinderFactory: this, binder: enclosing));
}
// Currently the types of these are restricted to only be whatever the syntax parameter is, plus any LocalFunctionStatementSyntax contained within it.
// This may change if the language is extended to allow iterator lambdas, in which case the lambda would also be returned.
// (lambdas currently throw a diagnostic in WithLambdaParametersBinder.GetIteratorElementType when a yield is used within them)
public static SmallDictionary<SyntaxNode, Binder> BuildMap(
Symbol containingMemberOrLambda,
SyntaxNode syntax,
Binder enclosing,
Action<Binder, SyntaxNode> binderUpdatedHandler = null)
{
var builder = new LocalBinderFactory(containingMemberOrLambda, syntax, enclosing);
StatementSyntax statement;
var expressionSyntax = syntax as ExpressionSyntax;
if (expressionSyntax != null)
{
enclosing = new ExpressionVariableBinder(syntax, enclosing);
if ((object)binderUpdatedHandler != null)
{
binderUpdatedHandler(enclosing, syntax);
}
builder.AddToMap(syntax, enclosing);
builder.Visit(expressionSyntax, enclosing);
}
else if (syntax.Kind() != SyntaxKind.Block && (statement = syntax as StatementSyntax) != null)
{
CSharpSyntaxNode embeddedScopeDesignator;
enclosing = builder.GetBinderForPossibleEmbeddedStatement(statement, enclosing, out embeddedScopeDesignator);
if ((object)binderUpdatedHandler != null)
{
binderUpdatedHandler(enclosing, embeddedScopeDesignator);
}
if (embeddedScopeDesignator != null)
{
builder.AddToMap(embeddedScopeDesignator, enclosing);
}
builder.Visit(statement, enclosing);
}
else
{
if ((object)binderUpdatedHandler != null)
{
binderUpdatedHandler(enclosing, null);
}
builder.Visit((CSharpSyntaxNode)syntax, enclosing);
}
return builder._map;
}
public override void VisitCompilationUnit(CompilationUnitSyntax node)
{
foreach (MemberDeclarationSyntax member in node.Members)
{
if (member.Kind() == SyntaxKind.GlobalStatement)
{
Visit(member);
}
}
}
private LocalBinderFactory(Symbol containingMemberOrLambda, SyntaxNode root, Binder enclosing)
{
Debug.Assert((object)containingMemberOrLambda != null);
Debug.Assert(containingMemberOrLambda.Kind != SymbolKind.Local && containingMemberOrLambda.Kind != SymbolKind.RangeVariable && containingMemberOrLambda.Kind != SymbolKind.Parameter);
_map = new SmallDictionary<SyntaxNode, Binder>(ReferenceEqualityComparer.Instance);
_containingMemberOrLambda = containingMemberOrLambda;
_enclosing = enclosing;
_root = root;
}
#region Starting points - these nodes contain statements
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
Visit(node.Body);
Visit(node.ExpressionBody);
}
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
Binder enclosing = new ExpressionVariableBinder(node, _enclosing);
AddToMap(node, enclosing);
Visit(node.Initializer, enclosing);
Visit(node.Body, enclosing);
Visit(node.ExpressionBody, enclosing);
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
VisitTypeDeclaration(node);
}
public override void VisitRecordDeclaration(RecordDeclarationSyntax node)
{
VisitTypeDeclaration(node);
}
private void VisitTypeDeclaration(TypeDeclarationSyntax node)
{
Debug.Assert(node.ParameterList is object);
Debug.Assert(node.Kind() is SyntaxKind.RecordDeclaration or SyntaxKind.ClassDeclaration);
Visit(node.PrimaryConstructorBaseTypeIfClass);
}
public override void VisitPrimaryConstructorBaseType(PrimaryConstructorBaseTypeSyntax node)
{
Binder enclosing = new ExpressionVariableBinder(node, _enclosing).WithAdditionalFlags(BinderFlags.ConstructorInitializer);
AddToMap(node, enclosing);
VisitConstructorInitializerArgumentList(node, node.ArgumentList, enclosing);
}
public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node)
{
Visit(node.Body);
Visit(node.ExpressionBody);
}
public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node)
{
Visit(node.Body);
Visit(node.ExpressionBody);
}
public override void VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
{
Visit(node.Body);
Visit(node.ExpressionBody);
}
public override void VisitOperatorDeclaration(OperatorDeclarationSyntax node)
{
Visit(node.Body);
Visit(node.ExpressionBody);
}
#nullable enable
public override void VisitInvocationExpression(InvocationExpressionSyntax node)
{
if (node.MayBeNameofOperator())
{
var oldEnclosing = _enclosing;
WithTypeParametersBinder? withTypeParametersBinder;
Binder? withParametersBinder;
if ((_enclosing.Flags & BinderFlags.InContextualAttributeBinder) != 0)
{
var attributeTarget = getAttributeTarget(_enclosing);
withTypeParametersBinder = getExtraWithTypeParametersBinder(_enclosing, attributeTarget);
withParametersBinder = getExtraWithParametersBinder(_enclosing, attributeTarget);
}
else
{
withTypeParametersBinder = null;
withParametersBinder = null;
}
var argumentExpression = node.ArgumentList.Arguments[0].Expression;
var possibleNameofBinder = new NameofBinder(argumentExpression, _enclosing, withTypeParametersBinder, withParametersBinder);
AddToMap(node, possibleNameofBinder);
_enclosing = possibleNameofBinder;
base.VisitInvocationExpression(node);
_enclosing = oldEnclosing;
return;
}
if (receiverIsInvocation(node, out InvocationExpressionSyntax? nested))
{
var invocations = ArrayBuilder<InvocationExpressionSyntax>.GetInstance();
invocations.Push(node);
node = nested;
while (receiverIsInvocation(node, out nested))
{
invocations.Push(node);
node = nested;
}
Visit(node.Expression);
do
{
Visit(node.ArgumentList);
}
while (invocations.TryPop(out node!));
invocations.Free();
}
else
{
Visit(node.Expression);
Visit(node.ArgumentList);
}
return;
static bool receiverIsInvocation(InvocationExpressionSyntax node, [NotNullWhen(true)] out InvocationExpressionSyntax? nested)
{
if (node.Expression is MemberAccessExpressionSyntax { Expression: InvocationExpressionSyntax receiver } && !receiver.MayBeNameofOperator())
{
nested = receiver;
return true;
}
nested = null;
return false;
}
static Symbol getAttributeTarget(Binder current)
{
Debug.Assert((current.Flags & BinderFlags.InContextualAttributeBinder) != 0);
var contextualAttributeBinder = Binder.TryGetContextualAttributeBinder(current);
Debug.Assert(contextualAttributeBinder is not null);
return contextualAttributeBinder.AttributeTarget;
}
static WithTypeParametersBinder? getExtraWithTypeParametersBinder(Binder enclosing, Symbol target)
=> target.Kind == SymbolKind.Method ? new WithMethodTypeParametersBinder((MethodSymbol)target, enclosing) : null;
// We're bringing parameters in scope inside `nameof` in attributes on methods, their type parameters and parameters.
// This also applies to local functions, lambdas, indexers and delegates.
static Binder? getExtraWithParametersBinder(Binder enclosing, Symbol target)
{
if (target is LambdaSymbol lambda)
{
// lambda parameters have some special rules around parameters named `_`
return new WithLambdaParametersBinder(lambda, enclosing);
}
var parameters = target switch
{
SourcePropertyAccessorSymbol { MethodKind: MethodKind.PropertySet } setter => getSetterParameters(setter),
MethodSymbol methodSymbol => methodSymbol.Parameters,
ParameterSymbol parameter => getAllParameters(parameter),
TypeParameterSymbol typeParameter => getMethodParametersFromTypeParameter(typeParameter),
PropertySymbol property => property.Parameters,
NamedTypeSymbol namedType when namedType.IsDelegateType() => getDelegateParameters(namedType),
_ => default
};
return parameters.IsDefaultOrEmpty
? null
: new WithParametersBinder(parameters, enclosing);
}
static ImmutableArray<ParameterSymbol> getAllParameters(ParameterSymbol parameter)
{
switch (parameter.ContainingSymbol)
{
case MethodSymbol method:
return method.Parameters;
case PropertySymbol property:
return property.Parameters;
default:
Debug.Assert(false);
return default;
}
}
static ImmutableArray<ParameterSymbol> getMethodParametersFromTypeParameter(TypeParameterSymbol typeParameter)
{
switch (typeParameter.ContainingSymbol)
{
case MethodSymbol method:
return method.Parameters;
case NamedTypeSymbol namedType when namedType.IsDelegateType():
return getDelegateParameters(namedType);
default:
Debug.Assert(false);
return default;
}
}
static ImmutableArray<ParameterSymbol> getDelegateParameters(NamedTypeSymbol delegateType)
{
Debug.Assert(delegateType.IsDelegateType());
if (delegateType.DelegateInvokeMethod is { } invokeMethod)
{
return invokeMethod.Parameters;
}
Debug.Assert(false);
return default;
}
static ImmutableArray<ParameterSymbol> getSetterParameters(SourcePropertyAccessorSymbol setter)
{
var parameters = setter.Parameters;
Debug.Assert(parameters[^1] is SynthesizedAccessorValueParameterSymbol);
return parameters.RemoveAt(parameters.Length - 1);
}
}
#nullable disable
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
{
VisitLambdaExpression(node);
}
private void VisitLambdaExpression(LambdaExpressionSyntax node)
{
// Do not descend into a lambda unless it is a root node
if (_root != node)
{
return;
}
CSharpSyntaxNode body = node.Body;
if (body.Kind() == SyntaxKind.Block)
{
VisitBlock((BlockSyntax)body);
}
else
{
var binder = new ExpressionVariableBinder(body, _enclosing);
AddToMap(body, binder);
Visit(body, binder);
}
}
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
{
VisitLambdaExpression(node);
}
public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node)
{
Symbol oldMethod = _containingMemberOrLambda;
Binder binder = _enclosing;
LocalFunctionSymbol match = FindLocalFunction(node, _enclosing);
if ((object)match != null)
{
_containingMemberOrLambda = match;
binder = match.IsGenericMethod
? new WithMethodTypeParametersBinder(match, _enclosing)
: _enclosing;
binder = binder.SetOrClearUnsafeRegionIfNecessary(node.Modifiers,
isIteratorBody: match.IsIterator);
binder = new InMethodBinder(match, binder);
}
BlockSyntax blockBody = node.Body;
if (blockBody != null)
{
Visit(blockBody, binder);
}
ArrowExpressionClauseSyntax arrowBody = node.ExpressionBody;
if (arrowBody != null)
{
Visit(arrowBody, binder);
}
_containingMemberOrLambda = oldMethod;
}
private static LocalFunctionSymbol FindLocalFunction(LocalFunctionStatementSyntax node, Binder enclosing)
{
LocalFunctionSymbol match = null;
// Don't use LookupLocalFunction because it recurses up the tree, as it
// should be defined in the directly enclosing block (see note below)
Binder possibleScopeBinder = enclosing;
while (possibleScopeBinder != null && !possibleScopeBinder.IsLocalFunctionsScopeBinder)
{
possibleScopeBinder = possibleScopeBinder.Next;
}
if (possibleScopeBinder != null)
{
foreach (var candidate in possibleScopeBinder.LocalFunctions)
{
if (candidate.GetFirstLocation() == node.Identifier.GetLocation())
{
match = candidate;
}
}
}
return match;
}
public override void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node)
{
var arrowBinder = new ExpressionVariableBinder(node, _enclosing);
AddToMap(node, arrowBinder);
Visit(node.Expression, arrowBinder);
}
public override void VisitEqualsValueClause(EqualsValueClauseSyntax node)
{
var valueBinder = new ExpressionVariableBinder(node, _enclosing);
AddToMap(node, valueBinder);
Visit(node.Value, valueBinder);
}
public override void VisitAttribute(AttributeSyntax node)
{
var attrBinder = new ExpressionVariableBinder(node, _enclosing);
AddToMap(node, attrBinder);
if (node.ArgumentList?.Arguments.Count > 0)
{
foreach (AttributeArgumentSyntax argument in node.ArgumentList.Arguments)
{
Visit(argument.Expression, attrBinder);
}
}
}
public override void VisitConstructorInitializer(ConstructorInitializerSyntax node)
{
var binder = _enclosing.WithAdditionalFlags(BinderFlags.ConstructorInitializer);
AddToMap(node, binder);
VisitConstructorInitializerArgumentList(node, node.ArgumentList, binder);
}
private void VisitConstructorInitializerArgumentList(CSharpSyntaxNode node, ArgumentListSyntax argumentList, Binder binder)
{
if (argumentList != null)
{
if (_root == node)
{
binder = new ExpressionVariableBinder(argumentList, binder);
AddToMap(argumentList, binder);
}
Visit(argumentList, binder);
}
}
public override void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node)
{
// Do not descend into a lambda unless it is a root node
if (_root != node)
{
return;
}
VisitBlock(node.Block);
}
public override void VisitGlobalStatement(GlobalStatementSyntax node)
{
Visit(node.Statement);
}
#endregion
// Top-level block has an enclosing that is not a BinderContext. All others must (so that variables can be declared).
public override void VisitBlock(BlockSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var blockBinder = new BlockBinder(_enclosing, node);
AddToMap(node, blockBinder);
// Visit all the statements inside this block
foreach (StatementSyntax statement in node.Statements)
{
Visit(statement, blockBinder);
}
}
public override void VisitUsingStatement(UsingStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var usingBinder = new UsingStatementBinder(_enclosing, node);
AddToMap(node, usingBinder);
ExpressionSyntax expressionSyntax = node.Expression;
VariableDeclarationSyntax declarationSyntax = node.Declaration;
Debug.Assert((expressionSyntax == null) ^ (declarationSyntax == null)); // Can't have both or neither.
if (expressionSyntax != null)
{
Visit(expressionSyntax, usingBinder);
}
else
{
VisitRankSpecifiers(declarationSyntax.Type, usingBinder);
foreach (VariableDeclaratorSyntax declarator in declarationSyntax.Variables)
{
Visit(declarator, usingBinder);
}
}
VisitPossibleEmbeddedStatement(node.Statement, usingBinder);
}
public override void VisitWhileStatement(WhileStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var whileBinder = new WhileBinder(_enclosing, node);
AddToMap(node, whileBinder);
Visit(node.Condition, whileBinder);
VisitPossibleEmbeddedStatement(node.Statement, whileBinder);
}
public override void VisitDoStatement(DoStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var whileBinder = new WhileBinder(_enclosing, node);
AddToMap(node, whileBinder);
Visit(node.Condition, whileBinder);
VisitPossibleEmbeddedStatement(node.Statement, whileBinder);
}
public override void VisitForStatement(ForStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
Binder binder = new ForLoopBinder(_enclosing, node);
AddToMap(node, binder);
VariableDeclarationSyntax declaration = node.Declaration;
if (declaration != null)
{
VisitRankSpecifiers(declaration.Type, binder);
foreach (VariableDeclaratorSyntax variable in declaration.Variables)
{
Visit(variable, binder);
}
}
else
{
foreach (ExpressionSyntax initializer in node.Initializers)
{
Visit(initializer, binder);
}
}
ExpressionSyntax condition = node.Condition;
if (condition != null)
{
binder = new ExpressionVariableBinder(condition, binder);
AddToMap(condition, binder);
Visit(condition, binder);
}
SeparatedSyntaxList<ExpressionSyntax> incrementors = node.Incrementors;
if (incrementors.Count > 0)
{
var incrementorsBinder = new ExpressionListVariableBinder(incrementors, binder);
AddToMap(incrementors.First(), incrementorsBinder);
foreach (ExpressionSyntax incrementor in incrementors)
{
Visit(incrementor, incrementorsBinder);
}
}
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
private void VisitCommonForEachStatement(CommonForEachStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var patternBinder = new ExpressionVariableBinder(node.Expression, _enclosing);
AddToMap(node.Expression, patternBinder);
Visit(node.Expression, patternBinder);
var binder = new ForEachLoopBinder(patternBinder, node);
AddToMap(node, binder);
if (node is ForEachVariableStatementSyntax forEachVariable && !forEachVariable.Variable.IsDeconstructionLeft())
{
// We will bind this expression for error recovery, anything could be there
Visit(forEachVariable.Variable, binder);
}
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
public override void VisitForEachStatement(ForEachStatementSyntax node)
{
VisitCommonForEachStatement(node);
}
public override void VisitForEachVariableStatement(ForEachVariableStatementSyntax node)
{
VisitCommonForEachStatement(node);
}
public override void VisitCheckedExpression(CheckedExpressionSyntax node)
{
Binder binder = _enclosing.WithCheckedOrUncheckedRegion(@checked: node.Kind() == SyntaxKind.CheckedExpression);
AddToMap(node, binder);
Visit(node.Expression, binder);
}
public override void VisitCheckedStatement(CheckedStatementSyntax node)
{
Binder binder = _enclosing.WithCheckedOrUncheckedRegion(@checked: node.Kind() == SyntaxKind.CheckedStatement);
AddToMap(node, binder);
Visit(node.Block, binder);
}
public override void VisitUnsafeStatement(UnsafeStatementSyntax node)
{
Binder binder = _enclosing.WithAdditionalFlags(BinderFlags.UnsafeRegion);
AddToMap(node, binder);
Visit(node.Block, binder); // This will create the block binder for the block.
}
public override void VisitFixedStatement(FixedStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var binder = new FixedStatementBinder(_enclosing, node);
AddToMap(node, binder);
if (node.Declaration != null)
{
VisitRankSpecifiers(node.Declaration.Type, binder);
foreach (VariableDeclaratorSyntax declarator in node.Declaration.Variables)
{
Visit(declarator, binder);
}
}
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
public override void VisitLockStatement(LockStatementSyntax node)
{
var lockBinder = new LockBinder(_enclosing, node);
AddToMap(node, lockBinder);
Visit(node.Expression, lockBinder);
StatementSyntax statement = node.Statement;
Binder statementBinder = lockBinder.WithAdditionalFlags(BinderFlags.InLockBody);
if (statementBinder != lockBinder)
{
AddToMap(statement, statementBinder);
}
VisitPossibleEmbeddedStatement(statement, statementBinder);
}
public override void VisitSwitchStatement(SwitchStatementSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
AddToMap(node.Expression, _enclosing);
Visit(node.Expression, _enclosing);
var switchBinder = SwitchBinder.Create(_enclosing, node);
AddToMap(node, switchBinder);
foreach (SwitchSectionSyntax section in node.Sections)
{
Visit(section, switchBinder);
}
}
public override void VisitSwitchSection(SwitchSectionSyntax node)
{
var patternBinder = new ExpressionVariableBinder(node, _enclosing);
AddToMap(node, patternBinder);
foreach (SwitchLabelSyntax label in node.Labels)
{
switch (label.Kind())
{
case SyntaxKind.CasePatternSwitchLabel:
{
var switchLabel = (CasePatternSwitchLabelSyntax)label;
Visit(switchLabel.Pattern, patternBinder);
if (switchLabel.WhenClause != null)
{
Visit(switchLabel.WhenClause.Condition, patternBinder);
}
break;
}
case SyntaxKind.CaseSwitchLabel:
{
var switchLabel = (CaseSwitchLabelSyntax)label;
Visit(switchLabel.Value, patternBinder);
break;
}
}
}
foreach (StatementSyntax statement in node.Statements)
{
Visit(statement, patternBinder);
}
}
public override void VisitSwitchExpression(SwitchExpressionSyntax node)
{
var switchExpressionBinder = new SwitchExpressionBinder(node, _enclosing);
AddToMap(node, switchExpressionBinder);
Visit(node.GoverningExpression, switchExpressionBinder);
foreach (SwitchExpressionArmSyntax arm in node.Arms)
{
var armScopeBinder = new ExpressionVariableBinder(arm, switchExpressionBinder);
var armBinder = new SwitchExpressionArmBinder(arm, armScopeBinder, switchExpressionBinder);
AddToMap(arm, armBinder);
Visit(arm.Pattern, armBinder);
if (arm.WhenClause != null)
{
Visit(arm.WhenClause, armBinder);
}
Visit(arm.Expression, armBinder);
}
}
public override void VisitBinaryPattern(BinaryPatternSyntax node)
{
// Users (such as ourselves) can have many, many nested binary patterns. To avoid crashing, do left recursion manually.
while (true)
{
Visit(node.Right);
if (node.Left is not BinaryPatternSyntax binOp)
{
Visit(node.Left);
break;
}
node = binOp;
}
}
public override void VisitIfStatement(IfStatementSyntax node)
{
Binder enclosing = _enclosing;
while (true)
{
Visit(node.Condition, enclosing);
VisitPossibleEmbeddedStatement(node.Statement, enclosing);
if (node.Else == null)
{
break;
}
var elseStatementSyntax = node.Else.Statement;
if (elseStatementSyntax is IfStatementSyntax ifStatementSyntax)
{
node = ifStatementSyntax;
enclosing = GetBinderForPossibleEmbeddedStatement(node, enclosing);
}
else
{
VisitPossibleEmbeddedStatement(elseStatementSyntax, enclosing);
break;
}
}
}
public override void VisitElseClause(ElseClauseSyntax node)
{
VisitPossibleEmbeddedStatement(node.Statement, _enclosing);
}
public override void VisitLabeledStatement(LabeledStatementSyntax node)
{
Visit(node.Statement, _enclosing);
}
public override void VisitTryStatement(TryStatementSyntax node)
{
if (node.Catches.Any())
{
// NOTE: We're going to cheat a bit - we know that the block is definitely going
// to get a map entry, so we don't need to worry about the WithAdditionalFlags
// binder being dropped. That is, there's no point in adding the WithAdditionalFlags
// binder to the map ourselves and having VisitBlock unconditionally overwrite it.
Visit(node.Block, _enclosing.WithAdditionalFlags(BinderFlags.InTryBlockOfTryCatch));
}
else
{
Visit(node.Block, _enclosing);
}
foreach (CatchClauseSyntax c in node.Catches)
{
Visit(c, _enclosing);
}
if (node.Finally != null)
{
Visit(node.Finally, _enclosing);
}
}
public override void VisitCatchClause(CatchClauseSyntax node)
{
Debug.Assert((object)_containingMemberOrLambda == _enclosing.ContainingMemberOrLambda);
var clauseBinder = new CatchClauseBinder(_enclosing, node);
AddToMap(node, clauseBinder);
if (node.Filter != null)
{
Binder filterBinder = clauseBinder.WithAdditionalFlags(BinderFlags.InCatchFilter);
AddToMap(node.Filter, filterBinder);
Visit(node.Filter, filterBinder);
}
Visit(node.Block, clauseBinder);
}
public override void VisitCatchFilterClause(CatchFilterClauseSyntax node)
{
Visit(node.FilterExpression);
}
public override void VisitFinallyClause(FinallyClauseSyntax node)
{
// NOTE: We're going to cheat a bit - we know that the block is definitely going
// to get a map entry, so we don't need to worry about the WithAdditionalFlags
// binder being dropped. That is, there's no point in adding the WithAdditionalFlags
// binder to the map ourselves and having VisitBlock unconditionally overwrite it.
// If this finally block is nested inside a catch block, we need to use a distinct
// binder flag so that we can detect the nesting order for error CS074: A throw
// statement with no arguments is not allowed in a finally clause that is nested inside
// the nearest enclosing catch clause.
var additionalFlags = BinderFlags.InFinallyBlock;
if (_enclosing.Flags.Includes(BinderFlags.InCatchBlock))
{
additionalFlags |= BinderFlags.InNestedFinallyBlock;
}
Visit(node.Block, _enclosing.WithAdditionalFlags(additionalFlags));
Binder finallyBinder;
Debug.Assert(_map.TryGetValue(node.Block, out finallyBinder) && finallyBinder.Flags.Includes(BinderFlags.InFinallyBlock));
}
public override void VisitYieldStatement(YieldStatementSyntax node)
{
if (node.Expression != null)
{
Visit(node.Expression, _enclosing);
}
}
public override void VisitExpressionStatement(ExpressionStatementSyntax node)
{
Visit(node.Expression, _enclosing);
}
public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
VisitRankSpecifiers(node.Declaration.Type, _enclosing);
foreach (VariableDeclaratorSyntax decl in node.Declaration.Variables)
{
Visit(decl);
}
}
public override void VisitVariableDeclarator(VariableDeclaratorSyntax node)
{
Visit(node.ArgumentList);
if (node.Initializer is { } initializer)
{
var enclosing = _enclosing;
if (node.Parent is VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { IsConst: true } })
{
enclosing = new LocalInProgressBinder(initializer, _enclosing);
AddToMap(initializer, enclosing);
}
Visit(initializer.Value, enclosing);
}
}
public override void VisitReturnStatement(ReturnStatementSyntax node)
{
if (node.Expression != null)
{
Visit(node.Expression, _enclosing);
}
}
public override void VisitThrowStatement(ThrowStatementSyntax node)
{
if (node.Expression != null)
{
Visit(node.Expression, _enclosing);
}
}
public override void VisitBinaryExpression(BinaryExpressionSyntax node)
{
// The binary operators (except ??) are left-associative, and expressions of the form
// a + b + c + d .... are relatively common in machine-generated code. The parser can handle
// creating a deep-on-the-left syntax tree no problem, and then we promptly blow the stack.
// For the purpose of creating binders, the order, in which we visit expressions, is not
// significant.
while (true)
{
Visit(node.Right);
var binOp = node.Left as BinaryExpressionSyntax;
if (binOp == null)
{
Visit(node.Left);
break;
}
node = binOp;
}
}
public override void DefaultVisit(SyntaxNode node)
{
// We should only get here for statements that don't introduce new scopes.
// Given pattern variables, they must have no subexpressions either.
// It is fine to get here for non-statements.
base.DefaultVisit(node);
}
private void AddToMap(SyntaxNode node, Binder binder)
{
// If this ever breaks, make sure that all callers of
// CanHaveAssociatedLocalBinder are in sync.
Debug.Assert(node.CanHaveAssociatedLocalBinder() ||
(node == _root && node is ExpressionSyntax));
// Cleverness: for some nodes (e.g. lock), we want to specify a binder flag that
// applies to the embedded statement, but not to the entire node. Since the
// embedded statement may or may not have its own binder, we need a way to ensure
// that the flag is set regardless. We accomplish this by adding a binder for
// the embedded statement immediately, and then overwriting it with one constructed
// in the usual way, if there is such a binder. That's why we're using update,
// rather than add, semantics.
Binder existing;
// Note that a lock statement has two outer binders (a second one for pattern variable scope)
Debug.Assert(!_map.TryGetValue(node, out existing) || existing == binder || existing == binder.Next || existing == binder.Next?.Next);
_map[node] = binder;
}
/// <summary>
/// Some statements by default do not introduce its own scope for locals.
/// For example: Expression Statement, Return Statement, etc. However,
/// when a statement like that is an embedded statement (like IfStatementSyntax.Statement),
/// then it should introduce a scope for locals declared within it.
/// Here we are detecting such statements and creating a binder that should own the scope.
/// </summary>
private Binder GetBinderForPossibleEmbeddedStatement(StatementSyntax statement, Binder enclosing, out CSharpSyntaxNode embeddedScopeDesignator)
{
switch (statement.Kind())
{
case SyntaxKind.LocalDeclarationStatement:
case SyntaxKind.LabeledStatement:
case SyntaxKind.LocalFunctionStatement:
// It is an error to have a declaration or a label in an embedded statement,
// but we still want to bind it.
case SyntaxKind.ExpressionStatement:
case SyntaxKind.LockStatement:
case SyntaxKind.IfStatement:
case SyntaxKind.YieldReturnStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.ThrowStatement:
Debug.Assert((object)_containingMemberOrLambda == enclosing.ContainingMemberOrLambda);
embeddedScopeDesignator = statement;
return new EmbeddedStatementBinder(enclosing, statement);
case SyntaxKind.SwitchStatement:
Debug.Assert((object)_containingMemberOrLambda == enclosing.ContainingMemberOrLambda);
var switchStatement = (SwitchStatementSyntax)statement;
embeddedScopeDesignator = switchStatement.Expression;
return new ExpressionVariableBinder(switchStatement.Expression, enclosing);
default:
embeddedScopeDesignator = null;
return enclosing;
}
}
private Binder GetBinderForPossibleEmbeddedStatement(StatementSyntax statement, Binder enclosing)
{
CSharpSyntaxNode embeddedScopeDesignator;
// Some statements by default do not introduce its own scope for locals.
// For example: Expression Statement, Return Statement, etc. However,
// when a statement like that is an embedded statement (like IfStatementSyntax.Statement),
// then it should introduce a scope for locals declared within it. Here we are detecting
// such statements and creating a binder that should own the scope.
enclosing = GetBinderForPossibleEmbeddedStatement(statement, enclosing, out embeddedScopeDesignator);
if (embeddedScopeDesignator != null)
{
AddToMap(embeddedScopeDesignator, enclosing);
}
return enclosing;
}
private void VisitPossibleEmbeddedStatement(StatementSyntax statement, Binder enclosing)
{
if (statement != null)
{
enclosing = GetBinderForPossibleEmbeddedStatement(statement, enclosing);
Visit(statement, enclosing);
}
}
public override void VisitQueryExpression(QueryExpressionSyntax node)
{
Visit(node.FromClause.Expression);
Visit(node.Body);
}
public override void VisitQueryBody(QueryBodySyntax node)
{
foreach (QueryClauseSyntax clause in node.Clauses)
{
if (clause.Kind() == SyntaxKind.JoinClause)
{
Visit(((JoinClauseSyntax)clause).InExpression);
}
}
Visit(node.Continuation);
}
}
}
|