/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
1 668 строк
80 KB
Jan Jones
Validate pooled objects in compiler tests (#82784)
15 май 2026, 09:55
Не верифицирован
15 май 2026, 09:55
e141f0d
Код
Авторство
О чём код?
// 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.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { internal interface IBoundLambdaOrFunction { MethodSymbol Symbol { get; } SyntaxNode Syntax { get; } BoundBlock? Body { get; } bool WasCompilerGenerated { get; } } internal sealed partial class BoundLocalFunctionStatement : IBoundLambdaOrFunction { MethodSymbol IBoundLambdaOrFunction.Symbol { get { return Symbol; } } SyntaxNode IBoundLambdaOrFunction.Syntax { get { return Syntax; } } BoundBlock? IBoundLambdaOrFunction.Body { get => this.Body; } } internal readonly struct InferredLambdaReturnType { internal readonly int NumExpressions; internal readonly bool IsExplicitType; internal readonly bool HadExpressionlessReturn; internal readonly RefKind RefKind; internal readonly ImmutableArray<CustomModifier> RefCustomModifiers; internal readonly TypeWithAnnotations TypeWithAnnotations; internal readonly bool InferredFromFunctionType; internal readonly ImmutableArray<DiagnosticInfo> UseSiteDiagnostics; internal readonly ImmutableArray<AssemblySymbol> Dependencies; internal InferredLambdaReturnType( int numExpressions, bool isExplicitType, bool hadExpressionlessReturn, RefKind refKind, ImmutableArray<CustomModifier> refCustomModifiers, TypeWithAnnotations typeWithAnnotations, bool inferredFromFunctionType, ImmutableArray<DiagnosticInfo> useSiteDiagnostics, ImmutableArray<AssemblySymbol> dependencies) { NumExpressions = numExpressions; IsExplicitType = isExplicitType; HadExpressionlessReturn = hadExpressionlessReturn; RefKind = refKind; RefCustomModifiers = refCustomModifiers; TypeWithAnnotations = typeWithAnnotations; InferredFromFunctionType = inferredFromFunctionType; UseSiteDiagnostics = useSiteDiagnostics; Dependencies = dependencies; } } internal sealed partial class BoundLambda : IBoundLambdaOrFunction { public MessageID MessageID { get { return Syntax.Kind() == SyntaxKind.AnonymousMethodExpression ? MessageID.IDS_AnonMethod : MessageID.IDS_Lambda; } } internal InferredLambdaReturnType InferredReturnType { get; } internal bool InAnonymousFunctionConversion { get; private set; } MethodSymbol IBoundLambdaOrFunction.Symbol { get { return Symbol; } } SyntaxNode IBoundLambdaOrFunction.Syntax { get { return Syntax; } } public BoundLambda(SyntaxNode syntax, UnboundLambda unboundLambda, BoundBlock body, ReadOnlyBindingDiagnostic<AssemblySymbol> diagnostics, Binder binder, TypeSymbol? delegateType, InferredLambdaReturnType inferredReturnType) : this(syntax, unboundLambda.WithNoCache(), (LambdaSymbol)binder.ContainingMemberOrLambda!, body, diagnostics, binder, delegateType) { InferredReturnType = inferredReturnType; Debug.Assert( syntax.IsAnonymousFunction() || // lambda expressions syntax is ExpressionSyntax && LambdaUtilities.IsLambdaBody(syntax, allowReducedLambdas: true) || // query lambdas LambdaUtilities.IsQueryPairLambda(syntax) // "pair" lambdas in queries ); } internal BoundLambda WithInAnonymousFunctionConversion() { if (InAnonymousFunctionConversion) { return this; } var result = (BoundLambda)MemberwiseClone(); result.InAnonymousFunctionConversion = true; return result; } public TypeWithAnnotations GetInferredReturnType(ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool inferredFromFunctionType) { // Nullability (and conversions) are ignored. return GetInferredReturnType(conversions: null, nullableState: null, getterNullResilienceData: null, ref useSiteInfo, out inferredFromFunctionType); } /// <summary> /// Infer return type. If `nullableState` is non-null, nullability is also inferred and `NullableWalker.Analyze` /// uses that state to set the inferred nullability of variables in the enclosing scope. `conversions` is /// only needed when nullability is inferred. /// /// If 'getterNullResilienceData' is non-null, it is propagated down to the child analysis pass, /// so that it does not attempt to infer the field's nullable annotation, while a parent pass is also attempting to infer that. /// </summary> public TypeWithAnnotations GetInferredReturnType(ConversionsBase? conversions, NullableWalker.VariableState? nullableState, NullableWalker.GetterNullResilienceData? getterNullResilienceData, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool inferredFromFunctionType) { // Cannot pass 'getterNullResilienceData' without also passing 'nullableState'. Debug.Assert(getterNullResilienceData is null || nullableState is not null); if (!InferredReturnType.UseSiteDiagnostics.IsEmpty) { useSiteInfo.AddDiagnostics(InferredReturnType.UseSiteDiagnostics); } if (!InferredReturnType.Dependencies.IsEmpty) { useSiteInfo.AddDependencies(InferredReturnType.Dependencies); } InferredLambdaReturnType inferredReturnType; if (nullableState == null || InferredReturnType.IsExplicitType) { inferredReturnType = InferredReturnType; } else { Debug.Assert(!UnboundLambda.HasExplicitReturnType(out _, out _, out _)); Debug.Assert(conversions != null); // Diagnostics from NullableWalker.Analyze can be dropped here since Analyze // will be called again from NullableWalker.ApplyConversion when the // BoundLambda is converted to an anonymous function. // https://github.com/dotnet/roslyn/issues/31752: Can we avoid generating extra // diagnostics? And is this exponential when there are nested lambdas? var returnTypes = ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>.GetInstance(); var diagnostics = DiagnosticBag.GetInstance(); var delegateType = Type.GetDelegateType(); var compilation = Binder.Compilation; NullableWalker.Analyze(compilation, lambda: this, (Conversions)conversions, diagnostics, delegateInvokeMethodOpt: delegateType?.DelegateInvokeMethod, initialState: nullableState, returnTypes, getterNullResilienceData); diagnostics.Free(); inferredReturnType = InferReturnType(returnTypes, node: this, Binder, delegateType, Symbol.IsAsync, conversions); returnTypes.Free(); } inferredFromFunctionType = inferredReturnType.InferredFromFunctionType; return inferredReturnType.TypeWithAnnotations; } internal LambdaSymbol CreateLambdaSymbol(NamedTypeSymbol delegateType, Symbol containingSymbol) => UnboundLambda.Data.CreateLambdaSymbol(delegateType, containingSymbol); internal LambdaSymbol CreateLambdaSymbol( Symbol containingSymbol, TypeWithAnnotations returnType, ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds, RefKind refKind, ImmutableArray<CustomModifier> refCustomModifiers) => UnboundLambda.Data.CreateLambdaSymbol( containingSymbol, returnType, parameterTypes, parameterRefKinds.IsDefault ? Enumerable.Repeat(RefKind.None, parameterTypes.Length).ToImmutableArray() : parameterRefKinds, refKind, refCustomModifiers); /// <summary> /// Indicates the type of return statement with no expression. Used in InferReturnType. /// </summary> internal static readonly TypeSymbol NoReturnExpression = new UnsupportedMetadataTypeSymbol(); internal static InferredLambdaReturnType InferReturnType(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, BoundLambda node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions) { Debug.Assert(!node.UnboundLambda.HasExplicitReturnType(out _, out _, out _)); return InferReturnTypeImpl(returnTypes, node, binder, delegateType, isAsync, conversions, node.UnboundLambda.WithDependencies); } internal static InferredLambdaReturnType InferReturnType(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, UnboundLambda node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions) { Debug.Assert(!node.HasExplicitReturnType(out _, out _, out _)); return InferReturnTypeImpl(returnTypes, node, binder, delegateType, isAsync, conversions, node.WithDependencies); } /// <summary> /// Behavior of this function should be kept aligned with <see cref="UnboundLambdaState.ReturnInferenceCacheKey"/>. /// </summary> private static InferredLambdaReturnType InferReturnTypeImpl(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> returnTypes, BoundNode node, Binder binder, TypeSymbol? delegateType, bool isAsync, ConversionsBase conversions, bool withDependencies) { var types = ArrayBuilder<(BoundExpression expr, TypeWithAnnotations resultType, bool isChecked)>.GetInstance(); bool hasReturnWithoutArgument = false; RefKind refKind = RefKind.None; foreach (var (returnStatement, type) in returnTypes) { RefKind rk = returnStatement.RefKind; if (rk != RefKind.None) { refKind = rk; } if ((object)type.Type == NoReturnExpression) { hasReturnWithoutArgument = true; } else { types.Add((returnStatement.ExpressionOpt!, type, returnStatement.Checked)); } } var useSiteInfo = withDependencies ? new CompoundUseSiteInfo<AssemblySymbol>(binder.Compilation.Assembly) : CompoundUseSiteInfo<AssemblySymbol>.DiscardedDependencies; var bestType = CalculateReturnType(binder, conversions, delegateType, types, isAsync, node, ref useSiteInfo, out bool inferredFromFunctionType); Debug.Assert(bestType.Type is not FunctionTypeSymbol); int numExpressions = types.Count; types.Free(); Debug.Assert(refKind != RefKind.RefReadOnly); return new InferredLambdaReturnType( numExpressions, isExplicitType: false, hadExpressionlessReturn: hasReturnWithoutArgument, refKind, refCustomModifiers: [], bestType, inferredFromFunctionType: inferredFromFunctionType, useSiteInfo.Diagnostics.AsImmutableOrEmpty(), useSiteInfo.AccumulatesDependencies ? useSiteInfo.Dependencies.AsImmutableOrEmpty() : ImmutableArray<AssemblySymbol>.Empty); } private static TypeWithAnnotations CalculateReturnType( Binder binder, ConversionsBase conversions, TypeSymbol? delegateType, ArrayBuilder<(BoundExpression expr, TypeWithAnnotations resultType, bool isChecked)> returns, bool isAsync, BoundNode node, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool inferredFromFunctionType) { TypeWithAnnotations bestResultType; int n = returns.Count; switch (n) { case 0: inferredFromFunctionType = false; bestResultType = default; break; case 1: if (conversions.IncludeNullability) { inferredFromFunctionType = false; bestResultType = returns[0].resultType; } else { var bestType = returns[0].expr.GetTypeOrFunctionType(); if (bestType is FunctionTypeSymbol functionType) { bestType = functionType.GetInternalDelegateType(); inferredFromFunctionType = bestType is { }; } else { inferredFromFunctionType = false; } bestResultType = TypeWithAnnotations.Create(bestType); } break; default: // Need to handle ref returns. See https://github.com/dotnet/roslyn/issues/30432 if (conversions.IncludeNullability) { bestResultType = NullableWalker.BestTypeForLambdaReturns(returns, binder, node, (Conversions)conversions, out inferredFromFunctionType); } else { var bestType = BestTypeInferrer.InferBestType(returns.SelectAsArray(pair => pair.expr), conversions, ref useSiteInfo, out inferredFromFunctionType); bestResultType = TypeWithAnnotations.Create(bestType); } break; } if (!isAsync) { return bestResultType; } // For async lambdas, the return type is the return type of the // delegate Invoke method if Invoke has a Task-like return type. // Otherwise the return type is Task or Task<T>. NamedTypeSymbol? taskType = null; var delegateReturnType = delegateType?.GetDelegateType()?.DelegateInvokeMethod?.ReturnType as NamedTypeSymbol; if (delegateReturnType?.IsVoidType() == false) { if (delegateReturnType.IsCustomTaskType(builderArgument: out _)) { taskType = delegateReturnType.ConstructedFrom; } } if (n == 0) { // No return statements have expressions; use delegate InvokeMethod // or infer type Task if delegate type not available. var resultType = taskType?.Arity == 0 ? taskType : binder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task); return TypeWithAnnotations.Create(resultType); } if (!bestResultType.HasType || bestResultType.IsVoidType()) { // If the best type was 'void', ERR_CantReturnVoid is reported while binding the "return void" // statement(s). return default; } // Some non-void best type T was found; use delegate InvokeMethod // or infer type Task<T> if delegate type not available. var taskTypeT = taskType?.Arity == 1 ? taskType : binder.Compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_Task_T); return TypeWithAnnotations.Create(taskTypeT.Construct(ImmutableArray.Create(bestResultType))); } internal sealed class BlockReturns : BoundTreeWalker { private readonly ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> _builder; private BlockReturns(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> builder) { _builder = builder; } public static void GetReturnTypes(ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)> builder, BoundBlock block) { var visitor = new BlockReturns(builder); visitor.Visit(block); } public override BoundNode? Visit(BoundNode node) { if (!(node is BoundExpression)) { return base.Visit(node); } return null; } protected override BoundNode VisitExpressionOrPatternWithoutStackGuard(BoundNode node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode? VisitLocalFunctionStatement(BoundLocalFunctionStatement node) { // Do not recurse into local functions; we don't want their returns. return null; } public override BoundNode? VisitReturnStatement(BoundReturnStatement node) { var expression = node.ExpressionOpt; var type = (expression is null) ? NoReturnExpression : expression.Type?.SetUnknownNullabilityForReferenceTypes(); _builder.Add((node, TypeWithAnnotations.Create(type))); return null; } } } internal partial class UnboundLambda { private readonly NullableWalker.VariableState? _nullableState; private readonly NullableWalker.GetterNullResilienceData? _getterNullResilienceData; public static UnboundLambda Create( CSharpSyntaxNode syntax, Binder binder, bool withDependencies, RefKind returnRefKind, ImmutableArray<CustomModifier> refCustomModifiers, TypeWithAnnotations returnType, ImmutableArray<SyntaxList<AttributeListSyntax>> parameterAttributes, ImmutableArray<RefKind> refKinds, ImmutableArray<ScopedKind> declaredScopes, ImmutableArray<TypeWithAnnotations> types, ImmutableArray<string> names, ImmutableArray<bool> discardsOpt, SeparatedSyntaxList<ParameterSyntax>? syntaxList, ImmutableArray<EqualsValueClauseSyntax?> defaultValues, bool isAsync, bool isStatic) { Debug.Assert(binder != null); Debug.Assert(syntax.IsAnonymousFunction()); bool hasErrors = !types.IsDefault && types.Any(static t => t.Type?.Kind == SymbolKind.ErrorType); var functionType = FunctionTypeSymbol.CreateIfFeatureEnabled(syntax, binder, static (binder, expr) => ((UnboundLambda)expr).Data.InferDelegateType()); var data = new PlainUnboundLambdaState(binder, returnRefKind, refCustomModifiers, returnType, parameterAttributes, names, discardsOpt, types, refKinds, declaredScopes, defaultValues, syntaxList, isAsync: isAsync, isStatic: isStatic, includeCache: true); var lambda = new UnboundLambda(syntax, data, functionType, withDependencies, hasErrors: hasErrors); data.SetUnboundLambda(lambda); functionType?.SetExpression(lambda.WithNoCache()); return lambda; } private UnboundLambda(SyntaxNode syntax, UnboundLambdaState state, FunctionTypeSymbol? functionType, bool withDependencies, NullableWalker.VariableState? nullableState, NullableWalker.GetterNullResilienceData? getterNullResilienceData, bool hasErrors) : this(syntax, state, functionType, withDependencies, hasErrors) { this._nullableState = nullableState; this._getterNullResilienceData = getterNullResilienceData; } internal UnboundLambda WithNullabilityInfo(NullableWalker.VariableState nullableState, NullableWalker.GetterNullResilienceData? getterNullResilienceData) { var data = Data.WithCaching(true); var lambda = new UnboundLambda(Syntax, data, FunctionType, WithDependencies, nullableState, getterNullResilienceData, HasErrors); data.SetUnboundLambda(lambda); return lambda; } internal UnboundLambda WithNoCache() { var data = Data.WithCaching(false); if ((object)data == Data) { return this; } var lambda = new UnboundLambda(Syntax, data, FunctionType, WithDependencies, _nullableState, _getterNullResilienceData, HasErrors); data.SetUnboundLambda(lambda); return lambda; } public MessageID MessageID { get { return Data.MessageID; } } public BoundLambda Bind(NamedTypeSymbol delegateType, bool isExpressionTree) => SuppressIfNeeded(Data.Bind(delegateType, isExpressionTree)); public BoundLambda BindForErrorRecovery() => SuppressIfNeeded(Data.BindForErrorRecovery()); public BoundLambda BindForReturnTypeInference(NamedTypeSymbol delegateType) => SuppressIfNeeded(Data.BindForReturnTypeInference(delegateType)); private BoundLambda SuppressIfNeeded(BoundLambda lambda) => this.IsSuppressed ? (BoundLambda)lambda.WithSuppression() : lambda; public bool HasSignature { get { return Data.HasSignature; } } public bool HasExplicitReturnType(out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out TypeWithAnnotations returnType) => Data.HasExplicitReturnType(out refKind, out refCustomModifiers, out returnType); public Binder GetWithParametersBinder(LambdaSymbol lambdaSymbol, Binder binder) => Data.GetWithParametersBinder(lambdaSymbol, binder); /// <summary> /// Whether or not the original syntax had explicit parameter list specified where all parameters had an /// explicit type syntax included. Examples of where this is true are: `() => ...` `(int a) => ...` `(int a, /// ref int b) => ...` and so on. /// /// Examples of where this is false is `a => ...` `(a) => ...` `(a, b) => ...` `(ref a) => ...` `(int a, ref b) => ...`. /// /// Note 1: in the case where some parameters have types and some do not, this will return false. That case is /// an error case and an error will have already been reported to the user. In this case, we treat the /// parameter list as if no parameter types were provided. /// /// Note 2: `(ref a) => ...` is legal. So this property should not be used to determine if a parameter should /// have its ref/scoped/attributes checked. /// </summary> public bool HasExplicitlyTypedParameterList => Data.HasExplicitlyTypedParameterList; public int ParameterCount { get { return Data.ParameterCount; } } public TypeWithAnnotations InferReturnType(ConversionsBase conversions, NamedTypeSymbol delegateType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool inferredFromFunctionType) => BindForReturnTypeInference(delegateType).GetInferredReturnType(conversions, _nullableState, _getterNullResilienceData, ref useSiteInfo, out inferredFromFunctionType); public RefKind RefKind(int index) { return Data.RefKind(index); } public ScopedKind DeclaredScope(int index) { return Data.DeclaredScope(index); } public void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diagnostics, TypeSymbol targetType) { Data.GenerateAnonymousFunctionConversionError(diagnostics, targetType); } public bool GenerateSummaryErrors(BindingDiagnosticBag diagnostics) { return Data.GenerateSummaryErrors(diagnostics); } public bool IsAsync { get { return Data.IsAsync; } } public bool IsStatic => Data.IsStatic; public SyntaxList<AttributeListSyntax> ParameterAttributes(int index) { return Data.ParameterAttributes(index); } public TypeWithAnnotations ParameterTypeWithAnnotations(int index) { return Data.ParameterTypeWithAnnotations(index); } public TypeSymbol ParameterType(int index) { return ParameterTypeWithAnnotations(index).Type; } /// <summary> /// Returns the corresponding <see cref="ParameterSyntax"/> at the given index if the lambda was declared with /// explicit parameter syntax. /// </summary> public ParameterSyntax? ParameterSyntax(int index) => Data.ParameterSyntax(index); public Location ParameterLocation(int index) { return Data.ParameterLocation(index); } public string ParameterName(int index) { return Data.ParameterName(index); } public bool ParameterIsDiscard(int index) { return Data.ParameterIsDiscard(index); } } /// <summary> /// Lambda binding state, recorded during testing only. /// </summary> internal sealed class LambdaBindingData { /// <summary> /// Number of lambdas bound. /// </summary> internal int LambdaBindingCount; } internal abstract class UnboundLambdaState { private UnboundLambda _unboundLambda = null!; // we would prefer this readonly, but we have an initialization cycle. internal readonly Binder Binder; [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Avoid " + nameof(ConcurrentDictionary<(NamedTypeSymbol, bool), BoundLambda>) + " which has a large default size, but this cache is normally small.")] private ImmutableDictionary<(NamedTypeSymbol Type, bool IsExpressionLambda), BoundLambda>? _bindingCache; [PerformanceSensitive( "https://github.com/dotnet/roslyn/issues/23582", Constraint = "Avoid " + nameof(ConcurrentDictionary<ReturnInferenceCacheKey, BoundLambda>) + " which has a large default size, but this cache is normally small.")] private ImmutableDictionary<ReturnInferenceCacheKey, BoundLambda>? _returnInferenceCache; private BoundLambda? _errorBinding; public UnboundLambdaState(Binder binder, bool includeCache) { Debug.Assert(binder != null); Debug.Assert(binder.ContainingMemberOrLambda != null); if (includeCache) { _bindingCache = ImmutableDictionary<(NamedTypeSymbol Type, bool IsExpressionLambda), BoundLambda>.Empty.WithComparers(BindingCacheComparer.Instance); _returnInferenceCache = ImmutableDictionary<ReturnInferenceCacheKey, BoundLambda>.Empty; } this.Binder = binder; } public void SetUnboundLambda(UnboundLambda unbound) { Debug.Assert(unbound != null); Debug.Assert(_unboundLambda == null || (object)_unboundLambda == unbound); _unboundLambda = unbound; } protected abstract UnboundLambdaState WithCachingCore(bool includeCache); internal UnboundLambdaState WithCaching(bool includeCache) { if ((_bindingCache == null) != includeCache) { return this; } var state = WithCachingCore(includeCache); Debug.Assert((state._bindingCache == null) != includeCache); return state; } public UnboundLambda UnboundLambda => _unboundLambda; public abstract MessageID MessageID { get; } public abstract string ParameterName(int index); public abstract bool ParameterIsDiscard(int index); public abstract SyntaxList<AttributeListSyntax> ParameterAttributes(int index); public abstract bool HasSignature { get; } public abstract bool HasExplicitReturnType(out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out TypeWithAnnotations returnType); public abstract bool HasExplicitlyTypedParameterList { get; } public abstract int ParameterCount { get; } public abstract bool IsAsync { get; } public abstract bool IsStatic { get; } public abstract Location ParameterLocation(int index); public abstract TypeWithAnnotations ParameterTypeWithAnnotations(int index); public abstract RefKind RefKind(int index); public abstract ScopedKind DeclaredScope(int index); public abstract ParameterSyntax? ParameterSyntax(int i); protected BoundBlock BindLambdaBody(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics) { if (lambdaSymbol.DeclaringCompilation?.TestOnlyCompilationData is LambdaBindingData data) { Interlocked.Increment(ref data.LambdaBindingCount); } Binder.RecordLambdaBinding(UnboundLambda.Syntax); return BindLambdaBodyCore(lambdaSymbol, lambdaBodyBinder, diagnostics); } protected abstract BoundBlock BindLambdaBodyCore(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics); /// <summary> /// Return the bound expression if the lambda has an expression body and can be reused easily. /// This is an optimization only. Implementations can return null to skip reuse. /// </summary> protected abstract BoundExpression? GetLambdaExpressionBody(BoundBlock body); /// <summary> /// Produce a bound block for the expression returned from GetLambdaExpressionBody. /// </summary> protected abstract BoundBlock CreateBlockFromLambdaExpressionBody(Binder lambdaBodyBinder, BoundExpression expression, BindingDiagnosticBag diagnostics); public virtual void GenerateAnonymousFunctionConversionError(BindingDiagnosticBag diagnostics, TypeSymbol targetType) { this.Binder.GenerateAnonymousFunctionConversionError(diagnostics, _unboundLambda.Syntax, _unboundLambda, targetType); } // Returns the inferred return type, or null if none can be inferred. public BoundLambda Bind(NamedTypeSymbol delegateType, bool isTargetExpressionTree) { bool inExpressionTree = Binder.InExpressionTree || isTargetExpressionTree; if (!_bindingCache!.TryGetValue((delegateType, inExpressionTree), out BoundLambda? result)) { result = ReallyBind(delegateType, inExpressionTree); result = ImmutableInterlocked.GetOrAdd(ref _bindingCache, (delegateType, inExpressionTree), result); } return result; } internal IEnumerable<TypeSymbol> InferredReturnTypes() { bool any = false; foreach (var lambda in _returnInferenceCache!.Values) { var type = lambda.InferredReturnType.TypeWithAnnotations; if (type.HasType) { any = true; yield return type.Type; } } if (!any) { var type = BindForErrorRecovery().InferredReturnType.TypeWithAnnotations; if (type.HasType) { yield return type.Type; } } } private static MethodSymbol? DelegateInvokeMethod(NamedTypeSymbol? delegateType) { return delegateType.GetDelegateType()?.DelegateInvokeMethod; } private static TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbol? invokeMethod, out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers) { if (invokeMethod is null) { refKind = CodeAnalysis.RefKind.None; refCustomModifiers = []; return default; } refKind = invokeMethod.RefKind; refCustomModifiers = invokeMethod.RefCustomModifiers; return invokeMethod.ReturnTypeWithAnnotations; } internal (ImmutableArray<RefKind>, ArrayBuilder<ScopedKind>, ImmutableArray<TypeWithAnnotations>, bool) CollectParameterProperties() { var parameterRefKindsBuilder = ArrayBuilder<RefKind>.GetInstance(ParameterCount); var parameterScopesBuilder = ArrayBuilder<ScopedKind>.GetInstance(ParameterCount); var parameterTypesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(ParameterCount); bool getEffectiveScopeFromSymbol = false; for (int i = 0; i < ParameterCount; i++) { var refKind = RefKind(i); var scope = DeclaredScope(i); var type = ParameterTypeWithAnnotations(i); if (scope == ScopedKind.None) { if (ParameterHelpers.IsRefScopedByDefault(Binder.UseUpdatedEscapeRules, refKind)) { scope = ScopedKind.ScopedRef; if (_unboundLambda.ParameterAttributes(i).Any()) { getEffectiveScopeFromSymbol = true; } } else if (type.IsRefLikeOrAllowsRefLikeType() && ParameterSyntax(i)?.Modifiers.Any(SyntaxKind.ParamsKeyword) == true) { scope = ScopedKind.ScopedValue; if (_unboundLambda.ParameterAttributes(i).Any()) { getEffectiveScopeFromSymbol = true; } } } else if (scope == ScopedKind.ScopedValue && _unboundLambda.ParameterAttributes(i).Any()) { getEffectiveScopeFromSymbol = true; } parameterRefKindsBuilder.Add(refKind); parameterScopesBuilder.Add(scope); parameterTypesBuilder.Add(type); } var parameterRefKinds = parameterRefKindsBuilder.ToImmutableAndFree(); var parameterTypes = parameterTypesBuilder.ToImmutableAndFree(); return (parameterRefKinds, parameterScopesBuilder, parameterTypes, getEffectiveScopeFromSymbol); } internal NamedTypeSymbol? InferDelegateType() { Debug.Assert(Binder.ContainingMemberOrLambda is { }); if (!HasExplicitlyTypedParameterList) { return null; } var (parameterRefKinds, parameterScopesBuilder, parameterTypes, getEffectiveScopeFromSymbol) = CollectParameterProperties(); var lambdaSymbol = CreateLambdaSymbol( Binder.ContainingMemberOrLambda, returnType: default, parameterTypes, parameterRefKinds, refKind: default, refCustomModifiers: []); if (!HasExplicitReturnType(out var returnRefKind, refCustomModifiers: out _, out var returnType)) { var lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, GetWithParametersBinder(lambdaSymbol, Binder)); var block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, BindingDiagnosticBag.Discarded); var returnTypes = ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>.GetInstance(); BoundLambda.BlockReturns.GetReturnTypes(returnTypes, block); var inferredReturnType = BoundLambda.InferReturnType( returnTypes, _unboundLambda, lambdaBodyBinder, delegateType: null, isAsync: IsAsync, Binder.Conversions); returnTypes.Free(); returnType = inferredReturnType.TypeWithAnnotations; returnRefKind = inferredReturnType.RefKind; if (!returnType.HasType && inferredReturnType.NumExpressions > 0) { parameterScopesBuilder.Free(); return null; } } #if !DEBUG if (getEffectiveScopeFromSymbol) #endif { for (int i = 0; i < ParameterCount; i++) { if (((DeclaredScope(i) == ScopedKind.None && parameterScopesBuilder[i] == ScopedKind.ScopedRef) || DeclaredScope(i) == ScopedKind.ScopedValue || parameterScopesBuilder[i] == ScopedKind.ScopedValue) && _unboundLambda.ParameterAttributes(i).Any()) { Debug.Assert(getEffectiveScopeFromSymbol); parameterScopesBuilder[i] = lambdaSymbol.Parameters[i].EffectiveScope; } else { Debug.Assert(lambdaSymbol.Parameters[i].EffectiveScope == parameterScopesBuilder[i]); } } } if (!returnType.HasType) { // Binder.GetMethodGroupOrLambdaDelegateType() expects a non-null return type. returnType = TypeWithAnnotations.Create(Binder.Compilation.GetSpecialType(SpecialType.System_Void)); } return Binder.GetMethodGroupOrLambdaDelegateType( _unboundLambda.Syntax, lambdaSymbol, hasParams: OverloadResolution.IsValidParams(Binder, lambdaSymbol, disallowExpandedNonArrayParams: false, out _), parameterScopesBuilder.ToImmutableAndFree(), lambdaSymbol.Parameters.SelectAsArray(p => p.HasUnscopedRefAttribute && p.UseUpdatedEscapeRules), returnRefKind, returnType); } private BoundLambda ReallyBind(NamedTypeSymbol delegateType, bool inExpressionTree) { Debug.Assert(Binder.ContainingMemberOrLambda is { }); var invokeMethod = DelegateInvokeMethod(delegateType); var returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers); LambdaSymbol lambdaSymbol; Binder lambdaBodyBinder; BoundBlock block; var diagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, _unboundLambda.WithDependencies); var compilation = Binder.Compilation; var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync); // When binding for real (not for return inference), there is still a good chance // we could reuse a body of a lambda previous bound for return type inference. // For simplicity, reuse is limited to expression-bodied lambdas. In those cases, // we reuse the bound expression and apply any conversion to the return value // since the inferred return type was not used when binding for return inference. // We don't reuse the body if we're binding in an expression tree, because we didn't // know that we were binding for an expression tree when originally binding the lambda // for return inference. // We also don't reuse the body if the value of CSharpCompilation.IsRuntimeAsyncEnabledInMethod // changed after inference with the final return type, as that will change the binding of await // calls within the lambda. For optimization purposes, we assume that if the lambda is async, it will // end up resolving to Task/ValueTask types, as that's the 95% case for C# code. If that ends up not being // true, then we need to bust the cache and rebind. if (!inExpressionTree && refKind == CodeAnalysis.RefKind.None && _returnInferenceCache!.TryGetValue(cacheKey, out BoundLambda? returnInferenceLambda) && GetLambdaExpressionBody(returnInferenceLambda.Body) is BoundExpression expression && (lambdaSymbol = (LambdaSymbol)returnInferenceLambda.Symbol).RefKind == refKind && !lambdaSymbol.RuntimeAsyncEnabledChangedDuringInference && (object)LambdaSymbol.InferenceFailureReturnType != lambdaSymbol.ReturnType && lambdaSymbol.ReturnTypeWithAnnotations.Equals(returnType, TypeCompareKind.ConsiderEverything) && lambdaSymbol.RefCustomModifiers.SequenceEqual(refCustomModifiers)) { lambdaBodyBinder = returnInferenceLambda.Binder; block = CreateBlockFromLambdaExpressionBody(lambdaBodyBinder, expression, diagnostics); diagnostics.AddRange(returnInferenceLambda.Diagnostics); } else { lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda, returnType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds, refKind, refCustomModifiers); lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, GetWithParametersBinder(lambdaSymbol, Binder), inExpressionTree ? BinderFlags.InExpressionTree : BinderFlags.None); block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics); } lambdaSymbol.GetDeclarationDiagnostics(diagnostics); if (lambdaSymbol.RefKind == CodeAnalysis.RefKind.RefReadOnly) { compilation.EnsureIsReadOnlyAttributeExists(diagnostics, lambdaSymbol.DiagnosticLocation, modifyCompilation: false); } var lambdaParameters = lambdaSymbol.Parameters; ParameterHelpers.EnsureRefKindAttributesExist(compilation, lambdaParameters, diagnostics, modifyCompilation: false); ParameterHelpers.EnsureParamCollectionAttributeExists(compilation, lambdaParameters, diagnostics, modifyCompilation: false); if (returnType.HasType) { if (compilation.ShouldEmitNativeIntegerAttributes(returnType.Type)) { compilation.EnsureNativeIntegerAttributeExists(diagnostics, lambdaSymbol.DiagnosticLocation, modifyCompilation: false); } if (compilation.ShouldEmitNullableAttributes(lambdaSymbol) && returnType.NeedsNullableAttribute()) { compilation.EnsureNullableAttributeExists(diagnostics, lambdaSymbol.DiagnosticLocation, modifyCompilation: false); // Note: we don't need to warn on annotations used in #nullable disable context for lambdas, as this is handled in binding already } } ParameterHelpers.EnsureNativeIntegerAttributeExists(compilation, lambdaParameters, diagnostics, modifyCompilation: false); ParameterHelpers.EnsureScopedRefAttributeExists(compilation, lambdaParameters, diagnostics, modifyCompilation: false); ParameterHelpers.EnsureNullableAttributeExists(compilation, lambdaSymbol, lambdaParameters, diagnostics, modifyCompilation: false); // Note: we don't need to warn on annotations used in #nullable disable context for lambdas, as this is handled in binding already ValidateUnsafeParameters(diagnostics, cacheKey.ParameterTypes); bool reachableEndpoint = ControlFlowPass.Analyze(compilation, lambdaSymbol, block, diagnostics.DiagnosticBag); if (reachableEndpoint) { if (Binder.MethodOrLambdaRequiresValue(lambdaSymbol, this.Binder.Compilation)) { // Not all code paths return a value in {0} of type '{1}' diagnostics.Add(ErrorCode.ERR_AnonymousReturnExpected, lambdaSymbol.DiagnosticLocation, this.MessageID.Localize(), delegateType); } else { block = FlowAnalysisPass.AppendImplicitReturn(block, lambdaSymbol); } } if (IsAsync && !ErrorFacts.PreventsSuccessfulDelegateConversion(diagnostics.DiagnosticBag)) { if (returnType.HasType && // Can be null if "delegateType" is not actually a delegate type. !returnType.IsVoidType() && !lambdaSymbol.IsAsyncEffectivelyReturningTask(compilation) && !lambdaSymbol.IsAsyncEffectivelyReturningGenericTask(compilation)) { // Cannot convert async {0} to delegate type '{1}'. An async {0} may return void, Task or Task<T>, none of which are convertible to '{1}'. diagnostics.Add(ErrorCode.ERR_CantConvAsyncAnonFuncReturns, lambdaSymbol.DiagnosticLocation, lambdaSymbol.MessageID.Localize(), delegateType); } } var result = new BoundLambda(_unboundLambda.Syntax, _unboundLambda, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferredReturnType: default) { WasCompilerGenerated = _unboundLambda.WasCompilerGenerated }; return result; } internal LambdaSymbol CreateLambdaSymbol( Symbol containingSymbol, TypeWithAnnotations returnType, ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds, RefKind refKind, ImmutableArray<CustomModifier> refCustomModifiers) => new LambdaSymbol( Binder, Binder.Compilation, containingSymbol, _unboundLambda, parameterTypes, parameterRefKinds, refKind, refCustomModifiers, returnType); internal LambdaSymbol CreateLambdaSymbol(NamedTypeSymbol delegateType, Symbol containingSymbol) { var invokeMethod = DelegateInvokeMethod(delegateType); var returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers); ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds, out _); return CreateLambdaSymbol(containingSymbol, returnType, parameterTypes, parameterRefKinds, refKind, refCustomModifiers); } private void ValidateUnsafeParameters(BindingDiagnosticBag diagnostics, ImmutableArray<TypeWithAnnotations> targetParameterTypes) { // It is legal to use a delegate type that has unsafe parameter types inside // a safe context if the anonymous method has no parameter list! // // unsafe delegate void D(int* p); // class C { D d = delegate {}; } // // is legal even if C is not an unsafe context because no int* is actually used. if (this.HasSignature) { // NOTE: we can get here with targetParameterTypes.Length > ParameterCount // in a case where we are binding for error reporting purposes var numParametersToCheck = Math.Min(targetParameterTypes.Length, ParameterCount); for (int i = 0; i < numParametersToCheck; i++) { if (targetParameterTypes[i].Type.ContainsPointerOrFunctionPointer()) { this.Binder.ReportUnsafeIfNotAllowed(this.ParameterLocation(i), diagnostics, disallowedUnder: MemorySafetyRules.Legacy); } } } } private BoundLambda ReallyInferReturnType( NamedTypeSymbol? delegateType, ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds) { bool hasExplicitReturnType = HasExplicitReturnType(out var refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out var returnType); (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithParameterAndReturnType(parameterTypes, parameterRefKinds, returnType, refKind, refCustomModifiers); InferredLambdaReturnType inferredReturnType; if (hasExplicitReturnType) { // The InferredLambdaReturnType fields other than RefKind and ReturnType // are only used when actually inferring a type, not when the type is explicit. inferredReturnType = new InferredLambdaReturnType( numExpressions: 0, isExplicitType: true, hadExpressionlessReturn: false, refKind, refCustomModifiers, returnType, inferredFromFunctionType: false, ImmutableArray<DiagnosticInfo>.Empty, ImmutableArray<AssemblySymbol>.Empty); } else { var returnTypes = ArrayBuilder<(BoundReturnStatement, TypeWithAnnotations)>.GetInstance(); BoundLambda.BlockReturns.GetReturnTypes(returnTypes, block); inferredReturnType = BoundLambda.InferReturnType(returnTypes, _unboundLambda, lambdaBodyBinder, delegateType, lambdaSymbol.IsAsync, lambdaBodyBinder.Conversions); // TODO: Should InferredReturnType.UseSiteDiagnostics be merged into BoundLambda.Diagnostics? refKind = inferredReturnType.RefKind; refCustomModifiers = inferredReturnType.RefCustomModifiers; returnType = inferredReturnType.TypeWithAnnotations; Debug.Assert(refKind != CodeAnalysis.RefKind.RefReadOnly); Debug.Assert(refCustomModifiers.IsEmpty); if (!returnType.HasType) { bool forErrorRecovery = delegateType is null; returnType = (forErrorRecovery && returnTypes.Count == 0) ? TypeWithAnnotations.Create(this.Binder.Compilation.GetSpecialType(SpecialType.System_Void)) : TypeWithAnnotations.Create(LambdaSymbol.InferenceFailureReturnType); } returnTypes.Free(); } var result = new BoundLambda( _unboundLambda.Syntax, _unboundLambda, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferredReturnType) { WasCompilerGenerated = _unboundLambda.WasCompilerGenerated }; if (!hasExplicitReturnType) { Debug.Assert(refKind != CodeAnalysis.RefKind.RefReadOnly); Debug.Assert(refCustomModifiers.IsEmpty); lambdaSymbol.SetInferredReturnType(refKind, returnType); } return result; } private (LambdaSymbol lambdaSymbol, BoundBlock block, ExecutableCodeBinder lambdaBodyBinder, BindingDiagnosticBag diagnostics) BindWithParameterAndReturnType( ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds, TypeWithAnnotations returnType, RefKind refKind, ImmutableArray<CustomModifier> refCustomModifiers ) { var diagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, _unboundLambda.WithDependencies); var lambdaSymbol = CreateLambdaSymbol(Binder.ContainingMemberOrLambda!, returnType, parameterTypes, parameterRefKinds, refKind, refCustomModifiers); var lambdaBodyBinder = new ExecutableCodeBinder(_unboundLambda.Syntax, lambdaSymbol, GetWithParametersBinder(lambdaSymbol, Binder)); var block = BindLambdaBody(lambdaSymbol, lambdaBodyBinder, diagnostics); lambdaSymbol.GetDeclarationDiagnostics(diagnostics); return (lambdaSymbol, block, lambdaBodyBinder, diagnostics); } public BoundLambda BindForReturnTypeInference(NamedTypeSymbol delegateType) { var cacheKey = ReturnInferenceCacheKey.Create(delegateType, IsAsync); BoundLambda? result; if (!_returnInferenceCache!.TryGetValue(cacheKey, out result)) { result = ReallyInferReturnType(delegateType, cacheKey.ParameterTypes, cacheKey.ParameterRefKinds); result = ImmutableInterlocked.GetOrAdd(ref _returnInferenceCache, cacheKey, result); } return result; } /// <summary> /// Behavior of this key should be kept aligned with <see cref="BoundLambda.InferReturnTypeImpl"/>. /// </summary> private sealed class ReturnInferenceCacheKey { public readonly ImmutableArray<TypeWithAnnotations> ParameterTypes; public readonly ImmutableArray<RefKind> ParameterRefKinds; public readonly NamedTypeSymbol? TaskLikeReturnTypeOpt; public static readonly ReturnInferenceCacheKey Empty = new ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations>.Empty, ImmutableArray<RefKind>.Empty, null); private ReturnInferenceCacheKey(ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds, NamedTypeSymbol? taskLikeReturnTypeOpt) { Debug.Assert(parameterTypes.Length == parameterRefKinds.Length); Debug.Assert(taskLikeReturnTypeOpt is null || ((object)taskLikeReturnTypeOpt == taskLikeReturnTypeOpt.ConstructedFrom && taskLikeReturnTypeOpt.IsCustomTaskType(out var builderArgument))); this.ParameterTypes = parameterTypes; this.ParameterRefKinds = parameterRefKinds; this.TaskLikeReturnTypeOpt = taskLikeReturnTypeOpt; } public override bool Equals(object? obj) { if ((object)this == obj) { return true; } var other = obj as ReturnInferenceCacheKey; if (other is null || other.ParameterTypes.Length != this.ParameterTypes.Length || !TypeSymbol.Equals(other.TaskLikeReturnTypeOpt, this.TaskLikeReturnTypeOpt, TypeCompareKind.ConsiderEverything2)) { return false; } for (int i = 0; i < this.ParameterTypes.Length; i++) { if (!other.ParameterTypes[i].Equals(this.ParameterTypes[i], TypeCompareKind.ConsiderEverything) || other.ParameterRefKinds[i] != this.ParameterRefKinds[i]) { return false; } } return true; } public override int GetHashCode() { var value = TaskLikeReturnTypeOpt?.GetHashCode() ?? 0; foreach (var type in ParameterTypes) { value = Hash.Combine(type.Type, value); } return value; } public static ReturnInferenceCacheKey Create(NamedTypeSymbol? delegateType, bool isAsync) { GetFields(delegateType, isAsync, out var parameterTypes, out var parameterRefKinds, out var taskLikeReturnTypeOpt); if (parameterTypes.IsEmpty && parameterRefKinds.IsEmpty && taskLikeReturnTypeOpt is null) { return Empty; } return new ReturnInferenceCacheKey(parameterTypes, parameterRefKinds, taskLikeReturnTypeOpt); } public static void GetFields( NamedTypeSymbol? delegateType, bool isAsync, out ImmutableArray<TypeWithAnnotations> parameterTypes, out ImmutableArray<RefKind> parameterRefKinds, out NamedTypeSymbol? taskLikeReturnTypeOpt) { // delegateType or DelegateInvokeMethod can be null in cases of malformed delegates // in such case we would want something trivial with no parameters parameterTypes = ImmutableArray<TypeWithAnnotations>.Empty; parameterRefKinds = ImmutableArray<RefKind>.Empty; taskLikeReturnTypeOpt = null; MethodSymbol? invoke = DelegateInvokeMethod(delegateType); if (invoke is not null) { int parameterCount = invoke.ParameterCount; if (parameterCount > 0) { var typesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(parameterCount); var refKindsBuilder = ArrayBuilder<RefKind>.GetInstance(parameterCount); foreach (var p in invoke.Parameters) { refKindsBuilder.Add(p.RefKind); typesBuilder.Add(p.TypeWithAnnotations); } parameterTypes = typesBuilder.ToImmutableAndFree(); parameterRefKinds = refKindsBuilder.ToImmutableAndFree(); } if (isAsync) { var delegateReturnType = invoke.ReturnType as NamedTypeSymbol; if (delegateReturnType?.IsVoidType() == false) { if (delegateReturnType.IsCustomTaskType(out var builderType)) { taskLikeReturnTypeOpt = delegateReturnType.ConstructedFrom; } } } } } } public virtual Binder GetWithParametersBinder(LambdaSymbol lambdaSymbol, Binder binder) { return new WithLambdaParametersBinder(lambdaSymbol, binder); } // UNDONE: [MattWar] // UNDONE: Here we enable the consumer of an unbound lambda that could not be // UNDONE: successfully converted to a best bound lambda to do error recovery // UNDONE: by either picking an existing binding, or by binding the body using // UNDONE: error types for parameter types as necessary. This is not exactly // UNDONE: the strategy we discussed in the design meeting; rather there we // UNDONE: decided to do this more the way we did it in the native compiler: // UNDONE: there we wrote a post-processing pass that searched the tree for // UNDONE: unbound lambdas and did this sort of replacement on them, so that // UNDONE: we never observed an unbound lambda in the tree. // UNDONE: // UNDONE: I think that is a reasonable approach but it is not implemented yet. // UNDONE: When we figure out precisely where that rewriting pass should go, // UNDONE: we can use the gear implemented in this method as an implementation // UNDONE: detail of it. // UNDONE: // UNDONE: Note: that rewriting can now be done in BindToTypeForErrorRecovery. public BoundLambda BindForErrorRecovery() { // It is possible that either (1) we never did a binding, because // we've got code like "var x = (z)=>{int y = 123; M(y, z);};" or // (2) we did a bunch of bindings but none of them turned out to // be the one we wanted. In such a situation we still want // IntelliSense to work on y in the body of the lambda, and // possibly to make a good guess as to what M means even if we // don't know the type of z. if (_errorBinding == null) { Interlocked.CompareExchange(ref _errorBinding, ReallyBindForErrorRecovery(), null); } return _errorBinding; } private BoundLambda ReallyBindForErrorRecovery() { // If we have bindings, we can use heuristics to choose one. // If not, we can assign error types to all the parameters // and bind. return GuessBestBoundLambda(_bindingCache!) ?? rebind(GuessBestBoundLambda(_returnInferenceCache!)) ?? rebind(ReallyInferReturnType(delegateType: null, ImmutableArray<TypeWithAnnotations>.Empty, ImmutableArray<RefKind>.Empty)); // Rebind a lambda to push target conversions through the return/result expressions [return: NotNullIfNotNull(nameof(lambda))] BoundLambda? rebind(BoundLambda? lambda) { if (lambda is null) return null; var delegateType = (NamedTypeSymbol?)lambda.Type; ReturnInferenceCacheKey.GetFields(delegateType, IsAsync, out var parameterTypes, out var parameterRefKinds, out _); return ReallyBindForErrorRecovery(delegateType, lambda.InferredReturnType, parameterTypes, parameterRefKinds); } } private BoundLambda ReallyBindForErrorRecovery( NamedTypeSymbol? delegateType, InferredLambdaReturnType inferredReturnType, ImmutableArray<TypeWithAnnotations> parameterTypes, ImmutableArray<RefKind> parameterRefKinds) { var returnType = inferredReturnType.TypeWithAnnotations; var refKind = inferredReturnType.RefKind; var refCustomModifiers = inferredReturnType.RefCustomModifiers; if (!returnType.HasType) { Debug.Assert(!inferredReturnType.IsExplicitType); var invokeMethod = DelegateInvokeMethod(delegateType); returnType = DelegateReturnTypeWithAnnotations(invokeMethod, out refKind, out refCustomModifiers); if (!returnType.HasType || returnType.Type.ContainsTypeParameter()) { var t = (inferredReturnType.HadExpressionlessReturn || inferredReturnType.NumExpressions == 0) ? this.Binder.Compilation.GetSpecialType(SpecialType.System_Void) : this.Binder.CreateErrorType(); returnType = TypeWithAnnotations.Create(t); refKind = CodeAnalysis.RefKind.None; } } (var lambdaSymbol, var block, var lambdaBodyBinder, var diagnostics) = BindWithParameterAndReturnType(parameterTypes, parameterRefKinds, returnType, refKind, refCustomModifiers); return new BoundLambda( _unboundLambda.Syntax, _unboundLambda, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, new InferredLambdaReturnType( inferredReturnType.NumExpressions, isExplicitType: inferredReturnType.IsExplicitType, inferredReturnType.HadExpressionlessReturn, refKind, refCustomModifiers, returnType, inferredFromFunctionType: inferredReturnType.InferredFromFunctionType, ImmutableArray<DiagnosticInfo>.Empty, ImmutableArray<AssemblySymbol>.Empty)) { WasCompilerGenerated = _unboundLambda.WasCompilerGenerated }; } private static BoundLambda? GuessBestBoundLambda<T>(ImmutableDictionary<T, BoundLambda> candidates) where T : notnull { switch (candidates.Count) { case 0: return null; case 1: return candidates.First().Value; default: // Prefer candidates with fewer diagnostics. IEnumerable<KeyValuePair<T, BoundLambda>> minDiagnosticsGroup = candidates.GroupBy(lambda => lambda.Value.Diagnostics.Diagnostics.Length).OrderBy(group => group.Key).First(); // If multiple candidates have the same number of diagnostics, order them by delegate type name. // It's not great, but it should be stable. return minDiagnosticsGroup .OrderBy(lambda => GetLambdaSortString((LambdaSymbol)lambda.Value.Symbol)) .FirstOrDefault() .Value; } } private static string GetLambdaSortString(LambdaSymbol lambda) { var builder = PooledStringBuilder.GetInstance(); foreach (var parameter in lambda.Parameters) { builder.Builder.Append(parameter.ToDisplayString(SymbolDisplayFormat.CSharpErrorMessageNoParameterNamesFormat)); } if (lambda.ReturnTypeWithAnnotations.HasType) { builder.Builder.Append(lambda.ReturnTypeWithAnnotations.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); } var result = builder.ToStringAndFree(); return result; } public bool GenerateSummaryErrors(BindingDiagnosticBag diagnostics) { // It is highly likely that "the same" error will be given for two different // bindings of the same lambda but with different values for the parameters // of the error. For example, if we have x=>x.Blah() where x could be int // or string, then the two errors will be "int does not have member Blah" and // "string does not have member Blah", but the locations and errors numbers // will be the same. // // We should first see if there is a set of errors that are "the same" by // this definition that occur in every lambda binding; if there are then // those are the errors we should report. // // If there are no errors that are common to *every* binding then we // can report the complete set of errors produced by every binding. However, // we still wish to avoid duplicates, so we will use the same logic for // building the union as the intersection; two errors with the same code // and location are to be treated as the same error and only reported once, // regardless of how that error is parameterized. // // The question then rears its head: when given two of "the same" error // to report that are nevertheless different in their arguments, which one // do we choose? To the user it hardly matters; either one points to the // right location in source code. But it surely matters to our testing team; // we do not want to be in a position where some small change to our internal // representation of lambdas causes tests to break because errors are reported // differently. // // What we need to do is find a *repeatable* arbitrary way to choose between // two errors; we can for example simply take the one that is lower in alphabetical // order when converted to a string. var convBags = from boundLambda in _bindingCache select boundLambda.Value.Diagnostics; var retBags = from boundLambda in _returnInferenceCache!.Values select boundLambda.Diagnostics; var allBags = convBags.Concat(retBags); FirstAmongEqualsSet<Diagnostic>? intersection = null; foreach (ReadOnlyBindingDiagnostic<AssemblySymbol> bag in allBags) { if (intersection == null) { intersection = CreateFirstAmongEqualsSet(bag.Diagnostics); } else { intersection.IntersectWith(bag.Diagnostics); } } if (intersection != null) { if (PreventsSuccessfulDelegateConversion(intersection)) { diagnostics.AddRange(intersection); return true; } } FirstAmongEqualsSet<Diagnostic>? union = null; foreach (ReadOnlyBindingDiagnostic<AssemblySymbol> bag in allBags) { if (union == null) { union = CreateFirstAmongEqualsSet(bag.Diagnostics); } else { union.UnionWith(bag.Diagnostics); } } if (union != null) { if (PreventsSuccessfulDelegateConversion(union)) { diagnostics.AddRange(union); return true; } } return false; } private static bool PreventsSuccessfulDelegateConversion(FirstAmongEqualsSet<Diagnostic> set) { foreach (var diagnostic in set) { if (ErrorFacts.PreventsSuccessfulDelegateConversion((ErrorCode)diagnostic.Code)) { return true; } } return false; } private static FirstAmongEqualsSet<Diagnostic> CreateFirstAmongEqualsSet(ImmutableArray<Diagnostic> bag) { // For the purposes of lambda error reporting we wish to compare // diagnostics for equality only considering their code and location, // but not other factors such as the values supplied for the // parameters of the diagnostic. return new FirstAmongEqualsSet<Diagnostic>( bag, CommonDiagnosticComparer.Instance, CanonicallyCompareDiagnostics); } /// <summary> /// What we need to do is find a *repeatable* arbitrary way to choose between /// two errors; we can for example simply take the one whose arguments are lower in alphabetical /// order when converted to a string. As an optimization, we compare error codes /// first and skip string comparison if they differ. /// </summary> private static int CanonicallyCompareDiagnostics(Diagnostic x, Diagnostic y) { // Optimization: don't bother if (x.Code != y.Code) return x.Code - y.Code; var nx = x.Arguments?.Count ?? 0; var ny = y.Arguments?.Count ?? 0; for (int i = 0, n = Math.Min(nx, ny); i < n; i++) { object? argx = x.Arguments![i]; object? argy = y.Arguments![i]; int argCompare = string.CompareOrdinal(argx?.ToString(), argy?.ToString()); if (argCompare != 0) return argCompare; } return nx - ny; } private sealed class BindingCacheComparer : IEqualityComparer<(NamedTypeSymbol Type, bool IsExpressionTree)> { public static readonly BindingCacheComparer Instance = new BindingCacheComparer(); public bool Equals([AllowNull] (NamedTypeSymbol Type, bool IsExpressionTree) x, [AllowNull] (NamedTypeSymbol Type, bool IsExpressionTree) y) => x.IsExpressionTree == y.IsExpressionTree && Symbol.Equals(x.Type, y.Type, TypeCompareKind.ConsiderEverything); public int GetHashCode([DisallowNull] (NamedTypeSymbol Type, bool IsExpressionTree) obj) => Hash.Combine(obj.Type, obj.IsExpressionTree.GetHashCode()); } } internal sealed class PlainUnboundLambdaState : UnboundLambdaState { private readonly RefKind _returnRefKind; private readonly ImmutableArray<CustomModifier> _refCustomModifiers; private readonly TypeWithAnnotations _returnType; private readonly ImmutableArray<SyntaxList<AttributeListSyntax>> _parameterAttributes; private readonly ImmutableArray<string> _parameterNames; private readonly ImmutableArray<bool> _parameterIsDiscardOpt; private readonly ImmutableArray<TypeWithAnnotations> _parameterTypesWithAnnotations; private readonly ImmutableArray<RefKind> _parameterRefKinds; private readonly ImmutableArray<ScopedKind> _parameterDeclaredScopes; private readonly ImmutableArray<EqualsValueClauseSyntax?> _defaultValues; private readonly SeparatedSyntaxList<ParameterSyntax>? _parameterSyntaxList; private readonly bool _isAsync; private readonly bool _isStatic; internal PlainUnboundLambdaState( Binder binder, RefKind returnRefKind, ImmutableArray<CustomModifier> refCustomModifiers, TypeWithAnnotations returnType, ImmutableArray<SyntaxList<AttributeListSyntax>> parameterAttributes, ImmutableArray<string> parameterNames, ImmutableArray<bool> parameterIsDiscardOpt, ImmutableArray<TypeWithAnnotations> parameterTypesWithAnnotations, ImmutableArray<RefKind> parameterRefKinds, ImmutableArray<ScopedKind> parameterDeclaredScopes, ImmutableArray<EqualsValueClauseSyntax?> defaultValues, SeparatedSyntaxList<ParameterSyntax>? parameterSyntaxList, bool isAsync, bool isStatic, bool includeCache) : base(binder, includeCache) { _returnRefKind = returnRefKind; _refCustomModifiers = refCustomModifiers; _returnType = returnType; _parameterAttributes = parameterAttributes; _parameterNames = parameterNames; _parameterIsDiscardOpt = parameterIsDiscardOpt; _parameterTypesWithAnnotations = parameterTypesWithAnnotations; _parameterRefKinds = parameterRefKinds; _parameterDeclaredScopes = parameterDeclaredScopes; _defaultValues = defaultValues; _parameterSyntaxList = parameterSyntaxList; _isAsync = isAsync; _isStatic = isStatic; } public override bool HasSignature { get { return !_parameterNames.IsDefault; } } public override bool HasExplicitReturnType(out RefKind refKind, out ImmutableArray<CustomModifier> refCustomModifiers, out TypeWithAnnotations returnType) { refKind = _returnRefKind; refCustomModifiers = _refCustomModifiers; returnType = _returnType; return _returnType.HasType; } public override bool HasExplicitlyTypedParameterList { get { return !_parameterTypesWithAnnotations.IsDefault; } } public override int ParameterCount { get { return _parameterNames.IsDefault ? 0 : _parameterNames.Length; } } public override bool IsAsync { get { return _isAsync; } } public override bool IsStatic => _isStatic; public override MessageID MessageID { get { return this.UnboundLambda.Syntax.Kind() == SyntaxKind.AnonymousMethodExpression ? MessageID.IDS_AnonMethod : MessageID.IDS_Lambda; } } private CSharpSyntaxNode Body { get { return UnboundLambda.Syntax.AnonymousFunctionBody(); } } public override Location ParameterLocation(int index) { Debug.Assert(HasSignature && 0 <= index && index < ParameterCount); var syntax = UnboundLambda.Syntax; switch (syntax.Kind()) { default: case SyntaxKind.SimpleLambdaExpression: return ((SimpleLambdaExpressionSyntax)syntax).Parameter.Identifier.GetLocation(); case SyntaxKind.ParenthesizedLambdaExpression: return ((ParenthesizedLambdaExpressionSyntax)syntax).ParameterList.Parameters[index].Identifier.GetLocation(); case SyntaxKind.AnonymousMethodExpression: return ((AnonymousMethodExpressionSyntax)syntax).ParameterList!.Parameters[index].Identifier.GetLocation(); } } private bool IsExpressionLambda { get { return Body.Kind() != SyntaxKind.Block; } } public override SyntaxList<AttributeListSyntax> ParameterAttributes(int index) { return _parameterAttributes.IsDefault ? default : _parameterAttributes[index]; } public override string ParameterName(int index) { Debug.Assert(!_parameterNames.IsDefault && 0 <= index && index < _parameterNames.Length); return _parameterNames[index]; } public override bool ParameterIsDiscard(int index) { Debug.Assert(0 <= index && index < this.ParameterCount); return _parameterIsDiscardOpt.IsDefault ? false : _parameterIsDiscardOpt[index]; } public override RefKind RefKind(int index) { Debug.Assert(0 <= index && index < this.ParameterCount); return _parameterRefKinds.IsDefault ? Microsoft.CodeAnalysis.RefKind.None : _parameterRefKinds[index]; } public override ScopedKind DeclaredScope(int index) { Debug.Assert(0 <= index && index < this.ParameterCount); return _parameterDeclaredScopes.IsDefault ? ScopedKind.None : _parameterDeclaredScopes[index]; } public override ParameterSyntax? ParameterSyntax(int index) { Debug.Assert(0 <= index && index < this.ParameterCount); return _parameterSyntaxList?[index]; } public override TypeWithAnnotations ParameterTypeWithAnnotations(int index) { Debug.Assert(this.HasExplicitlyTypedParameterList); Debug.Assert(0 <= index && index < _parameterTypesWithAnnotations.Length); return _parameterTypesWithAnnotations[index]; } protected override UnboundLambdaState WithCachingCore(bool includeCache) { return new PlainUnboundLambdaState(Binder, _returnRefKind, _refCustomModifiers, _returnType, _parameterAttributes, _parameterNames, _parameterIsDiscardOpt, _parameterTypesWithAnnotations, _parameterRefKinds, _parameterDeclaredScopes, _defaultValues, _parameterSyntaxList, isAsync: _isAsync, isStatic: _isStatic, includeCache: includeCache); } protected override BoundExpression? GetLambdaExpressionBody(BoundBlock body) { if (IsExpressionLambda) { var statements = body.Statements; if (statements.Length == 1 && // To simplify Binder.CreateBlockFromExpression (used below), we only reuse by-value return values. statements[0] is BoundReturnStatement { RefKind: Microsoft.CodeAnalysis.RefKind.None, ExpressionOpt: BoundExpression expr }) { return expr; } } return null; } protected override BoundBlock CreateBlockFromLambdaExpressionBody(Binder lambdaBodyBinder, BoundExpression expression, BindingDiagnosticBag diagnostics) { return lambdaBodyBinder.CreateBlockFromExpression((ExpressionSyntax)this.Body, expression, diagnostics); } protected override BoundBlock BindLambdaBodyCore(LambdaSymbol lambdaSymbol, Binder lambdaBodyBinder, BindingDiagnosticBag diagnostics) { if (this.IsExpressionLambda) { return lambdaBodyBinder.BindLambdaExpressionAsBlock((ExpressionSyntax)this.Body, diagnostics); } else { return lambdaBodyBinder.BindEmbeddedBlock((BlockSyntax)this.Body, diagnostics); } } } }