File: Binder\LocalScopeBinder.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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 System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection.Metadata;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class LocalScopeBinder : Binder
    {
        protected const int DefaultLocalSymbolArrayCapacity = 16;
 
        private ImmutableArray<LocalSymbol> _locals;
        private ImmutableArray<LocalFunctionSymbol> _localFunctions;
        private ImmutableArray<LabelSymbol> _labels;
 
        internal LocalScopeBinder(Binder next)
            : this(next, next.Flags)
        {
        }
 
        internal LocalScopeBinder(Binder next, BinderFlags flags)
            : base(next, flags)
        {
        }
 
        internal sealed override ImmutableArray<LocalSymbol> Locals
        {
            get
            {
                if (_locals.IsDefault)
                {
                    ImmutableInterlocked.InterlockedCompareExchange(ref _locals, BuildLocals(), default(ImmutableArray<LocalSymbol>));
                }
 
                return _locals;
            }
        }
 
        protected virtual ImmutableArray<LocalSymbol> BuildLocals()
        {
            return ImmutableArray<LocalSymbol>.Empty;
        }
 
        internal sealed override ImmutableArray<LocalFunctionSymbol> LocalFunctions
        {
            get
            {
                if (_localFunctions.IsDefault)
                {
                    ImmutableInterlocked.InterlockedCompareExchange(ref _localFunctions, BuildLocalFunctions(), default(ImmutableArray<LocalFunctionSymbol>));
                }
 
                return _localFunctions;
            }
        }
 
        protected virtual ImmutableArray<LocalFunctionSymbol> BuildLocalFunctions()
        {
            return ImmutableArray<LocalFunctionSymbol>.Empty;
        }
 
        internal sealed override ImmutableArray<LabelSymbol> Labels
        {
            get
            {
                if (_labels.IsDefault)
                {
                    ImmutableInterlocked.InterlockedCompareExchange(ref _labels, BuildLabels(), default(ImmutableArray<LabelSymbol>));
                }
 
                return _labels;
            }
        }
 
        protected virtual ImmutableArray<LabelSymbol> BuildLabels()
        {
            return ImmutableArray<LabelSymbol>.Empty;
        }
 
        private SmallDictionary<string, LocalSymbol> _lazyLocalsMap;
        private SmallDictionary<string, LocalSymbol> LocalsMap
        {
            get
            {
                if (_lazyLocalsMap == null && this.Locals.Length > 0)
                {
                    _lazyLocalsMap = BuildMap(this.Locals);
                }
 
                return _lazyLocalsMap;
            }
        }
 
        private SmallDictionary<string, LocalFunctionSymbol> _lazyLocalFunctionsMap;
        private SmallDictionary<string, LocalFunctionSymbol> LocalFunctionsMap
        {
            get
            {
                if (_lazyLocalFunctionsMap == null && this.LocalFunctions.Length > 0)
                {
                    _lazyLocalFunctionsMap = BuildMap(this.LocalFunctions);
                }
 
                return _lazyLocalFunctionsMap;
            }
        }
 
        private SmallDictionary<string, LabelSymbol> _lazyLabelsMap;
        private SmallDictionary<string, LabelSymbol> LabelsMap
        {
            get
            {
                if (_lazyLabelsMap == null && this.Labels.Length > 0)
                {
                    _lazyLabelsMap = BuildMap(this.Labels);
                }
 
                return _lazyLabelsMap;
            }
        }
 
        private static SmallDictionary<string, TSymbol> BuildMap<TSymbol>(ImmutableArray<TSymbol> array)
            where TSymbol : Symbol
        {
            Debug.Assert(array.Length > 0);
 
            var map = new SmallDictionary<string, TSymbol>();
 
            // NOTE: in a rare case of having two symbols with same name the one closer to the array's start wins.
            for (int i = array.Length - 1; i >= 0; i--)
            {
                var symbol = array[i];
                map[symbol.Name] = symbol;
            }
 
            return map;
        }
 
        protected ImmutableArray<LocalSymbol> BuildLocals(SyntaxList<StatementSyntax> statements, Binder enclosingBinder)
        {
#if DEBUG
            Binder currentBinder = enclosingBinder;
 
            while (true)
            {
                if (this == currentBinder)
                {
                    break;
                }
 
                currentBinder = currentBinder.Next;
            }
#endif
 
            ArrayBuilder<LocalSymbol> locals = ArrayBuilder<LocalSymbol>.GetInstance(DefaultLocalSymbolArrayCapacity);
            foreach (var statement in statements)
            {
                BuildLocals(enclosingBinder, statement, locals);
            }
 
            return locals.ToImmutableAndFree();
        }
 
        internal void BuildLocals(Binder enclosingBinder, StatementSyntax statement, ArrayBuilder<LocalSymbol> locals)
        {
            var innerStatement = statement;
 
            // drill into any LabeledStatements -- atomic LabelStatements have been bound into
            // wrapped LabeledStatements by this point
            while (innerStatement.Kind() == SyntaxKind.LabeledStatement)
            {
                innerStatement = ((LabeledStatementSyntax)innerStatement).Statement;
            }
 
            switch (innerStatement.Kind())
            {
                case SyntaxKind.LocalDeclarationStatement:
                    {
                        Binder localDeclarationBinder = enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder;
                        var decl = (LocalDeclarationStatementSyntax)innerStatement;
 
                        decl.Declaration.Type.VisitRankSpecifiers((rankSpecifier, args) =>
                        {
                            foreach (var expression in rankSpecifier.Sizes)
                            {
                                findExpressionVariablesInRankSpecifier(expression, args);
                            }
 
                        }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localDeclarationBinder));
 
                        LocalDeclarationKind kind;
                        if (decl.IsConst)
                        {
                            kind = LocalDeclarationKind.Constant;
                        }
                        else if (decl.UsingKeyword != default(SyntaxToken))
                        {
                            kind = LocalDeclarationKind.UsingVariable;
                        }
                        else
                        {
                            kind = LocalDeclarationKind.RegularVariable;
                        }
 
                        foreach (var vdecl in decl.Declaration.Variables)
                        {
                            var localSymbol = MakeLocal(decl.Declaration, vdecl, kind, allowScoped: true, localDeclarationBinder);
                            locals.Add(localSymbol);
 
                            // also gather expression-declared variables from the bracketed argument lists and the initializers
                            ExpressionVariableFinder.FindExpressionVariables(this, locals, vdecl, localDeclarationBinder);
                        }
                    }
                    break;
 
                case SyntaxKind.LocalFunctionStatement:
                    {
                        Binder localFunctionDeclarationBinder = enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder;
                        var decl = (LocalFunctionStatementSyntax)innerStatement;
 
                        foreach (var parameter in decl.ParameterList.Parameters)
                        {
                            parameter.Type?.VisitRankSpecifiers((rankSpecifier, args) =>
                            {
                                foreach (var expression in rankSpecifier.Sizes)
                                {
                                    findExpressionVariablesInRankSpecifier(expression, args);
                                }
                            }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localFunctionDeclarationBinder));
                        }
 
                        foreach (var constraintClause in decl.ConstraintClauses)
                        {
                            foreach (var constraint in constraintClause.Constraints)
                            {
                                if (constraint is TypeConstraintSyntax typeConstraint)
                                {
                                    typeConstraint.Type.VisitRankSpecifiers((rankSpecifier, args) =>
                                    {
                                        foreach (var expression in rankSpecifier.Sizes)
                                        {
                                            findExpressionVariablesInRankSpecifier(expression, args);
                                        }
                                    }, (localScopeBinder: this, locals: locals, localDeclarationBinder: localFunctionDeclarationBinder));
                                }
                            }
                        }
                    }
                    break;
 
                case SyntaxKind.ExpressionStatement:
                case SyntaxKind.IfStatement:
                case SyntaxKind.YieldReturnStatement:
                case SyntaxKind.ReturnStatement:
                case SyntaxKind.ThrowStatement:
                case SyntaxKind.GotoCaseStatement:
                    ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, enclosingBinder.GetBinder(innerStatement) ?? enclosingBinder);
                    break;
 
                case SyntaxKind.SwitchStatement:
                    var switchStatement = (SwitchStatementSyntax)innerStatement;
                    ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, enclosingBinder.GetBinder(switchStatement.Expression) ?? enclosingBinder);
                    break;
 
                case SyntaxKind.LockStatement:
                    Binder statementBinder = enclosingBinder.GetBinder(innerStatement);
                    Debug.Assert(statementBinder != null); // Lock always has a binder.
                    ExpressionVariableFinder.FindExpressionVariables(this, locals, innerStatement, statementBinder);
                    break;
 
                default:
                    // no other statement introduces local variables into the enclosing scope
                    break;
            }
 
            return;
 
            static void findExpressionVariablesInRankSpecifier(ExpressionSyntax expression, (LocalScopeBinder localScopeBinder, ArrayBuilder<LocalSymbol> locals, Binder localDeclarationBinder) args)
            {
                if (expression.Kind() != SyntaxKind.OmittedArraySizeExpression)
                {
                    ExpressionVariableFinder.FindExpressionVariables(args.localScopeBinder, args.locals, expression, args.localDeclarationBinder);
                }
            }
        }
 
        protected ImmutableArray<LocalFunctionSymbol> BuildLocalFunctions(SyntaxList<StatementSyntax> statements)
        {
            ArrayBuilder<LocalFunctionSymbol> locals = null;
            foreach (var statement in statements)
            {
                BuildLocalFunctions(statement, ref locals);
            }
 
            return locals?.ToImmutableAndFree() ?? ImmutableArray<LocalFunctionSymbol>.Empty;
        }
 
        internal void BuildLocalFunctions(StatementSyntax statement, ref ArrayBuilder<LocalFunctionSymbol> locals)
        {
            var innerStatement = statement;
 
            // drill into any LabeledStatements -- atomic LabelStatements have been bound into
            // wrapped LabeledStatements by this point
            while (innerStatement.Kind() == SyntaxKind.LabeledStatement)
            {
                innerStatement = ((LabeledStatementSyntax)innerStatement).Statement;
            }
 
            if (innerStatement.Kind() == SyntaxKind.LocalFunctionStatement)
            {
                var decl = (LocalFunctionStatementSyntax)innerStatement;
                if (locals == null)
                {
                    locals = ArrayBuilder<LocalFunctionSymbol>.GetInstance();
                }
 
                var localSymbol = MakeLocalFunction(decl);
                locals.Add(localSymbol);
            }
        }
 
        protected SourceLocalSymbol MakeLocal(VariableDeclarationSyntax declaration, VariableDeclaratorSyntax declarator, LocalDeclarationKind kind, bool allowScoped, Binder initializerBinderOpt = null)
        {
            return SourceLocalSymbol.MakeLocal(
                this.ContainingMemberOrLambda,
                this,
                allowRefKind: true,
                allowScoped: allowScoped,
                declaration.Type,
                declarator.Identifier,
                kind,
                declarator.Initializer,
                initializerBinderOpt);
        }
 
        protected LocalFunctionSymbol MakeLocalFunction(LocalFunctionStatementSyntax declaration)
        {
            return new LocalFunctionSymbol(
                this,
                this.ContainingMemberOrLambda,
                declaration);
        }
 
        protected void BuildLabels(SyntaxList<StatementSyntax> statements, ref ArrayBuilder<LabelSymbol> labels)
        {
            var containingMethod = (MethodSymbol)this.ContainingMemberOrLambda;
            foreach (var statement in statements)
            {
                BuildLabels(containingMethod, statement, ref labels);
            }
        }
 
        internal static void BuildLabels(MethodSymbol containingMethod, StatementSyntax statement, ref ArrayBuilder<LabelSymbol> labels)
        {
            while (statement.Kind() == SyntaxKind.LabeledStatement)
            {
                var labeledStatement = (LabeledStatementSyntax)statement;
                if (labels == null)
                {
                    labels = ArrayBuilder<LabelSymbol>.GetInstance();
                }
 
                var labelSymbol = new SourceLabelSymbol(containingMethod, labeledStatement.Identifier);
                labels.Add(labelSymbol);
                statement = labeledStatement.Statement;
            }
        }
 
        /// <summary>
        /// Call this when you are sure there is a local declaration on this token.  Returns the local.
        /// </summary>
        protected override SourceLocalSymbol LookupLocal(SyntaxToken nameToken)
        {
            LocalSymbol result = null;
            if (LocalsMap != null && LocalsMap.TryGetValue(nameToken.ValueText, out result))
            {
                if (result.IdentifierToken == nameToken) return (SourceLocalSymbol)result;
 
                // in error cases we might have more than one declaration of the same name in the same scope
                foreach (var local in this.Locals)
                {
                    if (local.IdentifierToken == nameToken)
                    {
                        return (SourceLocalSymbol)local;
                    }
                }
            }
 
            return base.LookupLocal(nameToken);
        }
 
        protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken)
        {
            LocalFunctionSymbol result = null;
            if (LocalFunctionsMap != null && LocalFunctionsMap.TryGetValue(nameToken.ValueText, out result))
            {
                if (result.NameToken == nameToken) return result;
 
                // in error cases we might have more than one declaration of the same name in the same scope
                foreach (var local in this.LocalFunctions)
                {
                    if (local.NameToken == nameToken)
                    {
                        return local;
                    }
                }
            }
 
            return base.LookupLocalFunction(nameToken);
        }
 
        internal override void LookupSymbolsInSingleBinder(
            LookupResult result, string name, int arity, ConsList<TypeSymbol> basesBeingResolved, LookupOptions options, Binder originalBinder, bool diagnose, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo)
        {
            Debug.Assert(options.AreValid());
            Debug.Assert(result.IsClear);
 
            if ((options & LookupOptions.LabelsOnly) != 0)
            {
                var labelsMap = this.LabelsMap;
                if (labelsMap != null)
                {
                    LabelSymbol labelSymbol;
                    if (labelsMap.TryGetValue(name, out labelSymbol))
                    {
                        result.MergeEqual(LookupResult.Good(labelSymbol));
                    }
                }
                return;
            }
 
            var localsMap = this.LocalsMap;
            if (localsMap != null && (options & LookupOptions.NamespaceAliasesOnly) == 0)
            {
                LocalSymbol localSymbol;
                if (localsMap.TryGetValue(name, out localSymbol))
                {
                    result.MergeEqual(originalBinder.CheckViability(localSymbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved));
                }
            }
 
            var localFunctionsMap = this.LocalFunctionsMap;
            if (localFunctionsMap != null && options.CanConsiderLocals())
            {
                LocalFunctionSymbol localSymbol;
                if (localFunctionsMap.TryGetValue(name, out localSymbol))
                {
                    result.MergeEqual(originalBinder.CheckViability(localSymbol, arity, options, null, diagnose, ref useSiteInfo, basesBeingResolved));
                }
            }
        }
 
        internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder)
        {
            Debug.Assert(options.AreValid());
 
            if ((options & LookupOptions.LabelsOnly) != 0)
            {
                if (this.LabelsMap != null)
                {
                    foreach (var label in this.LabelsMap)
                    {
                        result.AddSymbol(label.Value, label.Key, 0);
                    }
                }
            }
            if (options.CanConsiderLocals())
            {
                if (this.LocalsMap != null)
                {
                    foreach (var local in this.LocalsMap)
                    {
                        if (originalBinder.CanAddLookupSymbolInfo(local.Value, options, result, null))
                        {
                            result.AddSymbol(local.Value, local.Key, 0);
                        }
                    }
                }
                if (this.LocalFunctionsMap != null)
                {
                    foreach (var local in this.LocalFunctionsMap)
                    {
                        if (originalBinder.CanAddLookupSymbolInfo(local.Value, options, result, null))
                        {
                            result.AddSymbol(local.Value, local.Key, 0);
                        }
                    }
                }
            }
        }
 
        private bool ReportConflictWithLocal(Symbol local, Symbol newSymbol, string name, Location newLocation, BindingDiagnosticBag diagnostics)
        {
            // Quirk of the way we represent lambda parameters.
            SymbolKind newSymbolKind = (object)newSymbol == null ? SymbolKind.Parameter : newSymbol.Kind;
 
            if (newSymbolKind == SymbolKind.ErrorType) return true;
 
            var declaredInThisScope = false;
 
            declaredInThisScope |= newSymbolKind == SymbolKind.Local && this.Locals.Contains((LocalSymbol)newSymbol);
            declaredInThisScope |= newSymbolKind == SymbolKind.Method && this.LocalFunctions.Contains((LocalFunctionSymbol)newSymbol);
 
            if (declaredInThisScope && newLocation.SourceSpan.Start >= local.GetFirstLocation().SourceSpan.Start)
            {
                // A local variable or function named '{0}' is already defined in this scope
                diagnostics.Add(ErrorCode.ERR_LocalDuplicate, newLocation, name);
                return true;
            }
 
            switch (newSymbolKind)
            {
                case SymbolKind.Local:
                case SymbolKind.Parameter:
                case SymbolKind.Method:
                case SymbolKind.TypeParameter:
                    // A local or parameter named '{0}' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
                    diagnostics.Add(ErrorCode.ERR_LocalIllegallyOverrides, newLocation, name);
                    return true;
 
                case SymbolKind.RangeVariable:
                    // The range variable '{0}' conflicts with a previous declaration of '{0}'
                    diagnostics.Add(ErrorCode.ERR_QueryRangeVariableOverrides, newLocation, name);
                    return true;
            }
 
            Debug.Assert(false, "what else can be declared inside a local scope?");
            diagnostics.Add(ErrorCode.ERR_InternalError, newLocation);
            return false;
        }
 
        internal virtual bool EnsureSingleDefinition(Symbol symbol, string name, Location location, BindingDiagnosticBag diagnostics)
        {
            LocalSymbol existingLocal = null;
            LocalFunctionSymbol existingLocalFunction = null;
 
            var localsMap = this.LocalsMap;
            var localFunctionsMap = this.LocalFunctionsMap;
 
            // TODO: Handle case where 'name' exists in both localsMap and localFunctionsMap. Right now locals are preferred over local functions.
            if ((localsMap != null && localsMap.TryGetValue(name, out existingLocal)) ||
                (localFunctionsMap != null && localFunctionsMap.TryGetValue(name, out existingLocalFunction)))
            {
                var existingSymbol = (Symbol)existingLocal ?? existingLocalFunction;
                if (symbol == existingSymbol)
                {
                    // reference to same symbol, by far the most common case.
                    return false;
                }
 
                return ReportConflictWithLocal(existingSymbol, symbol, name, location, diagnostics);
            }
 
            return false;
        }
    }
}