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..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 c301f2da1d298e..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
@@ -372,5 +375,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