File: Symbols\ReducedExtensionMethodSymbol.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
    /// <summary>
    /// An extension method with the "this" parameter removed.
    /// Used for the public binding API only, not for compilation.
    /// </summary>
    internal sealed class ReducedExtensionMethodSymbol : MethodSymbol
    {
        private readonly MethodSymbol _reducedFrom;
        private readonly TypeMap _typeMap;
        private readonly ImmutableArray<TypeParameterSymbol> _typeParameters;
        private readonly ImmutableArray<TypeWithAnnotations> _typeArguments;
        private ImmutableArray<ParameterSymbol> _lazyParameters;
 
        /// <summary>
        /// Return the extension method in reduced form if the extension method
        /// is applicable, and satisfies type parameter constraints, based on the
        /// "this" argument type. Otherwise, returns null.
        /// </summary>
        /// <param name="compilation">Compilation used to check constraints.
        /// The latest language version is assumed if this is null.</param>
        public static MethodSymbol Create(MethodSymbol method, TypeSymbol receiverType, CSharpCompilation compilation, out bool wasFullyInferred)
        {
            Debug.Assert(method.IsExtensionMethod && method.MethodKind != MethodKind.ReducedExtension);
            Debug.Assert(method.ParameterCount > 0);
            Debug.Assert((object)receiverType != null);
 
            var useSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.DiscardedDependencies;
 
            method = InferExtensionMethodTypeArguments(method, receiverType, compilation, ref useSiteInfo, out wasFullyInferred);
            if ((object)method == null)
            {
                return null;
            }
 
            var conversions = compilation?.Conversions ?? (ConversionsBase)method.ContainingAssembly.CorLibrary.TypeConversions;
            var conversion = conversions.ConvertExtensionMethodThisArg(method.Parameters[0].Type, receiverType, ref useSiteInfo, isMethodGroupConversion: false);
            if (!conversion.Exists)
            {
                return null;
            }
 
            if (useSiteInfo.Diagnostics != null)
            {
                foreach (var diag in useSiteInfo.Diagnostics)
                {
                    if (diag.Severity == DiagnosticSeverity.Error)
                    {
                        return null;
                    }
                }
            }
 
            return Create(method);
        }
 
        public static MethodSymbol Create(MethodSymbol method)
        {
            Debug.Assert(method.IsExtensionMethod && method.MethodKind != MethodKind.ReducedExtension);
 
            // The reduced form is always created from the unconstructed method symbol.
            var constructedFrom = method.ConstructedFrom;
            var reducedMethod = new ReducedExtensionMethodSymbol(constructedFrom);
 
            if (constructedFrom == method)
            {
                return reducedMethod;
            }
 
            // If the given method is a constructed method, the same type arguments
            // are applied to construct the result from the reduced form.
            Debug.Assert(!method.TypeArgumentsWithAnnotations.IsEmpty);
            return reducedMethod.Construct(method.TypeArgumentsWithAnnotations);
        }
 
        private ReducedExtensionMethodSymbol(MethodSymbol reducedFrom)
        {
            Debug.Assert((object)reducedFrom != null);
            Debug.Assert(reducedFrom.IsExtensionMethod);
            Debug.Assert((object)reducedFrom.ReducedFrom == null);
            Debug.Assert(reducedFrom.ConstructedFrom == reducedFrom);
            Debug.Assert(reducedFrom.ParameterCount > 0);
 
            _reducedFrom = reducedFrom;
            _typeMap = TypeMap.Empty.WithAlphaRename(reducedFrom, this, out _typeParameters);
            _typeArguments = _typeMap.SubstituteTypes(reducedFrom.TypeArgumentsWithAnnotations);
        }
 
        /// <summary>
        /// If the extension method is applicable based on the "this" argument type, return
        /// the method constructed with the inferred type arguments. If the method is not an
        /// unconstructed generic method, type inference is skipped. If the method is not
        /// applicable, or if constraints when inferring type parameters from the "this" type
        /// are not satisfied, the return value is null.
        /// </summary>
        /// <param name="compilation">Compilation used to check constraints.  The latest language version is assumed if this is null.</param>
        internal static MethodSymbol InferExtensionMethodTypeArguments(MethodSymbol method, TypeSymbol thisType, CSharpCompilation compilation,
            ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out bool wasFullyInferred)
        {
            Debug.Assert(method.IsExtensionMethod);
            Debug.Assert(method.MethodKind != MethodKind.ReducedExtension);
            Debug.Assert(method.ParameterCount > 0);
            Debug.Assert((object)thisType != null);
 
            if (!method.IsGenericMethod || method != method.ConstructedFrom)
            {
                wasFullyInferred = true;
                return method;
            }
 
            // We never resolve extension methods on a dynamic receiver.
            if (thisType.IsDynamic())
            {
                wasFullyInferred = false;
                return null;
            }
 
            var containingAssembly = method.ContainingAssembly;
            var errorNamespace = containingAssembly.GlobalNamespace;
            var conversions = containingAssembly.CorLibrary.TypeConversions;
 
            // There is absolutely no plausible syntax/tree that we could use for these
            // synthesized literals.  We could be speculatively binding a call to a PE method.
            var syntaxTree = CSharpSyntaxTree.Dummy;
            var syntax = (CSharpSyntaxNode)syntaxTree.GetRoot();
 
            // Create an argument value for the "this" argument of specific type,
            // and pass the same bad argument value for all other arguments.
            var thisArgumentValue = new BoundLiteral(syntax, ConstantValue.Bad, thisType) { WasCompilerGenerated = true };
            var otherArgumentType = new ExtendedErrorTypeSymbol(errorNamespace, name: string.Empty, arity: 0, errorInfo: null, unreported: false);
            var otherArgumentValue = new BoundLiteral(syntax, ConstantValue.Bad, otherArgumentType) { WasCompilerGenerated = true };
 
            var paramCount = method.ParameterCount;
            var arguments = new BoundExpression[paramCount];
 
            for (int i = 0; i < paramCount; i++)
            {
                var argument = (i == 0) ? thisArgumentValue : otherArgumentValue;
                arguments[i] = argument;
            }
 
            var typeArgs = MethodTypeInferrer.InferTypeArgumentsFromFirstArgument(
                compilation,
                conversions,
                method,
                arguments.AsImmutable(),
                useSiteInfo: ref useSiteInfo);
 
            if (typeArgs.IsDefault)
            {
                wasFullyInferred = false;
                return null;
            }
 
            // For the purpose of constraint checks we use error type symbol in place of type arguments that we couldn't infer from the first argument.
            // This prevents constraint checking from failing for corresponding type parameters.
            int firstNullInTypeArgs = -1;
            var notInferredTypeParameters = PooledHashSet<TypeParameterSymbol>.GetInstance();
            var typeParams = method.TypeParameters;
            var typeArgsForConstraintsCheck = typeArgs;
            for (int i = 0; i < typeArgsForConstraintsCheck.Length; i++)
            {
                if (!typeArgsForConstraintsCheck[i].HasType)
                {
                    firstNullInTypeArgs = i;
                    var builder = ArrayBuilder<TypeWithAnnotations>.GetInstance();
                    builder.AddRange(typeArgsForConstraintsCheck, firstNullInTypeArgs);
 
                    for (; i < typeArgsForConstraintsCheck.Length; i++)
                    {
                        var typeArg = typeArgsForConstraintsCheck[i];
                        if (!typeArg.HasType)
                        {
                            notInferredTypeParameters.Add(typeParams[i]);
                            builder.Add(TypeWithAnnotations.Create(ErrorTypeSymbol.UnknownResultType));
                        }
                        else
                        {
                            builder.Add(typeArg);
                        }
                    }
 
                    typeArgsForConstraintsCheck = builder.ToImmutableAndFree();
                    break;
                }
            }
 
            // Check constraints.
            var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance();
            var substitution = new TypeMap(typeParams, typeArgsForConstraintsCheck);
            ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null;
            var success = method.CheckConstraints(new ConstraintsHelper.CheckConstraintsArgs(compilation, conversions, includeNullability: false, NoLocation.Singleton, diagnostics: null, template: new CompoundUseSiteInfo<AssemblySymbol>(useSiteInfo)),
                                                  substitution, typeParams, typeArgsForConstraintsCheck, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: null,
                                                  ref useSiteDiagnosticsBuilder,
                                                  ignoreTypeConstraintsDependentOnTypeParametersOpt: notInferredTypeParameters.Count > 0 ? notInferredTypeParameters : null);
            diagnosticsBuilder.Free();
            notInferredTypeParameters.Free();
 
            if (useSiteDiagnosticsBuilder != null && useSiteDiagnosticsBuilder.Count > 0)
            {
                foreach (var diag in useSiteDiagnosticsBuilder)
                {
                    useSiteInfo.Add(diag.UseSiteInfo);
                }
            }
 
            if (!success)
            {
                wasFullyInferred = false;
                return null;
            }
 
            // For the purpose of construction we use original type parameters in place of type arguments that we couldn't infer from the first argument.
            ImmutableArray<TypeWithAnnotations> typeArgsForConstruct = typeArgs;
            wasFullyInferred = typeArgs.All(static t => t.HasType);
            if (!wasFullyInferred)
            {
                typeArgsForConstruct = typeArgs.ZipAsArray(
                    method.TypeParameters,
                    (t, tp) => t.HasType ? t : TypeWithAnnotations.Create(tp));
            }
 
            return method.Construct(typeArgsForConstruct);
        }
 
        internal override MethodSymbol CallsiteReducedFromMethod
        {
            get { return _reducedFrom.ConstructIfGeneric(_typeArguments); }
        }
 
        public override TypeSymbol ReceiverType
        {
            get
            {
                return _reducedFrom.Parameters[0].Type;
            }
        }
 
        internal override CodeAnalysis.NullableAnnotation ReceiverNullableAnnotation =>
            _reducedFrom.Parameters[0].TypeWithAnnotations.ToPublicAnnotation();
 
        public override TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
        {
            if ((object)reducedFromTypeParameter == null)
            {
                throw new System.ArgumentNullException();
            }
 
            if (reducedFromTypeParameter.ContainingSymbol != _reducedFrom)
            {
                throw new System.ArgumentException();
            }
 
            return null;
        }
 
        public override MethodSymbol ReducedFrom
        {
            get { return _reducedFrom; }
        }
 
        public override MethodSymbol ConstructedFrom
        {
            get
            {
                Debug.Assert(_reducedFrom.ConstructedFrom == _reducedFrom);
                return this;
            }
        }
 
        public override ImmutableArray<TypeParameterSymbol> TypeParameters
        {
            get { return _typeParameters; }
        }
 
        public override ImmutableArray<TypeWithAnnotations> TypeArgumentsWithAnnotations
        {
            get { return _typeArguments; }
        }
 
        internal override Microsoft.Cci.CallingConvention CallingConvention
        {
            get { return _reducedFrom.CallingConvention; }
        }
 
        public override int Arity
        {
            get { return _reducedFrom.Arity; }
        }
 
        public override string Name
        {
            get { return _reducedFrom.Name; }
        }
 
        internal override bool HasSpecialName
        {
            get { return _reducedFrom.HasSpecialName; }
        }
 
        internal override System.Reflection.MethodImplAttributes ImplementationAttributes
        {
            get { return _reducedFrom.ImplementationAttributes; }
        }
 
        internal override bool RequiresSecurityObject
        {
            get { return _reducedFrom.RequiresSecurityObject; }
        }
 
        public override DllImportData GetDllImportData()
        {
            return _reducedFrom.GetDllImportData();
        }
 
        public override bool AreLocalsZeroed
        {
            get { throw ExceptionUtilities.Unreachable(); }
        }
 
        internal override MarshalPseudoCustomAttributeData ReturnValueMarshallingInformation
        {
            get { return _reducedFrom.ReturnValueMarshallingInformation; }
        }
 
        internal override bool HasDeclarativeSecurity
        {
            get { return _reducedFrom.HasDeclarativeSecurity; }
        }
 
        internal override IEnumerable<Microsoft.Cci.SecurityAttribute> GetSecurityInformation()
        {
            return _reducedFrom.GetSecurityInformation();
        }
 
        internal override ImmutableArray<string> GetAppliedConditionalSymbols()
        {
            return _reducedFrom.GetAppliedConditionalSymbols();
        }
 
        public override AssemblySymbol ContainingAssembly
        {
            get { return _reducedFrom.ContainingAssembly; }
        }
 
        public override ImmutableArray<Location> Locations
        {
            get { return _reducedFrom.Locations; }
        }
 
        public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences
        {
            get { return _reducedFrom.DeclaringSyntaxReferences; }
        }
 
        public override string GetDocumentationCommentXml(CultureInfo preferredCulture = null, bool expandIncludes = false, CancellationToken cancellationToken = default(CancellationToken))
        {
            return _reducedFrom.GetDocumentationCommentXml(preferredCulture, expandIncludes, cancellationToken);
        }
 
        public override MethodSymbol OriginalDefinition
        {
            get { return this; }
        }
 
        public override bool IsExtern
        {
            get { return _reducedFrom.IsExtern; }
        }
 
        public override bool IsSealed
        {
            get { return _reducedFrom.IsSealed; }
        }
 
        public override bool IsVirtual
        {
            get { return _reducedFrom.IsVirtual; }
        }
 
        public override bool IsAbstract
        {
            get { return _reducedFrom.IsAbstract; }
        }
 
        public override bool IsOverride
        {
            get { return _reducedFrom.IsOverride; }
        }
 
        public override bool IsStatic
        {
            get { return false; }
        }
 
        public override bool IsAsync
        {
            get { return _reducedFrom.IsAsync; }
        }
 
        public override bool IsExtensionMethod
        {
            get { return true; }
        }
 
        internal sealed override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false)
        {
            return false;
        }
 
        internal sealed override bool IsMetadataVirtual(IsMetadataVirtualOption option = IsMetadataVirtualOption.None)
        {
            return false;
        }
 
        internal override bool IsMetadataFinal
        {
            get
            {
                return false;
            }
        }
 
        internal sealed override ObsoleteAttributeData ObsoleteAttributeData
        {
            get { return _reducedFrom.ObsoleteAttributeData; }
        }
 
        internal sealed override UnmanagedCallersOnlyAttributeData GetUnmanagedCallersOnlyAttributeData(bool forceComplete)
            => _reducedFrom.GetUnmanagedCallersOnlyAttributeData(forceComplete);
 
        public override Accessibility DeclaredAccessibility
        {
            get { return _reducedFrom.DeclaredAccessibility; }
        }
 
        public override Symbol ContainingSymbol
        {
            get { return _reducedFrom.ContainingSymbol; }
        }
 
        public override ImmutableArray<CSharpAttributeData> GetAttributes()
        {
            return _reducedFrom.GetAttributes();
        }
 
        public override Symbol AssociatedSymbol
        {
            get { return null; }
        }
 
        public override MethodKind MethodKind
        {
            get { return MethodKind.ReducedExtension; }
        }
 
        public override bool ReturnsVoid
        {
            get { return _reducedFrom.ReturnsVoid; }
        }
 
        public override bool IsGenericMethod
        {
            get { return _reducedFrom.IsGenericMethod; }
        }
 
        public override bool IsVararg
        {
            get { return _reducedFrom.IsVararg; }
        }
 
        public override RefKind RefKind
        {
            get { return _reducedFrom.RefKind; }
        }
 
        public override TypeWithAnnotations ReturnTypeWithAnnotations
        {
            get { return _typeMap.SubstituteType(_reducedFrom.ReturnTypeWithAnnotations); }
        }
 
        public override FlowAnalysisAnnotations ReturnTypeFlowAnalysisAnnotations => _reducedFrom.ReturnTypeFlowAnalysisAnnotations;
 
        public override ImmutableHashSet<string> ReturnNotNullIfParameterNotNull => _reducedFrom.ReturnNotNullIfParameterNotNull;
 
        public override FlowAnalysisAnnotations FlowAnalysisAnnotations => _reducedFrom.FlowAnalysisAnnotations;
 
        public override ImmutableArray<CustomModifier> RefCustomModifiers
        {
            get { return _typeMap.SubstituteCustomModifiers(_reducedFrom.RefCustomModifiers); }
        }
 
        internal override int ParameterCount
        {
            get { return _reducedFrom.ParameterCount - 1; }
        }
 
        internal override bool GenerateDebugInfo
        {
            get { return _reducedFrom.GenerateDebugInfo; }
        }
 
        public override ImmutableArray<ParameterSymbol> Parameters
        {
            get
            {
                if (_lazyParameters.IsDefault)
                {
                    ImmutableInterlocked.InterlockedInitialize(ref _lazyParameters, this.MakeParameters());
                }
                return _lazyParameters;
            }
        }
 
        internal override bool IsExplicitInterfaceImplementation
        {
            get { return false; }
        }
 
        internal override bool IsDeclaredReadOnly => false;
 
        internal override bool IsInitOnly => false;
 
        internal override bool IsEffectivelyReadOnly => _reducedFrom.Parameters[0].RefKind is RefKind.In or RefKind.RefReadOnlyParameter;
 
        public override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
        {
            get { return ImmutableArray<MethodSymbol>.Empty; }
        }
 
        public override bool HidesBaseMethodsByName
        {
            get { return false; }
        }
 
        internal override bool CallsAreOmitted(SyntaxTree syntaxTree)
        {
            return _reducedFrom.CallsAreOmitted(syntaxTree);
        }
 
        private ImmutableArray<ParameterSymbol> MakeParameters()
        {
            var reducedFromParameters = _reducedFrom.Parameters;
            int count = reducedFromParameters.Length;
 
            if (count <= 1)
            {
                Debug.Assert(count == 1);
                return ImmutableArray<ParameterSymbol>.Empty;
            }
            else
            {
                var parameters = ArrayBuilder<ParameterSymbol>.GetInstance(count - 1);
                for (int i = 0; i < count - 1; i++)
                {
                    parameters.Add(new ReducedExtensionMethodParameterSymbol(this, reducedFromParameters[i + 1]));
                }
 
                return parameters.ToImmutableAndFree();
            }
        }
 
        internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree)
        {
            throw ExceptionUtilities.Unreachable();
        }
 
        internal override bool IsNullableAnalysisEnabled() => throw ExceptionUtilities.Unreachable();
 
        public override bool Equals(Symbol obj, TypeCompareKind compareKind)
        {
            if ((object)this == obj) return true;
 
            ReducedExtensionMethodSymbol other = obj as ReducedExtensionMethodSymbol;
            return (object)other != null && _reducedFrom.Equals(other._reducedFrom, compareKind);
        }
 
        public override int GetHashCode()
        {
            return _reducedFrom.GetHashCode();
        }
 
        protected sealed override bool HasSetsRequiredMembersImpl => throw ExceptionUtilities.Unreachable();
 
        internal sealed override bool HasUnscopedRefAttribute => false;
 
        internal sealed override bool UseUpdatedEscapeRules => _reducedFrom.UseUpdatedEscapeRules;
 
        internal sealed override bool HasAsyncMethodBuilderAttribute(out TypeSymbol builderArgument)
        {
            return _reducedFrom.HasAsyncMethodBuilderAttribute(out builderArgument);
        }
 
#nullable enable
 
        internal override int? TryGetOverloadResolutionPriority()
        {
            return _reducedFrom.TryGetOverloadResolutionPriority();
        }
 
        private sealed class ReducedExtensionMethodParameterSymbol : WrappedParameterSymbol
        {
            private readonly ReducedExtensionMethodSymbol _containingMethod;
 
            public ReducedExtensionMethodParameterSymbol(ReducedExtensionMethodSymbol containingMethod, ParameterSymbol underlyingParameter) :
                base(underlyingParameter)
            {
                Debug.Assert(containingMethod != null);
                Debug.Assert(underlyingParameter.Ordinal > 0);
                _containingMethod = containingMethod;
            }
 
            public override Symbol ContainingSymbol
            {
                get { return _containingMethod; }
            }
 
            public override int Ordinal
            {
                get { return this._underlyingParameter.Ordinal - 1; }
            }
 
            public override TypeWithAnnotations TypeWithAnnotations
            {
                get { return _containingMethod._typeMap.SubstituteType(this._underlyingParameter.TypeWithAnnotations); }
            }
 
            public override ImmutableArray<CustomModifier> RefCustomModifiers
            {
                get
                {
                    return _containingMethod._typeMap.SubstituteCustomModifiers(this._underlyingParameter.RefCustomModifiers);
                }
            }
 
            internal override bool IsCallerLineNumber
            {
                // ReducedExtensionMethodParameterSymbol is only exposed to semantic model.
                get { throw ExceptionUtilities.Unreachable(); }
            }
 
            internal override bool IsCallerFilePath
            {
                // ReducedExtensionMethodParameterSymbol is only exposed to semantic model.
                get { throw ExceptionUtilities.Unreachable(); }
            }
 
            internal override bool IsCallerMemberName
            {
                // ReducedExtensionMethodParameterSymbol is only exposed to semantic model.
                get { throw ExceptionUtilities.Unreachable(); }
            }
 
            internal override int CallerArgumentExpressionParameterIndex
            {
                // ReducedExtensionMethodParameterSymbol is only exposed to semantic model.
                get { throw ExceptionUtilities.Unreachable(); }
            }
 
            internal override ImmutableArray<int> InterpolatedStringHandlerArgumentIndexes => throw ExceptionUtilities.Unreachable();
 
            internal override bool HasInterpolatedStringHandlerArgumentError => throw ExceptionUtilities.Unreachable();
 
            public sealed override bool Equals(Symbol obj, TypeCompareKind compareKind)
            {
                if ((object)this == obj)
                {
                    return true;
                }
 
                // Equality of ordinal and containing symbol is a correct
                // implementation for all ParameterSymbols, but we don't 
                // define it on the base type because most can simply use
                // ReferenceEquals.
 
                var other = obj as ReducedExtensionMethodParameterSymbol;
                return other is not null &&
                    this.Ordinal == other.Ordinal &&
                    this.ContainingSymbol.Equals(other.ContainingSymbol, compareKind);
            }
 
            public sealed override int GetHashCode()
            {
                return Hash.Combine(ContainingSymbol, _underlyingParameter.Ordinal);
            }
        }
    }
}