// 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 bool? IsNotNullableIfReferenceTypeFromConstraintTypes(ImmutableArray constraintTypes, ConsList inProgress) { Debug.Assert(!constraintTypes.IsDefaultOrEmpty); inProgress = inProgress.Prepend(this); bool? result = false; foreach (TypeSymbolWithAnnotations constraintType in constraintTypes) { bool? fromType = IsNotNullableIfReferenceTypeFromConstraintType(constraintType, inProgress); if (fromType == true) { return true; } else if (fromType == null) { result = null; } } return result; } internal static bool? IsNotNullableIfReferenceTypeFromConstraintType(TypeSymbolWithAnnotations constraintType, ConsList inProgress) { if (constraintType.IsAnnotated) { return false; } if (constraintType.TypeKind == TypeKind.TypeParameter) { bool? isNotNullableIfReferenceType = ((TypeParameterSymbol)constraintType.TypeSymbol).GetIsNotNullableIfReferenceType(inProgress); 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(ConsList inProgress) { if (inProgress.ContainsReference(this)) { return false; } bool? fromReferenceTypeConstraint = false; if (this.HasReferenceTypeConstraint) { fromReferenceTypeConstraint = !this.ReferenceTypeConstraintIsNullable; if (fromReferenceTypeConstraint == true) { return true; } } ImmutableArray constraintTypes = this.GetConstraintTypesNoUseSiteDiagnostics(inProgress, early: true); if (constraintTypes.IsEmpty) { return fromReferenceTypeConstraint; } bool? fromTypes = IsNotNullableIfReferenceTypeFromConstraintTypes(constraintTypes, inProgress); if (fromTypes == true || fromReferenceTypeConstraint == false) { return fromTypes; } Debug.Assert(fromReferenceTypeConstraint == null); Debug.Assert(fromTypes != true); return null; } // PROTOTYPE(NullableReferenceTypes): Should this API be exposed through ITypeParameterSymbol? internal bool? IsNotNullableIfReferenceType => GetIsNotNullableIfReferenceType(ConsList.Empty); 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; } // PROTOTYPE(NullableReferenceTypes): Should this API be exposed through ITypeParameterSymbol? /// /// 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; } 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 } }