Skip to content

Commit ee2a8a9

Browse files
Handle canonical types in constraints checks (#129278)
Fixes #126604 First commit is a revert of the revert. The second commit is an update to the casting logic so that we can have both casting logic that treats `__Canon` as a `class __Canon : object` and nothing else (this is compatible with how casting in CoreCLR VM works), but also another casting logic that treats `__Canon` as "some reference type, could be something that does cast". Then update constraints check to use this flavor of the casting logic.
1 parent a3f07eb commit ee2a8a9

13 files changed

Lines changed: 491 additions & 62 deletions

File tree

src/coreclr/nativeaot/System.Private.TypeLoader/src/System.Private.TypeLoader.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@
120120
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\CastingHelper.cs">
121121
<Link>Internal\TypeSystem\CastingHelper.cs</Link>
122122
</Compile>
123+
<Compile Include="$(CompilerCommonPath)\TypeSystem\Canon\CastingHelper.Canon.cs">
124+
<Link>Internal\TypeSystem\CastingHelper.Canon.cs</Link>
125+
</Compile>
123126
<Compile Include="$(CompilerCommonPath)\TypeSystem\Common\DefType.cs">
124127
<Link>Internal\TypeSystem\DefType.cs</Link>
125128
</Compile>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Internal.TypeSystem
5+
{
6+
internal sealed class CanonicalTypeCastingHandler : ICanonicalTypeCastingHandler
7+
{
8+
/// <summary>
9+
/// Check if <paramref name="otherType"/> is a canonical type that <paramref name="thisType"/>
10+
/// can be cast to. __Canon accepts any reference type; __UniversalCanon accepts any type.
11+
/// Pointers, byrefs, and function pointers are not valid instantiation arguments.
12+
/// </summary>
13+
public static bool IsCanonicalCastTarget(TypeDesc thisType, TypeDesc otherType)
14+
{
15+
TypeSystemContext context = thisType.Context;
16+
17+
if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Universal))
18+
return true;
19+
20+
if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Specific))
21+
return thisType.IsGCPointer;
22+
23+
return false;
24+
}
25+
26+
/// <summary>
27+
/// Check if two type arguments can be considered matching because one (or both) is canonical.
28+
/// __Canon matches any reference type; __UniversalCanon matches any type.
29+
/// </summary>
30+
public static bool IsCanonicalTypeArgMatch(TypeDesc type, TypeDesc otherType)
31+
{
32+
TypeSystemContext context = type.Context;
33+
34+
if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Universal))
35+
return true;
36+
37+
if (context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Specific))
38+
return type.IsGCPointer || context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any);
39+
40+
if (context.IsCanonicalDefinitionType(type, CanonicalFormKind.Universal))
41+
return true;
42+
43+
if (context.IsCanonicalDefinitionType(type, CanonicalFormKind.Specific))
44+
return otherType.IsGCPointer || context.IsCanonicalDefinitionType(otherType, CanonicalFormKind.Any);
45+
46+
// For non-leaf types (e.g., Arg2<string> vs Arg2<__Canon>), check if they are
47+
// canon-equivalent: same type definition with canon-compatible type arguments.
48+
if (IsCanonEquivalent(type, otherType))
49+
return true;
50+
51+
// For parameterized types like arrays (string[] vs __Canon[]),
52+
// recursively match the element/parameter type.
53+
if (type is ParameterizedType paramType && otherType is ParameterizedType otherParamType
54+
&& type.Category == otherType.Category)
55+
{
56+
if (type is ArrayType arrayType && otherType is ArrayType otherArrayType
57+
&& arrayType.Rank != otherArrayType.Rank)
58+
return false;
59+
60+
return IsCanonicalTypeArgMatch(paramType.ParameterType, otherParamType.ParameterType);
61+
}
62+
63+
return false;
64+
}
65+
66+
/// <summary>
67+
/// Check if two types are equivalent considering canonical type matching rules.
68+
/// Same type definition with all type arguments either equal or canon-compatible.
69+
/// </summary>
70+
public static bool IsCanonEquivalent(TypeDesc thisType, TypeDesc otherType)
71+
{
72+
if (!thisType.HasSameTypeDefinition(otherType))
73+
return false;
74+
75+
Instantiation thisInst = thisType.Instantiation;
76+
Instantiation otherInst = otherType.Instantiation;
77+
78+
if (thisInst.Length == 0)
79+
return false;
80+
81+
for (int i = 0; i < thisInst.Length; i++)
82+
{
83+
if (thisInst[i] == otherInst[i])
84+
continue;
85+
86+
if (!IsCanonicalTypeArgMatch(thisInst[i], otherInst[i]))
87+
return false;
88+
}
89+
90+
return true;
91+
}
92+
}
93+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Diagnostics;
5+
6+
namespace Internal.TypeSystem
7+
{
8+
public static partial class TypeSystemConstraintsHelpers
9+
{
10+
private static bool IsSpecialTypeMeetingConstraint(TypeDesc type, GenericConstraints constraint)
11+
{
12+
TypeSystemContext context = type.Context;
13+
14+
return constraint switch
15+
{
16+
GenericConstraints.ReferenceTypeConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any),
17+
GenericConstraints.DefaultConstructorConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Any),
18+
GenericConstraints.NotNullableValueTypeConstraint => context.IsCanonicalDefinitionType(type, CanonicalFormKind.Universal),
19+
_ => throw new UnreachableException()
20+
};
21+
}
22+
23+
/// <summary>
24+
/// Checks if <paramref name="instantiationParam"/> can satisfy the type constraint
25+
/// <paramref name="instantiatedConstraintType"/> when the param or constraint IS a canonical
26+
/// definition type (__Canon or __UniversalCanon). Handles wildcard semantics only;
27+
/// structural matching (interface walking, base chain, variance) is in CastingHelper.
28+
/// </summary>
29+
private static bool CanCastToConstraintWithCanon(TypeDesc instantiationParam, TypeDesc instantiatedConstraintType)
30+
{
31+
TypeSystemContext context = instantiationParam.Context;
32+
33+
// If the instantiation param is a canonical definition type (__Canon or __UniversalCanon),
34+
// it acts as a wildcard — any concrete type substituted at runtime will be validated then.
35+
if (context.IsCanonicalDefinitionType(instantiationParam, CanonicalFormKind.Any))
36+
return true;
37+
38+
// If the constraint type itself is a canonical definition type, check compatibility directly.
39+
// E.g., "where T : U" with U=__Canon means T must be a plausible match for __Canon.
40+
if (context.IsCanonicalDefinitionType(instantiatedConstraintType, CanonicalFormKind.Universal))
41+
return true;
42+
if (context.IsCanonicalDefinitionType(instantiatedConstraintType, CanonicalFormKind.Specific))
43+
return instantiationParam.IsGCPointer;
44+
45+
return false;
46+
}
47+
}
48+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Internal.TypeSystem
5+
{
6+
internal sealed class CanonicalTypeCastingHandler : INonCanonicalTypeCastingHandler;
7+
}

0 commit comments

Comments
 (0)