// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
///
/// Represents a type parameter in a generic type or generic method.
///
internal abstract partial class TypeParameterSymbol : TypeSymbol, ITypeParameterSymbol
{
///
/// The original definition of this symbol. If this symbol is constructed from another
/// symbol by type substitution then OriginalDefinition gets the original symbol as it was defined in
/// source or metadata.
///
public new virtual TypeParameterSymbol OriginalDefinition
{
get
{
return this;
}
}
protected override sealed TypeSymbol OriginalTypeSymbolDefinition
{
get
{
return this.OriginalDefinition;
}
}
///
/// If this is a type parameter of a reduced extension method, gets the type parameter definition that
/// this type parameter was reduced from. Otherwise, returns Nothing.
///
public virtual TypeParameterSymbol ReducedFrom
{
get
{
return null;
}
}
///
/// The ordinal position of the type parameter in the parameter list which declares
/// it. The first type parameter has ordinal zero.
///
public abstract int Ordinal
{
// This is needed to determine hiding in C#:
//
// interface IB { void M(C x); }
// interface ID : IB { new void M(C x); }
//
// ID.M hides IB.M even though their formal parameters have different
// types. When comparing formal parameter types for hiding purposes we must
// compare method type parameters by ordinal, not by identity.
get;
}
internal virtual DiagnosticInfo GetConstraintsUseSiteErrorInfo()
{
return null;
}
///
/// The types that were directly specified as constraints on the type parameter.
/// Duplicates and cycles are removed, although the collection may include
/// redundant constraints where one constraint is a base type of another.
///
internal ImmutableArray GetConstraintTypesNoUseSiteDiagnostics(ConsList inProgress, bool early)
{
// We could call EnsureAllConstraintsAreResolved(early) directly rather than splitting
// this into two separate calls. However, the split between early and late must be explicit
// somewhere to ensure that the early work for all sibling type parameters is completed
// before the late work for any sibling. It seems simpler to handle the split here (in the
// one caller of EnsureAllConstraintsAreResolved that supports the late phase) rather than
// in several implementations of the abstract EnsureAllConstraintsAreResolved method.
this.EnsureAllConstraintsAreResolved(early: true);
if (!early)
{
this.EnsureAllConstraintsAreResolved(early);
}
return this.GetConstraintTypes(inProgress, early);
}
internal ImmutableArray ConstraintTypesNoUseSiteDiagnostics =>
GetConstraintTypesNoUseSiteDiagnostics(ConsList.Empty, early: false);
internal ImmutableArray ConstraintTypesWithDefinitionUseSiteDiagnostics(ref HashSet useSiteDiagnostics)
{
var result = ConstraintTypesNoUseSiteDiagnostics;
AppendConstraintsUseSiteErrorInfo(ref useSiteDiagnostics);
foreach (var constraint in result)
{
((TypeSymbol)constraint.TypeSymbol.OriginalDefinition).AddUseSiteDiagnostics(ref useSiteDiagnostics);
}
return result;
}
private void AppendConstraintsUseSiteErrorInfo(ref HashSet useSiteDiagnostics)
{
DiagnosticInfo errorInfo = this.GetConstraintsUseSiteErrorInfo();
if ((object)errorInfo != null)
{
if (useSiteDiagnostics == null)
{
useSiteDiagnostics = new HashSet();
}
useSiteDiagnostics.Add(errorInfo);
}
}
///
/// True if the parameterless constructor constraint was specified for the type parameter.
///
public abstract bool HasConstructorConstraint { get; }
///
/// The type parameter kind of this type parameter.
///
public abstract TypeParameterKind TypeParameterKind { get; }
///
/// The method that declared this type parameter, or null.
///
public MethodSymbol DeclaringMethod
{
get
{
return this.ContainingSymbol as MethodSymbol;
}
}
///
/// The type that declared this type parameter, or null.
///
public NamedTypeSymbol DeclaringType
{
get
{
return this.ContainingSymbol as NamedTypeSymbol;
}
}
// Type parameters do not have members
public sealed override ImmutableArray GetMembers()
{
return ImmutableArray.Empty;
}
// Type parameters do not have members
public sealed override ImmutableArray GetMembers(string name)
{
return ImmutableArray.Empty;
}
// Type parameters do not have members
public sealed override ImmutableArray GetTypeMembers()
{
return ImmutableArray.Empty;
}
// Type parameters do not have members
public sealed override ImmutableArray GetTypeMembers(string name)
{
return ImmutableArray.Empty;
}
// Type parameters do not have members
public sealed override ImmutableArray GetTypeMembers(string name, int arity)
{
return ImmutableArray.Empty;
}
internal override TResult Accept(CSharpSymbolVisitor visitor, TArgument argument)
{
return visitor.VisitTypeParameter(this, argument);
}
public override void Accept(CSharpSymbolVisitor visitor)
{
visitor.VisitTypeParameter(this);
}
public override TResult Accept(CSharpSymbolVisitor visitor)
{
return visitor.VisitTypeParameter(this);
}
public sealed override SymbolKind Kind
{
get
{
return SymbolKind.TypeParameter;
}
}
public sealed override TypeKind TypeKind
{
get
{
return TypeKind.TypeParameter;
}
}
// Only the compiler can create TypeParameterSymbols.
internal TypeParameterSymbol()
{
}
public sealed override Accessibility DeclaredAccessibility
{
get
{
return Accessibility.NotApplicable;
}
}
public sealed override bool IsStatic
{
get
{
return false;
}
}
public sealed override bool IsAbstract
{
get
{
return false;
}
}
public sealed override bool IsSealed
{
get
{
return false;
}
}
internal sealed override NamedTypeSymbol BaseTypeNoUseSiteDiagnostics => null;
internal sealed override ImmutableArray InterfacesNoUseSiteDiagnostics(ConsList basesBeingResolved = null)
{
return ImmutableArray.Empty;
}
protected override ImmutableArray GetAllInterfaces()
{
return ImmutableArray.Empty;
}
///
/// The effective base class of the type parameter (spec 10.1.5). If the deduced
/// base type is a reference type, the effective base type will be the same as
/// the deduced base type. Otherwise if the deduced base type is a value type,
/// the effective base type will be the most derived reference type from which
/// deduced base type is derived.
///
internal NamedTypeSymbol EffectiveBaseClassNoUseSiteDiagnostics
{
get
{
this.EnsureAllConstraintsAreResolved(early: false);
return this.GetEffectiveBaseClass(ConsList.Empty);
}
}
internal NamedTypeSymbol EffectiveBaseClass(ref HashSet useSiteDiagnostics)
{
AppendConstraintsUseSiteErrorInfo(ref useSiteDiagnostics);
var result = EffectiveBaseClassNoUseSiteDiagnostics;
if ((object)result != null)
{
result.OriginalDefinition.AddUseSiteDiagnostics(ref useSiteDiagnostics);
}
return result;
}
///
/// The effective interface set (spec 10.1.5).
///
internal ImmutableArray EffectiveInterfacesNoUseSiteDiagnostics
{
get
{
this.EnsureAllConstraintsAreResolved(early: false);
return this.GetInterfaces(ConsList.Empty);
}
}
///
/// The most encompassed type (spec 6.4.2) from the constraints.
///
internal TypeSymbol DeducedBaseTypeNoUseSiteDiagnostics
{
get
{
this.EnsureAllConstraintsAreResolved(early: false);
return this.GetDeducedBaseType(ConsList.Empty);
}
}
internal TypeSymbol DeducedBaseType(ref HashSet useSiteDiagnostics)
{
AppendConstraintsUseSiteErrorInfo(ref useSiteDiagnostics);
var result = DeducedBaseTypeNoUseSiteDiagnostics;
if ((object)result != null)
{
((TypeSymbol)result.OriginalDefinition).AddUseSiteDiagnostics(ref useSiteDiagnostics);
}
return result;
}
///
/// The effective interface set and any base interfaces of those
/// interfaces. This is AllInterfaces excluding interfaces that are
/// only implemented by the effective base type.
///
internal ImmutableArray AllEffectiveInterfacesNoUseSiteDiagnostics
{
get
{
return base.GetAllInterfaces();
}
}
internal ImmutableArray AllEffectiveInterfacesWithDefinitionUseSiteDiagnostics(ref HashSet useSiteDiagnostics)
{
var result = AllEffectiveInterfacesNoUseSiteDiagnostics;
// Since bases affect content of AllInterfaces set, we need to make sure they all are good.
var current = DeducedBaseType(ref useSiteDiagnostics);
while ((object)current != null)
{
current = current.BaseTypeWithDefinitionUseSiteDiagnostics(ref useSiteDiagnostics);
}
foreach (var iface in result)
{
iface.OriginalDefinition.AddUseSiteDiagnostics(ref useSiteDiagnostics);
}
return result;
}
///
/// Called by , , , and .
/// to allow derived classes to ensure constraints within the containing
/// type or method are resolved in a consistent order, regardless of the
/// order the callers query individual type parameters.
/// The `early` parameter indicates whether the constraints can be from
/// the initial phase of constraint checking where the constraint types may
/// still contain invalid or duplicate types.
///
internal abstract void EnsureAllConstraintsAreResolved(bool early);
///
/// Helper method to force type parameter constraints to be resolved.
///
protected static void EnsureAllConstraintsAreResolved(ImmutableArray typeParameters, bool early)
{
foreach (var typeParameter in typeParameters)
{
// Invoke any method that forces constraints to be resolved.
var unused = typeParameter.GetConstraintTypes(ConsList.Empty, early);
}
}
internal abstract ImmutableArray GetConstraintTypes(ConsList inProgress, bool early);
internal abstract ImmutableArray GetInterfaces(ConsList inProgress);
internal abstract NamedTypeSymbol GetEffectiveBaseClass(ConsList inProgress);
internal abstract TypeSymbol GetDeducedBaseType(ConsList inProgress);
private bool ConstraintImpliesReferenceType(TypeSymbol constraint, ConsList inProgress)
{
if (constraint.TypeKind == TypeKind.TypeParameter)
{
var typeParameter = ((TypeParameterSymbol)constraint);
if (inProgress.ContainsReference(typeParameter))
{
return false;
}
var constraints = typeParameter.GetConstraintTypesNoUseSiteDiagnostics(inProgress, early: true);
return IsReferenceTypeFromConstraintTypes(constraints, inProgress);
}
else if (!constraint.IsReferenceType)
{
return false;
}
else
{
switch (constraint.TypeKind)
{
case TypeKind.Interface:
return false; // can be satisfied by value types
case TypeKind.Error:
return false;
}
switch (constraint.SpecialType)
{
case SpecialType.System_Object:
case SpecialType.System_ValueType:
case SpecialType.System_Enum:
return false; // can be satisfied by value types
}
return true;
}
}
// From typedesc.cpp :
// > A recursive helper that helps determine whether this variable is constrained as ObjRef.
// > Please note that we do not check the gpReferenceTypeConstraint special constraint here
// > because this property does not propagate up the constraining hierarchy.
// > (e.g. "class A where S : T, where T : class" does not guarantee that S is ObjRef)
internal bool IsReferenceTypeFromConstraintTypes(ImmutableArray constraintTypes, ConsList inProgress)
{
return AnyConstraintTypes(constraintTypes, inProgress, (type, arg) => ConstraintImpliesReferenceType(type.TypeSymbol, arg));
}
internal static bool? IsNotNullableIfReferenceTypeFromConstraintTypes(ImmutableArray constraintTypes)
{
Debug.Assert(!constraintTypes.IsDefaultOrEmpty);
bool? result = false;
foreach (TypeSymbolWithAnnotations constraintType in constraintTypes)
{
bool? fromType = IsNotNullableIfReferenceTypeFromConstraintType(constraintType);
if (fromType == true)
{
return true;
}
else if (fromType == null)
{
result = null;
}
}
return result;
}
internal static bool? IsNotNullableIfReferenceTypeFromConstraintType(TypeSymbolWithAnnotations constraintType)
{
if (constraintType.IsAnnotated)
{
return false;
}
if (constraintType.TypeKind == TypeKind.TypeParameter)
{
bool? isNotNullableIfReferenceType = ((TypeParameterSymbol)constraintType.TypeSymbol).GetIsNotNullableIfReferenceType();
if (isNotNullableIfReferenceType == false)
{
return false;
}
else if (isNotNullableIfReferenceType == null)
{
return null;
}
}
if (constraintType.NonNullTypesContext.NonNullTypes == true)
{
return true;
}
return null;
}
internal bool IsValueTypeFromConstraintTypes(ImmutableArray constraintTypes, ConsList inProgress)
{
return AnyConstraintTypes(constraintTypes, inProgress, (type, arg) => type.GetIsValueType(arg));
}
private bool AnyConstraintTypes(
ImmutableArray constraintTypes,
ConsList inProgress,
Func, bool> predicate)
{
if (constraintTypes.IsEmpty)
{
return false;
}
inProgress = inProgress.Prepend(this);
foreach (var constraintType in constraintTypes)
{
if (predicate(constraintType, inProgress))
{
return true;
}
}
return false;
}
internal bool GetIsReferenceType(ConsList inProgress)
{
if (inProgress.ContainsReference(this))
{
return false;
}
if (this.HasReferenceTypeConstraint)
{
return true;
}
return IsReferenceTypeFromConstraintTypes(this.GetConstraintTypesNoUseSiteDiagnostics(inProgress, early: true), inProgress);
}
public sealed override bool IsReferenceType => GetIsReferenceType(ConsList.Empty);
internal bool? GetIsNotNullableIfReferenceType()
{
bool? fromReferenceTypeConstraint = false;
if (this.HasReferenceTypeConstraint)
{
fromReferenceTypeConstraint = !this.ReferenceTypeConstraintIsNullable;
if (fromReferenceTypeConstraint == true)
{
return true;
}
}
ImmutableArray constraintTypes = this.ConstraintTypesNoUseSiteDiagnostics;
if (constraintTypes.IsEmpty)
{
return fromReferenceTypeConstraint;
}
bool? fromTypes = IsNotNullableIfReferenceTypeFromConstraintTypes(constraintTypes);
if (fromTypes == true || fromReferenceTypeConstraint == false)
{
return fromTypes;
}
Debug.Assert(fromReferenceTypeConstraint == null);
Debug.Assert(fromTypes != true);
return null;
}
// https://github.com/dotnet/roslyn/issues/26198 Should this API be exposed through ITypeParameterSymbol?
internal bool? IsNotNullableIfReferenceType => GetIsNotNullableIfReferenceType();
internal bool GetIsValueType(ConsList inProgress)
{
if (inProgress.ContainsReference(this))
{
return false;
}
if (this.HasValueTypeConstraint)
{
return true;
}
return IsValueTypeFromConstraintTypes(this.GetConstraintTypesNoUseSiteDiagnostics(inProgress, early: true), inProgress);
}
public sealed override bool IsValueType => GetIsValueType(ConsList.Empty);
internal sealed override bool IsManagedType
{
get
{
return !this.HasUnmanagedTypeConstraint;
}
}
internal sealed override bool IsByRefLikeType
{
get
{
return false;
}
}
internal sealed override bool IsReadOnly
{
get
{
// even if T is indirectly constrained to a struct,
// we only can use members via constrained calls, so "true" would have no effect
return false;
}
}
internal sealed override ObsoleteAttributeData ObsoleteAttributeData
{
get { return null; }
}
public abstract bool HasReferenceTypeConstraint { get; }
///
/// Returns whether the reference type constraint (the 'class' constraint) should also be treated as nullable ('class?') or non-nullable (class!).
/// In some cases this aspect is unknown (null value is returned). For example, when 'class' constraint is specified in a NonNullTypes(false) context.
/// This API returns false when is false.
///
internal abstract bool? ReferenceTypeConstraintIsNullable { get; }
public abstract bool HasValueTypeConstraint { get; }
public abstract bool HasUnmanagedTypeConstraint { get; }
public abstract VarianceKind Variance { get; }
internal sealed override bool GetUnificationUseSiteDiagnosticRecursive(ref DiagnosticInfo result, Symbol owner, ref HashSet checkedTypes)
{
return false;
}
internal override bool Equals(TypeSymbol t2, TypeCompareKind comparison)
{
return this.Equals(t2 as TypeParameterSymbol, comparison);
}
internal bool Equals(TypeParameterSymbol other)
{
return Equals(other, TypeCompareKind.ConsiderEverything);
}
private bool Equals(TypeParameterSymbol other, TypeCompareKind comparison)
{
if (ReferenceEquals(this, other))
{
return true;
}
if ((object)other == null || !ReferenceEquals(other.OriginalDefinition, this.OriginalDefinition))
{
return false;
}
// Type parameters may be equal but not reference equal due to independent alpha renamings.
return other.ContainingSymbol.ContainingType.Equals(this.ContainingSymbol.ContainingType, comparison);
}
public override int GetHashCode()
{
return Hash.Combine(ContainingSymbol, Ordinal);
}
internal override void AddNullableTransforms(ArrayBuilder transforms)
{
}
internal override bool ApplyNullableTransforms(ImmutableArray transforms, INonNullTypesContext nonNullTypesContext, ref int position, out TypeSymbol result)
{
result = this;
return true;
}
internal override TypeSymbol SetUnknownNullabilityForReferenceTypes()
{
return this;
}
internal override TypeSymbol MergeNullability(TypeSymbol other, VarianceKind variance, out bool hadNullabilityMismatch)
{
Debug.Assert(this.Equals(other, TypeCompareKind.IgnoreDynamicAndTupleNames));
hadNullabilityMismatch = false;
return this;
}
public override sealed bool? NonNullTypes
{
get
{
return ContainingSymbol.NonNullTypes;
}
}
#region ITypeParameterTypeSymbol Members
TypeParameterKind ITypeParameterSymbol.TypeParameterKind
{
get
{
return (TypeParameterKind)this.TypeParameterKind;
}
}
IMethodSymbol ITypeParameterSymbol.DeclaringMethod
{
get { return this.DeclaringMethod; }
}
INamedTypeSymbol ITypeParameterSymbol.DeclaringType
{
get { return this.DeclaringType; }
}
ImmutableArray ITypeParameterSymbol.ConstraintTypes
{
get
{
return this.ConstraintTypesNoUseSiteDiagnostics.SelectAsArray(c => (ITypeSymbol)c.TypeSymbol);
}
}
ITypeParameterSymbol ITypeParameterSymbol.OriginalDefinition
{
get { return this.OriginalDefinition; }
}
ITypeParameterSymbol ITypeParameterSymbol.ReducedFrom
{
get { return this.ReducedFrom; }
}
#endregion
#region ISymbol Members
public override void Accept(SymbolVisitor visitor)
{
visitor.VisitTypeParameter(this);
}
public override TResult Accept(SymbolVisitor visitor)
{
return visitor.VisitTypeParameter(this);
}
#endregion
}
}