From c74f22b12fbecf16ba6f075a214b7ca50a126961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 20 Apr 2026 16:04:14 +0900 Subject: [PATCH 1/5] Make constraint checking canon-aware for type constraints The AOT compiler's CheckConstraints was rejecting valid canonical instantiations because CanCastTo has no awareness of canonical types. When MakeGenericType produces canonical forms like GenericType, type constraints (interface/base-type) would fail because e.g. IRequest doesn't match IRequest. Add CanCastToConstraintWithCanon to TypeSystemConstraintsHelpers that handles canonical types in constraint checking: - __Canon/__UniversalCanon as the instantiation param: wildcard, accepts any type constraint (runtime validates with concrete types). - __Canon/__UniversalCanon as the constraint type itself: __Canon accepts reference types, __UniversalCanon accepts anything. - Canonical subtypes in constraint type args: uses ConvertToCanonForm to compare canonicalized forms, with variance support for variant positions. - Universal canon fallback for nested __UniversalCanon under Specific canonicalization. Split into Canon/NonCanon partial files following the existing pattern for IsSpecialTypeMeetingConstraint. Add NormalizeInstantiation call in HandleCallAction's MakeGenericTypeSite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TypeSystemConstraintsHelpers.Canon.cs | 154 ++++++++++++++++ .../TypeSystemConstraintsHelpers.NonCanon.cs | 17 ++ .../Common/TypeSystemConstraintsHelpers.cs | 14 +- .../ILVerification/ILVerification.projitems | 3 + .../Compiler/Dataflow/HandleCallAction.cs | 4 + .../ConstraintsValidationTest.cs | 164 ++++++++++++++++++ .../CoreTestAssembly/GenericConstraints.cs | 6 + .../ILCompiler.TypeSystem.csproj | 3 + 8 files changed, 361 insertions(+), 4 deletions(-) create mode 100644 src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs create mode 100644 src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs new file mode 100644 index 00000000000000..da02254471ff9d --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs @@ -0,0 +1,154 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Internal.TypeSystem +{ + public static partial class TypeSystemConstraintsHelpers + { + private static bool IsSpecialTypeMeetingConstraint(TypeDesc type, GenericConstraints constraint) + { + TypeSystemContext context = type.Context; + + return constraint switch + { + GenericConstraints.ReferenceTypeConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any), + GenericConstraints.DefaultConstructorConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any), + GenericConstraints.NotNullableValueTypeConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Universal), + _ => throw new UnreachableException() + }; + } + + /// + /// Checks if can satisfy the type constraint + /// when the constraint contains canonical types. + /// Canonical types act as wildcards: __Canon matches any reference type, + /// __UniversalCanon matches any type. + /// + private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, TypeDesc instantiatedConstraintType) + { + TypeSystemContext context = instantiationParam.Context; + + // If the instantiation param is a canonical definition type (__Canon or __UniversalCanon), + // it acts as a wildcard — any concrete type substituted at runtime will be validated then. + // The special constraints (class, struct, new()) are already handled separately; + // for type constraints we optimistically accept since we can't know the concrete type. + if (context.IsCanonicalDefinitionType(instantiationParam, CanonicalFormKind.Any)) + return true; + + // If the constraint type itself is a canonical definition type, check compatibility directly. + // E.g., "where T : U" with U=__Canon means T must be a plausible match for __Canon. + if (context.IsCanonicalDefinitionType(instantiatedConstraintType, CanonicalFormKind.Universal)) + return true; + if (context.IsCanonicalDefinitionType(instantiatedConstraintType, CanonicalFormKind.Specific)) + return instantiationParam.IsGCPointer; + + if (!instantiatedConstraintType.IsCanonicalSubtype(CanonicalFormKind.Any)) + return false; + + if (instantiatedConstraintType.IsInterface) + { + if (IsCanonCompatibleMatch(instantiationParam, instantiatedConstraintType)) + return true; + + foreach (var iface in instantiationParam.RuntimeInterfaces) + { + if (IsCanonCompatibleMatch(iface, instantiatedConstraintType)) + return true; + } + } + else + { + TypeDesc curType = instantiationParam; + while (curType is not null) + { + if (IsCanonCompatibleMatch(curType, instantiatedConstraintType)) + return true; + curType = curType.BaseType; + } + } + + return false; + } + + private static bool IsCanonCompatibleMatch(TypeDesc concreteType, TypeDesc canonType) + { + if (!concreteType.HasSameTypeDefinition(canonType)) + return false; + + Instantiation concreteInst = concreteType.Instantiation; + Instantiation canonInst = canonType.Instantiation; + Instantiation openInst = concreteType.GetTypeDefinition().Instantiation; + + for (int i = 0; i < concreteInst.Length; i++) + { + TypeDesc concreteArg = concreteInst[i]; + TypeDesc canonArg = canonInst[i]; + + if (concreteArg == canonArg) + continue; + + if (IsCanonicalTypeArgMatch(concreteArg, canonArg)) + continue; + + // Neither exact nor canon match — check if variance allows it + GenericVariance variance = ((GenericParameterDesc)openInst[i]).Variance; + switch (variance) + { + case GenericVariance.Covariant: + if (!concreteArg.CanCastTo(canonArg)) + return false; + break; + + case GenericVariance.Contravariant: + if (!canonArg.CanCastTo(concreteArg)) + return false; + break; + + default: + return false; + } + } + + return true; + } + + private static bool IsCanonicalTypeArgMatch(TypeDesc concreteArg, TypeDesc canonArg) + { + TypeSystemContext context = canonArg.Context; + + // Leaf canonical definition type checks (__Canon, __UniversalCanon themselves) + if (context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Universal)) + return true; + + if (context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Specific)) + return concreteArg.IsGCPointer || context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Any); + + if (context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Universal)) + return true; + + if (context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Specific)) + return canonArg.IsGCPointer || context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Any); + + // For parameterized types containing canonical components (e.g., __Canon[] vs string[], + // or List<__Canon> vs List), canonicalize both sides and compare. + // Guard: at least one side must actually contain canonical types to avoid false matches + // (e.g., string and object both canonicalize to __Canon but aren't interchangeable). + if (concreteArg.IsCanonicalSubtype(CanonicalFormKind.Any) || canonArg.IsCanonicalSubtype(CanonicalFormKind.Any)) + { + if (concreteArg.ConvertToCanonForm(CanonicalFormKind.Specific) == canonArg.ConvertToCanonForm(CanonicalFormKind.Specific)) + return true; + + // Specific canonicalization doesn't affect __UniversalCanon (a value type). + // When Universal canon is involved, fall back to Universal canonicalization + // where all types collapse to __UniversalCanon. + if (concreteArg.IsCanonicalSubtype(CanonicalFormKind.Universal) || canonArg.IsCanonicalSubtype(CanonicalFormKind.Universal)) + return concreteArg.ConvertToCanonForm(CanonicalFormKind.Universal) == canonArg.ConvertToCanonForm(CanonicalFormKind.Universal); + } + + return false; + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs new file mode 100644 index 00000000000000..3ccf10916ab99f --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace Internal.TypeSystem +{ + public static partial class TypeSystemConstraintsHelpers + { + private static bool IsSpecialTypeMeetingConstraint(TypeDesc type, GenericConstraints constraint) + => false; + + private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, TypeDesc instantiatedConstraintType) + => false; + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs index 5cb42cfc22cc2b..7e8b23c109da74 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs @@ -18,7 +18,7 @@ public InstantiationContext(Instantiation typeInstantiation, Instantiation metho } } - public static class TypeSystemConstraintsHelpers + public static partial class TypeSystemConstraintsHelpers { private static bool VerifyGenericParamConstraint(InstantiationContext genericParamContext, GenericParameterDesc genericParam, InstantiationContext instantiationParamContext, TypeDesc instantiationParam) @@ -29,7 +29,8 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar if ((constraints & GenericConstraints.ReferenceTypeConstraint) != 0) { if (!instantiationParam.IsGCPointer - && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.ReferenceTypeConstraint)) + && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.ReferenceTypeConstraint) + && !IsSpecialTypeMeetingConstraint(instantiationParam, GenericConstraints.ReferenceTypeConstraint)) return false; } @@ -37,7 +38,8 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar if ((constraints & GenericConstraints.DefaultConstructorConstraint) != 0) { if (!instantiationParam.HasExplicitOrImplicitDefaultConstructor() - && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.DefaultConstructorConstraint)) + && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.DefaultConstructorConstraint) + && !IsSpecialTypeMeetingConstraint(instantiationParam, GenericConstraints.DefaultConstructorConstraint)) return false; } @@ -45,7 +47,8 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar if ((constraints & GenericConstraints.NotNullableValueTypeConstraint) != 0) { if ((!instantiationParam.IsValueType || instantiationParam.IsNullable) - && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.NotNullableValueTypeConstraint)) + && !CheckGenericSpecialConstraint(instantiationParam, GenericConstraints.NotNullableValueTypeConstraint) + && !IsSpecialTypeMeetingConstraint(instantiationParam, GenericConstraints.NotNullableValueTypeConstraint)) return false; } @@ -62,6 +65,9 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar if (CanCastConstraint(ref instantiatedConstraints, instantiatedType)) continue; + if (CanCastToConstraintWithCanon(instantiationParam, instantiatedType)) + continue; + // CanCastTo below assumes thisType is boxed and that allows additional cases // to be considered castable. But int is not castable to Nullable and neither are enums. if (instantiationParam.IsValueType && instantiatedType.IsValueType && !instantiationParam.IsEquivalentTo(instantiatedType)) diff --git a/src/coreclr/tools/ILVerification/ILVerification.projitems b/src/coreclr/tools/ILVerification/ILVerification.projitems index c301f2da1d298e..1806b996da935c 100644 --- a/src/coreclr/tools/ILVerification/ILVerification.projitems +++ b/src/coreclr/tools/ILVerification/ILVerification.projitems @@ -372,5 +372,8 @@ TypeSystem\Common\TypeSystemConstraintsHelpers.cs + + TypeSystem\Common\TypeSystemConstraintsHelpers.NonCanon.cs + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index b41dbe38d250c9..e286aa17bbebd1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -783,6 +783,10 @@ private sealed class MakeGenericTypeSite : INodeWithRuntimeDeterminedDependencie { var list = new DependencyList(); TypeDesc instantiatedType = _type.InstantiateSignature(typeInstantiation, methodInstantiation); + + // InstantiateSignature could end up with a denormalized shape (Foo) so normalize. + instantiatedType = instantiatedType.NormalizeInstantiation(); + if (instantiatedType.CheckConstraints(new InstantiationContext(typeInstantiation, methodInstantiation))) RootingHelpers.TryGetDependenciesForReflectedType(ref list, factory, instantiatedType, "MakeGenericType"); return list; diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs index a3fb0b75b0b8a6..1b525500cae84d 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs @@ -354,5 +354,169 @@ public void TestTypeConstraints() Assert.False(instantiatedType.Instantiation.CheckValidInstantiationArguments()); } } + + [Fact] + public void TestCanonicalTypeConstraints() + { + TypeDesc canon = _context.CanonType; + TypeDesc universalCanon = _context.UniversalCanonType; + TypeDesc objectType = _context.GetWellKnownType(WellKnownType.Object); + TypeDesc stringType = _context.GetWellKnownType(WellKnownType.String); + TypeDesc intType = _context.GetWellKnownType(WellKnownType.Int32); + TypeDesc uintType = _context.GetWellKnownType(WellKnownType.UInt32); + + MetadataType nonVariantInterfaceConstraintType = _testModule.GetType("GenericConstraints"u8, "NonVariantInterfaceConstraint`2"u8); + MetadataType nonVariantGenImplType = _testModule.GetType("GenericConstraints"u8, "NonVariantGenImpl`1"u8); + + TypeDesc instantiatedType; + + // __Canon satisfies special constraints: class, new() + { + instantiatedType = _referenceTypeConstraintType.MakeInstantiatedType(canon); + Assert.True(instantiatedType.CheckConstraints()); + + instantiatedType = _defaultConstructorConstraintType.MakeInstantiatedType(canon); + Assert.True(instantiatedType.CheckConstraints()); + + // __Canon should NOT satisfy struct constraint + instantiatedType = _notNullableValueTypeConstraintType.MakeInstantiatedType(canon); + Assert.False(instantiatedType.CheckConstraints()); + } + + // __UniversalCanon satisfies all special constraints + { + instantiatedType = _referenceTypeConstraintType.MakeInstantiatedType(universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + + instantiatedType = _defaultConstructorConstraintType.MakeInstantiatedType(universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + + instantiatedType = _notNullableValueTypeConstraintType.MakeInstantiatedType(universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + } + + // __Canon as instantiation param satisfies type constraints (wildcard — runtime will validate) + { + // NonVariantInterfaceConstraint<__Canon, object>: __Canon is wildcard, passes any type constraint + instantiatedType = nonVariantInterfaceConstraintType.MakeInstantiatedType(canon, objectType); + Assert.True(instantiatedType.CheckConstraints()); + + // ComplexGenericConstraint3<__Canon, object>: where T : IGen, T=__Canon, U=object + instantiatedType = _complexGenericConstraint3Type.MakeInstantiatedType(canon, objectType); + Assert.True(instantiatedType.CheckConstraints()); + } + + // Invariant interface constraint with __Canon in the constraint's type args + // NonVariantGenImpl : INonVariantGen + // NonVariantInterfaceConstraint where T : INonVariantGen + // Check: NonVariantInterfaceConstraint, __Canon> + // constraint becomes INonVariantGen<__Canon>, concrete implements INonVariantGen + // string is a ref type, __Canon matches ref types + { + TypeDesc nonVariantGenImplOfString = nonVariantGenImplType.MakeInstantiatedType(stringType); + instantiatedType = nonVariantInterfaceConstraintType.MakeInstantiatedType(nonVariantGenImplOfString, canon); + Assert.True(instantiatedType.CheckConstraints()); + + // With int (value type) — __Canon should NOT match value types + TypeDesc nonVariantGenImplOfInt = nonVariantGenImplType.MakeInstantiatedType(intType); + instantiatedType = nonVariantInterfaceConstraintType.MakeInstantiatedType(nonVariantGenImplOfInt, canon); + Assert.False(instantiatedType.CheckConstraints()); + } + + // Variant interface constraint with __Canon in the constraint's type args + // ComplexGenericConstraint3 where T : IGen (IGen is contravariant) + // Arg3 : IGen + // ComplexGenericConstraint3, __Canon> + // constraint: IGen<__Canon>. Arg3 implements IGen. + // __Canon matches object (ref type) in invariant arg position of IGen + { + TypeDesc arg3OfObject = _arg3Type.MakeInstantiatedType(objectType); + instantiatedType = _complexGenericConstraint3Type.MakeInstantiatedType(arg3OfObject, canon); + Assert.True(instantiatedType.CheckConstraints()); + } + + // Base type constraint with __Canon + // ComplexGenericConstraint2 where T : Arg2> + // Arg2> — constraint has canonical subtype + // T=Arg2> should match because Arg2 canonicalizes to Arg2<__Canon> + { + TypeDesc arg2OfString = _arg2Type.MakeInstantiatedType(stringType); + TypeDesc arg2OfArg2OfString = _arg2Type.MakeInstantiatedType(arg2OfString); + instantiatedType = _complexGenericConstraint2Type.MakeInstantiatedType(arg2OfArg2OfString, canon); + Assert.True(instantiatedType.CheckConstraints()); + + // Value type should not match __Canon in base type constraint + TypeDesc arg2OfInt = _arg2Type.MakeInstantiatedType(intType); + TypeDesc arg2OfArg2OfInt = _arg2Type.MakeInstantiatedType(arg2OfInt); + instantiatedType = _complexGenericConstraint2Type.MakeInstantiatedType(arg2OfArg2OfInt, canon); + Assert.False(instantiatedType.CheckConstraints()); + } + + // Parameterized canonical types (e.g., __Canon[] as type arg in constraint) + // ComplexGenericConstraint3 where T : IGen (IGen) + // T=IGen, U=int[] : IGen implements IGen, passes normally. + // Canonicalized: T becomes __Canon (ref type), U=int[] stays. + // Check: __Canon satisfies IGen? __Canon is wildcard → true. + { + TypeDesc intArray = intType.MakeArrayType(); + instantiatedType = _complexGenericConstraint3Type.MakeInstantiatedType(canon, intArray); + Assert.True(instantiatedType.CheckConstraints()); + } + + // Variance + __Canon interaction: MultipleConstraints where T : class, IGen, new() + // MultipleConstraints + // ClassArgWithDefaultCtor : IGen + // constraint: IGen<__Canon>, __Canon matches object + { + instantiatedType = _multipleConstraintsType.MakeInstantiatedType(_classArgWithDefaultCtorType, canon); + Assert.True(instantiatedType.CheckConstraints()); + } + + // Interface type used directly as instantiation param + // ComplexGenericConstraint3, __Canon>: IGen satisfies IGen<__Canon> + { + TypeDesc igenOfString = _iGenType.MakeInstantiatedType(stringType); + instantiatedType = _complexGenericConstraint3Type.MakeInstantiatedType(igenOfString, canon); + Assert.True(instantiatedType.CheckConstraints()); + } + + // __UniversalCanon as instantiation param should pass all type constraints + { + instantiatedType = nonVariantInterfaceConstraintType.MakeInstantiatedType(universalCanon, objectType); + Assert.True(instantiatedType.CheckConstraints()); + + instantiatedType = _complexGenericConstraint3Type.MakeInstantiatedType(universalCanon, intType); + Assert.True(instantiatedType.CheckConstraints()); + } + + // __Canon / __UniversalCanon as the constraint type itself + // SimpleGenericConstraint where T : U + // T=Arg1, U=__Canon → constraint type is __Canon, a ref type should satisfy it + { + instantiatedType = _simpleGenericConstraintType.MakeInstantiatedType(_arg1Type, canon); + Assert.True(instantiatedType.CheckConstraints()); + + // Value type should not satisfy __Canon constraint + instantiatedType = _simpleGenericConstraintType.MakeInstantiatedType(_structArgWithDefaultCtorType, canon); + Assert.False(instantiatedType.CheckConstraints()); + + // Any type satisfies __UniversalCanon constraint + instantiatedType = _simpleGenericConstraintType.MakeInstantiatedType(_arg1Type, universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + + instantiatedType = _simpleGenericConstraintType.MakeInstantiatedType(_structArgWithDefaultCtorType, universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + } + + // Nested __UniversalCanon under invariant generic shape + // ComplexGenericConstraint2 where T : Arg2> + // T=Arg2>, U=__UniversalCanon → constraint is Arg2> + { + TypeDesc arg2OfInt = _arg2Type.MakeInstantiatedType(intType); + TypeDesc arg2OfArg2OfInt = _arg2Type.MakeInstantiatedType(arg2OfInt); + instantiatedType = _complexGenericConstraint2Type.MakeInstantiatedType(arg2OfArg2OfInt, universalCanon); + Assert.True(instantiatedType.CheckConstraints()); + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/GenericConstraints.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/GenericConstraints.cs index 5c050742c353dc..2cd0385f2f42de 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/GenericConstraints.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/CoreTestAssembly/GenericConstraints.cs @@ -68,4 +68,10 @@ public static void SimpleGenericConstraintMethod() where T : U { } public static void ComplexGenericConstraintMethod() where T : U where U : IGen { } } + + public interface INonVariantGen { } + + public class NonVariantGenImpl : INonVariantGen { } + + public class NonVariantInterfaceConstraint where T : INonVariantGen { } } diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index 279b463b2c7594..8f6479b7b80c28 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -101,6 +101,9 @@ TypeSystem\Canon\TypeDesc.Canon.cs + + TypeSystem\Canon\TypeSystemConstraintsHelpers.Canon.cs + TypeSystem\Canon\TypeSystemContext.Canon.cs From bb84090919f533dc69008007a3db1bb762b3cf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 20 Apr 2026 16:37:09 +0900 Subject: [PATCH 2/5] Move canon awareness from constraint helpers into CastingHelper Move IsCanonicalTypeArgMatch and IsCanonEquivalent from TypeSystemConstraintsHelpers.Canon.cs into CastingHelper.Canon.cs so that CanCastTo naturally handles canonical types. This eliminates the duplicated interface-walking, base-chain-walking, and variance logic that was in CanCastToConstraintWithCanon. Key changes to CastingHelper: - IsCanonicalCastTarget in CanCastToInternal: __Canon accepts any reference type as a cast source, __UniversalCanon accepts any type. This makes array covariance (string[] -> __Canon[]) work naturally. - IsCanonicalTypeArgMatch in CanCastByVarianceToInterfaceOrDelegate: handles invariant positions where type args differ by canonicalization. - IsCanonEquivalent in CanCastToNonVariantInterface and CanCastToClass: handles non-variant interfaces and base types with canon type args. CanCastToConstraintWithCanon is simplified to only handle wildcard cases where the param or constraint IS __Canon/__UniversalCanon directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/System.Private.TypeLoader.csproj | 3 + .../TypeSystem/Canon/CastingHelper.Canon.cs | 78 ++++++++++++ .../TypeSystemConstraintsHelpers.Canon.cs | 111 +----------------- .../Common/CastingHelper.NonCanon.cs | 17 +++ .../Common/TypeSystem/Common/CastingHelper.cs | 14 ++- .../ILVerification/ILVerification.projitems | 3 + .../ILCompiler.TypeSystem.csproj | 3 + 7 files changed, 118 insertions(+), 111 deletions(-) create mode 100644 src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs create mode 100644 src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs diff --git a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj index 67540af5894b7c..31628abafd0f15 100644 --- a/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj +++ b/src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj @@ -117,6 +117,9 @@ Internal\TypeSystem\CastingHelper.cs + + Internal\TypeSystem\CastingHelper.Canon.cs + Internal\TypeSystem\DefType.cs diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs new file mode 100644 index 00000000000000..0da530b2b6ebbc --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs @@ -0,0 +1,78 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Internal.TypeSystem +{ + public static partial class CastingHelper + { + /// + /// Check if is a canonical type that + /// can be cast to. __Canon accepts any reference type; __UniversalCanon accepts any type. + /// + private static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) + { + TypeSystemContext context = thisType.Context; + + if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Universal)) + return true; + + if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Specific)) + return thisType.IsGCPointer; + + return false; + } + + /// + /// Check if two type arguments can be considered matching because one (or both) is canonical. + /// __Canon matches any reference type; __UniversalCanon matches any type. + /// + private static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType) + { + TypeSystemContext context = type.Context; + + if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Universal)) + return true; + + if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Specific)) + return type.IsGCPointer || context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any); + + if (context.IsCanonicalDefinitionType(type, CanonicalFormKind.Universal)) + return true; + + if (context.IsCanonicalDefinitionType(type, CanonicalFormKind.Specific)) + return otherType.IsGCPointer || context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Any); + + // For non-leaf types (e.g., Arg2 vs Arg2<__Canon>), check if they are + // canon-equivalent: same type definition with canon-compatible type arguments. + // This also covers arrays via CanCastTo's array covariance path + IsCanonicalCastTarget. + return IsCanonEquivalent(type, otherType); + } + + /// + /// Check if two types are equivalent considering canonical type matching rules. + /// Same type definition with all type arguments either equal or canon-compatible. + /// + private static bool IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType) + { + if (!thisType.HasSameTypeDefinition(otherType)) + return false; + + Instantiation thisInst = thisType.Instantiation; + Instantiation otherInst = otherType.Instantiation; + + if (thisInst.Length == 0) + return false; + + for (int i = 0; i < thisInst.Length; i++) + { + if (thisInst[i] == otherInst[i]) + continue; + + if (!IsCanonicalTypeArgMatch(thisInst[i], otherInst[i])) + return false; + } + + return true; + } + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs index da02254471ff9d..fd458523bc5a99 100644 --- a/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs @@ -23,9 +23,9 @@ private static bool IsSpecialTypeMeetingConstraint(TypeDesc type, GenericConstra /// /// Checks if can satisfy the type constraint - /// when the constraint contains canonical types. - /// Canonical types act as wildcards: __Canon matches any reference type, - /// __UniversalCanon matches any type. + /// when the param or constraint IS a canonical + /// definition type (__Canon or __UniversalCanon). Handles wildcard semantics only; + /// structural matching (interface walking, base chain, variance) is in CastingHelper. /// private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, TypeDesc instantiatedConstraintType) { @@ -33,8 +33,6 @@ private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, Ty // If the instantiation param is a canonical definition type (__Canon or __UniversalCanon), // it acts as a wildcard — any concrete type substituted at runtime will be validated then. - // The special constraints (class, struct, new()) are already handled separately; - // for type constraints we optimistically accept since we can't know the concrete type. if (context.IsCanonicalDefinitionType(instantiationParam, CanonicalFormKind.Any)) return true; @@ -45,109 +43,6 @@ private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, Ty if (context.IsCanonicalDefinitionType(instantiatedConstraintType, CanonicalFormKind.Specific)) return instantiationParam.IsGCPointer; - if (!instantiatedConstraintType.IsCanonicalSubtype(CanonicalFormKind.Any)) - return false; - - if (instantiatedConstraintType.IsInterface) - { - if (IsCanonCompatibleMatch(instantiationParam, instantiatedConstraintType)) - return true; - - foreach (var iface in instantiationParam.RuntimeInterfaces) - { - if (IsCanonCompatibleMatch(iface, instantiatedConstraintType)) - return true; - } - } - else - { - TypeDesc curType = instantiationParam; - while (curType is not null) - { - if (IsCanonCompatibleMatch(curType, instantiatedConstraintType)) - return true; - curType = curType.BaseType; - } - } - - return false; - } - - private static bool IsCanonCompatibleMatch(TypeDesc concreteType, TypeDesc canonType) - { - if (!concreteType.HasSameTypeDefinition(canonType)) - return false; - - Instantiation concreteInst = concreteType.Instantiation; - Instantiation canonInst = canonType.Instantiation; - Instantiation openInst = concreteType.GetTypeDefinition().Instantiation; - - for (int i = 0; i < concreteInst.Length; i++) - { - TypeDesc concreteArg = concreteInst[i]; - TypeDesc canonArg = canonInst[i]; - - if (concreteArg == canonArg) - continue; - - if (IsCanonicalTypeArgMatch(concreteArg, canonArg)) - continue; - - // Neither exact nor canon match — check if variance allows it - GenericVariance variance = ((GenericParameterDesc)openInst[i]).Variance; - switch (variance) - { - case GenericVariance.Covariant: - if (!concreteArg.CanCastTo(canonArg)) - return false; - break; - - case GenericVariance.Contravariant: - if (!canonArg.CanCastTo(concreteArg)) - return false; - break; - - default: - return false; - } - } - - return true; - } - - private static bool IsCanonicalTypeArgMatch(TypeDesc concreteArg, TypeDesc canonArg) - { - TypeSystemContext context = canonArg.Context; - - // Leaf canonical definition type checks (__Canon, __UniversalCanon themselves) - if (context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Universal)) - return true; - - if (context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Specific)) - return concreteArg.IsGCPointer || context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Any); - - if (context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Universal)) - return true; - - if (context.IsCanonicalDefinitionType(concreteArg, CanonicalFormKind.Specific)) - return canonArg.IsGCPointer || context.IsCanonicalDefinitionType(canonArg, CanonicalFormKind.Any); - - // For parameterized types containing canonical components (e.g., __Canon[] vs string[], - // or List<__Canon> vs List), canonicalize both sides and compare. - // Guard: at least one side must actually contain canonical types to avoid false matches - // (e.g., string and object both canonicalize to __Canon but aren't interchangeable). - if (concreteArg.IsCanonicalSubtype(CanonicalFormKind.Any) || canonArg.IsCanonicalSubtype(CanonicalFormKind.Any)) - { - if (concreteArg.ConvertToCanonForm(CanonicalFormKind.Specific) == canonArg.ConvertToCanonForm(CanonicalFormKind.Specific)) - return true; - - // Specific canonicalization doesn't affect __UniversalCanon (a value type). - // When Universal canon is involved, fall back to Universal canonicalization - // where all types collapse to __UniversalCanon. - if (concreteArg.IsCanonicalSubtype(CanonicalFormKind.Universal) || canonArg.IsCanonicalSubtype(CanonicalFormKind.Universal)) - return concreteArg.ConvertToCanonForm(CanonicalFormKind.Universal) == canonArg.ConvertToCanonForm(CanonicalFormKind.Universal); - } - return false; } } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs new file mode 100644 index 00000000000000..017b3d113d1ad1 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Internal.TypeSystem +{ + public static partial class CastingHelper + { + private static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) + => false; + + private static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType) + => false; + + private static bool IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType) + => false; + } +} diff --git a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs index 79b36d14cd2871..9ceb1cab35d1fc 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs @@ -173,6 +173,11 @@ private static bool CanCastToInternal(this TypeDesc thisType, TypeDesc otherType return true; } + if (IsCanonicalCastTarget(thisType, otherType)) + { + return true; + } + switch (thisType.Category) { case TypeFlags.GenericParameter: @@ -418,14 +423,14 @@ private static bool CanCastToInterface(this TypeDesc thisType, TypeDesc otherTyp private static bool CanCastToNonVariantInterface(this TypeDesc thisType, TypeDesc otherType) { - if (otherType.IsEquivalentTo(thisType)) + if (otherType.IsEquivalentTo(thisType) || IsCanonEquivalent(thisType, otherType)) { return true; } foreach (var interfaceType in thisType.RuntimeInterfaces) { - if (interfaceType.IsEquivalentTo(otherType)) + if (interfaceType.IsEquivalentTo(otherType) || IsCanonEquivalent(interfaceType, otherType)) { return true; } @@ -469,6 +474,9 @@ private static bool CanCastByVarianceToInterfaceOrDelegate(this TypeDesc thisTyp if (!arg.IsEquivalentTo(targetArg)) { + if (IsCanonicalTypeArgMatch(arg, targetArg)) + continue; + GenericVariance variance = arrayCovariance ? GenericVariance.Covariant : ((GenericParameterDesc)instantiationOpen[i]).Variance; @@ -541,7 +549,7 @@ private static bool CanCastToClass(this TypeDesc thisType, TypeDesc otherType, S do { - if (curType.IsEquivalentTo(otherType)) + if (curType.IsEquivalentTo(otherType) || IsCanonEquivalent(curType, otherType)) return true; curType = curType.BaseType; diff --git a/src/coreclr/tools/ILVerification/ILVerification.projitems b/src/coreclr/tools/ILVerification/ILVerification.projitems index 1806b996da935c..ef3fcb799adf79 100644 --- a/src/coreclr/tools/ILVerification/ILVerification.projitems +++ b/src/coreclr/tools/ILVerification/ILVerification.projitems @@ -36,6 +36,9 @@ TypeSystem\Common\CastingHelper.cs + + TypeSystem\Common\CastingHelper.NonCanon.cs + TypeSystem\Common\FunctionPointerType.cs diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj index 8f6479b7b80c28..3bef4a5b3bc9ab 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -131,6 +131,9 @@ TypeSystem\Common\CastingHelper.cs + + TypeSystem\Canon\CastingHelper.Canon.cs + TypeSystem\Common\CastingHelper.TypeEquivalence.cs From 3fe978ae286096cbcb73dc31e3fff95b533e2307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 20 Apr 2026 17:01:05 +0900 Subject: [PATCH 3/5] Handle parameterized types in canonical type arg matching IsCanonicalTypeArgMatch did not handle parameterized types like arrays: string[] vs __Canon[] failed because IsCanonEquivalent checks Instantiation which is empty for arrays. Add ParameterizedType structural matching that recursively compares element types. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../TypeSystem/Canon/CastingHelper.Canon.cs | 19 +++++++++++++++++-- .../ConstraintsValidationTest.cs | 13 +++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs index 0da530b2b6ebbc..ee75fcc3fed19e 100644 --- a/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs @@ -8,6 +8,7 @@ public static partial class CastingHelper /// /// Check if is a canonical type that /// can be cast to. __Canon accepts any reference type; __UniversalCanon accepts any type. + /// Pointers, byrefs, and function pointers are not valid instantiation arguments. /// private static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) { @@ -44,8 +45,22 @@ private static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType) // For non-leaf types (e.g., Arg2 vs Arg2<__Canon>), check if they are // canon-equivalent: same type definition with canon-compatible type arguments. - // This also covers arrays via CanCastTo's array covariance path + IsCanonicalCastTarget. - return IsCanonEquivalent(type, otherType); + if (IsCanonEquivalent(type, otherType)) + return true; + + // For parameterized types like arrays (string[] vs __Canon[]), + // recursively match the element/parameter type. + if (type is ParameterizedType paramType && otherType is ParameterizedType otherParamType + && type.Category == otherType.Category) + { + if (type is ArrayType arrayType && otherType is ArrayType otherArrayType + && arrayType.Rank != otherArrayType.Rank) + return false; + + return IsCanonicalTypeArgMatch(paramType.ParameterType, otherParamType.ParameterType); + } + + return false; } /// diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs index 1b525500cae84d..8dc75f4eb309ae 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs @@ -517,6 +517,19 @@ public void TestCanonicalTypeConstraints() instantiatedType = _complexGenericConstraint2Type.MakeInstantiatedType(arg2OfArg2OfInt, universalCanon); Assert.True(instantiatedType.CheckConstraints()); } + + // Array type args with __Canon in invariant position + // NonVariantInterfaceConstraint where T : INonVariantGen + // T=NonVariantGenImpl, U=__Canon[] → constraint is INonVariantGen<__Canon[]> + // NonVariantGenImpl implements INonVariantGen + // string[] is a ref type, so string[] should be compatible with __Canon[] + { + TypeDesc stringArray = stringType.MakeArrayType(); + TypeDesc canonArray = canon.MakeArrayType(); + TypeDesc nonVariantGenImplOfStringArray = nonVariantGenImplType.MakeInstantiatedType(stringArray); + instantiatedType = nonVariantInterfaceConstraintType.MakeInstantiatedType(nonVariantGenImplOfStringArray, canonArray); + Assert.True(instantiatedType.CheckConstraints()); + } } } } From 6f5cf187412dfe8ae4c653704421023ab4054b19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Mon, 20 Apr 2026 17:26:54 +0900 Subject: [PATCH 4/5] Add regression test for MakeGenericType with constrained canonicals Exercises MakeGenericType where the canonical instantiation (Gen) must pass constraint checking with __Canon satisfying interface and new() constraints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../SmokeTests/TrimmingBehaviors/Dataflow.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs index dbdbcae08372e5..c275b2a8b2c9d0 100644 --- a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs +++ b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs @@ -28,6 +28,7 @@ public static int Run() TestObjectGetTypeDataflow.Run(); TestMakeGenericDataflow.Run(); TestMakeGenericDataflowInvalid.Run(); + TestMakeGenericConstrainedDataflow.Run(); TestMarshalIntrinsics.Run(); Regression97758.Run(); @@ -698,6 +699,26 @@ public static void Run() } } + class TestMakeGenericConstrainedDataflow + { + struct Atom; + + class Gen where U : IFoo, new(); + + interface IFoo; + class Foo : IFoo; + + public static object Handle() where U : new() + { + return Activator.CreateInstance(typeof(Gen<,,>).MakeGenericType(typeof(T), typeof(U), typeof(object))); + } + + public static void Run() + { + Handle().ToString(); + } + } + class TestMarshalIntrinsics { [StructLayout(LayoutKind.Sequential)] From 8289ab18da1b7dca261eccc098d44e69c9989837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Tue, 21 Apr 2026 09:48:44 +0900 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs | 1 - .../TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs | 1 - .../aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs index fd458523bc5a99..169b5df9dbf7b7 100644 --- a/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Diagnostics; namespace Internal.TypeSystem diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs index 3ccf10916ab99f..87a37f476525a2 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; using System.Diagnostics; namespace Internal.TypeSystem diff --git a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs index 8dc75f4eb309ae..a1e6916ff463fd 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs @@ -363,7 +363,6 @@ public void TestCanonicalTypeConstraints() TypeDesc objectType = _context.GetWellKnownType(WellKnownType.Object); TypeDesc stringType = _context.GetWellKnownType(WellKnownType.String); TypeDesc intType = _context.GetWellKnownType(WellKnownType.Int32); - TypeDesc uintType = _context.GetWellKnownType(WellKnownType.UInt32); MetadataType nonVariantInterfaceConstraintType = _testModule.GetType("GenericConstraints"u8, "NonVariantInterfaceConstraint`2"u8); MetadataType nonVariantGenImplType = _testModule.GetType("GenericConstraints"u8, "NonVariantGenImpl`1"u8);