File: Lowering\ClosureConversion\SynthesizedClosureMethod.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.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.PooledObjects;
using System.Diagnostics;
using Roslyn.Utilities;
using Microsoft.CodeAnalysis.Symbols;
 
namespace Microsoft.CodeAnalysis.CSharp
{
    /// <summary>
    /// A method that results from the translation of a single lambda expression.
    /// </summary>
    internal sealed class SynthesizedClosureMethod : SynthesizedMethodBaseSymbol, ISynthesizedMethodBodyImplementationSymbol
    {
        private readonly ImmutableArray<NamedTypeSymbol> _structEnvironments;
 
        internal MethodSymbol TopLevelMethod { get; }
        internal readonly DebugId LambdaId;
 
        internal SynthesizedClosureMethod(
            NamedTypeSymbol containingType,
            ImmutableArray<SynthesizedClosureEnvironment> structEnvironments,
            ClosureKind closureKind,
            MethodSymbol topLevelMethod,
            DebugId topLevelMethodId,
            MethodSymbol originalMethod,
            SyntaxReference blockSyntax,
            DebugId lambdaId,
            TypeCompilationState compilationState)
            : base(containingType,
                   originalMethod,
                   blockSyntax,
                   originalMethod.DeclaringSyntaxReferences[0].GetLocation(),
                   originalMethod is LocalFunctionSymbol
                    ? MakeName(topLevelMethod.Name, originalMethod.Name, topLevelMethodId, closureKind, lambdaId)
                    : MakeName(topLevelMethod.Name, topLevelMethodId, closureKind, lambdaId),
                   MakeDeclarationModifiers(closureKind, originalMethod),
                   isIterator: originalMethod.IsIterator)
        {
            Debug.Assert(containingType.DeclaringCompilation is not null);
 
            TopLevelMethod = topLevelMethod;
            ClosureKind = closureKind;
            LambdaId = lambdaId;
 
            TypeMap typeMap;
            ImmutableArray<TypeParameterSymbol> typeParameters;
 
            var lambdaFrame = ContainingType as SynthesizedClosureEnvironment;
            switch (closureKind)
            {
                case ClosureKind.Singleton: // all type parameters on method (except the top level method's)
                case ClosureKind.General: // only lambda's type parameters on method (rest on class)
                    RoslynDebug.Assert(!(lambdaFrame is null));
                    typeMap = lambdaFrame.TypeMap.WithConcatAlphaRename(
                        originalMethod,
                        this,
                        out typeParameters,
                        out _,
                        lambdaFrame.OriginalContainingMethodOpt);
                    break;
                case ClosureKind.ThisOnly: // all type parameters on method
                case ClosureKind.Static:
                    RoslynDebug.Assert(lambdaFrame is null);
                    typeMap = TypeMap.Empty.WithConcatAlphaRename(
                        originalMethod,
                        this,
                        out typeParameters,
                        out _,
                        stopAt: null);
                    break;
                default:
                    throw ExceptionUtilities.UnexpectedValue(closureKind);
            }
 
            if (!structEnvironments.IsDefaultOrEmpty && typeParameters.Length != 0)
            {
                var constructedStructClosures = ArrayBuilder<NamedTypeSymbol>.GetInstance();
                foreach (var env in structEnvironments)
                {
                    NamedTypeSymbol constructed;
                    if (env.Arity == 0)
                    {
                        constructed = env;
                    }
                    else
                    {
                        var originals = env.ConstructedFromTypeParameters;
                        var newArgs = typeMap.SubstituteTypeParameters(originals);
                        constructed = env.Construct(newArgs);
                    }
                    constructedStructClosures.Add(constructed);
                }
                _structEnvironments = constructedStructClosures.ToImmutableAndFree();
            }
            else
            {
                _structEnvironments = ImmutableArray<NamedTypeSymbol>.CastUp(structEnvironments);
            }
 
            AssignTypeMapAndTypeParameters(typeMap, typeParameters);
            EnsureAttributesExist(compilationState);
 
            // static local functions should be emitted as static.
            Debug.Assert(!(originalMethod is LocalFunctionSymbol) || !originalMethod.IsStatic || IsStatic);
        }
 
        private void EnsureAttributesExist(TypeCompilationState compilationState)
        {
            var moduleBuilder = compilationState.ModuleBuilderOpt;
            if (moduleBuilder is null)
            {
                return;
            }
 
            if (RefKind == RefKind.RefReadOnly)
            {
                moduleBuilder.EnsureIsReadOnlyAttributeExists();
            }
 
            ParameterHelpers.EnsureRefKindAttributesExist(moduleBuilder, Parameters);
            // Not emitting ParamCollectionAttribute/ParamArrayAttribute for these methods because it is not a SynthesizedDelegateInvokeMethod
 
            if (moduleBuilder.Compilation.ShouldEmitNativeIntegerAttributes())
            {
                if (ReturnType.ContainsNativeIntegerWrapperType())
                {
                    moduleBuilder.EnsureNativeIntegerAttributeExists();
                }
 
                ParameterHelpers.EnsureNativeIntegerAttributeExists(moduleBuilder, Parameters);
            }
 
            ParameterHelpers.EnsureScopedRefAttributeExists(moduleBuilder, Parameters);
 
            if (compilationState.Compilation.ShouldEmitNullableAttributes(this))
            {
                if (ShouldEmitNullableContextValue(out _))
                {
                    moduleBuilder.EnsureNullableContextAttributeExists();
                }
 
                if (ReturnTypeWithAnnotations.NeedsNullableAttribute())
                {
                    moduleBuilder.EnsureNullableAttributeExists();
                }
            }
 
            ParameterHelpers.EnsureNullableAttributeExists(moduleBuilder, this, Parameters);
        }
 
        private static DeclarationModifiers MakeDeclarationModifiers(ClosureKind closureKind, MethodSymbol originalMethod)
        {
            var mods = closureKind == ClosureKind.ThisOnly ? DeclarationModifiers.Private : DeclarationModifiers.Internal;
 
            if (closureKind == ClosureKind.Static)
            {
                mods |= DeclarationModifiers.Static;
            }
 
            if (originalMethod.IsAsync)
            {
                mods |= DeclarationModifiers.Async;
            }
 
            if (originalMethod.IsExtern)
            {
                mods |= DeclarationModifiers.Extern;
            }
 
            return mods;
        }
 
        private static string MakeName(string topLevelMethodName, string localFunctionName, DebugId topLevelMethodId, ClosureKind closureKind, DebugId lambdaId)
        {
            return GeneratedNames.MakeLocalFunctionName(
                topLevelMethodName,
                localFunctionName,
                (closureKind == ClosureKind.General) ? -1 : topLevelMethodId.Ordinal,
                topLevelMethodId.Generation,
                lambdaId.Ordinal,
                lambdaId.Generation);
        }
 
        private static string MakeName(string topLevelMethodName, DebugId topLevelMethodId, ClosureKind closureKind, DebugId lambdaId)
        {
            // Lambda method name must contain the declaring method ordinal to be unique unless the method is emitted into a closure class exclusive to the declaring method.
            // Lambdas that only close over "this" are emitted directly into the top-level method containing type.
            // Lambdas that don't close over anything (static) are emitted into a shared closure singleton.
            return GeneratedNames.MakeLambdaMethodName(
                topLevelMethodName,
                (closureKind == ClosureKind.General) ? -1 : topLevelMethodId.Ordinal,
                topLevelMethodId.Generation,
                lambdaId.Ordinal,
                lambdaId.Generation);
        }
 
        // The lambda symbol might have declared no parameters in the case
        //
        // D d = delegate {};
        //
        // but there still might be parameters that need to be generated for the
        // synthetic method. If there are no lambda parameters, try the delegate 
        // parameters instead. 
        // 
        // UNDONE: In the native compiler in this scenario we make up new names for
        // UNDONE: synthetic parameters; in this implementation we use the parameter
        // UNDONE: names from the delegate. Does it really matter?
        protected override ImmutableArray<ParameterSymbol> BaseMethodParameters => this.BaseMethod.Parameters;
 
        protected override ImmutableArray<TypeSymbol> ExtraSynthesizedRefParameters
            => ImmutableArray<TypeSymbol>.CastUp(_structEnvironments);
        internal int ExtraSynthesizedParameterCount => this._structEnvironments.IsDefault ? 0 : this._structEnvironments.Length;
 
        internal override bool InheritsBaseMethodAttributes => true;
        internal override bool GenerateDebugInfo => !this.IsAsync;
 
        internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree)
        {
            // Syntax offset of a syntax node contained in a lambda body is calculated by the containing top-level method.
            // The offset is thus relative to the top-level method body start.
            return TopLevelMethod.CalculateLocalSyntaxOffset(localPosition, localTree);
        }
 
        IMethodSymbolInternal? ISynthesizedMethodBodyImplementationSymbol.Method => TopLevelMethod;
 
        // The lambda method body needs to be updated when the containing top-level method body is updated.
        bool ISynthesizedMethodBodyImplementationSymbol.HasMethodBodyDependency => true;
 
        public ClosureKind ClosureKind { get; }
 
        internal override ExecutableCodeBinder? TryGetBodyBinder(BinderFactory? binderFactoryOpt = null, bool ignoreAccessibility = false)
        {
            throw ExceptionUtilities.Unreachable();
        }
    }
}