Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CoreCLR NativeAOT - ByRef generics and ByRef implementing interfaces #98070

Merged
merged 8 commits into from
Mar 2, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions docs/design/features/byreflike-generics.md
Original file line number Diff line number Diff line change
@@ -127,3 +127,129 @@ The following are IL sequences involving the `box` instruction. They are used fo
`box` ; `isinst` ; `unbox.any` – The box, `isint`, and unbox target types are all equal.

`box` ; `isinst` ; `br_true/false` &ndash; The box target type is equal to the unboxed target type or the box target type is `Nullable<T>` and target type equalities can be computed.

## Examples

Below are valid and invalid examples of ByRefLike as Generic parameters. All examples use the **not official** syntax, `allows ref struct`, for indicating the Generic permits ByRefLike types.

**1) Valid**
```csharp
class A<T1> where T1: allows ref struct
{
public void M();
}

// The derived class is okay to lack the 'allows'
// because the base permits non-ByRefLike (default)
// _and_ ByRefLike types.
class B<T2> : A<T2>
{
public void N()
=> M(); // Any T2 satisfies the constraints from A<>
}
```

**2) Invalid**
```csharp
class A<T1>
{
public void M();
}

// The derived class cannot push up the allows
// constraint for ByRefLike types.
class B<T2> : A<T2> where T2: allows ref struct
{
public void N()
=> M(); // A<> may not permit a T2
}
```

**3) Valid**
```csharp
interface IA
{
void M();
}

ref struct A : IA
{
public void M() { }
}

class B
{
// This call is permitted because no boxing is needed
// to dispatch to the method - it is implemented on A.
public static void C<T>(T t) where T: IA, allows ref struct
=> t.M();
}
```

**4) Invalid**
```csharp
interface IA
{
public void M() { }
}

ref struct A : IA
{
// Relies on IA::M() implementation.
}

class B
{
// Reliance on a DIM forces the generic parameter
// to be boxed, which is invalid for ByRefLike types.
public static void C<T>(T t) where T: IA, allows ref struct
=> t.M();
}
```

**5) Valid**
```csharp
class A<T1> where T1: allows ref struct
{
}

class B<T2>
{
// The type parameter is okay to lack the 'allows'
// because the field permits non-ByRefLike (default)
// _and_ ByRefLike types.
A<T2> Field;
}
```

**6) Invalid**
```csharp
class A<T1>
{
}

class B<T2> where T2: allows ref struct
{
// The type parameter can be passed to
// the field type, but will fail if
// T2 is a ByRefLike type.
A<T2> Field;
}
```

**7) Invalid**
```csharp
class A
{
virtual void M<T1>() where T1: allows ref struct;
}

class B : A
{
// Override methods need to match be at least
// as restrictive with respect to constraints.
// If a user has an instance of A, they are
// not aware they could be calling B.
override void M<T2>();
}
```
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/asmparse.y
Original file line number Diff line number Diff line change
@@ -486,7 +486,7 @@ typarAttrib : '+' { $$ = gpCovariant;
| '-' { $$ = gpContravariant; }
| CLASS_ { $$ = gpReferenceTypeConstraint; }
| VALUETYPE_ { $$ = gpNotNullableValueTypeConstraint; }
| BYREFLIKE_ { $$ = gpAcceptByRefLike; }
| BYREFLIKE_ { $$ = gpAllowByRefLike; }
| _CTOR { $$ = gpDefaultConstructorConstraint; }
| FLAGS_ '(' int32 ')' { $$ = (CorGenericParamAttr)$3; }
;
2 changes: 1 addition & 1 deletion src/coreclr/ilasm/prebuilt/asmparse.cpp
Original file line number Diff line number Diff line change
@@ -2523,7 +2523,7 @@ case 152:
{ yyval.int32 = gpNotNullableValueTypeConstraint; } break;
case 153:
#line 489 "asmparse.y"
{ yyval.int32 = gpAcceptByRefLike; } break;
{ yyval.int32 = gpAllowByRefLike; } break;
case 154:
#line 490 "asmparse.y"
{ yyval.int32 = gpDefaultConstructorConstraint; } break;
2 changes: 1 addition & 1 deletion src/coreclr/ildasm/dasm.cpp
Original file line number Diff line number Diff line change
@@ -3081,7 +3081,7 @@ char *DumpGenericPars(_Inout_updates_(SZSTRING_SIZE) char* szString, mdToken tok
if ((attr & gpNotNullableValueTypeConstraint) != 0)
szptr += sprintf_s(szptr,SZSTRING_REMAINING_SIZE(szptr), "valuetype ");
CHECK_REMAINING_SIZE;
if ((attr & gpAcceptByRefLike) != 0)
if ((attr & gpAllowByRefLike) != 0)
szptr += sprintf_s(szptr,SZSTRING_REMAINING_SIZE(szptr), "byreflike ");
CHECK_REMAINING_SIZE;
if ((attr & gpDefaultConstructorConstraint) != 0)
2 changes: 1 addition & 1 deletion src/coreclr/inc/corhdr.h
Original file line number Diff line number Diff line change
@@ -847,7 +847,7 @@ typedef enum CorGenericParamAttr
gpReferenceTypeConstraint = 0x0004, // type argument must be a reference type
gpNotNullableValueTypeConstraint = 0x0008, // type argument must be a value type but not Nullable
gpDefaultConstructorConstraint = 0x0010, // type argument must have a public default constructor
gpAcceptByRefLike = 0x0020, // type argument can be ByRefLike
gpAllowByRefLike = 0x0020, // type argument can be ByRefLike
} CorGenericParamAttr;

// structures and enums moved from COR.H
Original file line number Diff line number Diff line change
@@ -13,9 +13,9 @@ internal static partial class ConstraintValidator
{
private static bool SatisfiesConstraints(this Type genericVariable, SigTypeContext typeContextOfConstraintDeclarer, Type typeArg)
{
GenericParameterAttributes specialConstraints = genericVariable.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes attributes = genericVariable.GenericParameterAttributes;

if ((specialConstraints & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
if ((attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
{
if (!typeArg.IsValueType)
{
@@ -30,19 +30,19 @@ private static bool SatisfiesConstraints(this Type genericVariable, SigTypeConte
}
}

if ((specialConstraints & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
if ((attributes & GenericParameterAttributes.ReferenceTypeConstraint) != 0)
{
if (typeArg.IsValueType)
return false;
}

if ((specialConstraints & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
if ((attributes & GenericParameterAttributes.DefaultConstructorConstraint) != 0)
{
if (!typeArg.HasExplicitOrImplicitPublicDefaultConstructor())
return false;
}

if (typeArg.IsByRefLike && (specialConstraints & (GenericParameterAttributes)0x20 /* GenericParameterAttributes.AcceptByRefLike */) == 0)
if (typeArg.IsByRefLike && (attributes & (GenericParameterAttributes)0x20 /* GenericParameterAttributes.AllowByRefLike */) == 0)
return false;

// Now check general subtype constraints
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ public enum GenericConstraints
/// <summary>
/// A type is permitted to be ByRefLike.
/// </summary>
AcceptByRefLike = 0x20,
AllowByRefLike = 0x20,
}

public abstract partial class GenericParameterDesc : TypeDesc
@@ -159,13 +159,13 @@ public bool HasDefaultConstructorConstraint
}

/// <summary>
/// Does this generic parameter have the AcceptByRefLike flag
/// Does this generic parameter have the AllowByRefLike flag
/// </summary>
public bool HasAcceptByRefLikeConstraint
public bool HasAllowByRefLikeConstraint
{
get
{
return (Constraints & GenericConstraints.AcceptByRefLike) != 0;
return (Constraints & GenericConstraints.AllowByRefLike) != 0;
}
}

Original file line number Diff line number Diff line change
@@ -50,7 +50,7 @@ private static bool VerifyGenericParamConstraint(InstantiationContext genericPar
}

// Check for ByRefLike support
if (instantiationParam.IsByRefLike && (constraints & GenericConstraints.AcceptByRefLike) == 0)
if (instantiationParam.IsByRefLike && (constraints & GenericConstraints.AllowByRefLike) == 0)
return false;

var instantiatedConstraints = default(ArrayBuilder<TypeDesc>);
Original file line number Diff line number Diff line change
@@ -113,7 +113,7 @@ public override GenericConstraints Constraints
{
Debug.Assert((int)GenericConstraints.DefaultConstructorConstraint == (int)GenericParameterAttributes.DefaultConstructorConstraint);
GenericParameter parameter = _module.MetadataReader.GetGenericParameter(_handle);
const GenericParameterAttributes mask = GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AcceptByRefLike;
const GenericParameterAttributes mask = GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AllowByRefLike;
return (GenericConstraints)(parameter.Attributes & mask);
}
}
Original file line number Diff line number Diff line change
@@ -59,7 +59,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
if ((genericParameter.Attributes & GenericParameterAttributes.VarianceMask) != GenericParameterAttributes.None)
hasVariance = true;

if ((genericParameter.Attributes & (GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AcceptByRefLike)) != default(GenericParameterAttributes) ||
if ((genericParameter.Attributes & (GenericParameterAttributes.SpecialConstraintMask | (GenericParameterAttributes)GenericConstraints.AllowByRefLike)) != default(GenericParameterAttributes) ||
(genericParameter.GetConstraints().Count > 0))
{
hasConstraints = true;
Original file line number Diff line number Diff line change
@@ -107,7 +107,7 @@ Task<bool> ValidateTypeWorkerHelper(TypeDesc typeToCheckForSkipValidation)
// The runtime has a number of checks in the type loader which it will skip running if the SkipValidation flag is set
// This function attempts to document all of them, and implement *some* of them.

// This function performs a portion of the validation skipping that has been found to have some importance, or to serve as
// This function performs a portion of the validation skipping that has been found to have some importance, or to serve as
// In addition, there are comments about all validation skipping activities that the runtime will perform.
try
{
@@ -488,8 +488,9 @@ static bool CompareGenericParameterConstraint(MethodDesc declMethod, GenericPara
if (!parameterOfDecl.HasReferenceTypeConstraint)
return false;

if (parameterOfDecl.HasAcceptByRefLikeConstraint)
if (!parameterOfImpl.HasAcceptByRefLikeConstraint)
// Constraints that 'allow' must check the impl first
if (parameterOfImpl.HasAllowByRefLikeConstraint)
if (!parameterOfDecl.HasAllowByRefLikeConstraint)
return false;

HashSet<TypeDesc> constraintsOnDecl = new HashSet<TypeDesc>();
Original file line number Diff line number Diff line change
@@ -244,6 +244,7 @@ public class CallConvSuppressGCTransition { }
public static class RuntimeFeature
{
public const string ByRefFields = nameof(ByRefFields);
public const string ByRefLikeGenerics = nameof(ByRefLikeGenerics);
public const string UnmanagedSignatureCallingConvention = nameof(UnmanagedSignatureCallingConvention);
public const string VirtualStaticsInInterfaces = nameof(VirtualStaticsInInterfaces);
}
6 changes: 4 additions & 2 deletions src/coreclr/vm/siginfo.cpp
Original file line number Diff line number Diff line change
@@ -4813,9 +4813,11 @@ BOOL MetaSig::CompareVariableConstraints(const Substitution *pSubst1,
if ((specialConstraints2 & (gpDefaultConstructorConstraint | gpNotNullableValueTypeConstraint)) == 0)
return FALSE;
}
if ((specialConstraints1 & gpAcceptByRefLike) != 0)

// Constraints that 'allow' must check the overridden first
if ((specialConstraints2 & gpAllowByRefLike) != 0)
{
if ((specialConstraints2 & gpAcceptByRefLike) == 0)
if ((specialConstraints1 & gpAllowByRefLike) == 0)
return FALSE;
}
}
2 changes: 1 addition & 1 deletion src/coreclr/vm/typedesc.cpp
Original file line number Diff line number Diff line change
@@ -1512,7 +1512,7 @@ BOOL TypeVarTypeDesc::SatisfiesConstraints(SigTypeContext *pTypeContextOfConstra
return FALSE;
}

if (thArg.IsByRefLike() && (specialConstraints & gpAcceptByRefLike) == 0)
if (thArg.IsByRefLike() && (specialConstraints & gpAllowByRefLike) == 0)
return FALSE;
}

Original file line number Diff line number Diff line change
@@ -14,5 +14,6 @@ public enum GenericParameterAttributes
ReferenceTypeConstraint = 0x0004,
NotNullableValueTypeConstraint = 0x0008,
DefaultConstructorConstraint = 0x0010,
AllowByRefLike = 0x0020,
}
}
Original file line number Diff line number Diff line change
@@ -32,6 +32,11 @@ public static partial class RuntimeFeature
/// </summary>
public const string ByRefFields = nameof(ByRefFields);

/// <summary>
/// Represents a runtime feature where byref-like types can be used in Generic parameters.
/// </summary>
public const string ByRefLikeGenerics = nameof(ByRefLikeGenerics);

/// <summary>
/// Indicates that this version of runtime supports virtual static members of interfaces.
/// </summary>
@@ -52,6 +57,7 @@ public static bool IsSupported(string feature)
case PortablePdb:
case CovariantReturnsOfClasses:
case ByRefFields:
case ByRefLikeGenerics:
case UnmanagedSignatureCallingConvention:
case DefaultImplementationsOfInterfaces:
case VirtualStaticsInInterfaces:
Original file line number Diff line number Diff line change
@@ -692,7 +692,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)

if (constraint.IsGenericParameter)
{
GenericParameterAttributes special = constraint.GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes special = constraint.GenericParameterAttributes;

if ((special & GenericParameterAttributes.ReferenceTypeConstraint) == 0 &&
(special & GenericParameterAttributes.NotNullableValueTypeConstraint) == 0)
@@ -704,7 +704,7 @@ public override bool IsAssignableFrom([NotNullWhen(true)] Type? c)

if (baseType == ObjectType)
{
GenericParameterAttributes special = GenericParameterAttributes & GenericParameterAttributes.SpecialConstraintMask;
GenericParameterAttributes special = GenericParameterAttributes;
if ((special & GenericParameterAttributes.NotNullableValueTypeConstraint) != 0)
baseType = ValueType;
}
2 changes: 2 additions & 0 deletions src/libraries/System.Runtime/ref/System.Runtime.cs
Original file line number Diff line number Diff line change
@@ -11588,6 +11588,7 @@ public enum GenericParameterAttributes
NotNullableValueTypeConstraint = 8,
DefaultConstructorConstraint = 16,
SpecialConstraintMask = 28,
AllowByRefLike = 32,
}
public partial interface ICustomAttributeProvider
{
@@ -13102,6 +13103,7 @@ public RuntimeCompatibilityAttribute() { }
public static partial class RuntimeFeature
{
public const string ByRefFields = "ByRefFields";
public const string ByRefLikeGenerics = "ByRefLikeGenerics";
public const string CovariantReturnsOfClasses = "CovariantReturnsOfClasses";
public const string DefaultImplementationsOfInterfaces = "DefaultImplementationsOfInterfaces";
public const string NumericIntPtr = "NumericIntPtr";
Original file line number Diff line number Diff line change
@@ -806,6 +806,10 @@ private static IEnumerable<ITestInfo> GetTestMethodInfosForMethod(IMethodSymbol
// If we're building tests not for Mono, we can skip handling the specifics of the SkipOnMonoAttribute.
continue;
}
if (filterAttribute.ConstructorArguments.Length <= 1)
{
return ImmutableArray<ITestInfo>.Empty;
}
testInfos = DecorateWithSkipOnPlatform(testInfos, (int)filterAttribute.ConstructorArguments[1].Value!, options);
break;
case "Xunit.SkipOnPlatformAttribute":
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.assembly extern System.Console { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) }
.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) }
.assembly extern Microsoft.DotNet.XUnitExtensions {}
.assembly extern xunit.core {}
.assembly constrained2_brl { }

.class interface private auto ansi abstract
IAdder
{
.method public hidebysig newslot virtual
instance int32 Add(int32) cil managed
{
ldstr "Calling DIM from ByRefLike type is invalid"
newobj instance void [System.Runtime]System.Exception::.ctor(string)
throw
}
}

.class private sequential ansi sealed beforefieldinit Adder
extends [System.Runtime]System.ValueType
implements IAdder
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
01 00 00 00
)

.field private int32 _field

.method public hidebysig specialname rtspecialname
instance void .ctor (int32) cil managed
{
ldarg.0
ldarg.1
stfld int32 Adder::_field
ret
}

.method public hidebysig newslot virtual
instance int32 Add(int32) cil managed
{
// Load field and add with argument
ldarg.0
dup
ldfld int32 valuetype Adder::_field
ldarg.1
add

// Update the field
stfld int32 valuetype Adder::_field

// Return the field value
ldarg.0
ldfld int32 valuetype Adder::_field
ret
}
}

.class private sequential ansi sealed beforefieldinit Adder_Invalid
extends [System.Runtime]System.ValueType
implements IAdder
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
01 00 00 00
)

.method public hidebysig specialname rtspecialname
instance void .ctor (int32) cil managed
{
ret
}

//
// Deferring to the DIM on IAdder
//
}

.method public hidebysig static int32 Check<byreflike (IAdder) T>(!!0, int32)
{
ldarga.s 0
ldarg.1
constrained. !!0
callvirt instance int32 IAdder::Add(int32)
ret
}

.class public auto ansi abstract sealed beforefieldinit constrained2_brl
extends [System.Runtime]System.Object
{
.method public hidebysig static int32 Main()
{
.custom instance void [xunit.core]Xunit.FactAttribute::.ctor() = (
01 00 00 00
)
.custom instance void [Microsoft.DotNet.XUnitExtensions]Xunit.SkipOnMonoAttribute::.ctor(string, valuetype [Microsoft.DotNet.XUnitExtensions]Xunit.TestPlatforms) = (
01 00 2c 4d 6f 6e 6f 20 64 6f 65 73 20 6e 6f 74
20 73 75 70 70 6f 72 74 20 42 79 52 65 66 4c 69
6b 65 20 67 65 6e 65 72 69 63 73 20 79 65 74 ff
ff ff ff 00 00
)
.entrypoint

.locals init (
valuetype Adder,
valuetype Adder_Invalid
)

// Initialize Adder instance
ldloca.s 0
ldc.i4 10
call instance void Adder::.ctor(int32)

ldstr "Validate constrained call of ByRefLike interface method passes"
call void [System.Console]System.Console::WriteLine(string)
ldloc.0
ldc.i4 20
call int32 Check<valuetype Adder>(!!0, int32)
ldc.i4 30
ceq
brfalse FAIL

// Initialize Adder_Invalid instance
ldloca.s 1
ldc.i4 10
call instance void Adder_Invalid::.ctor(int32)

.try
{
ldstr "Validate constrained call of ByRefLike interface DIM fails"
call void [System.Console]System.Console::WriteLine(string)

ldloc.1
ldc.i4 20
call int32 Check<valuetype Adder_Invalid>(!!0, int32)
leave FAIL
}
catch [System.Runtime]System.Exception
{
pop
leave ExpectedFailure
}

ExpectedFailure:
ldc.i4 100
ret

FAIL:
ldc.i4 101
ret
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<ItemGroup>
<Compile Include="constrained2_brl.il" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -16,7 +16,7 @@ public class GenericTypeSubstitution
public static void AllowByRefLike_Substituted_For_AllowByRefLike()
{
Console.WriteLine($"{nameof(AllowByRefLike_Substituted_For_AllowByRefLike)}...");

Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionInterfaceImplementationAllowByRefLike()}");
Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionInheritanceAllowByRefLike()}");
Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionFieldAllowByRefLike()}");
@@ -26,18 +26,18 @@ public static void AllowByRefLike_Substituted_For_AllowByRefLike()
[SkipOnMono("Mono does not support ByRefLike generics yet")]
public static void NonByRefLike_Substituted_For_AllowByRefLike()
{
Console.WriteLine($"{nameof(NonByRefLike_Substituted_For_AllowByRefLike)}...");

Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionInterfaceImplementationNonByRefLike()}");
Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionInheritanceNonByRefLike()}");
Console.WriteLine($" -- Instantiate: {Exec.TypeSubstitutionFieldNonByRefLike()}");
}

[Fact]
[ActiveIssue("To be created", TestRuntimes.CoreCLR)]
[SkipOnMono("Mono does not support ByRefLike generics yet")]
public static void AllowByRefLike_Substituted_For_NonByRefLike_Invalid()
public static void AllowByRefLike_Substituted_For_NonByRefLike()
{
Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionInterfaceImplementationAllowByRefLikeIntoNonByRefLike(); });
Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionInheritanceAllowByRefLikeIntoNonByRefLike(); });
Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionFieldAllowByRefLikeIntoNonByRefLike(); });
Console.WriteLine($"{nameof(AllowByRefLike_Substituted_For_NonByRefLike)}...");
Exec.TypeSubstitutionFieldAllowNonByRefLikeIntoNonByRefLike();
}
}
29 changes: 1 addition & 28 deletions src/tests/Loader/classloader/generics/ByRefLike/InvalidCSharp.il
Original file line number Diff line number Diff line change
@@ -439,15 +439,6 @@
}

// Invalid generic substitution of non-allow-byreflike with allow-byreflike
.class interface public auto ansi abstract InvalidCSharp.GenericDerivedInterface_Invalid`1<byreflike T>
implements class InvalidCSharp.GenericInterface_Invalid`1<!T>
{
}

.class public sequential ansi sealed beforefieldinit InvalidCSharp.GenericDerivedClass_Invalid`1<byreflike T>
extends class InvalidCSharp.GenericClass_Invalid`1<!T>
{
}

.class public sequential ansi sealed beforefieldinit InvalidCSharp.GenericValueTypeWrapper_Invalid`1<byreflike T>
extends [System.Runtime]System.ValueType
@@ -798,27 +789,9 @@
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
string TypeSubstitutionInterfaceImplementationAllowByRefLikeIntoNonByRefLike() cil managed
{
ldtoken class InvalidCSharp.GenericDerivedInterface_Invalid`1<valuetype RegularValueType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
string TypeSubstitutionInheritanceAllowByRefLikeIntoNonByRefLike() cil managed
{
ldtoken class InvalidCSharp.GenericDerivedClass_Invalid`1<valuetype RegularValueType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
string TypeSubstitutionFieldAllowByRefLikeIntoNonByRefLike() cil managed
string TypeSubstitutionFieldAllowNonByRefLikeIntoNonByRefLike() cil managed
{
ldtoken valuetype InvalidCSharp.GenericValueTypeWrapper_Invalid`1<valuetype RegularValueType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

.assembly extern System.Console { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) }
.assembly extern System.Runtime { .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) }

.assembly InvalidCSharpNegative { }

.class public sequential ansi sealed beforefieldinit ByRefLikeType
extends [System.Runtime]System.ValueType
{
.custom instance void [System.Runtime]System.Runtime.CompilerServices.IsByRefLikeAttribute::.ctor() = (
01 00 00 00
)
}

//
// Begin invalid
//

.class public sequential ansi sealed beforefieldinit InvalidCSharpNegative.GenericClass_Invalid`1<T>
extends [System.Runtime]System.Object
{
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
ldarg.0
call instance void [System.Runtime]System.Object::.ctor()
ret
}
}

.class interface public auto ansi abstract InvalidCSharpNegative.GenericInterface_Invalid`1<T>
{
}

.class public sequential ansi sealed beforefieldinit InvalidCSharpNegative.GenericValueType_Invalid`1<T>
extends [System.Runtime]System.ValueType
{
}

// Invalid generic substitution of non-allow-byreflike with allow-byreflike
.class interface public auto ansi abstract InvalidCSharpNegative.GenericDerivedInterface_Invalid`1<byreflike T>
implements class InvalidCSharpNegative.GenericInterface_Invalid`1<!T>
{
}

.class public sequential ansi sealed beforefieldinit InvalidCSharpNegative.GenericDerivedClass_Invalid`1<byreflike T>
extends class InvalidCSharpNegative.GenericClass_Invalid`1<!T>
{
}

.class public sequential ansi sealed beforefieldinit InvalidCSharpNegative.GenericValueTypeWrapper_Invalid`1<byreflike T>
extends [System.Runtime]System.ValueType
{
.field public valuetype InvalidCSharpNegative.GenericValueType_Invalid`1<!T> fld;
}

.class public auto ansi beforefieldinit InvalidCSharpNegative.BaseClassWithGenericMethod
extends [System.Runtime]System.Object
{
.method public hidebysig newslot virtual
instance void AcceptsByRefLike<byreflike T> () cil managed
{
ret
}
.method public hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
ldarg.0
call instance void [System.Runtime]System.Object::.ctor()
ret
}
}

.class public auto ansi beforefieldinit InvalidCSharpNegative.DerivedClassWithGenericMethod_Invalid
extends InvalidCSharpNegative.BaseClassWithGenericMethod
{
.method public hidebysig static
class InvalidCSharpNegative.BaseClassWithGenericMethod Create () cil managed noinlining
{
newobj instance void InvalidCSharpNegative.DerivedClassWithGenericMethod_Invalid::.ctor()
ret
}
.method public hidebysig virtual
instance void AcceptsByRefLike<T> () cil managed // Missing constraint
{
ret
}
.method private hidebysig specialname rtspecialname
instance void .ctor () cil managed
{
ldarg.0
call instance void InvalidCSharpNegative.BaseClassWithGenericMethod::.ctor()
ret
}
}

// Entry points

.class public auto ansi abstract sealed beforefieldinit Exec
extends [System.Runtime]System.Object
{
.method public hidebysig static
string TypeSubstitutionInterfaceImplementationAllowByRefLikeIntoNonByRefLike() cil managed
{
ldtoken class InvalidCSharpNegative.GenericDerivedInterface_Invalid`1<valuetype ByRefLikeType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
string TypeSubstitutionInheritanceAllowByRefLikeIntoNonByRefLike() cil managed
{
ldtoken class InvalidCSharpNegative.GenericDerivedClass_Invalid`1<valuetype ByRefLikeType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
string TypeSubstitutionFieldAllowByRefLikeIntoNonByRefLike() cil managed
{
ldtoken valuetype InvalidCSharpNegative.GenericValueTypeWrapper_Invalid`1<valuetype ByRefLikeType>
call class [System.Runtime]System.Type [System.Runtime]System.Type::GetTypeFromHandle(valuetype [System.Runtime]System.RuntimeTypeHandle)
callvirt instance string [System.Runtime]System.Object::ToString()
ret
}

.method public hidebysig static
void OverrideMethodNotByRefLike() cil managed
{
.locals init (
[0] class InvalidCSharpNegative.BaseClassWithGenericMethod
)
call class InvalidCSharpNegative.BaseClassWithGenericMethod InvalidCSharpNegative.DerivedClassWithGenericMethod_Invalid::Create()
stloc.0
ldloc.0
callvirt instance void InvalidCSharpNegative.BaseClassWithGenericMethod::AcceptsByRefLike<valuetype ByRefLikeType>()
ret
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.IL">
<PropertyGroup>
<OutputType>library</OutputType>
<MonoAotIncompatible>true</MonoAotIncompatible>
<DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'Mono'">true</DisableProjectBuild>
</PropertyGroup>
<ItemGroup>
<Compile Include="InvalidCSharpNegative.il" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using InvalidCSharpNegative;

using Xunit;

public class ValidateNegative
{
[Fact]
[SkipOnMono("Mono does not support ByRefLike generics yet")]
public static void AllowByRefLike_Substituted_For_NonByRefLike_Invalid()
{
Console.WriteLine($"{nameof(AllowByRefLike_Substituted_For_NonByRefLike_Invalid)}...");

Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionInterfaceImplementationAllowByRefLikeIntoNonByRefLike(); });
Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionInheritanceAllowByRefLikeIntoNonByRefLike(); });
Assert.Throws<TypeLoadException>(() => { Exec.TypeSubstitutionFieldAllowByRefLikeIntoNonByRefLike(); });
Assert.Throws<TypeLoadException>(() => { Exec.OverrideMethodNotByRefLike(); });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Needed for mechanical merging of all remaining tests, this particular project may not actually need process isolation -->
<RequiresProcessIsolation>true</RequiresProcessIsolation>
<DisableProjectBuild Condition="'$(RuntimeFlavor)' == 'Mono'">true</DisableProjectBuild>

<!-- Crossgen is not intended to serve as an IL verifier, and is not designed to provide error semantics on bad input. -->
<CrossGenTest>false</CrossGenTest>
<NativeAotIncompatible>true</NativeAotIncompatible>
</PropertyGroup>
<ItemGroup>
<Compile Include="ValidateNegative.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(TestLibraryProjectPath)" />
<ProjectReference Include="InvalidCSharpNegative.ilproj" />
</ItemGroup>
</Project>