|
// 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.Immutable;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
// Suppose we have class C<T> { void M<U>(T, U) {}} and additional class types X and Y.
// C<> is a NamedTypeSymbol.
// C<>.M<> is a MethodSymbol.
// C<X> is a ConstructedTypeSymbol.
// C<X>.M<> is a SubstitutedMethodSymbol. It has parameters of types X and U.
// C<X>.M<Y> is a ConstructedMethodSymbol.
internal class SubstitutedMethodSymbol : WrappedMethodSymbol
{
private readonly Symbol _containingSymbol;
private readonly MethodSymbol _underlyingMethod;
private readonly TypeMap _inputMap;
private readonly MethodSymbol _constructedFrom;
private TypeWithAnnotations.Boxed _lazyReturnType;
private ImmutableArray<ParameterSymbol> _lazyParameters;
private TypeMap _lazyMap;
private ImmutableArray<TypeParameterSymbol> _lazyTypeParameters;
//we want to compute these lazily since it may be expensive for the underlying symbol
private ImmutableArray<MethodSymbol> _lazyExplicitInterfaceImplementations;
private OverriddenOrHiddenMembersResult _lazyOverriddenOrHiddenMembers;
private int _hashCode; // computed on demand
internal SubstitutedMethodSymbol(NamedTypeSymbol containingSymbol, MethodSymbol originalDefinition)
: this(containingSymbol, containingSymbol.TypeSubstitution, originalDefinition, constructedFrom: null)
{
Debug.Assert(containingSymbol is SubstitutedNamedTypeSymbol || containingSymbol is SubstitutedErrorTypeSymbol);
Debug.Assert(TypeSymbol.Equals(originalDefinition.ContainingType, containingSymbol.OriginalDefinition, TypeCompareKind.ConsiderEverything2));
}
protected SubstitutedMethodSymbol(Symbol containingSymbol, TypeMap map, MethodSymbol originalDefinition, MethodSymbol constructedFrom)
{
Debug.Assert((object)originalDefinition != null);
Debug.Assert(originalDefinition.IsDefinition);
_containingSymbol = containingSymbol;
_underlyingMethod = originalDefinition;
_inputMap = map;
if ((object)constructedFrom != null)
{
_constructedFrom = constructedFrom;
Debug.Assert(ReferenceEquals(constructedFrom.ConstructedFrom, constructedFrom));
_lazyTypeParameters = constructedFrom.TypeParameters;
_lazyMap = map;
}
else
{
_constructedFrom = this;
}
}
public override MethodSymbol UnderlyingMethod
{
get
{
return _underlyingMethod;
}
}
public override MethodSymbol ConstructedFrom
{
get
{
return _constructedFrom;
}
}
private TypeMap Map
{
get
{
EnsureMapAndTypeParameters();
return _lazyMap;
}
}
public sealed override ImmutableArray<TypeParameterSymbol> TypeParameters
{
get
{
EnsureMapAndTypeParameters();
return _lazyTypeParameters;
}
}
private void EnsureMapAndTypeParameters()
{
if (!_lazyTypeParameters.IsDefault)
{
return;
}
ImmutableArray<TypeParameterSymbol> typeParameters;
Debug.Assert(ReferenceEquals(_constructedFrom, this));
// We're creating a new unconstructed Method from another; alpha-rename type parameters.
var newMap = _inputMap.WithAlphaRename(this.OriginalDefinition, this, out typeParameters);
var prevMap = Interlocked.CompareExchange(ref _lazyMap, newMap, null);
if (prevMap != null)
{
// There is a race with another thread who has already set the map
// need to ensure that typeParameters, matches the map
typeParameters = prevMap.SubstituteTypeParameters(this.OriginalDefinition.TypeParameters);
}
ImmutableInterlocked.InterlockedCompareExchange(ref _lazyTypeParameters, typeParameters, default(ImmutableArray<TypeParameterSymbol>));
Debug.Assert(_lazyTypeParameters != null);
}
public sealed override AssemblySymbol ContainingAssembly
{
get
{
return OriginalDefinition.ContainingAssembly;
}
}
public override ImmutableArray<TypeWithAnnotations> TypeArgumentsWithAnnotations
{
get
{
return GetTypeParametersAsTypeArguments();
}
}
public sealed override MethodSymbol OriginalDefinition
{
get
{
return _underlyingMethod;
}
}
internal sealed override MethodSymbol CallsiteReducedFromMethod
{
get
{
var method = OriginalDefinition.ReducedFrom;
return ((object)method == null) ? null : method.Construct(this.TypeArgumentsWithAnnotations);
}
}
public override TypeSymbol ReceiverType
{
get
{
var reduced = this.CallsiteReducedFromMethod;
if ((object)reduced == null)
{
return this.ContainingType;
}
return reduced.Parameters[0].Type;
}
}
public override TypeSymbol GetTypeInferredDuringReduction(TypeParameterSymbol reducedFromTypeParameter)
{
// This will throw if API shouldn't be supported or there is a problem with the argument.
var notUsed = OriginalDefinition.GetTypeInferredDuringReduction(reducedFromTypeParameter);
Debug.Assert((object)notUsed == null && (object)OriginalDefinition.ReducedFrom != null);
return this.TypeArgumentsWithAnnotations[reducedFromTypeParameter.Ordinal].Type;
}
public sealed override MethodSymbol ReducedFrom
{
get
{
return OriginalDefinition.ReducedFrom;
}
}
public sealed override Symbol ContainingSymbol
{
get
{
return _containingSymbol;
}
}
public override NamedTypeSymbol ContainingType
{
get
{
return _containingSymbol is NamedTypeSymbol nt ? nt : _containingSymbol.ContainingType;
}
}
public sealed override ImmutableArray<CSharpAttributeData> GetAttributes()
{
return this.OriginalDefinition.GetAttributes();
}
public override ImmutableArray<CSharpAttributeData> GetReturnTypeAttributes()
{
return this.OriginalDefinition.GetReturnTypeAttributes();
}
internal sealed override UnmanagedCallersOnlyAttributeData GetUnmanagedCallersOnlyAttributeData(bool forceComplete)
=> this.OriginalDefinition.GetUnmanagedCallersOnlyAttributeData(forceComplete);
public sealed override Symbol AssociatedSymbol
{
get
{
Symbol underlying = OriginalDefinition.AssociatedSymbol;
return ((object)underlying == null) ? null : underlying.SymbolAsMember(ContainingType);
}
}
public sealed override TypeWithAnnotations ReturnTypeWithAnnotations
{
get
{
if (_lazyReturnType == null)
{
var returnType = Map.SubstituteType(OriginalDefinition.ReturnTypeWithAnnotations);
Interlocked.CompareExchange(ref _lazyReturnType, new TypeWithAnnotations.Boxed(returnType), null);
}
return _lazyReturnType.Value;
}
}
public sealed override ImmutableArray<CustomModifier> RefCustomModifiers
{
get
{
return Map.SubstituteCustomModifiers(OriginalDefinition.RefCustomModifiers);
}
}
public sealed override ImmutableArray<ParameterSymbol> Parameters
{
get
{
if (_lazyParameters.IsDefault)
{
ImmutableInterlocked.InterlockedInitialize(ref _lazyParameters, SubstituteParameters());
}
return _lazyParameters;
}
}
internal sealed override bool IsExplicitInterfaceImplementation
{
get { return this.OriginalDefinition.IsExplicitInterfaceImplementation; }
}
public sealed override ImmutableArray<MethodSymbol> ExplicitInterfaceImplementations
{
get
{
if (!ReferenceEquals(this.ConstructedFrom, this))
{
return ImmutableArray<MethodSymbol>.Empty;
}
if (_lazyExplicitInterfaceImplementations.IsDefault)
{
ImmutableInterlocked.InterlockedCompareExchange(
ref _lazyExplicitInterfaceImplementations,
ExplicitInterfaceHelpers.SubstituteExplicitInterfaceImplementations(this.OriginalDefinition.ExplicitInterfaceImplementations, Map),
default(ImmutableArray<MethodSymbol>));
}
return _lazyExplicitInterfaceImplementations;
}
}
internal sealed override OverriddenOrHiddenMembersResult OverriddenOrHiddenMembers
{
get
{
if (_lazyOverriddenOrHiddenMembers == null)
{
// We need to compute the overridden or hidden members for this type, rather than applying
// our type map to those of the underlying type, because the substitution may have introduced
// ambiguities.
Interlocked.CompareExchange(ref _lazyOverriddenOrHiddenMembers, this.MakeOverriddenOrHiddenMembers(), null);
}
return _lazyOverriddenOrHiddenMembers;
}
}
internal sealed override bool CallsAreOmitted(SyntaxTree syntaxTree)
{
return OriginalDefinition.CallsAreOmitted(syntaxTree);
}
internal sealed override TypeMap TypeSubstitution
{
get { return this.Map; }
}
internal sealed override bool TryGetThisParameter(out ParameterSymbol thisParameter)
{
// Required in EE scenarios. Specifically, the EE binds in the context of a
// substituted method, whereas the core compiler always binds within the
// context of an original definition.
// There should never be any reason to call this in normal compilation
// scenarios, but the behavior should be sensible if it does occur.
ParameterSymbol originalThisParameter;
if (!OriginalDefinition.TryGetThisParameter(out originalThisParameter))
{
thisParameter = null;
return false;
}
thisParameter = (object)originalThisParameter != null
? new ThisParameterSymbol(this)
: null;
return true;
}
private ImmutableArray<ParameterSymbol> SubstituteParameters()
{
var unsubstitutedParameters = OriginalDefinition.Parameters;
int count = unsubstitutedParameters.Length;
if (count == 0)
{
return ImmutableArray<ParameterSymbol>.Empty;
}
else
{
var substituted = ArrayBuilder<ParameterSymbol>.GetInstance(count);
TypeMap map = Map;
foreach (var p in unsubstitutedParameters)
{
substituted.Add(new SubstitutedParameterSymbol(this, map, p));
}
return substituted.ToImmutableAndFree();
}
}
internal override int CalculateLocalSyntaxOffset(int localPosition, SyntaxTree localTree)
{
throw ExceptionUtilities.Unreachable();
}
internal override bool IsNullableAnalysisEnabled() => throw ExceptionUtilities.Unreachable();
private int ComputeHashCode()
{
int code = this.OriginalDefinition.GetHashCode();
int containingHashCode;
// If the containing type of the original definition is the same as our containing type
// it's possible that we will compare equal to the original definition under certain conditions
// (e.g, ignoring nullability) and want to retain the same hashcode. As such, consider only
// the original definition for the hashcode when we know equality is possible
containingHashCode = _containingSymbol.GetHashCode();
if (containingHashCode == this.OriginalDefinition.ContainingType.GetHashCode() &&
wasConstructedForAnnotations(this))
{
return code;
}
code = Hash.Combine(containingHashCode, code);
// Unconstructed methods may contain alpha-renamed type parameters while
// still be considered equal; we do not want to give a different hashcode to such types.
//
// Example:
// Having original method A<U>.Goo<V>() we create two _unconstructed_ methods
// A<int>.Goo<V'>
// A<int>.Goo<V">
// Note that V' and V" are type parameters substituted via alpha-renaming of original V
// These are different objects, but represent the same "type parameter at index 1"
//
// In short - we are not interested in the type arguments of unconstructed methods.
if ((object)ConstructedFrom != (object)this)
{
foreach (var arg in this.TypeArgumentsWithAnnotations)
{
code = Hash.Combine(arg.Type, code);
}
}
// 0 means that hashcode is not initialized.
// in a case we really get 0 for the hashcode, tweak it by +1
if (code == 0)
{
code++;
}
return code;
static bool wasConstructedForAnnotations(SubstitutedMethodSymbol method)
{
var typeArguments = method.TypeArgumentsWithAnnotations;
var typeParameters = method.OriginalDefinition.TypeParameters;
for (int i = 0; i < typeArguments.Length; i++)
{
if (!typeParameters[i].Equals(
typeArguments[i].Type,
TypeCompareKind.ConsiderEverything))
{
return false;
}
}
return true;
}
}
public sealed override bool Equals(Symbol obj, TypeCompareKind compareKind)
{
MethodSymbol other = obj as MethodSymbol;
if ((object)other == null) return false;
if ((object)this.OriginalDefinition != (object)other.OriginalDefinition &&
this.OriginalDefinition != other.OriginalDefinition)
{
return false;
}
// This checks if the methods have the same definition and the type parameters on the containing types have been
// substituted in the same way.
if (!TypeSymbol.Equals(this.ContainingType, other.ContainingType, compareKind)) return false;
// If both are declarations, then we don't need to check type arguments
// If exactly one is a declaration, then they re not equal
bool selfIsDeclaration = (object)this == (object)this.ConstructedFrom;
bool otherIsDeclaration = (object)other == (object)other.ConstructedFrom;
// PERF: VSadov specifically replaced the short-circuited operators in changeset #24717.
if (selfIsDeclaration | otherIsDeclaration)
{
return selfIsDeclaration & otherIsDeclaration;
}
// This checks if the type parameters on the method itself have been substituted in the same way.
int arity = this.Arity;
for (int i = 0; i < arity; i++)
{
if (!this.TypeArgumentsWithAnnotations[i].Equals(other.TypeArgumentsWithAnnotations[i], compareKind))
{
return false;
}
}
return true;
}
public override int GetHashCode()
{
int code = _hashCode;
if (code == 0)
{
code = ComputeHashCode();
_hashCode = code;
}
return code;
}
internal sealed override bool HasAsyncMethodBuilderAttribute(out TypeSymbol builderArgument)
{
return _underlyingMethod.HasAsyncMethodBuilderAttribute(out builderArgument);
}
}
}
|