From 65c217fbc735cffa484cead282ba62202820f997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Jun 2026 13:25:21 +0900 Subject: [PATCH 1/2] Reapply "Handle canonical types in casting logic" (#127301) This reverts commit 9619cf5696f4e63232b5edf429df6014955193fa. --- .../src/System.Private.TypeLoader.csproj | 3 + .../TypeSystem/Canon/CastingHelper.Canon.cs | 93 +++++++++ .../TypeSystemConstraintsHelpers.Canon.cs | 48 +++++ .../Common/CastingHelper.NonCanon.cs | 17 ++ .../Common/TypeSystem/Common/CastingHelper.cs | 14 +- .../TypeSystemConstraintsHelpers.NonCanon.cs | 16 ++ .../Common/TypeSystemConstraintsHelpers.cs | 14 +- .../ILVerification/ILVerification.projitems | 6 + .../Compiler/Dataflow/HandleCallAction.cs | 4 + .../ConstraintsValidationTest.cs | 176 ++++++++++++++++++ .../CoreTestAssembly/GenericConstraints.cs | 6 + .../ILCompiler.TypeSystem.csproj | 6 + .../SmokeTests/TrimmingBehaviors/Dataflow.cs | 21 +++ 13 files changed, 417 insertions(+), 7 deletions(-) create mode 100644 src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs create mode 100644 src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs create mode 100644 src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs create mode 100644 src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.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 6158a89613fec4..02327cc5905fb5 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 @@ -120,6 +120,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..ee75fcc3fed19e --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs @@ -0,0 +1,93 @@ +// 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. + /// Pointers, byrefs, and function pointers are not valid instantiation arguments. + /// + 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. + 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; + } + + /// + /// 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 new file mode 100644 index 00000000000000..169b5df9dbf7b7 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Canon/TypeSystemConstraintsHelpers.Canon.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +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 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) + { + 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. + 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; + + 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/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs new file mode 100644 index 00000000000000..87a37f476525a2 --- /dev/null +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.NonCanon.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +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 ab41851c791f91..fcd63c8b0cf6f2 100644 --- a/src/coreclr/tools/ILVerification/ILVerification.projitems +++ b/src/coreclr/tools/ILVerification/ILVerification.projitems @@ -39,6 +39,9 @@ TypeSystem\Common\CastingHelper.cs + + TypeSystem\Common\CastingHelper.NonCanon.cs + TypeSystem\Common\FunctionPointerType.cs @@ -375,5 +378,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 49e16e4317a641..e213ba642d4d21 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -815,6 +815,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..a1e6916ff463fd 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem.Tests/ConstraintsValidationTest.cs @@ -354,5 +354,181 @@ 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); + + 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()); + } + + // 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()); + } + } } } 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 536b7ac549eba0..03339e7e0bfe7d 100644 --- a/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj +++ b/src/coreclr/tools/aot/ILCompiler.TypeSystem/ILCompiler.TypeSystem.csproj @@ -104,6 +104,9 @@ TypeSystem\Canon\TypeDesc.Canon.cs + + TypeSystem\Canon\TypeSystemConstraintsHelpers.Canon.cs + TypeSystem\Canon\TypeSystemContext.Canon.cs @@ -131,6 +134,9 @@ TypeSystem\Common\CastingHelper.cs + + TypeSystem\Canon\CastingHelper.Canon.cs + TypeSystem\Common\CastingHelper.TypeEquivalence.cs diff --git a/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs b/src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs index 4c01d6aaeca072..b4b4239c8bc240 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(); TestMakeGenericDataflowInLocalMethod.Run(); @@ -699,6 +700,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 43f5bd098c2d382e04e89756ff85a2aff8159200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Strehovsk=C3=BD?= Date: Thu, 11 Jun 2026 14:34:05 +0900 Subject: [PATCH 2/2] Do not change the overall casting logic --- .../TypeSystem/Canon/CastingHelper.Canon.cs | 8 +- .../Common/CastingHelper.NonCanon.cs | 12 +- .../Common/TypeSystem/Common/CastingHelper.cs | 145 +++++++++++------- .../Common/TypeSystemConstraintsHelpers.cs | 4 +- 4 files changed, 94 insertions(+), 75 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs index ee75fcc3fed19e..93ceda72d19293 100644 --- a/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Canon/CastingHelper.Canon.cs @@ -3,14 +3,14 @@ namespace Internal.TypeSystem { - public static partial class CastingHelper + internal sealed class CanonicalTypeCastingHandler : ICanonicalTypeCastingHandler { /// /// 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) + public static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) { TypeSystemContext context = thisType.Context; @@ -27,7 +27,7 @@ private static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) /// 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) + public static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType) { TypeSystemContext context = type.Context; @@ -67,7 +67,7 @@ private static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc 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) + public static bool IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType) { if (!thisType.HasSameTypeDefinition(otherType)) return false; diff --git a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs index 017b3d113d1ad1..737a10b59839bc 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.NonCanon.cs @@ -3,15 +3,5 @@ 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; - } + internal sealed class CanonicalTypeCastingHandler : INonCanonicalTypeCastingHandler; } diff --git a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs index 9ceb1cab35d1fc..422294f5bedeaf 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/CastingHelper.cs @@ -14,7 +14,38 @@ public static partial class CastingHelper /// public static bool CanCastTo(this TypeDesc thisType, TypeDesc otherType) { - return thisType.CanCastToInternal(otherType, null); + return CastingHelper.CanCastToInternal(thisType, otherType, null); + } + + /// + /// Returns true if '' can be cast to ''. + /// Assumes '' is in it's boxed form if it's a value type (i.e. + /// [System.Int32].CanCastTo([System.Object]) will return true). + /// Handles canonical types, so [System.Object].CanCastTo([System.__UniversalCanon]) will be true. + /// + public static bool CanCastToWithCanon(this TypeDesc thisType, TypeDesc otherType) + { + return CastingHelper.CanCastToInternal(thisType, otherType, null); + } + + public static bool IsArrayElementTypeCastableBySize(TypeDesc elementType) + { + switch (elementType.UnderlyingType.Category) + { + case TypeFlags.Byte: + case TypeFlags.SByte: + case TypeFlags.UInt16: + case TypeFlags.Int16: + case TypeFlags.UInt32: + case TypeFlags.Int32: + case TypeFlags.UInt64: + case TypeFlags.Int64: + case TypeFlags.UIntPtr: + case TypeFlags.IntPtr: + return true; + } + + return false; } /// @@ -166,14 +197,33 @@ internal static bool IsEquivalentTo(this TypeDesc thisType, TypeDesc otherType, } static partial void IsEquivalentTo(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect visited, ref bool isEquivalentTo); - private static bool CanCastToInternal(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) + } + + internal interface ICanonicalTypeCastingHandler + { + static abstract bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType); + static abstract bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType); + static abstract bool IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType); + + } + + internal interface INonCanonicalTypeCastingHandler : ICanonicalTypeCastingHandler + { + static bool ICanonicalTypeCastingHandler.IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType) => false; + static bool ICanonicalTypeCastingHandler.IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType) => false; + static bool ICanonicalTypeCastingHandler.IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType) => false; + } + + internal static class CastingHelper where T : ICanonicalTypeCastingHandler + { + internal static bool CanCastToInternal(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) { if (thisType == otherType) { return true; } - if (IsCanonicalCastTarget(thisType, otherType)) + if (T.IsCanonicalCastTarget(thisType, otherType)) { return true; } @@ -181,17 +231,17 @@ private static bool CanCastToInternal(this TypeDesc thisType, TypeDesc otherType switch (thisType.Category) { case TypeFlags.GenericParameter: - return ((GenericParameterDesc)thisType).CanCastGenericParameterTo(otherType, protect); + return CanCastGenericParameterTo((GenericParameterDesc)thisType, otherType, protect); case TypeFlags.Array: case TypeFlags.SzArray: - return ((ArrayType)thisType).CanCastArrayTo(otherType, protect); + return CanCastArrayTo((ArrayType)thisType, otherType, protect); case TypeFlags.ByRef: case TypeFlags.Pointer: if (otherType.Category == thisType.Category) { - return ((ParameterizedType)thisType).CanCastParamTo(((ParameterizedType)otherType).ParameterType, protect); + return CanCastParamTo((ParameterizedType)thisType, ((ParameterizedType)otherType).ParameterType, protect); } return false; @@ -200,11 +250,11 @@ private static bool CanCastToInternal(this TypeDesc thisType, TypeDesc otherType default: Debug.Assert(thisType.IsDefType); - return thisType.CanCastToClassOrInterface(otherType, protect); + return CanCastToClassOrInterface(thisType, otherType, protect); } } - private static bool CanCastGenericParameterTo(this GenericParameterDesc thisType, TypeDesc otherType, StackOverflowProtect protect) + private static bool CanCastGenericParameterTo(GenericParameterDesc thisType, TypeDesc otherType, StackOverflowProtect protect) { // A boxed variable type can be cast to any of its constraints, or object, if none are specified if (otherType.IsObject) @@ -232,7 +282,7 @@ private static bool CanCastGenericParameterTo(this GenericParameterDesc thisType foreach (var typeConstraint in thisType.TypeConstraints) { TypeDesc instantiatedConstraint = typeConstraint.InstantiateSignature(typeInstantiation, methodInstantiation); - if (instantiatedConstraint.CanCastToInternal(otherType, protect)) + if (CanCastToInternal(instantiatedConstraint, otherType, protect)) { return true; } @@ -241,12 +291,12 @@ private static bool CanCastGenericParameterTo(this GenericParameterDesc thisType return false; } - private static bool CanCastArrayTo(this ArrayType thisType, TypeDesc otherType, StackOverflowProtect protect) + private static bool CanCastArrayTo(ArrayType thisType, TypeDesc otherType, StackOverflowProtect protect) { // Casting the array to one of the base types or interfaces? if (otherType.IsDefType) { - return thisType.CanCastToClassOrInterface(otherType, protect); + return CanCastToClassOrInterface(thisType, otherType, protect); } // Casting array to something else (between SzArray and Array, for example)? @@ -257,7 +307,7 @@ private static bool CanCastArrayTo(this ArrayType thisType, TypeDesc otherType, && otherType.Category == TypeFlags.Array && ((ArrayType)otherType).Rank == 1) { - return thisType.CanCastParamTo(((ArrayType)otherType).ParameterType, protect); + return CanCastParamTo(thisType, ((ArrayType)otherType).ParameterType, protect); } return false; @@ -271,10 +321,10 @@ private static bool CanCastArrayTo(this ArrayType thisType, TypeDesc otherType, return false; } - return thisType.CanCastParamTo(otherArrayType.ParameterType, protect); + return CanCastParamTo(thisType, otherArrayType.ParameterType, protect); } - private static bool CanCastParamTo(this ParameterizedType thisType, TypeDesc paramType, StackOverflowProtect protect) + private static bool CanCastParamTo(ParameterizedType thisType, TypeDesc paramType, StackOverflowProtect protect) { // While boxed value classes inherit from object their // unboxed versions do not. Parameterized types have the @@ -291,14 +341,14 @@ private static bool CanCastParamTo(this ParameterizedType thisType, TypeDesc par TypeDesc fromParamUnderlyingType = curTypesParm.UnderlyingType; if (fromParamUnderlyingType.IsGCPointer) { - return curTypesParm.CanCastToInternal(paramType, protect); + return CanCastToInternal(curTypesParm, paramType, protect); } else if (curTypesParm.IsGenericParameter) { var genericVariableFromParam = (GenericParameterDesc)curTypesParm; if (genericVariableFromParam.HasReferenceTypeConstraint || IsConstrainedAsGCPointer(genericVariableFromParam)) { - return genericVariableFromParam.CanCastToInternal(paramType, protect); + return CanCastToInternal(genericVariableFromParam, paramType, protect); } } else if (fromParamUnderlyingType.IsPrimitive) @@ -361,57 +411,36 @@ private static TypeFlags GetNormalizedIntegralArrayElementType(TypeDesc type) return elementType; } - - public static bool IsArrayElementTypeCastableBySize(TypeDesc elementType) - { - switch (elementType.UnderlyingType.Category) - { - case TypeFlags.Byte: - case TypeFlags.SByte: - case TypeFlags.UInt16: - case TypeFlags.Int16: - case TypeFlags.UInt32: - case TypeFlags.Int32: - case TypeFlags.UInt64: - case TypeFlags.Int64: - case TypeFlags.UIntPtr: - case TypeFlags.IntPtr: - return true; - } - - return false; - } - - private static bool CanCastToClassOrInterface(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) + private static bool CanCastToClassOrInterface(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) { if (otherType.IsInterface) { - return thisType.CanCastToInterface(otherType, protect); + return CanCastToInterface(thisType, otherType, protect); } else { - return thisType.CanCastToClass(otherType, protect); + return CanCastToClass(thisType, otherType, protect); } } - private static bool CanCastToInterface(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) + private static bool CanCastToInterface(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) { // Interfaces that don't have variance can still behave variantly when arrays are involved. bool arrayCovariance = thisType.IsSzArray && otherType.HasInstantiation; if (!otherType.HasVariance && !arrayCovariance) { - return thisType.CanCastToNonVariantInterface(otherType); + return CanCastToNonVariantInterface(thisType, otherType); } else { - if (thisType.CanCastByVarianceToInterfaceOrDelegate(otherType, protect)) + if (CanCastByVarianceToInterfaceOrDelegate(thisType, otherType, protect)) { return true; } foreach (var interfaceType in thisType.RuntimeInterfaces) { - if (interfaceType.CanCastByVarianceToInterfaceOrDelegate(otherType, protect, arrayCovariance)) + if (CanCastByVarianceToInterfaceOrDelegate(interfaceType, otherType, protect, arrayCovariance)) { return true; } @@ -421,16 +450,16 @@ private static bool CanCastToInterface(this TypeDesc thisType, TypeDesc otherTyp return false; } - private static bool CanCastToNonVariantInterface(this TypeDesc thisType, TypeDesc otherType) + private static bool CanCastToNonVariantInterface(TypeDesc thisType, TypeDesc otherType) { - if (otherType.IsEquivalentTo(thisType) || IsCanonEquivalent(thisType, otherType)) + if (otherType.IsEquivalentTo(thisType) || T.IsCanonEquivalent(thisType, otherType)) { return true; } foreach (var interfaceType in thisType.RuntimeInterfaces) { - if (interfaceType.IsEquivalentTo(otherType) || IsCanonEquivalent(interfaceType, otherType)) + if (interfaceType.IsEquivalentTo(otherType) || T.IsCanonEquivalent(interfaceType, otherType)) { return true; } @@ -439,7 +468,7 @@ private static bool CanCastToNonVariantInterface(this TypeDesc thisType, TypeDes return false; } - private static bool CanCastByVarianceToInterfaceOrDelegate(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protectInput, bool arrayCovariance = false) + private static bool CanCastByVarianceToInterfaceOrDelegate(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protectInput, bool arrayCovariance = false) { if (thisType == otherType) { @@ -474,7 +503,7 @@ private static bool CanCastByVarianceToInterfaceOrDelegate(this TypeDesc thisTyp if (!arg.IsEquivalentTo(targetArg)) { - if (IsCanonicalTypeArgMatch(arg, targetArg)) + if (T.IsCanonicalTypeArgMatch(arg, targetArg)) continue; GenericVariance variance = arrayCovariance @@ -483,12 +512,12 @@ private static bool CanCastByVarianceToInterfaceOrDelegate(this TypeDesc thisTyp switch (variance) { case GenericVariance.Covariant: - if (!arg.IsBoxedAndCanCastTo(targetArg, protect, arrayCovariance)) + if (!IsBoxedAndCanCastTo(arg, targetArg, protect, arrayCovariance)) return false; break; case GenericVariance.Contravariant: - if (!targetArg.IsBoxedAndCanCastTo(arg, protect, arrayCovariance)) + if (!IsBoxedAndCanCastTo(targetArg, arg, protect, arrayCovariance)) return false; break; @@ -503,7 +532,7 @@ private static bool CanCastByVarianceToInterfaceOrDelegate(this TypeDesc thisTyp return true; } - private static bool CanCastToClass(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) + private static bool CanCastToClass(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect) { TypeDesc curType = thisType; @@ -523,7 +552,7 @@ private static bool CanCastToClass(this TypeDesc thisType, TypeDesc otherType, S return true; } - if (curType.CanCastByVarianceToInterfaceOrDelegate(otherType, protect)) + if (CanCastByVarianceToInterfaceOrDelegate(curType, otherType, protect)) { return true; } @@ -544,12 +573,12 @@ private static bool CanCastToClass(this TypeDesc thisType, TypeDesc otherType, S // Always strip Nullable from the otherType, if present if (otherType.IsNullable && !curType.IsNullable) { - return thisType.CanCastTo(otherType.Instantiation[0]); + return CanCastToInternal(thisType, otherType.Instantiation[0], protect); } do { - if (curType.IsEquivalentTo(otherType) || IsCanonEquivalent(curType, otherType)) + if (curType.IsEquivalentTo(otherType) || T.IsCanonEquivalent(curType, otherType)) return true; curType = curType.BaseType; @@ -559,20 +588,20 @@ private static bool CanCastToClass(this TypeDesc thisType, TypeDesc otherType, S return false; } - private static bool IsBoxedAndCanCastTo(this TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect, bool arrayCovariance) + private static bool IsBoxedAndCanCastTo(TypeDesc thisType, TypeDesc otherType, StackOverflowProtect protect, bool arrayCovariance) { TypeDesc fromUnderlyingType = thisType.UnderlyingType; if (fromUnderlyingType.IsGCPointer) { - return thisType.CanCastToInternal(otherType, protect); + return CanCastToInternal(thisType, otherType, protect); } else if (thisType.IsGenericParameter) { var genericVariableFromParam = (GenericParameterDesc)thisType; if (genericVariableFromParam.HasReferenceTypeConstraint || IsConstrainedAsGCPointer(genericVariableFromParam)) { - return genericVariableFromParam.CanCastToInternal(otherType, protect); + return CanCastToInternal(genericVariableFromParam, otherType, protect); } } else if (arrayCovariance && fromUnderlyingType.IsPrimitive) diff --git a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs index 7e8b23c109da74..780a2d363ef08c 100644 --- a/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs +++ b/src/coreclr/tools/Common/TypeSystem/Common/TypeSystemConstraintsHelpers.cs @@ -73,7 +73,7 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar if (instantiationParam.IsValueType && instantiatedType.IsValueType && !instantiationParam.IsEquivalentTo(instantiatedType)) return false; - if (!instantiationParam.CanCastTo(instantiatedType)) + if (!instantiationParam.CanCastToWithCanon(instantiatedType)) return false; } @@ -164,7 +164,7 @@ private static bool CanCastConstraint(ref ArrayBuilder instantiatedCon { for (int i = 0; i < instantiatedConstraints.Count; ++i) { - if (instantiatedConstraints[i].CanCastTo(instantiatedType)) + if (instantiatedConstraints[i].CanCastToWithCanon(instantiatedType)) return true; }