// 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.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
///
/// 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
/// 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 it's 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.
///
internal sealed class LocalBinderFactory : CSharpSyntaxVisitor
{
private readonly SmallDictionary _map;
private bool _sawYield;
private readonly ArrayBuilder _methodsWithYields;
private MethodSymbol _method;
private Binder _enclosing;
private void Visit(CSharpSyntaxNode syntax, Binder enclosing)
{
if (_enclosing == enclosing)
{
this.Visit(syntax);
}
else
{
Binder oldEnclosing = _enclosing;
_enclosing = enclosing;
this.Visit(syntax);
_enclosing = oldEnclosing;
}
}
// methodsWithYields will contain all function-declaration-like CSharpSyntaxNodes with yield statements contained within them.
// 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 BuildMap(MethodSymbol method, CSharpSyntaxNode syntax, Binder enclosing, ArrayBuilder methodsWithYields)
{
var builder = new LocalBinderFactory(method, enclosing, methodsWithYields);
builder.Visit(syntax);
// the other place this is possible is in a local function
if (builder._sawYield)
methodsWithYields.Add(syntax);
return builder._map;
}
public override void VisitCompilationUnit(CompilationUnitSyntax node)
{
foreach (MemberDeclarationSyntax member in node.Members)
{
if (member.Kind() == SyntaxKind.GlobalStatement)
{
Visit(member);
}
}
}
private LocalBinderFactory(MethodSymbol method, Binder enclosing, ArrayBuilder methodsWithYields)
{
Debug.Assert((object)method != null);
_map = new SmallDictionary(ReferenceEqualityComparer.Instance);
_method = method;
_enclosing = enclosing;
_methodsWithYields = methodsWithYields;
}
#region Starting points - these nodes contain statements
public override void VisitMethodDeclaration(MethodDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitDestructorDeclaration(DestructorDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitAccessorDeclaration(AccessorDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitConversionOperatorDeclaration(ConversionOperatorDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitOperatorDeclaration(OperatorDeclarationSyntax node)
{
VisitBlock(node.Body);
}
public override void VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node)
{
CSharpSyntaxNode body = node.Body;
if (body.Kind() == SyntaxKind.Block)
{
VisitBlock((BlockSyntax)body);
}
}
public override void VisitParenthesizedLambdaExpression(ParenthesizedLambdaExpressionSyntax node)
{
CSharpSyntaxNode body = node.Body;
if (body.Kind() == SyntaxKind.Block)
{
VisitBlock((BlockSyntax)body);
}
}
public override void VisitLocalFunctionStatement(LocalFunctionStatementSyntax node)
{
var body = (CSharpSyntaxNode)node.Body ?? node.ExpressionBody;
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)
foreach (var candidate in _enclosing.LocalFunctions)
{
if (candidate.Locations[0] == node.Identifier.GetLocation())
{
match = candidate;
}
}
bool oldSawYield = _sawYield;
_sawYield = false;
if (match != null)
{
var oldMethod = _method;
_method = match;
Binder addToMap;
if (match.IsGenericMethod)
{
addToMap = new WithMethodTypeParametersBinder(match, _enclosing);
}
else
{
addToMap = _enclosing;
}
addToMap = new InMethodBinder(match, addToMap);
AddToMap(node, addToMap);
if (body != null)
{
Visit(body, addToMap);
}
_method = oldMethod;
}
else
{
// The enclosing block should have found this node and created a LocalFunctionMethodSymbol
// The code that does so is in LocalScopeBinder.BuildLocalFunctions
if (body != null)
{
// do our best to attempt to bind
Visit(body);
}
}
if (_sawYield)
{
_methodsWithYields.Add(node);
}
_sawYield = oldSawYield;
}
public override void VisitArrowExpressionClause(ArrowExpressionClauseSyntax node)
{
// TODO: Pattern variables in the => expression are local
//var arrowBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
//AddToMap(node, arrowBinder);
}
public override void VisitAnonymousMethodExpression(AnonymousMethodExpressionSyntax node)
{
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)_method == _enclosing.ContainingMemberOrLambda);
var blockBinder = new BlockBinder(_enclosing, node.Statements);
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)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
var usingBinder = new UsingStatementBinder(patternBinder, node);
AddToMap(node, usingBinder);
VisitPossibleEmbeddedStatement(node.Statement, usingBinder);
}
public override void VisitWhileStatement(WhileStatementSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Condition, _enclosing);
var whileBinder = new WhileBinder(patternBinder, node);
AddToMap(node, whileBinder);
VisitPossibleEmbeddedStatement(node.Statement, whileBinder);
}
public override void VisitDoStatement(DoStatementSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Condition, _enclosing);
var whileBinder = new WhileBinder(patternBinder, node);
AddToMap(node, whileBinder);
VisitPossibleEmbeddedStatement(node.Statement, whileBinder);
}
public override void VisitForStatement(ForStatementSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, _enclosing);
var binder = new ForLoopBinder(patternBinder, node);
AddToMap(node, binder);
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
public override void VisitForEachStatement(ForEachStatementSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
var binder = new ForEachLoopBinder(patternBinder, node);
AddToMap(node, binder);
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
public override void VisitCheckedStatement(CheckedStatementSyntax node)
{
var binder = _enclosing.WithCheckedOrUncheckedRegion(@checked: node.Kind() == SyntaxKind.CheckedStatement);
AddToMap(node, binder);
Visit(node.Block, binder);
}
public override void VisitUnsafeStatement(UnsafeStatementSyntax node)
{
var 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)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Declaration.Variables, _enclosing);
var binder = new FixedStatementBinder(_enclosing, node);
AddToMap(node, binder);
VisitPossibleEmbeddedStatement(node.Statement, binder);
}
public override void VisitLockStatement(LockStatementSyntax node)
{
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
var lockBinder = new LockBinder(patternBinder, node);
AddToMap(node, lockBinder);
StatementSyntax statement = node.Statement;
var statementBinder = lockBinder.WithAdditionalFlags(BinderFlags.InLockBody);
if (statementBinder != lockBinder)
{
AddToMap(statement, statementBinder);
}
VisitPossibleEmbeddedStatement(statement, statementBinder);
}
public override void VisitSwitchStatement(SwitchStatementSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
var switchBinder = new SwitchBinder(patternBinder, node);
AddToMap(node, switchBinder);
foreach (SwitchSectionSyntax section in node.Sections)
{
Visit(section, switchBinder);
}
}
public override void VisitSwitchSection(SwitchSectionSyntax node)
{
var patternBinder = new PatternVariableBinder(node, _enclosing);
AddToMap(node, patternBinder);
foreach (StatementSyntax statement in node.Statements)
{
Visit(statement, patternBinder);
}
}
public override void VisitIfStatement(IfStatementSyntax node)
{
var ifBinder = new PatternVariableBinder(node, node.Condition, _enclosing);
VisitPossibleEmbeddedStatement(node.Statement, ifBinder);
AddToMap(node, ifBinder);
// pattern variables from the condition are not in scope within the else clause
if (node.Else != null) AddToMap(node.Else.Statement, _enclosing);
Visit(node.Else, _enclosing);
}
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 (var c in node.Catches)
{
Visit(c, _enclosing);
}
if (node.Finally != null)
{
Visit(node.Finally, _enclosing);
}
}
public override void VisitCatchClause(CatchClauseSyntax node)
{
Debug.Assert((object)_method == _enclosing.ContainingMemberOrLambda);
var clauseBinder = new CatchClauseBinder(_enclosing, node);
AddToMap(node, clauseBinder);
Visit(node.Block, clauseBinder);
if (node.Filter != null)
{
var filterBinder = clauseBinder.WithAdditionalFlags(BinderFlags.InCatchFilter);
AddToMap(node.Filter, filterBinder);
Visit(node.Filter, filterBinder);
}
}
public override void VisitCatchFilterClause(CatchFilterClauseSyntax node)
{
}
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)
{
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
AddToMap(node, patternBinder);
}
_sawYield = true;
}
public override void VisitExpressionStatement(ExpressionStatementSyntax node)
{
var patternBinder = new PatternVariableBinder(node, node.Expression, _enclosing);
AddToMap(node, patternBinder);
}
public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node)
{
var patternBinder = new PatternVariableBinder(node, node.Declaration.Variables, _enclosing);
AddToMap(node, patternBinder);
}
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.
Debug.Assert(node is StatementSyntax || _method.IsSynthesizedLambda());
base.DefaultVisit(node);
}
private void AddToMap(CSharpSyntaxNode node, Binder binder)
{
// If this ever breaks, make sure that all callers of
// CanHaveAssociatedLocalBinder are in sync.
Debug.Assert(node.CanHaveAssociatedLocalBinder());
// 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.Next || existing == binder.Next?.Next);
_map[node] = binder;
}
private void VisitPossibleEmbeddedStatement(StatementSyntax statement, Binder enclosing)
{
if (statement != null)
{
switch (statement.Kind())
{
case SyntaxKind.LocalDeclarationStatement:
case SyntaxKind.LabeledStatement:
// It is an error to have a declaration or a label in an embedded statement,
// but we still want to bind it. We'll pretend that the statement was
// inside a block.
Debug.Assert((object)_method == enclosing.ContainingMemberOrLambda);
var blockBinder = new BlockBinder(enclosing, new SyntaxList(statement));
AddToMap(statement, blockBinder);
Visit(statement, blockBinder);
return;
default:
break;
}
Visit(statement, enclosing);
}
}
}
}