Skip to content

Commit

Permalink
Specially handle more scenarios for type parameters with 'allows ref …
Browse files Browse the repository at this point in the history
…struct' constraint (#73232)

Also related to #73242.
  • Loading branch information
AlekseyTs authored Apr 30, 2024
1 parent 81992c9 commit 33e5614
Show file tree
Hide file tree
Showing 16 changed files with 1,815 additions and 28 deletions.
10 changes: 5 additions & 5 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1872,7 +1872,7 @@ private BoundExpression SynthesizeMethodGroupReceiver(CSharpSyntaxNode syntax, A

private bool IsBadLocalOrParameterCapture(Symbol symbol, TypeSymbol type, RefKind refKind)
{
if (refKind != RefKind.None || type.IsRestrictedType()) // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
if (refKind != RefKind.None || type.IsRestrictedType())
{
var containingMethod = this.ContainingMemberOrLambda as MethodSymbol;
if ((object)containingMethod != null && (object)symbol.ContainingSymbol != (object)containingMethod)
Expand Down Expand Up @@ -2046,7 +2046,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
}
else
{
Debug.Assert(parameter.Type.IsRefLikeType); // PROTOTYPE(RefStructInterfaces): adjust?
Debug.Assert(parameter.Type.IsRefLikeTypeOrAllowsByRefLike());
Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUseRefLike, node, parameter.Name);
}
}
Expand All @@ -2056,7 +2056,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
bool capture = (this.ContainingMember() is MethodSymbol containingMethod && (object)primaryCtor != containingMethod);

if (capture &&
(parameter.RefKind != RefKind.None || parameter.Type.IsRestrictedType()) && // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
(parameter.RefKind != RefKind.None || parameter.Type.IsRestrictedType()) &&
!IsInsideNameof)
{
if (parameter.RefKind != RefKind.None)
Expand All @@ -2069,7 +2069,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
}
else
{
Debug.Assert(parameter.Type.IsRefLikeType); // PROTOTYPE(RefStructInterfaces): adjust?
Debug.Assert(parameter.Type.IsRefLikeTypeOrAllowsByRefLike());
Error(diagnostics, ErrorCode.ERR_UnsupportedPrimaryConstructorParameterCapturingRefLike, node, parameter.Name);
}
}
Expand Down Expand Up @@ -3187,7 +3187,7 @@ internal static void CheckRestrictedTypeInAsyncMethod(Symbol containingSymbol, T
Debug.Assert(errorCode is ErrorCode.ERR_BadSpecialByRefLocal or ErrorCode.ERR_BadSpecialByRefUsing or ErrorCode.ERR_BadSpecialByRefLock);
if (containingSymbol.Kind == SymbolKind.Method
&& ((MethodSymbol)containingSymbol).IsAsync
&& type.IsRestrictedType()) // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
&& type.IsRestrictedType())
{
if (errorCode == ErrorCode.ERR_BadSpecialByRefLock)
{
Expand Down
36 changes: 34 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3566,10 +3566,37 @@ internal static ConstantValue GetIsOperatorConstantResult(
return ConstantValue.False;
}

// * If either type is a restricted type, the type check isn't supported because
// * If either type is a restricted type, the type check isn't supported for some scenarios because
// a restricted type cannot be boxed or unboxed into.
if (targetType.IsRestrictedType() || operandType.IsRestrictedType()) // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
if (targetType.IsRestrictedType() || operandType.IsRestrictedType())
{
if (targetType is TypeParameterSymbol { AllowByRefLike: true })
{
if (!operandType.IsRefLikeType && operandType is not TypeParameterSymbol)
{
return null;
}
}
else if (operandType is not TypeParameterSymbol { AllowByRefLike: true })
{
if (targetType.IsRefLikeType)
{
if (operandType is TypeParameterSymbol)
{
Debug.Assert(operandType is TypeParameterSymbol { AllowByRefLike: false });
return ConstantValue.False;
}
}
else if (operandType.IsRefLikeType)
{
if (targetType is TypeParameterSymbol)
{
Debug.Assert(targetType is TypeParameterSymbol { AllowByRefLike: false });
return ConstantValue.False;
}
}
}

return ConstantValue.Bad;
}

Expand Down Expand Up @@ -3981,6 +4008,11 @@ internal static ConstantValue GetAsOperatorConstantResult(TypeSymbol operandType

if (!isOperatorConstantResult.BooleanValue)
{
if (operandType?.IsRefLikeType == true)
{
return ConstantValue.Bad;
}

return ConstantValue.Null;
}
}
Expand Down
1 change: 0 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -752,7 +752,6 @@ internal MethodSymbol TryFindDisposePatternMethod(BoundExpression expr, SyntaxNo
{
Debug.Assert(expr is object);
Debug.Assert(expr.Type is object);
// PROTOTYPE(RefStructInterfaces): adjust?
Debug.Assert(expr.Type.IsRefLikeType || hasAwait); // pattern dispose lookup is only valid on ref structs or asynchronous usings

var result = PerformPatternMethodLookup(expr,
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ private static TypeWithAnnotations DelegateReturnTypeWithAnnotations(MethodSymbo
getEffectiveScopeFromSymbol = true;
}
}
else if (type.IsRefLikeType() && ParameterSyntax(i)?.Modifiers.Any(SyntaxKind.ParamsKeyword) == true) // PROTOTYPE(RefStructInterfaces): adjust?
else if (type.IsRefLikeTypeOrAllowsByRefLike() && ParameterSyntax(i)?.Modifiers.Any(SyntaxKind.ParamsKeyword) == true)
{
scope = ScopedKind.ScopedValue;
if (_unboundLambda.ParameterAttributes(i).Any())
Expand Down
2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/CodeGen/EmitExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3989,7 +3989,7 @@ private void EmitStaticCast(TypeSymbol to, SyntaxNode syntax)

private void EmitBox(TypeSymbol type, SyntaxNode syntaxNode)
{
Debug.Assert(!type.IsRefLikeType); // PROTOTYPE(RefStructInterfaces): adjust?
Debug.Assert(!type.IsRefLikeType);

_builder.EmitOpCode(ILOpCode.Box);
EmitSymbolToken(type, syntaxNode);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11784,7 +11784,7 @@ protected override void VisitInterpolatedStringHandlerConstructor(BoundExpressio

public override BoundNode? VisitStackAllocArrayCreation(BoundStackAllocArrayCreation node)
{
Debug.Assert(node.Type is null || node.Type.IsErrorType() || node.Type.IsRefLikeType); // PROTOTYPE(RefStructInterfaces): adjust?
Debug.Assert(node.Type is null || node.Type.IsErrorType() || node.Type.IsRefLikeType);
return VisitStackAllocArrayCreationBase(node);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -695,8 +695,9 @@ private void AddDiagnosticIfRestrictedType(Symbol capturedVariable, SyntaxNode s
return;
}

if (type.IsRestrictedType() == true) // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
if (type.IsRestrictedType() == true)
{
Debug.Assert(false); // Add test(s) for scenarios that hit this code path
_diagnostics.Add(ErrorCode.ERR_SpecialByRefInLambda, syntax.Location, type);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,10 @@ _ when variableType.IsPointerOrFunctionPointer()
=> WellKnownMember.Microsoft_CodeAnalysis_Runtime_LocalStoreTracker__LogLocalStorePointer,
_ when !variableType.IsManagedTypeNoUseSiteDiagnostics
=> WellKnownMember.Microsoft_CodeAnalysis_Runtime_LocalStoreTracker__LogLocalStoreUnmanaged,
_ when variableType.IsRefLikeType && !hasOverriddenToString(variableType) // PROTOTYPE(RefStructInterfaces): adjust?
_ when variableType.IsRefLikeType && !hasOverriddenToString(variableType)
=> null, // not possible to invoke ToString on ref struct that doesn't override it
_ when variableType is TypeParameterSymbol { AllowByRefLike: true }
=> null, // not possible to invoke ToString on ref struct type parameter
_ when variableType.TypeKind is TypeKind.Struct
// we'll emit ToString constrained virtcall to avoid boxing the struct
=> WellKnownMember.Microsoft_CodeAnalysis_Runtime_LocalStoreTracker__LogLocalStoreString,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ private BoundExpression Spill(
continue;

case BoundKind.Sequence:
if (refKind != RefKind.None || expression.Type?.IsRefLikeType == true) // PROTOTYPE(RefStructInterfaces): adjust?
if (refKind != RefKind.None || expression.Type?.IsRefLikeTypeOrAllowsByRefLike() == true)
{
var sequence = (BoundSequence)expression;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ private static bool HoistInDebugBuild(Symbol symbol)
{
ParameterSymbol parameter =>
// in Debug build hoist all parameters that can be hoisted:
!parameter.Type.IsRestrictedType(), // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
!parameter.Type.IsRestrictedType(),
LocalSymbol { IsConst: false, IsPinned: false, IsRef: false } local =>
// hoist all user-defined locals and long-lived temps that can be hoisted:
local.SynthesizedKind.MustSurviveStateMachineSuspension() &&
!local.Type.IsRestrictedType(), // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters?
!local.Type.IsRestrictedType(),
_ => false
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public AnonymousTypeField(
bool isParams = false,
bool hasUnscopedRefAttribute = false)
{
Debug.Assert(!isParams || !typeWithAnnotations.Type.IsTypeParameter());
this.Name = name;
this.Location = location;
this.TypeWithAnnotations = typeWithAnnotations;
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/TypeWithAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ public bool IsSZArray() =>
_extensions.IsSZArray(DefaultType);
public bool IsRefLikeType() =>
_extensions.IsRefLikeType(DefaultType);
public bool IsRefLikeTypeOrAllowsByRefLike() =>
_extensions.IsRefLikeTypeOrAllowsByRefLike(DefaultType);
public bool IsStatic =>
_extensions.IsStatic(DefaultType);
public bool IsRestrictedType(bool ignoreSpanLikeTypes = false) =>
Expand Down Expand Up @@ -865,6 +867,7 @@ internal static Extensions Create(ImmutableArray<CustomModifier> customModifiers
internal abstract bool IsVoid(TypeSymbol typeSymbol);
internal abstract bool IsSZArray(TypeSymbol typeSymbol);
internal abstract bool IsRefLikeType(TypeSymbol typeSymbol);
internal abstract bool IsRefLikeTypeOrAllowsByRefLike(TypeSymbol typeSymbol);

internal abstract TypeWithAnnotations WithTypeAndModifiers(TypeWithAnnotations type, TypeSymbol typeSymbol, ImmutableArray<CustomModifier> customModifiers);

Expand Down Expand Up @@ -896,6 +899,7 @@ public NonLazyType(ImmutableArray<CustomModifier> customModifiers)
internal override bool IsVoid(TypeSymbol typeSymbol) => typeSymbol.IsVoidType();
internal override bool IsSZArray(TypeSymbol typeSymbol) => typeSymbol.IsSZArray();
internal override bool IsRefLikeType(TypeSymbol typeSymbol) => typeSymbol.IsRefLikeType;
internal override bool IsRefLikeTypeOrAllowsByRefLike(TypeSymbol typeSymbol) => typeSymbol.IsRefLikeTypeOrAllowsByRefLike();

internal override TypeSymbol GetNullableUnderlyingTypeOrSelf(TypeSymbol typeSymbol) => typeSymbol.StrippedType();

Expand Down Expand Up @@ -968,6 +972,7 @@ public LazySubstitutedType(ImmutableArray<CustomModifier> customModifiers, TypeP
internal override bool IsVoid(TypeSymbol typeSymbol) => typeSymbol.IsVoidType();
internal override bool IsSZArray(TypeSymbol typeSymbol) => typeSymbol.IsSZArray();
internal override bool IsRefLikeType(TypeSymbol typeSymbol) => typeSymbol.IsRefLikeType;
internal override bool IsRefLikeTypeOrAllowsByRefLike(TypeSymbol typeSymbol) => typeSymbol.IsRefLikeTypeOrAllowsByRefLike();

internal override NullableAnnotation GetResolvedAnnotation(NullableAnnotation defaultAnnotation)
{
Expand Down Expand Up @@ -1068,6 +1073,7 @@ public LazyNullableTypeParameter(CSharpCompilation compilation, TypeWithAnnotati
internal override bool IsVoid(TypeSymbol typeSymbol) => false;
internal override bool IsSZArray(TypeSymbol typeSymbol) => false;
internal override bool IsRefLikeType(TypeSymbol typeSymbol) => false;
internal override bool IsRefLikeTypeOrAllowsByRefLike(TypeSymbol typeSymbol) => typeSymbol.IsRefLikeTypeOrAllowsByRefLike();
internal override bool IsStatic(TypeSymbol typeSymbol) => false;

private TypeSymbol GetResolvedType()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ private static string MemoryToString(void* address, int size)
private static string WithHelpers(string source)
=> source + s_helpers;

private static readonly TargetFramework s_targetFramework = TargetFramework.Net70;
private const TargetFramework s_targetFramework = TargetFramework.Net70;

private static readonly Verification s_verification = Verification.Fails with
{
Expand All @@ -180,13 +180,13 @@ private static string WithHelpers(string source)
"""
};

private CompilationVerifier CompileAndVerify(string source, string? ilVerifyMessage = null, string? expectedOutput = null)
private CompilationVerifier CompileAndVerify(string source, string? ilVerifyMessage = null, string? expectedOutput = null, TargetFramework targetFramework = s_targetFramework)
=> CompileAndVerify(
source,
options: (expectedOutput != null) ? TestOptions.UnsafeDebugExe : TestOptions.UnsafeDebugDll,
emitOptions: s_emitOptions,
verify: s_verification with { ILVerifyMessage = ilVerifyMessage + Environment.NewLine + s_verification.ILVerifyMessage },
targetFramework: s_targetFramework,
targetFramework: targetFramework,
expectedOutput: expectedOutput);

// Only used to diagnose test verification failures (rename CompileAndVerify to CompileAndVerifyFails and rerun).
Expand Down Expand Up @@ -1682,6 +1682,82 @@ .locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0,
}");
}

[Fact]
public void RefStructTypeParameter()
{
var source = WithHelpers("""
S.F(new S());

ref struct S
{
ref int X;

public static void F<T>(T p)
where T : struct, allows ref struct
{
int a = 1;
var x = p = default(T);
}
}
""");
var verifier = CompileAndVerify(
source,
targetFramework: TargetFramework.Net80, // PROTOTYPE(RefStructInterfaces): Switch to supporting target framework once we have its ref assemblies.
expectedOutput: @"
<Main>$: Entered
<Main>$: P'args'[0] = System.String[]
F: Entered
F: L1 = 1
F: Returned
<Main>$: Returned
");

// writes to x and p are not logged since we can't invoke ToString()
verifier.VerifyMethodBody("S.F<T>(T)", @"
{
// Code size 46 (0x2e)
.maxstack 3
.locals init (Microsoft.CodeAnalysis.Runtime.LocalStoreTracker V_0,
int V_1, //a
T V_2) //x
// sequence point: <hidden>
IL_0000: ldtoken ""void S.F<T>(T)""
IL_0005: call ""Microsoft.CodeAnalysis.Runtime.LocalStoreTracker Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogMethodEntry(int)""
IL_000a: stloc.0
.try
{
// sequence point: {
IL_000b: nop
// sequence point: int a = 1;
IL_000c: ldloca.s V_0
IL_000e: ldc.i4.1
IL_000f: dup
IL_0010: stloc.1
IL_0011: ldc.i4.1
IL_0012: call ""void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogLocalStore(uint, int)""
IL_0017: nop
// sequence point: var x = p = default(T);
IL_0018: ldarga.s V_0
IL_001a: initobj ""T""
IL_0020: ldarg.0
IL_0021: stloc.2
// sequence point: }
IL_0022: leave.s IL_002d
}
finally
{
// sequence point: <hidden>
IL_0024: ldloca.s V_0
IL_0026: call ""void Microsoft.CodeAnalysis.Runtime.LocalStoreTracker.LogReturn()""
IL_002b: nop
IL_002c: endfinally
}
// sequence point: }
IL_002d: ret
}
");
}

[Fact]
public void UnmanagedRefStruct()
{
Expand Down
Loading

0 comments on commit 33e5614

Please sign in to comment.