/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Symbols/ConstraintsHelper.cs
1 697 строк
82 KB
AlekseyTs
Rename GetIsNewExtensionMember helpers (#80925)
28 окт 2025, 20:38
Не верифицирован
28 окт 2025, 20:38
53890ee
Код
Авторство
О чём код?
// 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; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { /// <summary> /// A tuple of TypeParameterSymbol and DiagnosticInfo, created for errors /// reported from ConstraintsHelper rather than creating Diagnostics directly. /// This decouples constraints checking from syntax and Locations, and supports /// callers that may want to create Location instances lazily or not at all. /// </summary> internal readonly struct TypeParameterDiagnosticInfo { public readonly TypeParameterSymbol TypeParameter; public readonly UseSiteInfo<AssemblySymbol> UseSiteInfo; public TypeParameterDiagnosticInfo(TypeParameterSymbol typeParameter, UseSiteInfo<AssemblySymbol> useSiteInfo) { this.TypeParameter = typeParameter; this.UseSiteInfo = useSiteInfo; } } /// <summary> /// Helper methods for generic type parameter constraints. There are two sets of methods: one /// set for resolving constraint "bounds" (that is, determining the effective base type, interface set, /// etc.), and another set for checking for constraint violations in type and method references. /// /// Bounds are resolved by calling one of the ResolveBounds overloads. Typically bounds are /// resolved by each TypeParameterSymbol at, or before, one of the corresponding properties /// (BaseType, Interfaces, etc.) is accessed. Resolving bounds may result in errors (cycles, /// inconsistent constraints, etc.) and it is the responsibility of the caller to report any such /// errors as declaration errors or use-site errors (depending on whether the type parameter /// was from source or metadata) and to ensure bounds are resolved for source type parameters /// even if the corresponding properties are never accessed directly. /// /// Constraints are checked by calling one of the CheckConstraints or CheckAllConstraints /// overloads for any generic type or method reference from source. In some circumstances, /// references are checked at the time the generic type or generic method is bound and constructed /// by the Binder. In those case, it is sufficient to call one of the CheckConstraints overloads /// since compound types (such as A<T>.B<U> or A<B<T>>) are checked /// incrementally as each part is bound. In other cases however, constraint checking needs to be /// delayed to prevent cycles where checking constraints requires binding the syntax that is currently /// being bound (such as the constraint in class C<T> where T : C<T>). In those cases, /// the caller must lazily check constraints, and since the types may be compound types, it is /// necessary to call CheckAllConstraints. /// </summary> internal static class ConstraintsHelper { /// <summary> /// Determine the effective base type, effective interface set, and set of type /// parameters (excluding cycles) from the type parameter constraints. Conflicts /// within the constraints and constraint types are returned as diagnostics. /// 'inherited' should be true if the type parameters are from an overridden /// generic method. In those cases, additional constraint checks are applied. /// </summary> public static TypeParameterBounds ResolveBounds( this SourceTypeParameterSymbol typeParameter, AssemblySymbol corLibrary, ConsList<TypeParameterSymbol> inProgress, ImmutableArray<TypeWithAnnotations> constraintTypes, bool inherited, CSharpCompilation currentCompilation, BindingDiagnosticBag diagnostics) { var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; var bounds = typeParameter.ResolveBounds(corLibrary, inProgress, constraintTypes, inherited, currentCompilation, diagnosticsBuilder, ref useSiteDiagnosticsBuilder, template: new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, currentCompilation.Assembly)); if (useSiteDiagnosticsBuilder != null) { diagnosticsBuilder.AddRange(useSiteDiagnosticsBuilder); } foreach (var pair in diagnosticsBuilder) { diagnostics.Add(pair.UseSiteInfo, pair.TypeParameter.GetFirstLocation()); } diagnosticsBuilder.Free(); if (typeParameter.AllowsRefLikeType) { if (inherited) { Location location = typeParameter.GetFirstLocation(); Binder.CheckFeatureAvailability(location.SourceTree, MessageID.IDS_FeatureAllowsRefStructConstraint, diagnostics, location); if (!typeParameter.DeclaringCompilation.Assembly.RuntimeSupportsByRefLikeGenerics) { diagnostics.Add(ErrorCode.ERR_RuntimeDoesNotSupportByRefLikeGenerics, location); } } else { switch (typeParameter.HasReferenceTypeConstraint ? SpecialType.None : (bounds?.EffectiveBaseClass.SpecialType ?? SpecialType.System_Object)) { case SpecialType.System_Object: case SpecialType.System_ValueType: case SpecialType.System_Enum: break; default: diagnostics.Add(ErrorCode.ERR_ClassIsCombinedWithRefStruct, typeParameter.GetFirstLocation()); break; } } } return bounds; } // Based on SymbolLoader::ResolveBounds. public static TypeParameterBounds ResolveBounds( this TypeParameterSymbol typeParameter, AssemblySymbol corLibrary, ConsList<TypeParameterSymbol> inProgress, ImmutableArray<TypeWithAnnotations> constraintTypes, bool inherited, CSharpCompilation currentCompilation, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder, CompoundUseSiteInfo<AssemblySymbol> template) { Debug.Assert(currentCompilation == null || typeParameter.IsFromCompilation(currentCompilation)); ImmutableArray<NamedTypeSymbol> interfaces; NamedTypeSymbol effectiveBaseClass = corLibrary.GetSpecialType(typeParameter.HasValueTypeConstraint ? SpecialType.System_ValueType : SpecialType.System_Object); TypeSymbol deducedBaseType = effectiveBaseClass; if (constraintTypes.Length == 0) { interfaces = ImmutableArray<NamedTypeSymbol>.Empty; } else { var constraintTypesBuilder = ArrayBuilder<TypeWithAnnotations>.GetInstance(); var interfacesBuilder = ArrayBuilder<NamedTypeSymbol>.GetInstance(); var conversions = corLibrary.TypeConversions; var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(template); // Resolve base types, determine the effective base class and // interfaces, and filter out any constraint types that cause cycles. foreach (var constraintType in constraintTypes) { Debug.Assert(!constraintType.Type.ContainsDynamic()); NamedTypeSymbol constraintEffectiveBase; TypeSymbol constraintDeducedBase; switch (constraintType.TypeKind) { case TypeKind.TypeParameter: { var constraintTypeParameter = (TypeParameterSymbol)constraintType.Type; ConsList<TypeParameterSymbol> constraintsInProgress; if (constraintTypeParameter.ContainingSymbol == typeParameter.ContainingSymbol) { // The constraint type parameter is from the same containing type or method. if (inProgress.ContainsReference(constraintTypeParameter)) { // "Circular constraint dependency involving '{0}' and '{1}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(constraintTypeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_CircularConstraint, constraintTypeParameter, typeParameter)))); continue; } constraintsInProgress = inProgress; } else { // The constraint type parameter is from a different containing symbol so no cycle. constraintsInProgress = ConsList<TypeParameterSymbol>.Empty; } // Use the calculated bounds from the constraint type parameter. constraintEffectiveBase = constraintTypeParameter.GetEffectiveBaseClass(constraintsInProgress); constraintDeducedBase = constraintTypeParameter.GetDeducedBaseType(constraintsInProgress); AddInterfaces(interfacesBuilder, constraintTypeParameter.GetInterfaces(constraintsInProgress)); if (!inherited && currentCompilation != null && constraintTypeParameter.IsFromCompilation(currentCompilation)) { ErrorCode errorCode; if (constraintTypeParameter.HasUnmanagedTypeConstraint) { errorCode = ErrorCode.ERR_ConWithUnmanagedCon; } else if (constraintTypeParameter.HasValueTypeConstraint) { errorCode = ErrorCode.ERR_ConWithValCon; } else { break; } // "Type parameter '{1}' has the '?' constraint so '{1}' cannot be used as a constraint for '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(errorCode, typeParameter, constraintTypeParameter)))); continue; } } break; case TypeKind.Interface: case TypeKind.Class: case TypeKind.Delegate: Debug.Assert(inherited || currentCompilation == null || constraintType.TypeKind != TypeKind.Delegate); if (constraintType.Type.IsInterfaceType()) { AddInterface(interfacesBuilder, (NamedTypeSymbol)constraintType.Type); constraintTypesBuilder.Add(constraintType); continue; } else { constraintEffectiveBase = (NamedTypeSymbol)constraintType.Type; constraintDeducedBase = constraintType.Type; break; } case TypeKind.Struct: if (constraintType.IsNullableType()) { var underlyingType = constraintType.Type.GetNullableUnderlyingType(); if (underlyingType.TypeKind == TypeKind.TypeParameter) { var underlyingTypeParameter = (TypeParameterSymbol)underlyingType; if (underlyingTypeParameter.ContainingSymbol == typeParameter.ContainingSymbol) { // The constraint type parameter is from the same containing type or method. if (inProgress.ContainsReference(underlyingTypeParameter)) { // "Circular constraint dependency involving '{0}' and '{1}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(underlyingTypeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_CircularConstraint, underlyingTypeParameter, typeParameter)))); continue; } } } } Debug.Assert(inherited || currentCompilation == null); constraintEffectiveBase = corLibrary.GetSpecialType(SpecialType.System_ValueType); constraintDeducedBase = constraintType.Type; break; case TypeKind.Enum: Debug.Assert(inherited || currentCompilation == null); constraintEffectiveBase = corLibrary.GetSpecialType(SpecialType.System_Enum); constraintDeducedBase = constraintType.Type; break; case TypeKind.Array: Debug.Assert(inherited || currentCompilation == null); constraintEffectiveBase = corLibrary.GetSpecialType(SpecialType.System_Array); constraintDeducedBase = constraintType.Type; break; case TypeKind.Error: constraintEffectiveBase = (NamedTypeSymbol)constraintType.Type; constraintDeducedBase = constraintType.Type; break; case TypeKind.Pointer: case TypeKind.FunctionPointer: // Such a constraint can only be introduced by type substitution, // in which case it is already reported elsewhere, so we ignore this constraint. continue; case TypeKind.Submission: default: throw ExceptionUtilities.UnexpectedValue(constraintType.TypeKind); } CheckEffectiveAndDeducedBaseTypes(conversions, constraintEffectiveBase, constraintDeducedBase); constraintTypesBuilder.Add(constraintType); // Determine the more encompassed of the current effective base // class and the previously computed effective base class. if (!deducedBaseType.IsErrorType() && !constraintDeducedBase.IsErrorType()) { if (!IsEncompassedBy(conversions, deducedBaseType, constraintDeducedBase, ref useSiteInfo)) { if (!IsEncompassedBy(conversions, constraintDeducedBase, deducedBaseType, ref useSiteInfo)) { // "Type parameter '{0}' inherits conflicting constraints '{1}' and '{2}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_BaseConstraintConflict, typeParameter, constraintDeducedBase, deducedBaseType)))); } else { deducedBaseType = constraintDeducedBase; effectiveBaseClass = constraintEffectiveBase; } } } } AppendUseSiteDiagnostics(useSiteInfo, typeParameter, ref useSiteDiagnosticsBuilder); CheckEffectiveAndDeducedBaseTypes(conversions, effectiveBaseClass, deducedBaseType); constraintTypes = constraintTypesBuilder.ToImmutableAndFree(); interfaces = interfacesBuilder.ToImmutableAndFree(); } Debug.Assert((effectiveBaseClass.SpecialType == SpecialType.System_Object) || (deducedBaseType.SpecialType != SpecialType.System_Object)); // Only create a TypeParameterBounds instance for this type // parameter if the bounds are not the default values. if ((constraintTypes.Length == 0) && (deducedBaseType.SpecialType == SpecialType.System_Object)) { Debug.Assert(effectiveBaseClass.SpecialType == SpecialType.System_Object); Debug.Assert(interfaces.Length == 0); return null; } var bounds = new TypeParameterBounds(constraintTypes, interfaces, effectiveBaseClass, deducedBaseType); // Additional constraint checks for overrides. if (inherited) { CheckOverrideConstraints(typeParameter, bounds, diagnosticsBuilder); } return bounds; } internal static ImmutableArray<ImmutableArray<TypeWithAnnotations>> MakeTypeParameterConstraintTypes( this MethodSymbol containingSymbol, Binder withTypeParametersBinder, ImmutableArray<TypeParameterSymbol> typeParameters, TypeParameterListSyntax typeParameterList, SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses, BindingDiagnosticBag diagnostics) { if (typeParameters.Length == 0 || constraintClauses.Count == 0) { return ImmutableArray<ImmutableArray<TypeWithAnnotations>>.Empty; } // Wrap binder from factory in a generic constraints specific binder // to avoid checking constraints when binding type names. Debug.Assert(!withTypeParametersBinder.Flags.Includes(BinderFlags.GenericConstraintsClause)); withTypeParametersBinder = withTypeParametersBinder.WithAdditionalFlags(BinderFlags.GenericConstraintsClause | BinderFlags.SuppressConstraintChecks); ImmutableArray<TypeParameterConstraintClause> clauses; clauses = withTypeParametersBinder.BindTypeParameterConstraintClauses(containingSymbol, typeParameters, typeParameterList, constraintClauses, diagnostics, performOnlyCycleSafeValidation: false); if (clauses.All(clause => clause.ConstraintTypes.IsEmpty)) { return ImmutableArray<ImmutableArray<TypeWithAnnotations>>.Empty; } return clauses.SelectAsArray(clause => clause.ConstraintTypes); } internal static ImmutableArray<TypeParameterConstraintKind> MakeTypeParameterConstraintKinds( this MethodSymbol containingSymbol, Binder withTypeParametersBinder, ImmutableArray<TypeParameterSymbol> typeParameters, TypeParameterListSyntax typeParameterList, SyntaxList<TypeParameterConstraintClauseSyntax> constraintClauses) { if (typeParameters.Length == 0) { return ImmutableArray<TypeParameterConstraintKind>.Empty; } ImmutableArray<TypeParameterConstraintClause> clauses; if (constraintClauses.Count == 0) { clauses = withTypeParametersBinder.GetDefaultTypeParameterConstraintClauses(typeParameterList); } else { // Wrap binder from factory in a generic constraints specific binder // Also, suppress type argument binding in constraint types, this helps to avoid cycles while we figure out constraint kinds. // to avoid checking constraints when binding type names. Debug.Assert(!withTypeParametersBinder.Flags.Includes(BinderFlags.GenericConstraintsClause)); withTypeParametersBinder = withTypeParametersBinder.WithAdditionalFlags(BinderFlags.GenericConstraintsClause | BinderFlags.SuppressConstraintChecks | BinderFlags.SuppressTypeArgumentBinding); // We will recompute this diagnostics more accurately later, when binding without BinderFlags.SuppressTypeArgumentBinding clauses = withTypeParametersBinder.BindTypeParameterConstraintClauses(containingSymbol, typeParameters, typeParameterList, constraintClauses, BindingDiagnosticBag.Discarded, performOnlyCycleSafeValidation: true); clauses = AdjustConstraintKindsBasedOnConstraintTypes(typeParameters, clauses); } if (clauses.All(clause => clause.Constraints == TypeParameterConstraintKind.None)) { return ImmutableArray<TypeParameterConstraintKind>.Empty; } return clauses.SelectAsArray(clause => clause.Constraints); } internal static ImmutableArray<TypeParameterConstraintClause> AdjustConstraintKindsBasedOnConstraintTypes(ImmutableArray<TypeParameterSymbol> typeParameters, ImmutableArray<TypeParameterConstraintClause> constraintClauses) { int arity = typeParameters.Length; Debug.Assert(constraintClauses.Length == arity); SmallDictionary<TypeParameterSymbol, bool> isValueTypeMap = TypeParameterConstraintClause.BuildIsValueTypeMap(typeParameters, constraintClauses); SmallDictionary<TypeParameterSymbol, bool> isReferenceTypeFromConstraintTypesMap = TypeParameterConstraintClause.BuildIsReferenceTypeFromConstraintTypesMap(typeParameters, constraintClauses); ArrayBuilder<TypeParameterConstraintClause> builder = null; for (int i = 0; i < arity; i++) { var constraint = constraintClauses[i]; var typeParameter = typeParameters[i]; TypeParameterConstraintKind constraintKind = constraint.Constraints; Debug.Assert((constraintKind & (TypeParameterConstraintKind.ValueTypeFromConstraintTypes | TypeParameterConstraintKind.ReferenceTypeFromConstraintTypes)) == 0); if ((constraintKind & TypeParameterConstraintKind.AllValueTypeKinds) == 0 && isValueTypeMap[typeParameter]) { constraintKind |= TypeParameterConstraintKind.ValueTypeFromConstraintTypes; } if (isReferenceTypeFromConstraintTypesMap[typeParameter]) { constraintKind |= TypeParameterConstraintKind.ReferenceTypeFromConstraintTypes; } if (constraint.Constraints != constraintKind) { if (builder == null) { builder = ArrayBuilder<TypeParameterConstraintClause>.GetInstance(constraintClauses.Length); builder.AddRange(constraintClauses); } builder[i] = TypeParameterConstraintClause.Create(constraintKind, constraint.ConstraintTypes); } } if (builder != null) { constraintClauses = builder.ToImmutableAndFree(); } return constraintClauses; } // Based on SymbolLoader::SetOverrideConstraints. private static void CheckOverrideConstraints( TypeParameterSymbol typeParameter, TypeParameterBounds bounds, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder) { var deducedBase = bounds.DeducedBaseType; var constraintTypes = bounds.ConstraintTypes; if (IsValueType(typeParameter, constraintTypes) && IsReferenceType(typeParameter, constraintTypes)) { Debug.Assert(!deducedBase.IsValueType || typeParameter.HasReferenceTypeConstraint); diagnosticsBuilder.Add(GenerateConflictingConstraintsError(typeParameter, deducedBase, classConflict: deducedBase.IsValueType)); } else if (deducedBase.IsNullableType() && (typeParameter.HasValueTypeConstraint || typeParameter.HasReferenceTypeConstraint)) { diagnosticsBuilder.Add(GenerateConflictingConstraintsError(typeParameter, deducedBase, classConflict: typeParameter.HasReferenceTypeConstraint)); } } /// <summary> /// Check all generic constraints on the given type and any containing types /// (such as A<T> in A<T>.B<U>). This includes checking constraints /// on generic types within the type (such as B<T> in A<B<T>[]>). /// </summary> public static void CheckAllConstraints( this TypeSymbol type, CSharpCompilation compilation, ConversionsBase conversions, Location location, BindingDiagnosticBag diagnostics) { bool includeNullability = compilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes); var boxedArgs = CheckConstraintsArgsBoxed.Allocate(compilation, conversions, includeNullability, location, diagnostics); type.CheckAllConstraints(boxedArgs); boxedArgs.Free(); } public static bool CheckAllConstraints( this TypeSymbol type, CSharpCompilation compilation, ConversionsBase conversions) { var diagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics: true, withDependencies: false); // Nullability checks can only add warnings here so skip them for this check as we are only // concerned with errors. var boxedArgs = CheckConstraintsArgsBoxed.Allocate(compilation, conversions, includeNullability: false, NoLocation.Singleton, diagnostics); type.CheckAllConstraints(boxedArgs); bool ok = !diagnostics.HasAnyErrors(); boxedArgs.Free(); diagnostics.Free(); return ok; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void CheckAllConstraints(this TypeSymbol type, CheckConstraintsArgsBoxed args) { type.VisitType(s_checkConstraintsSingleTypeFunc, args); } internal readonly struct CheckConstraintsArgs { #nullable enable public readonly CSharpCompilation? CurrentCompilation; #nullable disable public readonly ConversionsBase Conversions; public readonly bool IncludeNullability; public readonly Location Location; public readonly BindingDiagnosticBag Diagnostics; public readonly CompoundUseSiteInfo<AssemblySymbol> Template; public CheckConstraintsArgs(CSharpCompilation currentCompilation, ConversionsBase conversions, Location location, BindingDiagnosticBag diagnostics) : this(currentCompilation, conversions, currentCompilation.IsFeatureEnabled(MessageID.IDS_FeatureNullableReferenceTypes), location, diagnostics) { } public CheckConstraintsArgs(CSharpCompilation currentCompilation, ConversionsBase conversions, bool includeNullability, Location location, BindingDiagnosticBag diagnostics) : this(currentCompilation, conversions, includeNullability, location, diagnostics, template: new CompoundUseSiteInfo<AssemblySymbol>(diagnostics, currentCompilation.Assembly)) { } public CheckConstraintsArgs(CSharpCompilation currentCompilation, ConversionsBase conversions, bool includeNullability, Location location, BindingDiagnosticBag diagnostics, CompoundUseSiteInfo<AssemblySymbol> template) { this.CurrentCompilation = currentCompilation; this.Conversions = conversions; this.IncludeNullability = includeNullability; this.Location = location; this.Diagnostics = diagnostics; this.Template = template; } } private static readonly ObjectPool<CheckConstraintsArgsBoxed> s_checkConstraintsArgsBoxedPool = new ObjectPool<CheckConstraintsArgsBoxed>(static () => new CheckConstraintsArgsBoxed()); internal sealed class CheckConstraintsArgsBoxed { public CheckConstraintsArgs Args; [MethodImpl(MethodImplOptions.NoInlining)] public static CheckConstraintsArgsBoxed Allocate(CSharpCompilation currentCompilation, ConversionsBase conversions, Location location, BindingDiagnosticBag diagnostics) { var boxedArgs = s_checkConstraintsArgsBoxedPool.Allocate(); boxedArgs.Args = new CheckConstraintsArgs(currentCompilation, conversions, location, diagnostics); return boxedArgs; } [MethodImpl(MethodImplOptions.NoInlining)] public static CheckConstraintsArgsBoxed Allocate(CSharpCompilation currentCompilation, ConversionsBase conversions, bool includeNullability, Location location, BindingDiagnosticBag diagnostics) { var boxedArgs = s_checkConstraintsArgsBoxedPool.Allocate(); boxedArgs.Args = new CheckConstraintsArgs(currentCompilation, conversions, includeNullability, location, diagnostics); return boxedArgs; } public void Free() { this.Args = default; s_checkConstraintsArgsBoxedPool.Free(this); } } private static readonly Func<TypeSymbol, CheckConstraintsArgsBoxed, bool, bool> s_checkConstraintsSingleTypeFunc = (type, arg, unused) => CheckConstraintsSingleType(type, in arg.Args); private static bool CheckConstraintsSingleType(TypeSymbol type, in CheckConstraintsArgs args) { if (type.Kind == SymbolKind.NamedType) { ((NamedTypeSymbol)type).CheckConstraints(args); } else if (type.Kind == SymbolKind.PointerType) { #nullable enable if (args.CurrentCompilation is not null) { Binder.CheckManagedAddr(args.CurrentCompilation, ((PointerTypeSymbol)type).PointedAtType, args.Location, args.Diagnostics); } #nullable disable } return false; // continue walking types } public static void CheckConstraints( this NamedTypeSymbol tuple, in CheckConstraintsArgs args, SyntaxNode typeSyntax, ImmutableArray<Location> elementLocations, BindingDiagnosticBag nullabilityDiagnosticsOpt) { Debug.Assert(tuple.IsTupleType); if (!RequiresChecking(tuple)) { return; } if (typeSyntax.HasErrors) { return; } var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); var nullabilityDiagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); var underlyingTupleTypeChain = ArrayBuilder<NamedTypeSymbol>.GetInstance(); NamedTypeSymbol.GetUnderlyingTypeChain(tuple, underlyingTupleTypeChain); int offset = 0; foreach (var underlyingTuple in underlyingTupleTypeChain) { ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; CheckTypeConstraints( underlyingTuple, in args, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: (nullabilityDiagnosticsOpt is null) ? null : nullabilityDiagnosticsBuilder, ref useSiteDiagnosticsBuilder); if (useSiteDiagnosticsBuilder != null) { diagnosticsBuilder.AddRange(useSiteDiagnosticsBuilder); } populateDiagnosticsAndClear(diagnosticsBuilder, args.Diagnostics); populateDiagnosticsAndClear(nullabilityDiagnosticsBuilder, nullabilityDiagnosticsOpt); offset += NamedTypeSymbol.ValueTupleRestIndex; void populateDiagnosticsAndClear(ArrayBuilder<TypeParameterDiagnosticInfo> builder, BindingDiagnosticBag bag) { if (bag is null) { builder.Clear(); return; } foreach (var pair in builder) { var ordinal = pair.TypeParameter.Ordinal; // If this is the TRest type parameter, we report it on // the entire type syntax as it does not map to any tuple element. var location = ordinal == NamedTypeSymbol.ValueTupleRestIndex ? typeSyntax.Location : elementLocations[ordinal + offset]; bag.Add(pair.UseSiteInfo, location); } builder.Clear(); } } underlyingTupleTypeChain.Free(); diagnosticsBuilder.Free(); nullabilityDiagnosticsBuilder.Free(); } public static bool CheckConstraintsForNamedType( this NamedTypeSymbol type, in CheckConstraintsArgs args, SyntaxNode typeSyntax, SeparatedSyntaxList<TypeSyntax> typeArgumentsSyntax, // may be omitted in synthesized invocations ConsList<TypeSymbol> basesBeingResolved) { Debug.Assert(typeArgumentsSyntax.Count == 0 /*omitted*/ || typeArgumentsSyntax.Count == type.Arity); if (!RequiresChecking(type)) { return true; } var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; var result = !typeSyntax.HasErrors && CheckTypeConstraints(type, in args, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: args.IncludeNullability ? diagnosticsBuilder : null, ref useSiteDiagnosticsBuilder); if (useSiteDiagnosticsBuilder != null) { diagnosticsBuilder.AddRange(useSiteDiagnosticsBuilder); } foreach (var pair in diagnosticsBuilder) { int ordinal = pair.TypeParameter.Ordinal; var location = ordinal < typeArgumentsSyntax.Count ? typeArgumentsSyntax[ordinal].Location : args.Location; args.Diagnostics.Add(pair.UseSiteInfo, location); } diagnosticsBuilder.Free(); if (HasDuplicateInterfaces(type, basesBeingResolved)) { result = false; args.Diagnostics.Add(ErrorCode.ERR_BogusType, args.Location, type); } return result; } public static bool CheckConstraints(this NamedTypeSymbol type, in CheckConstraintsArgs args) { if (!RequiresChecking(type)) { return true; } var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; var result = CheckTypeConstraints(type, in args, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: args.IncludeNullability ? diagnosticsBuilder : null, ref useSiteDiagnosticsBuilder); if (useSiteDiagnosticsBuilder != null) { diagnosticsBuilder.AddRange(useSiteDiagnosticsBuilder); } foreach (var pair in diagnosticsBuilder) { args.Diagnostics.Add(pair.UseSiteInfo, args.Location); } diagnosticsBuilder.Free(); #nullable enable // we only check for distinct interfaces when the type is not from source, as we // trust that types that are from source have already been checked by the compiler // to prevent this from happening in the first place. if (!(args.CurrentCompilation != null && type.IsFromCompilation(args.CurrentCompilation)) && HasDuplicateInterfaces(type, null)) { result = false; args.Diagnostics.Add(ErrorCode.ERR_BogusType, args.Location, type); } #nullable disable return result; } // C# does not let you declare a type in which it would be possible for distinct base interfaces // to unify under some instantiations. But such ill-formed classes can come in through // metadata and be instantiated in C#. We check to see if that's happened. private static bool HasDuplicateInterfaces(NamedTypeSymbol type, ConsList<TypeSymbol> basesBeingResolved) { if (type.OriginalDefinition is not PENamedTypeSymbol) { return false; } // PERF: avoid instantiating all interfaces here // Ex: if class implements just IEnumerable<> and IComparable<> it cannot have conflicting implementations var array = type.OriginalDefinition.InterfacesNoUseSiteDiagnostics(basesBeingResolved); switch (array.Length) { case 0: case 1: // less than 2 interfaces return false; case 2: if ((object)array[0].OriginalDefinition == array[1].OriginalDefinition) { break; } // two unrelated interfaces return false; default: var set = PooledHashSet<object>.GetInstance(); foreach (var i in array) { if (!set.Add(i.OriginalDefinition)) { set.Free(); goto hasRelatedInterfaces; } } // all interfaces are unrelated set.Free(); return false; } // very rare case. // some implemented interfaces are related // will have to instantiate interfaces and check hasRelatedInterfaces: return type.InterfacesNoUseSiteDiagnostics(basesBeingResolved).HasDuplicates<NamedTypeSymbol>(SymbolEqualityComparer.IgnoringDynamicTupleNamesAndNullability); } public static bool CheckConstraints( this MethodSymbol method, in CheckConstraintsArgs args) { if (!RequiresChecking(method)) { return true; } var diagnosticsBuilder = ArrayBuilder<TypeParameterDiagnosticInfo>.GetInstance(); ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder = null; var result = CheckMethodConstraints( method, in args, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt: args.IncludeNullability ? diagnosticsBuilder : null, ref useSiteDiagnosticsBuilder); if (useSiteDiagnosticsBuilder != null) { diagnosticsBuilder.AddRange(useSiteDiagnosticsBuilder); } foreach (var pair in diagnosticsBuilder) { args.Diagnostics.Add(pair.UseSiteInfo, args.Location); } diagnosticsBuilder.Free(); return result; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool CheckTypeConstraints( NamedTypeSymbol type, in CheckConstraintsArgs args, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder) { return CheckConstraints( type, in args, type.TypeSubstitution, type.OriginalDefinition.TypeParameters, type.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteDiagnosticsBuilder); } public static bool CheckMethodConstraints( MethodSymbol method, in CheckConstraintsArgs args, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder, BitVector skipParameters = default(BitVector)) { return CheckConstraints( method, in args, method.TypeSubstitution, ((MethodSymbol)method.OriginalDefinition).TypeParameters, method.TypeArgumentsWithAnnotations, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteDiagnosticsBuilder, skipParameters); } /// <summary> /// Check type parameter constraints for the containing type or method symbol. /// For extension members, also checks constraints for the containing extension. /// </summary> /// <param name="constructedContainingSymbol">The generic type or method.</param> /// <param name="args">Arguments for constraints checking.</param> /// <param name="substitution">The map from type parameters to type arguments.</param> /// <param name="typeParameters">Containing symbol type parameters.</param> /// <param name="typeArguments">Containing symbol type arguments.</param> /// <param name="diagnosticsBuilder">Diagnostics.</param> /// <param name="nullabilityDiagnosticsBuilderOpt">Nullability warnings.</param> /// <param name="skipParameters">Parameters to skip.</param> /// <param name="useSiteDiagnosticsBuilder"/> /// <param name="ignoreTypeConstraintsDependentOnTypeParametersOpt">If an original form of a type constraint /// depends on a type parameter from this set, do not verify this type constraint.</param> /// <returns>True if the constraints were satisfied, false otherwise.</returns> public static bool CheckConstraints( this Symbol constructedContainingSymbol, in CheckConstraintsArgs args, TypeMap substitution, ImmutableArray<TypeParameterSymbol> typeParameters, ImmutableArray<TypeWithAnnotations> typeArguments, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder, BitVector skipParameters = default(BitVector), HashSet<TypeParameterSymbol> ignoreTypeConstraintsDependentOnTypeParametersOpt = null) { Debug.Assert(typeParameters.Length == typeArguments.Length); Debug.Assert(!args.Conversions.IncludeNullability || (nullabilityDiagnosticsBuilderOpt != null)); bool succeeded = true; if (typeParameters.Length > 0 && substitution is not null) { // The type parameters must be original definitions of type parameters from the containing symbol. Debug.Assert(typeParameters.All(tp => ReferenceEquals(tp.ContainingSymbol, constructedContainingSymbol.OriginalDefinition))); int n = typeParameters.Length; for (int i = 0; i < n; i++) { if (skipParameters[i]) { continue; } if (!CheckConstraints(constructedContainingSymbol, in args, substitution, typeParameters[i], typeArguments[i], diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteDiagnosticsBuilder, ignoreTypeConstraintsDependentOnTypeParametersOpt)) { succeeded = false; } } } if (constructedContainingSymbol.IsExtensionBlockMember() && constructedContainingSymbol.ContainingType is { Arity: > 0 } extension && extension.TypeSubstitution is not null) { succeeded &= CheckConstraints(extension, in args, extension.TypeSubstitution, extension.TypeParameters, extension.TypeArgumentsWithAnnotationsNoUseSiteDiagnostics, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteDiagnosticsBuilder); } return succeeded; } [MethodImpl(MethodImplOptions.NoInlining)] private static bool CheckBasicConstraints( Symbol containingSymbol, in CheckConstraintsArgs args, TypeParameterSymbol typeParameter, TypeWithAnnotations typeArgument, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder) { if (typeArgument.Type.IsPointerOrFunctionPointer() || typeArgument.IsRestrictedType(ignoreSpanLikeTypes: true) || typeArgument.IsVoidType()) { // "The type '{0}' may not be used as a type argument" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_BadTypeArgument, typeArgument.Type)))); return false; } if (typeArgument.Type.IsRefLikeOrAllowsRefLikeType()) { if (typeParameter.AllowsRefLikeType) { #nullable enable if (args.CurrentCompilation is not null && args.CurrentCompilation.SourceModule != typeParameter.ContainingModule) { if (MessageID.IDS_FeatureAllowsRefStructConstraint.GetFeatureAvailabilityDiagnosticInfo(args.CurrentCompilation) is { } diagnosticInfo) { diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(diagnosticInfo))); } if (!args.CurrentCompilation.Assembly.RuntimeSupportsByRefLikeGenerics) { diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_RuntimeDoesNotSupportByRefLikeGenerics)))); } } #nullable disable } else { diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return false; } } if (typeArgument.IsStatic) { // "'{0}': static types cannot be used as type arguments" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_GenericArgIsStaticClass, typeArgument.Type)))); return false; } if (typeParameter.HasReferenceTypeConstraint) { if (!typeArgument.Type.IsReferenceType) { // "The type '{2}' must be a reference type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_RefConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return false; } } CheckNullability(containingSymbol, typeParameter, typeArgument, nullabilityDiagnosticsBuilderOpt); if (typeParameter.HasUnmanagedTypeConstraint) { var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(args.Template); var managedKind = typeArgument.Type.GetManagedKind(ref useSiteInfo); AppendUseSiteDiagnostics(useSiteInfo, typeParameter, ref useSiteDiagnosticsBuilder); if (managedKind == ManagedKind.Managed || !typeArgument.Type.IsNonNullableValueType()) { // "The type '{2}' must be a non-nullable value type, along with all fields at any level of nesting, in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_UnmanagedConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return false; } else if (managedKind == ManagedKind.UnmanagedWithGenerics) { #nullable enable // When there is no compilation, we are being invoked through the API IMethodSymbol.ReduceExtensionMethod(...). // In that case we consider the unmanaged constraint to be satisfied as if we were compiling with the latest // language version. The net effect of this is that in some IDE scenarios completion might consider an // extension method to be applicable, but then when you try to use it the IDE tells you to upgrade your language version. if (!(args.CurrentCompilation is null)) { var csDiagnosticInfo = MessageID.IDS_FeatureUnmanagedConstructedTypes.GetFeatureAvailabilityDiagnosticInfo(args.CurrentCompilation); if (csDiagnosticInfo != null) { diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(csDiagnosticInfo))); return false; } } #nullable disable } } if (typeParameter.HasValueTypeConstraint && !typeArgument.Type.IsNonNullableValueType()) { // "The type '{2}' must be a non-nullable value type in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_ValConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return false; } return true; } // See TypeBind::CheckSingleConstraint. // Any new locals added to this method are likely going to cause EndToEndTests.Constraints to overflow. Break new locals out into // another function. private static bool CheckConstraints( Symbol constructedContainingSymbol, in CheckConstraintsArgs args, TypeMap substitution, TypeParameterSymbol typeParameter, TypeWithAnnotations typeArgument, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder, HashSet<TypeParameterSymbol> ignoreTypeConstraintsDependentOnTypeParametersOpt) { Debug.Assert(substitution != null); // The type parameters must be original definitions of type parameters from the containing symbol. Debug.Assert(ReferenceEquals(typeParameter.ContainingSymbol, constructedContainingSymbol.OriginalDefinition)); if (typeArgument.Type.IsErrorType()) { return true; } if (!CheckBasicConstraints(constructedContainingSymbol, in args, typeParameter, typeArgument, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteDiagnosticsBuilder)) { return false; } // The type parameters for a constructed type/method are the type parameters of // the ConstructedFrom type/method, so the constraint types are not substituted. // For instance with "class C<T, U> where T : U", the type parameter for T in "C<object, int>" // has constraint "U", not "int". We need to substitute the constraints from the // original definition of the type parameters using the map from the constructed symbol. var constraintTypes = ArrayBuilder<TypeWithAnnotations>.GetInstance(); var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(args.Template); ImmutableArray<TypeWithAnnotations> originalConstraintTypes = typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteInfo); substitution.SubstituteConstraintTypesDistinctWithoutModifiers(typeParameter, originalConstraintTypes, constraintTypes, ignoreTypeConstraintsDependentOnTypeParametersOpt); bool hasError = false; if (typeArgument.Type is NamedTypeSymbol { IsInterface: true } iface && SelfOrBaseHasStaticAbstractMember(iface, ref useSiteInfo, out Symbol member)) { diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_GenericConstraintNotSatisfiedInterfaceWithStaticAbstractMembers, iface, member)))); hasError = true; } foreach (var constraintType in constraintTypes) { CheckConstraintType(constructedContainingSymbol, in args, typeParameter, typeArgument, diagnosticsBuilder, nullabilityDiagnosticsBuilderOpt, ref useSiteInfo, constraintType, ref hasError); } constraintTypes.Free(); if (AppendUseSiteDiagnostics(useSiteInfo, typeParameter, ref useSiteDiagnosticsBuilder)) { hasError = true; } // Check the constructor constraint. if (typeParameter.HasConstructorConstraint && errorIfNotSatisfiesConstructorConstraint(constructedContainingSymbol, typeParameter, typeArgument, diagnosticsBuilder)) { return false; } return !hasError; [MethodImpl(MethodImplOptions.NoInlining)] static bool errorIfNotSatisfiesConstructorConstraint(Symbol containingSymbol, TypeParameterSymbol typeParameter, TypeWithAnnotations typeArgument, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder) { var error = SatisfiesConstructorConstraint(typeArgument.Type); switch (error) { case ConstructorConstraintError.None: return false; case ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType: // "'{2}' must be a non-abstract type with a public parameterless constructor in order to use it as parameter '{1}' in the generic type or method '{0}'" diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_NewConstraintNotSatisfied, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return true; case ConstructorConstraintError.HasRequiredMembers: // '{2}' cannot satisfy the 'new()' constraint on parameter '{1}' in the generic type or or method '{0}' because '{2}' has required members. diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_NewConstraintCannotHaveRequiredMembers, containingSymbol.ConstructedFrom(), typeParameter, typeArgument.Type)))); return true; default: throw ExceptionUtilities.UnexpectedValue(error); } } } [MethodImpl(MethodImplOptions.NoInlining)] private static void CheckNullability( Symbol containingSymbol, TypeParameterSymbol typeParameter, TypeWithAnnotations typeArgument, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt) { if (nullabilityDiagnosticsBuilderOpt != null) { if (typeParameter.HasNotNullConstraint && typeArgument.GetValueNullableAnnotation().IsAnnotated() && !typeArgument.Type.IsNonNullableValueType()) { nullabilityDiagnosticsBuilderOpt.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.WRN_NullabilityMismatchInTypeParameterNotNullConstraint, containingSymbol.ConstructedFrom(), typeParameter, typeArgument)))); } if (typeParameter.HasReferenceTypeConstraint && typeParameter.ReferenceTypeConstraintIsNullable == false && typeArgument.GetValueNullableAnnotation().IsAnnotated()) { nullabilityDiagnosticsBuilderOpt.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.WRN_NullabilityMismatchInTypeParameterReferenceTypeConstraint, containingSymbol.ConstructedFrom(), typeParameter, typeArgument)))); } } } [MethodImpl(MethodImplOptions.NoInlining)] private static void CheckConstraintType( Symbol containingSymbol, in CheckConstraintsArgs args, TypeParameterSymbol typeParameter, TypeWithAnnotations typeArgument, ArrayBuilder<TypeParameterDiagnosticInfo> diagnosticsBuilder, ArrayBuilder<TypeParameterDiagnosticInfo> nullabilityDiagnosticsBuilderOpt, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, TypeWithAnnotations constraintType, ref bool hasError) { if (SatisfiesConstraintType(args.Conversions.WithNullability(false), typeArgument, constraintType, ref useSiteInfo)) { if (nullabilityDiagnosticsBuilderOpt != null) { if (!SatisfiesConstraintType(args.Conversions.WithNullability(true), typeArgument, constraintType, ref useSiteInfo) || !constraintTypeAllows(constraintType, getTypeArgumentState(typeArgument))) { nullabilityDiagnosticsBuilderOpt.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.WRN_NullabilityMismatchInTypeParameterConstraint, containingSymbol.ConstructedFrom(), constraintType, typeParameter, typeArgument)))); } } return; } ErrorCode errorCode; if (typeArgument.Type.IsReferenceType) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedRefType; } else if (typeArgument.IsNullableType()) { errorCode = constraintType.Type.IsInterfaceType() ? ErrorCode.ERR_GenericConstraintNotSatisfiedNullableInterface : ErrorCode.ERR_GenericConstraintNotSatisfiedNullableEnum; } else if (typeArgument.TypeKind == TypeKind.TypeParameter) { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedTyVar; } else { errorCode = ErrorCode.ERR_GenericConstraintNotSatisfiedValType; } object constraintTypeErrorArgument; object typeArgumentErrorArgument; if (constraintType.Type.Equals(typeArgument.Type, TypeCompareKind.AllIgnoreOptions)) { constraintTypeErrorArgument = constraintType.Type; typeArgumentErrorArgument = typeArgument.Type; } else { #nullable enable SymbolDistinguisher distinguisher = new SymbolDistinguisher(args.CurrentCompilation, constraintType.Type, typeArgument.Type); constraintTypeErrorArgument = distinguisher.First; typeArgumentErrorArgument = distinguisher.Second; #nullable disable } diagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(errorCode, containingSymbol.ConstructedFrom(), constraintTypeErrorArgument, typeParameter, typeArgumentErrorArgument)))); hasError = true; static NullableFlowState getTypeArgumentState(in TypeWithAnnotations typeWithAnnotations) { var type = typeWithAnnotations.Type; if (type is null) { return NullableFlowState.NotNull; } if (type.IsValueType) { return type.IsNullableTypeOrTypeParameter() ? NullableFlowState.MaybeNull : NullableFlowState.NotNull; } switch (typeWithAnnotations.NullableAnnotation) { case NullableAnnotation.Annotated: return type.IsTypeParameterDisallowingAnnotationInCSharp8() ? NullableFlowState.MaybeDefault : NullableFlowState.MaybeNull; case NullableAnnotation.Oblivious: return NullableFlowState.NotNull; } var typeParameter = type as TypeParameterSymbol; if (typeParameter is null || typeParameter.IsNotNullable == true) { return NullableFlowState.NotNull; } NullableFlowState? result = null; foreach (var constraintType in typeParameter.ConstraintTypesNoUseSiteDiagnostics) { var constraintState = getTypeArgumentState(constraintType); if (result == null) { result = constraintState; } else { result = result.Value.Meet(constraintState); } } return result ?? NullableFlowState.MaybeNull; } static bool constraintTypeAllows(in TypeWithAnnotations typeWithAnnotations, NullableFlowState state) { if (state == NullableFlowState.NotNull) { return true; } var type = typeWithAnnotations.Type; if (type is null || type.IsValueType) { return true; } switch (typeWithAnnotations.NullableAnnotation) { case NullableAnnotation.Oblivious: case NullableAnnotation.Annotated: return true; } var typeParameter = type as TypeParameterSymbol; if (typeParameter is null || typeParameter.IsNotNullable == true) { return false; } foreach (var constraintType in typeParameter.ConstraintTypesNoUseSiteDiagnostics) { if (!constraintTypeAllows(constraintType, state)) { return false; } } return state == NullableFlowState.MaybeNull; } } private static bool AppendUseSiteDiagnostics( CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, TypeParameterSymbol typeParameter, ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder) { if (!(useSiteInfo.AccumulatesDiagnostics && useSiteInfo.HasErrors) && useSiteInfo.AccumulatesDependencies && !useSiteInfo.Dependencies.IsNullOrEmpty()) { ensureUseSiteDiagnosticsBuilder(ref useSiteDiagnosticsBuilder).Add(new TypeParameterDiagnosticInfo(typeParameter, useSiteInfo.Dependencies.Count == 1 ? new UseSiteInfo<AssemblySymbol>(useSiteInfo.Dependencies.Single()) : new UseSiteInfo<AssemblySymbol>(useSiteInfo.Dependencies.ToImmutableHashSet()))); } if (!useSiteInfo.AccumulatesDiagnostics) { return false; } var useSiteDiagnostics = useSiteInfo.Diagnostics; if (useSiteDiagnostics.IsNullOrEmpty()) { return false; } ensureUseSiteDiagnosticsBuilder(ref useSiteDiagnosticsBuilder); bool hasErrors = false; foreach (var info in useSiteDiagnostics) { if (info.Severity == DiagnosticSeverity.Error) { hasErrors = true; } useSiteDiagnosticsBuilder.Add(new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(info))); } return hasErrors; static ArrayBuilder<TypeParameterDiagnosticInfo> ensureUseSiteDiagnosticsBuilder(ref ArrayBuilder<TypeParameterDiagnosticInfo> useSiteDiagnosticsBuilder) { return useSiteDiagnosticsBuilder ??= new ArrayBuilder<TypeParameterDiagnosticInfo>(); } } private static bool SatisfiesConstraintType( ConversionsBase conversions, TypeWithAnnotations typeArgument, TypeWithAnnotations constraintType, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) { if (constraintType.Type.IsErrorType()) { return false; } // Spec 4.4.4 describes the valid conversions from // type argument A to constraint type C: // "An identity conversion (6.1.1). // An implicit reference conversion (6.1.6). ..." if (conversions.HasIdentityOrImplicitReferenceConversion(typeArgument.Type, constraintType.Type, ref useSiteInfo)) { return true; } if (typeArgument.Type.IsValueType) { // "... A boxing conversion (6.1.7), provided that type A is a non-nullable value type. ..." // NOTE: we extend this to allow, for example, a conversion from Nullable<T> to object. if (conversions.HasBoxingConversion(typeArgument.Type.IsNullableType() ? ((NamedTypeSymbol)typeArgument.Type).ConstructedFrom : typeArgument.Type, constraintType.Type, ref useSiteInfo)) { return true; } if (typeArgument.Type is NamedTypeSymbol { IsRefLikeType: true } refLike && conversions.ImplementsVarianceCompatibleInterface(refLike, constraintType.Type, ref useSiteInfo)) { return true; } } if (typeArgument.TypeKind == TypeKind.TypeParameter) { var typeParameter = (TypeParameterSymbol)typeArgument.Type; // "... An implicit reference, boxing, or type parameter conversion // from type parameter A to C." if (conversions.HasImplicitTypeParameterConversion(typeParameter, constraintType.Type, ref useSiteInfo)) { return true; } // TypeBind::SatisfiesBound allows cases where one of the // type parameter constraints satisfies the constraint. foreach (var typeArgumentConstraint in typeParameter.ConstraintTypesWithDefinitionUseSiteDiagnostics(ref useSiteInfo)) { if (SatisfiesConstraintType(conversions, typeArgumentConstraint, constraintType, ref useSiteInfo)) { return true; } } } return false; } private static bool SelfOrBaseHasStaticAbstractMember(NamedTypeSymbol iface, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo, out Symbol memberWithoutImplementation) { Debug.Assert(iface.IsInterfaceType()); foreach (Symbol m in iface.GetMembers()) { if (m.IsStatic && m.IsImplementableInterfaceMember() && iface.FindImplementationForInterfaceMember(m) is null) { memberWithoutImplementation = m; return true; } } foreach (var baseInterface in iface.InterfacesAndTheirBaseInterfacesNoUseSiteDiagnostics.Keys) { foreach (Symbol m in baseInterface.GetMembers()) { if (m.IsStatic && m.IsImplementableInterfaceMember() && iface.FindImplementationForInterfaceMember(m) is null) { memberWithoutImplementation = m; return true; } } baseInterface.OriginalDefinition.AddUseSiteInfo(ref useSiteInfo); } memberWithoutImplementation = null; return false; } private static bool IsReferenceType(TypeParameterSymbol typeParameter, ImmutableArray<TypeWithAnnotations> constraintTypes) { return typeParameter.HasReferenceTypeConstraint || TypeParameterSymbol.CalculateIsReferenceTypeFromConstraintTypes(constraintTypes); } private static bool IsValueType(TypeParameterSymbol typeParameter, ImmutableArray<TypeWithAnnotations> constraintTypes) { return typeParameter.HasValueTypeConstraint || TypeParameterSymbol.CalculateIsValueTypeFromConstraintTypes(constraintTypes); } private static TypeParameterDiagnosticInfo GenerateConflictingConstraintsError(TypeParameterSymbol typeParameter, TypeSymbol deducedBase, bool classConflict) { // "Type parameter '{0}' inherits conflicting constraints '{1}' and '{2}'" return new TypeParameterDiagnosticInfo(typeParameter, new UseSiteInfo<AssemblySymbol>(new CSDiagnosticInfo(ErrorCode.ERR_BaseConstraintConflict, typeParameter, deducedBase, classConflict ? "class" : "struct"))); } private static void AddInterfaces(ArrayBuilder<NamedTypeSymbol> builder, ImmutableArray<NamedTypeSymbol> interfaces) { foreach (var @interface in interfaces) { AddInterface(builder, @interface); } } private static void AddInterface(ArrayBuilder<NamedTypeSymbol> builder, NamedTypeSymbol @interface) { if (!builder.Contains(@interface)) { builder.Add(@interface); } } private enum ConstructorConstraintError { None, NoPublicParameterlessConstructorOrAbstractType, HasRequiredMembers, } [MethodImpl(MethodImplOptions.NoInlining)] private static ConstructorConstraintError SatisfiesConstructorConstraint(TypeSymbol typeArgument) { switch (typeArgument.TypeKind) { case TypeKind.Struct: return SatisfiesPublicParameterlessConstructor((NamedTypeSymbol)typeArgument, synthesizedIfMissing: true); case TypeKind.Enum: case TypeKind.Dynamic: return ConstructorConstraintError.None; case TypeKind.Class: if (typeArgument.IsAbstract) { return ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType; } return SatisfiesPublicParameterlessConstructor((NamedTypeSymbol)typeArgument, synthesizedIfMissing: false); case TypeKind.TypeParameter: { var typeParameter = (TypeParameterSymbol)typeArgument; return typeParameter.HasConstructorConstraint || typeParameter.IsValueType ? ConstructorConstraintError.None : ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType; } case TypeKind.Submission: // submission can't be used as type argument throw ExceptionUtilities.UnexpectedValue(typeArgument.TypeKind); default: return ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType; } } private static ConstructorConstraintError SatisfiesPublicParameterlessConstructor(NamedTypeSymbol type, bool synthesizedIfMissing) { Debug.Assert(type.TypeKind is TypeKind.Class or TypeKind.Struct); bool hasAnyRequiredMembers = type.HasAnyRequiredMembers; foreach (var constructor in type.InstanceConstructors) { if (constructor.ParameterCount == 0) { if (constructor.DeclaredAccessibility != Accessibility.Public) { return ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType; } else if (hasAnyRequiredMembers && constructor.ShouldCheckRequiredMembers()) { return ConstructorConstraintError.HasRequiredMembers; } else { return ConstructorConstraintError.None; } } } return (synthesizedIfMissing, hasAnyRequiredMembers) switch { (false, _) => ConstructorConstraintError.NoPublicParameterlessConstructorOrAbstractType, (true, true) => ConstructorConstraintError.HasRequiredMembers, (true, false) => ConstructorConstraintError.None, }; } /// <summary> /// Returns true if type a is encompassed by type b (spec 6.4.3), /// and returns false otherwise. /// </summary> private static bool IsEncompassedBy(ConversionsBase conversions, TypeSymbol a, TypeSymbol b, ref CompoundUseSiteInfo<AssemblySymbol> useSiteInfo) { Debug.Assert(IsValidEncompassedByArgument(a)); Debug.Assert(IsValidEncompassedByArgument(b)); // IncludeNullability should not be used when calculating EffectiveBaseType or EffectiveInterfaceSet. Debug.Assert(!conversions.IncludeNullability); return conversions.HasIdentityOrImplicitReferenceConversion(a, b, ref useSiteInfo) || conversions.HasBoxingConversion(a, b, ref useSiteInfo); } private static bool IsValidEncompassedByArgument(TypeSymbol type) { switch (type.TypeKind) { case TypeKind.Array: case TypeKind.Class: case TypeKind.Delegate: case TypeKind.Enum: case TypeKind.Struct: return true; default: return false; } } public static bool RequiresChecking(NamedTypeSymbol type) { if (type.Arity == 0) { return false; } // If type is the original definition, there is no need // to check constraints. In the following for instance: // class A<T> where T : struct // { // A<T> F; // } if (ReferenceEquals(type.OriginalDefinition, type)) { return false; } Debug.Assert(!type.ConstructedFrom.Equals(type, TypeCompareKind.ConsiderEverything)); return true; } public static bool RequiresChecking(MethodSymbol method) { if (method.GetMemberArityIncludingExtension() == 0) { return false; } // If method is the original definition, there is no need // to check constraints. In the following for instance: // void M<T>() where T : class // { // M<T>(); // } if (ReferenceEquals(method.OriginalDefinition, method)) { return false; } Debug.Assert(method.ConstructedFrom != method || (method.IsExtensionBlockMember() && !method.ContainingType.IsDefinition)); return true; } [Conditional("DEBUG")] private static void CheckEffectiveAndDeducedBaseTypes(ConversionsBase conversions, TypeSymbol effectiveBase, TypeSymbol deducedBase) { Debug.Assert((object)deducedBase != null); Debug.Assert((object)effectiveBase != null); var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; Debug.Assert(deducedBase.IsErrorType() || effectiveBase.IsErrorType() || conversions.HasIdentityOrImplicitReferenceConversion(deducedBase, effectiveBase, ref discardedUseSiteInfo) || conversions.HasBoxingConversion(deducedBase, effectiveBase, ref discardedUseSiteInfo)); } internal static TypeWithAnnotations ConstraintWithMostSignificantNullability(TypeWithAnnotations type1, TypeWithAnnotations type2) { switch (type2.NullableAnnotation) { case NullableAnnotation.Annotated: return type1; case NullableAnnotation.NotAnnotated: return type2; case NullableAnnotation.Oblivious: if (type1.NullableAnnotation.IsNotAnnotated()) { return type1; } return type2; default: throw ExceptionUtilities.UnexpectedValue(type2.NullableAnnotation); } } internal static bool IsObjectConstraint(TypeWithAnnotations type, ref TypeWithAnnotations bestObjectConstraint) { if (type.SpecialType == SpecialType.System_Object) { switch (type.NullableAnnotation) { case NullableAnnotation.Annotated: break; default: if (!bestObjectConstraint.HasType) { bestObjectConstraint = type; } else { bestObjectConstraint = ConstraintWithMostSignificantNullability(bestObjectConstraint, type); } break; } return true; } return false; } internal static bool IsObjectConstraintSignificant(bool? isNotNullable, TypeWithAnnotations objectConstraint) { switch (isNotNullable) { case true: return false; case null: if (objectConstraint.NullableAnnotation.IsOblivious()) { return false; } break; } return true; } } }