From 184156568a494fecd736867e55c1c562451477c2 Mon Sep 17 00:00:00 2001 From: AlekseyTs Date: Fri, 19 Apr 2024 14:59:12 -0700 Subject: [PATCH] Specially handle more scenarios for type parameters with 'allows ref struct' constraint (#73059) --- .../Portable/Binder/Binder_Deconstruct.cs | 2 +- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 2 +- .../Portable/Binder/ForEachLoopBinder.cs | 2 +- .../BoundTree/VariablePendingInference.cs | 2 +- .../AnonymousTypeManager.Templates.cs | 14 +- .../AnonymousType.TypeParameterSymbol.cs | 9 +- .../Source/SourceComplexParameterSymbol.cs | 2 +- ...thesizedReadOnlyListTypeParameterSymbol.cs | 4 +- .../Records/SynthesizedRecordEquals.cs | 2 +- .../Records/SynthesizedRecordPrintMembers.cs | 3 +- .../Portable/Symbols/TypeSymbolExtensions.cs | 2 +- .../Test/Emit3/RefStructInterfacesTests.cs | 532 +++++++++++++++++- 13 files changed, 557 insertions(+), 21 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index 1fe42d3f5eb73..6247a7823d4d8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -894,7 +894,7 @@ private BoundExpression BindDeconstructionVariable( } if (declTypeWithAnnotations.HasType && - localSymbol.Scope == ScopedKind.ScopedValue && !declTypeWithAnnotations.Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + localSymbol.Scope == ScopedKind.ScopedValue && !declTypeWithAnnotations.Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 5bd7a9c15e26e..776acef5d3b7e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -3126,7 +3126,7 @@ private BoundExpression BindOutVariableDeclarationArgument( CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax); - if (localSymbol.Scope == ScopedKind.ScopedValue && !declType.Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + if (localSymbol.Scope == ScopedKind.ScopedValue && !declType.Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location); } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 461f307716480..250360f70ba0a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1101,7 +1101,7 @@ protected BoundLocalDeclaration BindVariableDeclaration( CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declTypeOpt.Type, localDiagnostics, typeSyntax); - if (localSymbol.Scope == ScopedKind.ScopedValue && !declTypeOpt.Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + if (localSymbol.Scope == ScopedKind.ScopedValue && !declTypeOpt.Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { localDiagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location); } diff --git a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs index e4e160320b828..3e13564f25b05 100644 --- a/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs +++ b/src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs @@ -315,7 +315,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno CheckRestrictedTypeInAsyncMethod(this.ContainingMemberOrLambda, declType.Type, diagnostics, typeSyntax); - if (local.Scope == ScopedKind.ScopedValue && !declType.Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + if (local.Scope == ScopedKind.ScopedValue && !declType.Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, typeSyntax.Location); } diff --git a/src/Compilers/CSharp/Portable/BoundTree/VariablePendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/VariablePendingInference.cs index 536d82d8eab42..03017fd5e6065 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/VariablePendingInference.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/VariablePendingInference.cs @@ -64,7 +64,7 @@ internal BoundExpression SetInferredTypeWithAnnotations(TypeWithAnnotations type Binder.CheckRestrictedTypeInAsyncMethod(localSymbol.ContainingSymbol, type.Type, diagnosticsOpt, typeOrDesignationSyntax); - if (localSymbol.Scope == ScopedKind.ScopedValue && !type.Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + if (localSymbol.Scope == ScopedKind.ScopedValue && !type.Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { diagnosticsOpt.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, (typeOrDesignationSyntax is TypeSyntax typeSyntax ? typeSyntax.SkipScoped(out _).SkipRef() : typeOrDesignationSyntax).Location); diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs index bfe839bd3267c..157718ec50f0e 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/AnonymousTypeManager.Templates.cs @@ -190,7 +190,9 @@ private NamedTypeSymbol ConstructAnonymousDelegateImplementationSymbol(Anonymous // If all parameter types and return type are valid type arguments, construct // the delegate type from a generic template. Otherwise, use a non-generic template. bool useUpdatedEscapeRules = Compilation.SourceModule.UseUpdatedEscapeRules; - if (allValidTypeArguments(useUpdatedEscapeRules, typeDescr, out var needsIndexedName)) + bool runtimeSupportsByRefLikeGenerics = Compilation.SourceAssembly.RuntimeSupportsByRefLikeGenerics; + + if (allValidTypeArguments(useUpdatedEscapeRules, runtimeSupportsByRefLikeGenerics, typeDescr, out var needsIndexedName)) { var fields = typeDescr.Fields; Debug.Assert(fields.All(f => hasDefaultScope(useUpdatedEscapeRules, f))); @@ -294,20 +296,20 @@ private NamedTypeSymbol ConstructAnonymousDelegateImplementationSymbol(Anonymous template.Construct(typeParameters); } - static bool allValidTypeArguments(bool useUpdatedEscapeRules, AnonymousTypeDescriptor typeDescr, out bool needsIndexedName) + static bool allValidTypeArguments(bool useUpdatedEscapeRules, bool runtimeSupportsByRefLikeGenerics, AnonymousTypeDescriptor typeDescr, out bool needsIndexedName) { needsIndexedName = false; var fields = typeDescr.Fields; int n = fields.Length; for (int i = 0; i < n - 1; i++) { - if (!isValidTypeArgument(useUpdatedEscapeRules, fields[i], ref needsIndexedName)) + if (!isValidTypeArgument(useUpdatedEscapeRules, runtimeSupportsByRefLikeGenerics, fields[i], ref needsIndexedName)) { return false; } } var returnParameter = fields[n - 1]; - return returnParameter.Type.IsVoidType() || isValidTypeArgument(useUpdatedEscapeRules, returnParameter, ref needsIndexedName); + return returnParameter.Type.IsVoidType() || isValidTypeArgument(useUpdatedEscapeRules, runtimeSupportsByRefLikeGenerics, returnParameter, ref needsIndexedName); } static bool hasDefaultScope(bool useUpdatedEscapeRules, AnonymousTypeField field) @@ -324,13 +326,13 @@ static bool hasDefaultScope(bool useUpdatedEscapeRules, AnonymousTypeField field }; } - static bool isValidTypeArgument(bool useUpdatedEscapeRules, AnonymousTypeField field, ref bool needsIndexedName) + static bool isValidTypeArgument(bool useUpdatedEscapeRules, bool runtimeSupportsByRefLikeGenerics, AnonymousTypeField field, ref bool needsIndexedName) { needsIndexedName = needsIndexedName || field.IsParams || field.DefaultValue is not null; return hasDefaultScope(useUpdatedEscapeRules, field) && field.Type is { } type && !type.IsPointerOrFunctionPointer() && - !type.IsRestrictedType() && // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters? + (type.IsTypeParameter() || !type.IsRestrictedType(ignoreSpanLikeTypes: runtimeSupportsByRefLikeGenerics)) && (!field.IsParams || field.Type.IsSZArray()); // [params T collection] is not recognized as a valid params parameter definition } diff --git a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeParameterSymbol.cs index 6ac3a517256c4..5b52e0b3520a7 100644 --- a/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/AnonymousTypes/SynthesizedSymbols/AnonymousType.TypeParameterSymbol.cs @@ -17,11 +17,11 @@ internal sealed partial class AnonymousTypeManager /// internal sealed class AnonymousTypeParameterSymbol : TypeParameterSymbol { - private readonly Symbol _container; + private readonly AnonymousTypeOrDelegateTemplateSymbol _container; private readonly int _ordinal; private readonly string _name; - public AnonymousTypeParameterSymbol(Symbol container, int ordinal, string name) + public AnonymousTypeParameterSymbol(AnonymousTypeOrDelegateTemplateSymbol container, int ordinal, string name) { Debug.Assert((object)container != null); Debug.Assert(!string.IsNullOrEmpty(name)); @@ -93,7 +93,10 @@ public override bool HasValueTypeConstraint public override bool AllowByRefLike { - get { return false; } + get + { + return _container.IsDelegateType() && _container.Manager.Compilation.SourceAssembly.RuntimeSupportsByRefLikeGenerics; + } } public override bool IsValueTypeFromConstraintTypes diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs index 1d4361bb9ef18..535beb92bf240 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceComplexParameterSymbol.cs @@ -1538,7 +1538,7 @@ private void DoMiscValidation() validateParamsType(diagnostics); } - if (DeclaredScope == ScopedKind.ScopedValue && !Type.IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike()) + if (DeclaredScope == ScopedKind.ScopedValue && !Type.IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike()) { Debug.Assert(ParameterSyntax is not null); diagnostics.Add(ErrorCode.ERR_ScopedRefAndRefStructOnly, ParameterSyntax); diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListTypeParameterSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListTypeParameterSymbol.cs index 01cd2a72f686e..2ab8568fe8c03 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListTypeParameterSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/ReadOnlyListType/SynthesizedReadOnlyListTypeParameterSymbol.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using System.Diagnostics; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols @@ -13,6 +14,7 @@ internal sealed class SynthesizedReadOnlyListTypeParameterSymbol : TypeParameter internal SynthesizedReadOnlyListTypeParameterSymbol(SynthesizedReadOnlyListTypeSymbol containingType) { + Debug.Assert(containingType.IsClassType()); _containingType = containingType; } @@ -32,7 +34,7 @@ internal SynthesizedReadOnlyListTypeParameterSymbol(SynthesizedReadOnlyListTypeS public override bool HasValueTypeConstraint => false; - public override bool AllowByRefLike => false; // PROTOTYPE(RefStructInterfaces): That should probably match constraints on implemented interface(s). + public override bool AllowByRefLike => false; // The list is a class type and cannot store ref structs as elements. public override bool IsValueTypeFromConstraintTypes => false; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs index 871a9711d01b4..8301c43f491ec 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEquals.cs @@ -141,7 +141,7 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, diagnostics.Add(ErrorCode.ERR_BadFieldTypeInRecord, f.GetFirstLocationOrNone(), parameterType); foundBadField = true; } - else if (parameterType.IsRestrictedType()) // PROTOTYPE(RefStructInterfaces): Is this doing the right thing for 'allows ref struct' type parameters? + else if (parameterType.IsRestrictedType()) { // We'll have reported a diagnostic elsewhere (SourceMemberFieldSymbol.TypeChecks) foundBadField = true; diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs index ebb90076b2317..50e424981207b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPrintMembers.cs @@ -213,8 +213,9 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState, F.WellKnownMethod(WellKnownMember.System_Text_StringBuilder__AppendString), F.Call(value, F.SpecialMethod(SpecialMember.System_Object__ToString))))); } - else + else if (!value.Type.IsRestrictedType()) { + // Otherwise, an error has been reported elsewhere (SourceMemberFieldSymbol.TypeChecks) block.Add(F.ExpressionStatement( F.Call(receiver: builder, F.WellKnownMethod(WellKnownMember.System_Text_StringBuilder__AppendObject), diff --git a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs index e5decd045717b..0fd16914f5a12 100644 --- a/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs +++ b/src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs @@ -527,7 +527,7 @@ public static bool IsPossibleArrayGenericInterface(this TypeSymbol type) return false; } - internal static bool IsErrorTypeOrIsRefLikeTypeOrAllowByRefLike(this TypeSymbol type) + internal static bool IsErrorTypeOrIsRefLikeTypeOrAllowsByRefLike(this TypeSymbol type) { return type.IsErrorType() || type.IsRefLikeTypeOrAllowsByRefLike(); } diff --git a/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs b/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs index c552e11ec492c..181d3ac3c7d6e 100644 --- a/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/RefStructInterfacesTests.cs @@ -17251,6 +17251,8 @@ public void Add(int x){} // static void Test2([UnscopedRef] params scoped S y) Diagnostic(ErrorCode.ERR_UnscopedScoped, "UnscopedRef").WithLocation(12, 24) ); + + // PROTOTYPE(RefStructInterfaces): Consider testing similar scenario without params } [Fact] @@ -17398,6 +17400,8 @@ ref struct S // public static S P4; Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "S").WithArguments("S").WithLocation(15, 19) ); + + // PROTOTYPE(RefStructInterfaces): Consider testing capturing primary constructor parameter of type allows ref struct. } [Fact] @@ -17448,7 +17452,7 @@ ref struct S } [Fact] - public void InlineArrayElement() + public void InlineArrayElement_01() { var src = @" [System.Runtime.CompilerServices.InlineArray(10)] @@ -17467,6 +17471,13 @@ ref struct S2 ref struct S { } + +[System.Runtime.CompilerServices.InlineArray(10)] +struct S2 + where T2 : allows ref struct +{ + T2 _f; +} "; var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics); @@ -17476,8 +17487,106 @@ ref struct S Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(6, 7), // (12,7): warning CS9184: 'Inline arrays' language feature is not supported for an inline array type that is not valid as a type argument, or has element type that is not valid as a type argument. // S _f; - Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(12, 7) + Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(12, 7), + // (23,5): error CS8345: Field or auto-implemented property cannot be of type 'T2' unless it is an instance member of a ref struct. + // T2 _f; + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "T2").WithArguments("T2").WithLocation(23, 5), + + // PROTOTYPE(RefStructInterfaces): The warning below is somewhat misleading. 'S2' can be used as a type argument (it is not a ref struct) and 'T2' is a type argument. + // However, given the error above, this is probably not worth fixing. There is no way to declare a legal non-ref struct with a field + // of type 'T2'. + + // (23,8): warning CS9184: 'Inline arrays' language feature is not supported for an inline array type that is not valid as a type argument, or has element type that is not valid as a type argument. + // T2 _f; + Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(23, 8) + ); + } + + [Fact] + public void InlineArrayElement_02() + { + var src = @" +[System.Runtime.CompilerServices.InlineArray(2)] +ref struct S1 + where T : allows ref struct +{ + T _f; +} + +class C +{ + static void Main() + { + var x = new S1(); + x[0] = 123; + } +} + +"; + + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics); + comp.VerifyDiagnostics( + // (6,7): warning CS9184: 'Inline arrays' language feature is not supported for an inline array type that is not valid as a type argument, or has element type that is not valid as a type argument. + // T _f; + Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(6, 7), + // (14,9): error CS9504: The type 'S1' may not be a ref struct or a type parameter allowing ref structs in order to use it as parameter 'TFrom' in the generic type or method 'Unsafe.As(ref TFrom)' + // x[0] = 123; + Diagnostic(ErrorCode.ERR_NotRefStructConstraintNotSatisfied, "x[0]").WithArguments("System.Runtime.CompilerServices.Unsafe.As(ref TFrom)", "TFrom", "S1").WithLocation(14, 9) + ); + } + + [Fact] + public void InlineArrayElement_03() + { + var src = @" +[System.Runtime.CompilerServices.InlineArray(2)] +ref struct S1 + where T : allows ref struct +{ + T _f; +} + +class C +{ + static void Main() + { + var x = new S1(); + x[0] = 123; + } +} + +namespace System.Runtime.CompilerServices +{ + public class Unsafe + { + public static ref TTo As(ref TFrom input) where TFrom : allows ref struct => throw null; + } +} +"; + + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.DebugExe); + comp.VerifyEmitDiagnostics( + // (6,7): warning CS9184: 'Inline arrays' language feature is not supported for an inline array type that is not valid as a type argument, or has element type that is not valid as a type argument. + // T _f; + Diagnostic(ErrorCode.WRN_InlineArrayNotSupportedByLanguage, "_f").WithLocation(6, 7) ); + + // PROTOTYPE(RefStructInterfaces): Here, however, we managed to successfully compile an invalid program. + // We should either stop relying on constraints of Unsafe.As as a way to + // detect ref struct based inline arrays, or should propagate 'allows ref struct' to + // the helper methods that we generate in '', which + // could be tricky because they often call other generic APIs that might disagree + // in the 'allows ref struct' constraint with Unsafe.As. + + // Message: + // System.Security.VerificationException : Method.InlineArrayFirstElementRef: type argument 'S1`1[System.Int32]' violates the constraint of type parameter 'TBuffer'. + // + // Stack Trace:  + // C.Main() + // RuntimeMethodHandle.InvokeMethod(Object target, Void * *arguments, Signature sig, Boolean isConstructor) + // MethodBaseInvoker.InvokeWithNoArgs(Object obj, BindingFlags invokeAttr) + // + //CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: "nothing"); } [Fact] @@ -17554,6 +17663,8 @@ ref struct S // protected abstract override S Test2(S y); Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "Test2").WithArguments("y").WithLocation(14, 35) ); + + // PROTOTYPE(RefStructInterfaces): Consider testing similar scenario with implicitly scoped parameter } [Fact] @@ -17596,6 +17707,8 @@ ref struct S // protected abstract override void Test2(S y, out S z); Diagnostic(ErrorCode.ERR_ScopedMismatchInParameterOfOverrideOrImplementation, "Test2").WithArguments("y").WithLocation(14, 38) ); + + // PROTOTYPE(RefStructInterfaces): Consider testing ERR_ScopedMismatchInParameterOfPartial and ERR_ScopedMismatchInParameterOfTarget. } [Fact(Skip = "'byreflike' in IL is not supported yet")] // PROTOTYPE(RefStructInterfaces): Enable once we get support for 'byreflike' in IL. @@ -17631,5 +17744,420 @@ static void F(ref T r1) where T : allows ref struct Diagnostic(ErrorCode.ERR_BindToBogus, "F").WithArguments("R2.F").WithLocation(6, 12) ); } + + [Fact] + public void RestrictedTypesInRecords() + { + var src = @" +record C( + T P1 + ) where T : allows ref struct; +"; + + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics); + comp.VerifyEmitDiagnostics( + // (3,5): error CS8345: Field or auto-implemented property cannot be of type 'T' unless it is an instance member of a ref struct. + // T P1 + Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "T").WithArguments("T").WithLocation(3, 5) + ); + } + + [Theory] + [CombinatorialData] + public void AnonymousDelegateType_01_ActionDisallowsRefStruct(bool s2IsRefStruct) + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) where T : allows ref struct + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x, new S2()); + } +} + +class Helper where T : allows ref struct +{ + public static void Test1(T x, S2 y) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + } +} + +ref struct S1 {} + +" + (s2IsRefStruct ? "ref " : "") + @"struct S2 {} + +namespace System +{ + public delegate void Action(T1 x, T2 y); +} +"; + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>A`2[S1,S2] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped, + symbolValidator: (m) => + { + foreach (var tp in m.ContainingAssembly.GetTypeByMetadataName("<>A`2").TypeParameters) + { + Assert.True(tp.AllowByRefLike); + } + } + ).VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void AnonymousDelegateType_02_FuncDisallowsRefStruct(bool s2IsRefStruct) + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) where T : allows ref struct + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x); + } +} + +class Helper where T : allows ref struct +{ + public static S2 Test1(T x) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + return default; + } +} + +ref struct S1 {} + +" + (s2IsRefStruct ? "ref " : "") + @"struct S2 {} + +namespace System +{ + public delegate T2 Func(T1 x); +} +"; + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>F`2[S1,S2] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr && !s2IsRefStruct ? Verification.Passes : Verification.Skipped, + symbolValidator: (m) => + { + foreach (var tp in m.ContainingAssembly.GetTypeByMetadataName("<>F`2").TypeParameters) + { + Assert.True(tp.AllowByRefLike); + } + } + ).VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void AnonymousDelegateType_03_ActionAllowsRefStruct(bool s2IsRefStruct) + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) where T : allows ref struct + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x, new S2()); + } +} + +class Helper where T : allows ref struct +{ + public static void Test1(T x, S2 y) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + } +} + +ref struct S1 {} + +" + (s2IsRefStruct ? "ref " : "") + @"struct S2 {} + +namespace System +{ + public delegate void Action(T1 x, T2 y) where T1 : allows ref struct where T2 : allows ref struct; +} +"; + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.ReleaseExe); + + CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"System.Action`2[S1,S2] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + } + + [Theory] + [CombinatorialData] + public void AnonymousDelegateType_04_FuncAllowsRefStruct(bool s2IsRefStruct) + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) where T : allows ref struct + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x); + } +} + +class Helper where T : allows ref struct +{ + public static S2 Test1(T x) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + return default; + } +} + +ref struct S1 {} + +" + (s2IsRefStruct ? "ref " : "") + @"struct S2 {} + +namespace System +{ + public delegate T2 Func(T1 x) where T1 : allows ref struct where T2 : allows ref struct; +} +"; + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.ReleaseExe); + + CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"System.Func`2[S1,S2] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr && !s2IsRefStruct ? Verification.Passes : Verification.Skipped).VerifyDiagnostics(); + } + + [Fact] + public void AnonymousDelegateType_05_PartiallyGenericAnonymousDelegate() + { + var src = @" +unsafe class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) where T : allows ref struct + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x, null); + } +} + +unsafe class Helper where T : allows ref struct +{ + public static void Test1(T x, void* y) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + } +} + +ref struct S1 {} + +namespace System +{ + public delegate void Action(T1 x, T2 y) where T1 : allows ref struct where T2 : allows ref struct; +} +"; + var comp = CreateCompilation(src, targetFramework: s_targetFrameworkSupportingByRefLikeGenerics, options: TestOptions.UnsafeReleaseExe); + + CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>f__AnonymousDelegate0`1[S1] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped, + symbolValidator: (m) => + { + foreach (var tp in m.ContainingAssembly.GetTypeByMetadataName("<>f__AnonymousDelegate0`1").TypeParameters) + { + Assert.True(tp.AllowByRefLike); + } + } + ).VerifyDiagnostics(); + } + + [Fact] + public void AnonymousDelegateType_06_PartiallyGenericAnonymousDelegate_CannotAllowRefStruct() + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(T x) + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x, new S2()); + } +} + +class Helper +{ + public static void Test1(T x, S2 y) + { + System.Console.Write(""Test1""); + System.Console.Write("" ""); + System.Console.Write(typeof(T)); + } +} + +struct S1 {} + +ref struct S2 {} +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe); + + CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>f__AnonymousDelegate0`1[S1] Test1 S1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped, + symbolValidator: (m) => + { + foreach (var tp in m.ContainingAssembly.GetTypeByMetadataName("<>f__AnonymousDelegate0`1").TypeParameters) + { + Assert.False(tp.AllowByRefLike); + } + } + ).VerifyDiagnostics(); + } + + [Fact] + public void AnonymousDelegateType_07_ActionDisallowsRefStruct_CannotAllowRefStruct() + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(S1 x) + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x, new S2()); + } +} + +class Helper +{ + public static void Test1(S1 x, S2 y) + { + System.Console.Write(""Test1""); + } +} + +ref struct S1 {} + +ref struct S2 {} + +namespace System +{ + public delegate void Action(T1 x, T2 y); +} +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>f__AnonymousDelegate0 Test1" : null, + verify: ExecutionConditionUtil.IsMonoOrCoreClr ? Verification.Passes : Verification.Skipped + ).VerifyDiagnostics(); + } + + [Fact] + public void AnonymousDelegateType_08_FuncDisallowsRefStruct_CannotAllowRefStruct() + { + var src = @" +class C +{ + static void Main() + { + Test1(new S1()); + } + + static void Test1(S1 x) + { + var d = Helper.Test1; + System.Console.Write(d.GetType()); + System.Console.Write("" ""); + d(x); + } +} + +class Helper +{ + public static S2 Test1(S1 x) + { + System.Console.Write(""Test1""); + return default; + } +} + +ref struct S1 {} + +ref struct S2 {} + +namespace System +{ + public delegate T2 Func(T1 x); +} +"; + var comp = CreateCompilation(src, targetFramework: TargetFramework.Net70, options: TestOptions.ReleaseExe); + + var verifier = CompileAndVerify( + comp, expectedOutput: ExecutionConditionUtil.IsMonoOrCoreClr ? @"<>f__AnonymousDelegate0 Test1" : null, + verify: Verification.Skipped + ).VerifyDiagnostics(); + } } }