File: Binder\Binder_Lambda.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.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    internal partial class Binder
    {
        // An anonymous function can be of the form:
        // 
        // delegate { }              (missing parameter list)
        // delegate (int x) { }      (typed parameter list)
        // x => ...                  (type-inferred parameter list)
        // (x) => ...                (type-inferred parameter list)
        // (x, y) => ...             (type-inferred parameter list)
        // ( ) => ...                (typed parameter list)
        // (ref int x) => ...        (typed parameter list)
        // (int x, out int y) => ... (typed parameter list)
        //
        // and so on. We want to canonicalize these various ways of writing the signatures.
        // 
        // If we are in the first case then the name, modifier and type arrays are all null.
        // If we have a parameter list then the names array is non-null, but possibly empty.
        // If we have types then the types array is non-null, but possibly empty.
        // If we have no modifiers then the modifiers array is null; if we have any modifiers
        // then the modifiers array is non-null and not empty.
 
        private UnboundLambda AnalyzeAnonymousFunction(
            AnonymousFunctionExpressionSyntax syntax, BindingDiagnosticBag diagnostics)
        {
            // !!! The only binding operations allowed here - binding type references
 
            Debug.Assert(syntax != null);
            Debug.Assert(syntax.IsAnonymousFunction());
 
            ImmutableArray<string> names = default;
            ImmutableArray<RefKind> refKinds = default;
            ImmutableArray<ScopedKind> scopes = default;
            ImmutableArray<TypeWithAnnotations> types = default;
            ImmutableArray<EqualsValueClauseSyntax?> defaultValues = default;
            RefKind returnRefKind = RefKind.None;
            TypeWithAnnotations returnType = default;
            ImmutableArray<SyntaxList<AttributeListSyntax>> parameterAttributes = default;
 
            var namesBuilder = ArrayBuilder<string>.GetInstance();
            ImmutableArray<bool> discardsOpt = default;
            SeparatedSyntaxList<ParameterSyntax>? parameterSyntaxList = null;
            bool hasSignature;
 
            if (syntax is LambdaExpressionSyntax lambdaSyntax)
            {
                MessageID.IDS_FeatureLambda.CheckFeatureAvailability(diagnostics, lambdaSyntax.ArrowToken);
 
                checkAttributes(syntax, lambdaSyntax.AttributeLists, diagnostics);
            }
 
            switch (syntax.Kind())
            {
                default:
                case SyntaxKind.SimpleLambdaExpression:
                    // x => ...
                    hasSignature = true;
                    var simple = (SimpleLambdaExpressionSyntax)syntax;
                    namesBuilder.Add(simple.Parameter.Identifier.ValueText);
                    break;
                case SyntaxKind.ParenthesizedLambdaExpression:
                    // (T x, U y) => ...
                    // (x, y) => ...
                    hasSignature = true;
                    var paren = (ParenthesizedLambdaExpressionSyntax)syntax;
                    if (paren.ReturnType is { } returnTypeSyntax)
                    {
                        (returnRefKind, returnType) = BindExplicitLambdaReturnType(returnTypeSyntax, diagnostics);
                    }
                    parameterSyntaxList = paren.ParameterList.Parameters;
                    CheckParenthesizedLambdaParameters(parameterSyntaxList.Value, diagnostics);
                    break;
                case SyntaxKind.AnonymousMethodExpression:
                    // delegate (int x) { }
                    // delegate { }
                    var anon = (AnonymousMethodExpressionSyntax)syntax;
                    MessageID.IDS_FeatureAnonDelegates.CheckFeatureAvailability(diagnostics, anon.DelegateKeyword);
 
                    hasSignature = anon.ParameterList != null;
                    if (hasSignature)
                    {
                        parameterSyntaxList = anon.ParameterList!.Parameters;
                    }
 
                    break;
            }
 
            bool isAsync = false;
            bool isStatic = false;
 
            foreach (var modifier in syntax.Modifiers)
            {
                if (modifier.IsKind(SyntaxKind.AsyncKeyword))
                {
                    MessageID.IDS_FeatureAsync.CheckFeatureAvailability(diagnostics, modifier);
                    isAsync = true;
                }
                else if (modifier.IsKind(SyntaxKind.StaticKeyword))
                {
                    MessageID.IDS_FeatureStaticAnonymousFunction.CheckFeatureAvailability(diagnostics, modifier);
                    isStatic = true;
                }
            }
 
            if (parameterSyntaxList != null)
            {
                var hasExplicitlyTypedParameterList = true;
 
                var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
                var refKindsBuilder = ArrayBuilder<RefKind>.GetInstance();
                var scopesBuilder = ArrayBuilder<ScopedKind>.GetInstance();
                var attributesBuilder = ArrayBuilder<SyntaxList<AttributeListSyntax>>.GetInstance();
                var defaultValueBuilder = ArrayBuilder<EqualsValueClauseSyntax?>.GetInstance();
 
                // In the batch compiler case we probably should have given a syntax error if the
                // user did something like (int x, y)=>x+y -- but in the IDE scenario we might be in
                // this case. If we are, then rather than try to make partial deductions from the
                // typed formal parameters, simply bail out and treat it as an untyped lambda.
                //
                // However, we still want to give errors on every bad type in the list, even if one
                // is missing.
 
                int parameterCount = 0;
                int underscoresCount = 0;
                foreach (var p in parameterSyntaxList.Value)
                {
                    parameterCount++;
 
                    if (p.Identifier.IsUnderscoreToken())
                    {
                        underscoresCount++;
                    }
 
                    checkAttributes(syntax, p.AttributeLists, diagnostics);
 
                    var isAnonymousMethod = syntax.IsKind(SyntaxKind.AnonymousMethodExpression);
                    if (p.Default != null)
                    {
                        if (isAnonymousMethod)
                        {
                            Error(diagnostics, ErrorCode.ERR_DefaultValueNotAllowed, p.Default.EqualsToken);
                        }
                        else
                        {
                            MessageID.IDS_FeatureLambdaOptionalParameters.CheckFeatureAvailability(diagnostics, p.Default.EqualsToken);
                        }
                    }
 
                    if (p.IsArgList)
                    {
                        Error(diagnostics, ErrorCode.ERR_IllegalVarArgs, p);
                        continue;
                    }
 
                    var typeSyntax = p.Type;
                    TypeWithAnnotations type = default;
                    var refKind = RefKind.None;
                    var scope = ScopedKind.None;
 
                    if (typeSyntax == null)
                    {
                        hasExplicitlyTypedParameterList = false;
                    }
                    else
                    {
                        type = BindType(typeSyntax, diagnostics);
                        ParameterHelpers.CheckParameterModifiers(p, diagnostics, parsingFunctionPointerParams: false,
                            parsingLambdaParams: !isAnonymousMethod,
                            parsingAnonymousMethodParams: isAnonymousMethod);
                        refKind = ParameterHelpers.GetModifiers(p.Modifiers, out _, out var paramsKeyword, out _, out scope);
 
                        var isLastParameter = parameterCount == parameterSyntaxList.Value.Count;
                        if (isLastParameter && paramsKeyword.Kind() != SyntaxKind.None && type.IsSZArray())
                        {
                            ReportUseSiteDiagnosticForSynthesizedAttribute(Compilation,
                                WellKnownMember.System_ParamArrayAttribute__ctor,
                                diagnostics,
                                paramsKeyword.GetLocation());
                        }
                    }
 
                    namesBuilder.Add(p.Identifier.ValueText);
                    typesBuilder.Add(type);
                    refKindsBuilder.Add(refKind);
                    scopesBuilder.Add(scope);
                    attributesBuilder.Add(syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression ? p.AttributeLists : default);
                    defaultValueBuilder.Add(p.Default);
                }
 
                discardsOpt = computeDiscards(parameterSyntaxList.Value, underscoresCount);
 
                if (hasExplicitlyTypedParameterList)
                {
                    types = typesBuilder.ToImmutable();
                }
 
                if (refKindsBuilder.Any(r => r != RefKind.None))
                {
                    refKinds = refKindsBuilder.ToImmutable();
                }
 
                if (scopesBuilder.Any(s => s != ScopedKind.None))
                {
                    scopes = scopesBuilder.ToImmutable();
                }
 
                if (attributesBuilder.Any(a => a.Count > 0))
                {
                    parameterAttributes = attributesBuilder.ToImmutable();
                }
 
                if (defaultValueBuilder.Any(v => v != null))
                {
                    defaultValues = defaultValueBuilder.ToImmutable();
                }
 
                typesBuilder.Free();
                scopesBuilder.Free();
                refKindsBuilder.Free();
                attributesBuilder.Free();
                defaultValueBuilder.Free();
            }
 
            if (hasSignature)
            {
                names = namesBuilder.ToImmutable();
            }
 
            namesBuilder.Free();
 
            return UnboundLambda.Create(syntax, this, diagnostics.AccumulatesDependencies, returnRefKind, returnType, parameterAttributes, refKinds, scopes, types, names, discardsOpt, parameterSyntaxList, defaultValues, isAsync: isAsync, isStatic: isStatic);
 
            static ImmutableArray<bool> computeDiscards(SeparatedSyntaxList<ParameterSyntax> parameters, int underscoresCount)
            {
                if (underscoresCount <= 1)
                {
                    return default;
                }
 
                // When there are two or more underscores, they are discards
                var discardsBuilder = ArrayBuilder<bool>.GetInstance(parameters.Count);
                foreach (var p in parameters)
                {
                    discardsBuilder.Add(p.Identifier.IsUnderscoreToken());
                }
 
                return discardsBuilder.ToImmutableAndFree();
            }
 
            static void checkAttributes(AnonymousFunctionExpressionSyntax syntax, SyntaxList<AttributeListSyntax> attributeLists, BindingDiagnosticBag diagnostics)
            {
                foreach (var attributeList in attributeLists)
                {
                    if (syntax.Kind() == SyntaxKind.ParenthesizedLambdaExpression)
                    {
                        MessageID.IDS_FeatureLambdaAttributes.CheckFeatureAvailability(diagnostics, attributeList);
                    }
                    else
                    {
                        Error(diagnostics, syntax.Kind() == SyntaxKind.SimpleLambdaExpression ? ErrorCode.ERR_AttributesRequireParenthesizedLambdaExpression : ErrorCode.ERR_AttributesNotAllowed, attributeList);
                    }
                }
            }
 
        }
 
        private (RefKind, TypeWithAnnotations) BindExplicitLambdaReturnType(TypeSyntax syntax, BindingDiagnosticBag diagnostics)
        {
            MessageID.IDS_FeatureLambdaReturnType.CheckFeatureAvailability(diagnostics, syntax);
 
            Debug.Assert(syntax is not ScopedTypeSyntax);
            syntax = syntax.SkipScoped(out _).SkipRefInLocalOrReturn(diagnostics, out RefKind refKind);
            if (syntax is IdentifierNameSyntax { Identifier.RawContextualKind: (int)SyntaxKind.VarKeyword })
            {
                diagnostics.Add(ErrorCode.ERR_LambdaExplicitReturnTypeVar, syntax.Location);
            }
 
            var returnType = BindType(syntax, diagnostics);
            var type = returnType.Type;
 
            if (returnType.IsStatic)
            {
                diagnostics.Add(ErrorFacts.GetStaticClassReturnCode(useWarning: false), syntax.Location, type);
            }
            else if (returnType.IsRestrictedType(ignoreSpanLikeTypes: true))
            {
                diagnostics.Add(ErrorCode.ERR_MethodReturnCantBeRefAny, syntax.Location, type);
            }
 
            return (refKind, returnType);
        }
 
        private static void CheckParenthesizedLambdaParameters(
            SeparatedSyntaxList<ParameterSyntax> parameterSyntaxList, BindingDiagnosticBag diagnostics)
        {
            if (parameterSyntaxList.Count > 0)
            {
                var hasTypes = parameterSyntaxList[0].Type != null;
 
                checkForImplicitDefault(hasTypes, parameterSyntaxList[0], diagnostics);
 
                for (int i = 1, n = parameterSyntaxList.Count; i < n; i++)
                {
                    var parameter = parameterSyntaxList[i];
 
                    // Ignore parameters with missing names.  We'll have already reported an error
                    // about them in the parser.
                    if (!parameter.Identifier.IsMissing)
                    {
                        var thisParameterHasType = parameter.Type != null;
 
                        if (hasTypes != thisParameterHasType)
                        {
                            diagnostics.Add(ErrorCode.ERR_InconsistentLambdaParameterUsage,
                                parameter.Type?.GetLocation() ?? parameter.Identifier.GetLocation());
                        }
 
                        checkForImplicitDefault(thisParameterHasType, parameter, diagnostics);
                    }
                }
            }
 
            static void checkForImplicitDefault(bool hasType, ParameterSyntax param, BindingDiagnosticBag diagnostics)
            {
                if (!hasType && param.Default != null)
                {
                    diagnostics.Add(ErrorCode.ERR_ImplicitlyTypedDefaultParameter,
                        param.Identifier.GetLocation(), param.Identifier.Text);
                }
            }
        }
 
        private UnboundLambda BindAnonymousFunction(AnonymousFunctionExpressionSyntax syntax, BindingDiagnosticBag diagnostics)
        {
            Debug.Assert(syntax != null);
            Debug.Assert(syntax.IsAnonymousFunction());
 
            var lambda = AnalyzeAnonymousFunction(syntax, diagnostics);
            var data = lambda.Data;
            if (data.HasExplicitlyTypedParameterList)
            {
                int firstDefault = -1;
                for (int i = 0; i < lambda.ParameterCount; i++)
                {
                    // paramSyntax should not be null here; we should always be operating on an anonymous function which will have parameter information
                    var paramSyntax = lambda.ParameterSyntax(i);
                    Debug.Assert(paramSyntax is { });
                    if (paramSyntax.Default != null && firstDefault == -1)
                    {
                        firstDefault = i;
                    }
 
                    ParameterHelpers.GetModifiers(paramSyntax.Modifiers, refnessKeyword: out _, out var paramsKeyword, thisKeyword: out _, scope: out _);
                    var isParams = paramsKeyword.Kind() != SyntaxKind.None;
 
                    // UNDONE: Where do we report improper use of pointer types?
                    ParameterHelpers.ReportParameterErrors(owner: null, paramSyntax, ordinal: i, lastParameterIndex: lambda.ParameterCount - 1, isParams: isParams, lambda.ParameterTypeWithAnnotations(i),
                         lambda.RefKind(i), containingSymbol: null, thisKeyword: default, paramsKeyword: paramsKeyword, firstDefault, diagnostics);
                }
            }
 
            // Parser will only have accepted static/async as allowed modifiers on this construct.
            // However, it may have accepted duplicates of those modifiers.  Ensure that any dupes
            // are reported now.
            ModifierUtils.ToDeclarationModifiers(syntax.Modifiers, isForTypeDeclaration: false, diagnostics.DiagnosticBag ?? new DiagnosticBag());
 
            if (data.HasSignature)
            {
                var binder = new LocalScopeBinder(this);
                bool allowShadowingNames = binder.Compilation.IsFeatureEnabled(MessageID.IDS_FeatureNameShadowingInNestedFunctions);
                var pNames = PooledHashSet<string>.GetInstance();
                bool seenDiscard = false;
 
                for (int i = 0; i < lambda.ParameterCount; i++)
                {
                    var name = lambda.ParameterName(i);
 
                    if (string.IsNullOrEmpty(name))
                    {
                        continue;
                    }
 
                    if (lambda.ParameterIsDiscard(i))
                    {
                        if (seenDiscard)
                        {
                            // We only report the diagnostic on the second and subsequent underscores
                            MessageID.IDS_FeatureLambdaDiscardParameters.CheckFeatureAvailability(
                                diagnostics,
                                binder.Compilation,
                                lambda.ParameterLocation(i));
                        }
 
                        seenDiscard = true;
                        continue;
                    }
 
                    if (!pNames.Add(name))
                    {
                        // The parameter name '{0}' is a duplicate
                        diagnostics.Add(ErrorCode.ERR_DuplicateParamName, lambda.ParameterLocation(i), name);
                    }
                    else if (!allowShadowingNames)
                    {
                        binder.ValidateLambdaParameterNameConflictsInScope(lambda.ParameterLocation(i), name, diagnostics);
                    }
                }
                pNames.Free();
            }
 
            return lambda;
        }
 
        // Please don't use thread local storage widely. This should be one of only a few uses.
        [ThreadStatic] private static PooledDictionary<SyntaxNode, int>? s_lambdaBindings;
 
        internal TResult BindWithLambdaBindingCountDiagnostics<TSyntax, TArg, TResult>(
            TSyntax syntax,
            TArg arg,
            BindingDiagnosticBag diagnostics,
            Func<Binder, TSyntax, TArg, BindingDiagnosticBag, TResult> bind)
            where TSyntax : SyntaxNode
            where TResult : BoundNode
        {
            Debug.Assert(s_lambdaBindings is null);
            var bindings = PooledDictionary<SyntaxNode, int>.GetInstance();
            s_lambdaBindings = bindings;
 
            try
            {
                TResult result = bind(this, syntax, arg, diagnostics);
 
                foreach (var pair in bindings)
                {
                    // The particular max value is arbitrary, but large enough so diagnostics should
                    // only be reported for lambda expressions used as arguments to method calls
                    // where the product of the number of applicable overloads for that method call
                    // and for overloads for any containing lambda expressions is large.
                    const int maxLambdaBinding = 100;
                    int count = pair.Value;
                    if (count > maxLambdaBinding)
                    {
                        int truncatedToHundreds = (count / 100) * 100;
                        diagnostics.Add(ErrorCode.INF_TooManyBoundLambdas, GetAnonymousFunctionLocation(pair.Key), truncatedToHundreds);
                    }
                }
 
                return result;
            }
            finally
            {
                bindings.Free();
                s_lambdaBindings = null;
            }
        }
 
        internal static void RecordLambdaBinding(SyntaxNode syntax)
        {
            var bindings = s_lambdaBindings;
            if (bindings is null)
            {
                return;
            }
            if (bindings.TryGetValue(syntax, out int count))
            {
                bindings[syntax] = ++count;
            }
            else
            {
                bindings.Add(syntax, 1);
            }
        }
    }
}