File: Binder\InMethodBinder.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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A binder for a method body, which places the method's parameters in scope
    /// and notes if the method is an iterator method.
    /// Note: instances of this type can be re-used across different attempts at compiling the same method (caching by binder factory).
    /// </summary>
    internal sealed class InMethodBinder : LocalScopeBinder
    {
        private MultiDictionary<string, ParameterSymbol> _lazyParameterMap;
        private readonly MethodSymbol _methodSymbol;
        private SmallDictionary<string, Symbol> _lazyDefinitionMap;
 
#if DEBUG
        /// <summary>
        /// This map is used by <see cref="MethodCompiler.BindMethodBody(MethodSymbol, TypeCompilationState, BindingDiagnosticBag, bool, BoundNode?, bool, out ImportChain?, out bool, out bool, out MethodBodySemanticModel.InitialState)"/>
        /// and <see cref="Binder.BindIdentifier"/> to validate some assumptions around identifiers.
        /// 
        /// Values in the dictionary are bit flags.
        /// MethodCompiler.BindMethodBody adds keys with flag == 1 before binding a method body.
        /// Binder.BindIdentifier adds or updates keys with flag == 2.
        /// </summary>
        public ConcurrentDictionary<IdentifierNameSyntax, int> IdentifierMap;
#endif
 
        public InMethodBinder(MethodSymbol owner, Binder enclosing)
            : base(enclosing, enclosing.Flags & ~BinderFlags.AllClearedAtExecutableCodeBoundary)
        {
            Debug.Assert(!enclosing.Flags.Includes(BinderFlags.InCatchFilter));
            Debug.Assert((object)owner != null);
            _methodSymbol = owner;
        }
 
        private static void RecordDefinition<T>(SmallDictionary<string, Symbol> declarationMap, ImmutableArray<T> definitions) where T : Symbol
        {
            foreach (Symbol s in definitions)
            {
                if (!declarationMap.ContainsKey(s.Name))
                {
                    declarationMap.Add(s.Name, s);
                }
            }
        }
 
        protected override SourceLocalSymbol LookupLocal(SyntaxToken nameToken)
        {
            return null;
        }
 
        protected override LocalFunctionSymbol LookupLocalFunction(SyntaxToken nameToken)
        {
            return null;
        }
 
        protected override bool InExecutableBinder => true;
 
        internal override Symbol ContainingMemberOrLambda
        {
            get
            {
                return _methodSymbol;
            }
        }
 
        internal override bool IsInMethodBody
        {
            get
            {
                return true;
            }
        }
 
        internal override bool IsNestedFunctionBinder => _methodSymbol.MethodKind == MethodKind.LocalFunction;
 
        internal override bool IsDirectlyInIterator
        {
            get
            {
                return _methodSymbol.IsIterator;
            }
        }
 
        internal override bool IsIndirectlyInIterator
        {
            get
            {
                return IsDirectlyInIterator; // Sic: indirectly iff directly
            }
        }
 
        internal override GeneratedLabelSymbol BreakLabel
        {
            get
            {
                return null;
            }
        }
 
        internal override GeneratedLabelSymbol ContinueLabel
        {
            get
            {
                return null;
            }
        }
 
        protected override void ValidateYield(YieldStatementSyntax node, BindingDiagnosticBag diagnostics)
        {
        }
 
        internal override TypeWithAnnotations GetIteratorElementType()
        {
            RefKind refKind = _methodSymbol.RefKind;
            TypeSymbol returnType = _methodSymbol.ReturnType;
 
            if (!this.IsDirectlyInIterator)
            {
                // This should only happen when speculating, but we don't have a good way to assert that since the
                // original binder isn't available here.
                // If we're speculating about a yield statement inside a non-iterator method, we'll try to be nice
                // and deduce an iterator element type from the return type.  If we didn't do this, the 
                // TypeInfo.ConvertedType of the yield statement would always be an error type.  However, we will 
                // not mutate any state (i.e. we won't store the result).
                var elementType = GetIteratorElementTypeFromReturnType(Compilation, refKind, returnType, errorLocation: null, diagnostics: null);
                return !elementType.IsDefault ? elementType : TypeWithAnnotations.Create(CreateErrorType());
            }
 
            return _methodSymbol.IteratorElementTypeWithAnnotations;
        }
 
        internal static TypeWithAnnotations GetIteratorElementTypeFromReturnType(CSharpCompilation compilation,
            RefKind refKind, TypeSymbol returnType, Location errorLocation, BindingDiagnosticBag diagnostics)
        {
            if (refKind == RefKind.None && returnType.Kind == SymbolKind.NamedType)
            {
                TypeSymbol originalDefinition = returnType.OriginalDefinition;
                switch (originalDefinition.SpecialType)
                {
                    case SpecialType.System_Collections_IEnumerable:
                    case SpecialType.System_Collections_IEnumerator:
                        var objectType = compilation.GetSpecialType(SpecialType.System_Object);
                        if (diagnostics != null)
                        {
                            ReportUseSite(objectType, diagnostics, errorLocation);
                        }
                        return TypeWithAnnotations.Create(objectType);
 
                    case SpecialType.System_Collections_Generic_IEnumerable_T:
                    case SpecialType.System_Collections_Generic_IEnumerator_T:
                        return ((NamedTypeSymbol)returnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
                }
 
                if (TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T), TypeCompareKind.ConsiderEverything) ||
                    TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T), TypeCompareKind.ConsiderEverything))
                {
                    return ((NamedTypeSymbol)returnType).TypeArgumentsWithAnnotationsNoUseSiteDiagnostics[0];
                }
            }
 
            return default;
        }
 
        internal static bool IsAsyncStreamInterface(CSharpCompilation compilation, RefKind refKind, TypeSymbol returnType)
        {
            if (refKind == RefKind.None && returnType.Kind == SymbolKind.NamedType)
            {
                TypeSymbol originalDefinition = returnType.OriginalDefinition;
 
                if (TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerable_T), TypeCompareKind.ConsiderEverything) ||
                    TypeSymbol.Equals(originalDefinition, compilation.GetWellKnownType(WellKnownType.System_Collections_Generic_IAsyncEnumerator_T), TypeCompareKind.ConsiderEverything))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        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(result.IsClear);
 
            if (_methodSymbol.ParameterCount == 0 || (options & LookupOptions.NamespaceAliasesOnly) != 0)
            {
                return;
            }
 
            var parameterMap = _lazyParameterMap;
            if (parameterMap == null)
            {
                var parameters = _methodSymbol.Parameters;
                parameterMap = new MultiDictionary<string, ParameterSymbol>(parameters.Length, EqualityComparer<string>.Default);
                foreach (var parameter in parameters)
                {
                    if ((this.Flags & BinderFlags.InEEMethodBinder) != 0 && parameter.Type.IsDisplayClassType())
                    {
                        // Display class parameters shouldn't be accessible in EE
                        continue;
                    }
 
                    parameterMap.Add(parameter.Name, parameter);
                }
 
                _lazyParameterMap = parameterMap;
            }
 
            foreach (var parameterSymbol in parameterMap[name])
            {
                result.MergeEqual(originalBinder.CheckViability(parameterSymbol, arity, options, null, diagnose, ref useSiteInfo));
            }
        }
 
        internal override void AddLookupSymbolsInfoInSingleBinder(LookupSymbolsInfo result, LookupOptions options, Binder originalBinder)
        {
            if (options.CanConsiderMembers())
            {
                foreach (var parameter in _methodSymbol.Parameters)
                {
                    if (originalBinder.CanAddLookupSymbolInfo(parameter, options, result, null))
                    {
                        result.AddSymbol(parameter, parameter.Name, 0);
                    }
                }
            }
        }
 
        private static bool ReportConflictWithParameter(Symbol parameter, Symbol newSymbol, string name, Location newLocation, BindingDiagnosticBag diagnostics)
        {
#if DEBUG
            var locations = parameter.Locations;
            Debug.Assert(!locations.IsEmpty || parameter.IsImplicitlyDeclared);
            var oldLocation = parameter.GetFirstLocationOrNone();
            Debug.Assert(oldLocation != newLocation || oldLocation == Location.None || newLocation.SourceTree?.GetRoot().ContainsDiagnostics == true,
                "same nonempty location refers to different symbols?");
#endif 
            SymbolKind parameterKind = parameter.Kind;
 
            // Quirk of the way we represent lambda parameters.                
            SymbolKind newSymbolKind = (object)newSymbol == null ? SymbolKind.Parameter : newSymbol.Kind;
 
            if (newSymbolKind == SymbolKind.ErrorType)
            {
                return true;
            }
 
            if (parameterKind == SymbolKind.Parameter)
            {
                switch (newSymbolKind)
                {
                    case SymbolKind.Parameter:
                    case SymbolKind.Local:
                        // 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.Method:
                        if (((MethodSymbol)newSymbol).MethodKind == MethodKind.LocalFunction)
                        {
                            goto case SymbolKind.Parameter;
                        }
                        break;
 
                    case SymbolKind.TypeParameter:
                        // Type parameter declaration name conflicts are not reported, for backwards compatibility.
                        return false;
 
                    case SymbolKind.RangeVariable:
                        // The range variable '{0}' conflicts with a previous declaration of '{0}'
                        diagnostics.Add(ErrorCode.ERR_QueryRangeVariableOverrides, newLocation, name);
                        return true;
                }
            }
 
            if (parameterKind == SymbolKind.TypeParameter)
            {
                switch (newSymbolKind)
                {
                    case SymbolKind.Parameter:
                    case SymbolKind.Local:
                        // CS0412: '{0}': a parameter, local variable, or local function cannot have the same name as a method type parameter
                        diagnostics.Add(ErrorCode.ERR_LocalSameNameAsTypeParam, newLocation, name);
                        return true;
 
                    case SymbolKind.Method:
                        if (((MethodSymbol)newSymbol).MethodKind == MethodKind.LocalFunction)
                        {
                            goto case SymbolKind.Parameter;
                        }
                        break;
 
                    case SymbolKind.TypeParameter:
                        // Type parameter declaration name conflicts are detected elsewhere
                        return false;
 
                    case SymbolKind.RangeVariable:
                        // The range variable '{0}' cannot have the same name as a method type parameter
                        diagnostics.Add(ErrorCode.ERR_QueryRangeVariableSameAsTypeParam, newLocation, name);
                        return true;
                }
            }
 
            Debug.Assert(false, "what else could be defined in a method?");
            diagnostics.Add(ErrorCode.ERR_InternalError, newLocation);
            return true;
        }
 
        internal override bool EnsureSingleDefinition(Symbol symbol, string name, Location location, BindingDiagnosticBag diagnostics)
        {
            var parameters = _methodSymbol.Parameters;
            var typeParameters = _methodSymbol.TypeParameters;
 
            if (parameters.IsEmpty && typeParameters.IsEmpty)
            {
                return false;
            }
 
            var map = _lazyDefinitionMap;
 
            if (map == null)
            {
                map = new SmallDictionary<string, Symbol>();
                RecordDefinition(map, parameters);
                RecordDefinition(map, typeParameters);
 
                _lazyDefinitionMap = map;
            }
 
            Symbol existingDeclaration;
            if (map.TryGetValue(name, out existingDeclaration))
            {
                return ReportConflictWithParameter(existingDeclaration, symbol, name, location, diagnostics);
            }
 
            return false;
        }
    }
}