From 7f05bbb9d0f1522c3e1d7d30913fba42de42aeba Mon Sep 17 00:00:00 2001 From: Fred Silberberg Date: Fri, 5 Apr 2024 15:02:13 -0700 Subject: [PATCH] Disallow dynamic calls on ref struct receivers (#72674) Fixes https://github.com/dotnet/roslyn/issues/72606. Note that the behavior in the bug has slightly changed from the sharplab build used in the repro due to https://github.com/dotnet/roslyn/pull/71421. --- .../Portable/Binder/Binder_Expressions.cs | 6 +- .../Portable/Binder/Binder_Invocation.cs | 32 +- .../CSharp/Portable/CSharpResources.resx | 5 +- .../CSharp/Portable/Errors/ErrorCode.cs | 5 + .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 7 +- .../Portable/xlf/CSharpResources.de.xlf | 7 +- .../Portable/xlf/CSharpResources.es.xlf | 7 +- .../Portable/xlf/CSharpResources.fr.xlf | 7 +- .../Portable/xlf/CSharpResources.it.xlf | 7 +- .../Portable/xlf/CSharpResources.ja.xlf | 7 +- .../Portable/xlf/CSharpResources.ko.xlf | 7 +- .../Portable/xlf/CSharpResources.pl.xlf | 7 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 7 +- .../Portable/xlf/CSharpResources.ru.xlf | 7 +- .../Portable/xlf/CSharpResources.tr.xlf | 7 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 7 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 7 +- .../Semantics/CollectionExpressionTests.cs | 28 ++ .../Test/Semantic/Semantics/DynamicTests.cs | 415 ++++++++++++++++++ .../Semantic/Semantics/InterpolationTests.cs | 224 ++++++++++ .../ObjectAndCollectionInitializerTests.cs | 28 ++ 22 files changed, 814 insertions(+), 21 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index ec89bd55c711e..ce72e6c512004 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -6326,7 +6326,7 @@ BoundExpression bindCollectionInitializerElementAddMethod( if (implicitReceiver.Type.IsDynamic()) { - var hasErrors = ReportBadDynamicArguments(elementInitializer, boundElementInitializerExpressions, refKinds: default, diagnostics, queryClause: null); + var hasErrors = ReportBadDynamicArguments(elementInitializer, implicitReceiver, boundElementInitializerExpressions, refKinds: default, diagnostics, queryClause: null); return new BoundDynamicCollectionElementInitializer( elementInitializer, @@ -6605,7 +6605,7 @@ protected BoundExpression BindClassCreationExpression( var argArray = BuildArgumentsForDynamicInvocation(analyzedArguments, diagnostics); var refKindsArray = analyzedArguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(node, argArray, refKindsArray, diagnostics, queryClause: null); + hasErrors &= ReportBadDynamicArguments(node, receiver: null, argArray, refKindsArray, diagnostics, queryClause: null); BoundObjectInitializerExpressionBase boundInitializerOpt; boundInitializerOpt = MakeBoundInitializerOpt(typeNode, type, initializerSyntaxOpt, initializerTypeOpt, diagnostics); @@ -9659,7 +9659,7 @@ private BoundExpression BindDynamicIndexer( var argArray = BuildArgumentsForDynamicInvocation(arguments, diagnostics); var refKindsArray = arguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(syntax, argArray, refKindsArray, diagnostics, queryClause: null); + hasErrors &= ReportBadDynamicArguments(syntax, receiver, argArray, refKindsArray, diagnostics, queryClause: null); return new BoundDynamicIndexerAccess( syntax, diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 3b7c36fb9997c..0f76849877b03 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -380,6 +380,7 @@ private BoundExpression BindInvocationExpression( return result; } +#nullable enable private BoundExpression BindDynamicInvocation( SyntaxNode node, BoundExpression expression, @@ -391,10 +392,11 @@ private BoundExpression BindDynamicInvocation( CheckNamedArgumentsForDynamicInvocation(arguments, diagnostics); bool hasErrors = false; + BoundExpression? receiver; if (expression.Kind == BoundKind.MethodGroup) { BoundMethodGroup methodGroup = (BoundMethodGroup)expression; - BoundExpression receiver = methodGroup.ReceiverOpt; + receiver = methodGroup.ReceiverOpt; // receiver is null if we are calling a static method declared on an outer class via its simple name: if (receiver != null) @@ -414,6 +416,7 @@ private BoundExpression BindDynamicInvocation( // the runtime binder would ignore the receiver, but in a ctor initializer we can't read "this" before // the base constructor is called. We need to handle this as a type qualified static method call. // Also applicable to things like field initializers, which run before the ctor initializer. + Debug.Assert(ContainingType is not null); expression = methodGroup.Update( methodGroup.TypeArgumentsOpt, methodGroup.Name, @@ -456,12 +459,21 @@ private BoundExpression BindDynamicInvocation( else { expression = BindToNaturalType(expression, diagnostics); + + if (expression is BoundDynamicMemberAccess memberAccess) + { + receiver = memberAccess.Receiver; + } + else + { + receiver = expression; + } } ImmutableArray argArray = BuildArgumentsForDynamicInvocation(arguments, diagnostics); var refKindsArray = arguments.RefKinds.ToImmutableOrNull(); - hasErrors &= ReportBadDynamicArguments(node, argArray, refKindsArray, diagnostics, queryClause); + hasErrors &= ReportBadDynamicArguments(node, receiver, argArray, refKindsArray, diagnostics, queryClause); return new BoundDynamicInvocation( node, @@ -473,6 +485,7 @@ private BoundExpression BindDynamicInvocation( type: Compilation.DynamicType, hasErrors: hasErrors); } +#nullable disable private void CheckNamedArgumentsForDynamicInvocation(AnalyzedArguments arguments, BindingDiagnosticBag diagnostics) { @@ -519,16 +532,26 @@ private ImmutableArray BuildArgumentsForDynamicInvocation(Analy } // Returns true if there were errors. +#nullable enable private static bool ReportBadDynamicArguments( SyntaxNode node, + BoundExpression? receiver, ImmutableArray arguments, ImmutableArray refKinds, BindingDiagnosticBag diagnostics, - CSharpSyntaxNode queryClause) + CSharpSyntaxNode? queryClause) { bool hasErrors = false; bool reportedBadQuery = false; + if (receiver != null && !IsLegalDynamicOperand(receiver)) + { + // Cannot perform a dynamic invocation on an expression with type '{0}'. + Debug.Assert(receiver.Type is not null); + Error(diagnostics, ErrorCode.ERR_CannotDynamicInvokeOnExpression, receiver.Syntax, receiver.Type); + hasErrors = true; + } + if (!refKinds.IsDefault) { for (int argIndex = 0; argIndex < refKinds.Length; argIndex++) @@ -576,7 +599,7 @@ private static bool ReportBadDynamicArguments( { // Lambdas,anonymous methods and method groups are the typeless expressions that // are not usable as dynamic arguments; if we get here then the expression must have a type. - Debug.Assert((object)arg.Type != null); + Debug.Assert((object?)arg.Type != null); // error CS1978: Cannot use an expression of type 'int*' as an argument to a dynamically dispatched operation Error(diagnostics, ErrorCode.ERR_BadDynamicMethodArg, arg.Syntax, arg.Type); @@ -586,6 +609,7 @@ private static bool ReportBadDynamicArguments( } return hasErrors; } +#nullable disable private BoundExpression BindDelegateInvocation( SyntaxNode node, diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 20f0ea1de1770..35d9967120efb 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7905,4 +7905,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Modifiers cannot be placed on using declarations - \ No newline at end of file + + Cannot perform a dynamic invocation on an expression with type '{0}'. + + diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 70c3ed6d8b861..0b141c96fe9f6 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2301,10 +2301,15 @@ internal enum ErrorCode ERR_ParamsCollectionMissingConstructor = 9228, ERR_NoModifiersOnUsing = 9229, + ERR_CannotDynamicInvokeOnExpression = 9230, #endregion + // Note: you will need to do the following after adding errors: + // 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) + // Note: you will need to do the following after adding warnings: // 1) Re-generate compiler code (eng\generate-compiler-code.cmd). + // 2) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) } } diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index b4eaef25f8e7a..2342c333dfb1f 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2431,6 +2431,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code) case ErrorCode.ERR_ParamsCollectionExtensionAddMethod: case ErrorCode.ERR_ParamsCollectionMissingConstructor: case ErrorCode.ERR_NoModifiersOnUsing: + case ErrorCode.ERR_CannotDynamicInvokeOnExpression: return false; default: // NOTE: All error codes must be explicitly handled in this switch statement diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index d9f006a79d194..94e72ea2f90a1 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -327,6 +327,11 @@ Skupina &metody {0} se nedá převést na typ delegáta {1}. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Nedal se odvodit typ delegáta. @@ -13006,4 +13011,4 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 26be42bb19ae9..002d1aceb7570 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -327,6 +327,11 @@ Die &Methodengruppe "{0}" kann nicht in den Delegattyp "{1}" konvertiert werden. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Der Delegattyp konnte nicht abgeleitet werden. @@ -13006,4 +13011,4 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 4ed949d35836c..700e8f5912984 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -327,6 +327,11 @@ No se puede convertir el grupo de métodos '{0}' al tipo delegado '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. El tipo de delegado no se puede deducir. @@ -13006,4 +13011,4 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 329ef09965722..b5d7cacbfdb09 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -327,6 +327,11 @@ Impossible de convertir le groupe de &méthodes '{0}' en type délégué '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Impossible de déduire le type délégué. @@ -13006,4 +13011,4 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 0f9b352976f31..920818aa35c52 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -327,6 +327,11 @@ Non è possibile convertire il gruppo di &metodi '{0}' nel tipo delegato '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Non è possibile dedurre il tipo di delegato. @@ -13006,4 +13011,4 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 05cf6ca7366c7..9ccc403201939 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -327,6 +327,11 @@ メソッド グループ '{0}' をデリゲート型 '{1}' に変換することはできません。(&M) + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. デリゲート型を推論できませんでした。 @@ -13006,4 +13011,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 46f1ff7ca907e..736c1e7ed0f2c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -327,6 +327,11 @@ 메서드 그룹 '{0}'을(를) 대리자 형식 '{1}'(으)로 변환할 수 없습니다(&M). + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 대리자 형식을 유추할 수 없습니다. @@ -13006,4 +13011,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 12b30a7e7dd39..e05459fae3648 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -327,6 +327,11 @@ Nie można przekonwertować &grupy metod „{0}” na typ delegata „{1}”. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Nie można wywnioskować typu delegowania. @@ -13006,4 +13011,4 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7aefebf284f37..528dfbfdf5076 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -327,6 +327,11 @@ Não é possível converter o grupo &método '{0}' para o tipo delegado '{1}'. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. O tipo de representante não pôde ser inferido. @@ -13006,4 +13011,4 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 4fc830a14821c..14c4a7c5ce8fc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -327,6 +327,11 @@ Невозможно преобразовать &группу методов "{0}" в тип делегата "{1}". + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Не удалось вывести тип делегата. @@ -13007,4 +13012,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 196c1674dd855..45e26b5b7c14f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -327,6 +327,11 @@ '{0}' &metot grubu, '{1}' temsilci türüne dönüştürülemiyor. + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. Temsilci türü çıkarsanamadı. @@ -13006,4 +13011,4 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index bc384285d9249..bd7eb252201c2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -327,6 +327,11 @@ 无法将方法组“{0}”转换为委托类型“{1}”(&M)。 + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 无法推断委托类型。 @@ -13006,4 +13011,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 2555f1061f9fb..bf6487794259d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -327,6 +327,11 @@ 無法將方法群組 '{0}' 轉換成委派類型 '{1}'(&M)。 + + Cannot perform a dynamic invocation on an expression with type '{0}'. + Cannot perform a dynamic invocation on an expression with type '{0}'. + + The delegate type could not be inferred. 無法推斷委派類型。 @@ -13006,4 +13011,4 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - \ No newline at end of file + diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs index c441c5042e758..41338fb37430b 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/Semantics/CollectionExpressionTests.cs @@ -20452,6 +20452,34 @@ static void Main() CompileAndVerify(new[] { source, s_collectionExtensions }, verify: Verification.Skipped, expectedOutput: "[0, 1], "); } + [Fact] + public void RefStruct_04() + { + var source = """ + using System.Collections; + using System.Collections.Generic; + + dynamic d = null; + S s = [d]; + + ref struct S : IEnumerable + { + public IEnumerator GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(T t) => throw null; + } + """; + + CreateCompilation(source).VerifyDiagnostics( + // (5,7): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // S s = [d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "[d]").WithArguments("S").WithLocation(5, 7), + // (7,16): error CS8343: 'S': ref structs cannot implement interfaces + // ref struct S : IEnumerable + Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IEnumerable").WithArguments("S").WithLocation(7, 16) + ); + } + [CombinatorialData] [Theory] public void RefSafety_Return_01([CombinatorialValues(TargetFramework.Net70, TargetFramework.Net80)] TargetFramework targetFramework) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs index c965d1742584e..ea675e195a4cd 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DynamicTests.cs @@ -4545,5 +4545,420 @@ class C2 : C1 CompileAndVerify(comp, expectedOutput: "int").VerifyDiagnostics(); } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void RefStructReceiver01() + { + var code = """ + var s = new S(); + dynamic d = null; + + s.M(d); + + ref struct S + { + public void M(T t) { } + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver02(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: "Hello world", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + IL_000e: ldloca.s V_0 + IL_0010: ldloc.1 + IL_0011: call "void S.M({{argType}})" + IL_0016: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("string")] + [InlineData("int")] + public void RefStructReceiver03(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + try + { + s.M(d); + } + catch + { + System.Console.WriteLine("Caught exception"); + } + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: argType == "string" ? "Hello world" : "Caught exception", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + .try + { + IL_000e: ldloca.s V_0 + IL_0010: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0015: brtrue.s IL_003b + IL_0017: ldc.i4.0 + IL_0018: ldtoken "{{argType}}" + IL_001d: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0022: ldtoken "Program" + IL_0027: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_002c: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_0031: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0036: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_003b: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0040: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_0045: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_004a: ldloc.1 + IL_004b: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_0050: call "void S.M({{argType}})" + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver04(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + public void M(string s) => System.Console.WriteLine(s); + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver05(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + s.M(d); + + ref struct S + { + public void M({{argType}} o) => System.Console.WriteLine(o); + public void M(T t) => System.Console.WriteLine(t); + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s.M(d); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 1) + ); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver06(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + _ = s[d]; + s[d] = 1; + + ref struct S + { + public int this[{{argType}} o] + { + get + { + System.Console.WriteLine(o); + return 0; + } + set => System.Console.WriteLine(o); + } + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: """ + Hello world + Hello world + """, targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("", $$""" + { + // Code size 33 (0x21) + .maxstack 3 + .locals init (S V_0, //s + object V_1) //d + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + IL_0008: ldstr "Hello world" + IL_000d: stloc.1 + IL_000e: ldloca.s V_0 + IL_0010: ldloc.1 + IL_0011: call "int S.this[{{argType}}].get" + IL_0016: pop + IL_0017: ldloca.s V_0 + IL_0019: ldloc.1 + IL_001a: ldc.i4.1 + IL_001b: call "void S.this[{{argType}}].set" + IL_0020: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("string")] + [InlineData("int")] + public void RefStructReceiver07(string argType) + { + var code = $$""" + dynamic d = "Hello world"; + + get(); + set(); + + void get() + { + var s = new S(); + try + { + _ = s[d]; + } + catch + { + System.Console.WriteLine("Caught exception"); + } + } + + void set() + { + var s = new S(); + try + { + s[d] = 1; + } + catch + { + System.Console.WriteLine("Caught exception"); + } + } + + ref struct S + { + public int this[{{argType}} o] + { + get + { + System.Console.WriteLine(o); + return 0; + } + set => System.Console.WriteLine(o); + } + } + """; + + var verifier = CompileAndVerify(code, expectedOutput: argType == "string" + ? """ + Hello world + Hello world + """ + : """ + Caught exception + Caught exception + """, targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("Program.<
$>g__get|0_0(ref Program.<>c__DisplayClass0_0)", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0) //s + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + .try + { + IL_0008: ldloca.s V_0 + IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_000f: brtrue.s IL_0035 + IL_0011: ldc.i4.0 + IL_0012: ldtoken "{{argType}}" + IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_001c: ldtoken "Program" + IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0044: ldarg.0 + IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" + IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_004f: call "int S.this[{{argType}}].get" + IL_0054: pop + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + + verifier.VerifyIL("Program.<
$>g__set|0_1(ref Program.<>c__DisplayClass0_0)", $$""" + { + // Code size 101 (0x65) + .maxstack 4 + .locals init (S V_0) //s + IL_0000: ldloca.s V_0 + IL_0002: initobj "S" + .try + { + IL_0008: ldloca.s V_0 + IL_000a: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_000f: brtrue.s IL_0035 + IL_0011: ldc.i4.0 + IL_0012: ldtoken "{{argType}}" + IL_0017: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_001c: ldtoken "Program" + IL_0021: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0026: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_002b: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0030: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_0035: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_003a: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_003f: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__1" + IL_0044: ldarg.0 + IL_0045: ldfld "dynamic Program.<>c__DisplayClass0_0.d" + IL_004a: callvirt "{{argType}} System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_004f: ldc.i4.1 + IL_0050: call "void S.this[{{argType}}].set" + IL_0055: leave.s IL_0064 + } + catch object + { + IL_0057: pop + IL_0058: ldstr "Caught exception" + IL_005d: call "void System.Console.WriteLine(string)" + IL_0062: leave.s IL_0064 + } + IL_0064: ret + } + """); + } + + [Theory, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + [InlineData("object")] + [InlineData("dynamic")] + public void RefStructReceiver08(string argType) + { + var code = $$""" + var s = new S(); + dynamic d = "Hello world"; + + _ = s[d]; + s[d] = 1; + + ref struct S + { + public int this[{{argType}} o] + { + get => 0; + set => System.Console.WriteLine(o); + } + + public int this[string s] + { + get => 0; + set => System.Console.WriteLine(s); + } + } + """; + + CreateCompilation(code).VerifyDiagnostics( + // (4,5): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // _ = s[d]; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(4, 5), + // (5,1): error CS9230: Cannot perform a dynamic invocation on an expression with type 'S'. + // s[d] = 1; + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, "s").WithArguments("S").WithLocation(5, 1) + ); + } } } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs index 44237e0c5c671..2d3ef41415b5f 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InterpolationTests.cs @@ -14376,6 +14376,230 @@ .locals init (object V_0, //d "); } + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction10( + [CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression, + [CombinatorialValues("object", "dynamic", "string")] string type) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted({{type}} item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + var verifier = CompileAndVerify([source, InterpolatedStringHandlerAttribute], expectedOutput: "literalHello world!", targetFramework: TargetFramework.StandardAndCSharp); + verifier.VerifyDiagnostics(); + + verifier.VerifyIL("", type switch + { + "string" => """ + { + // Code size 110 (0x6e) + .maxstack 4 + .locals init (object V_0, //d + CustomInterpolationHandler V_1) + IL_0000: ldstr "Hello world!" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldc.i4.7 + IL_0009: ldc.i4.1 + IL_000a: call "CustomInterpolationHandler..ctor(int, int)" + IL_000f: ldloca.s V_1 + IL_0011: ldstr "literal" + IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" + IL_001b: ldloca.s V_1 + IL_001d: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0022: brtrue.s IL_0048 + IL_0024: ldc.i4.0 + IL_0025: ldtoken "string" + IL_002a: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_002f: ldtoken "Program" + IL_0034: call "System.Type System.Type.GetTypeFromHandle(System.RuntimeTypeHandle)" + IL_0039: call "System.Runtime.CompilerServices.CallSiteBinder Microsoft.CSharp.RuntimeBinder.Binder.Convert(Microsoft.CSharp.RuntimeBinder.CSharpBinderFlags, System.Type, System.Type)" + IL_003e: call "System.Runtime.CompilerServices.CallSite> System.Runtime.CompilerServices.CallSite>.Create(System.Runtime.CompilerServices.CallSiteBinder)" + IL_0043: stsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0048: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_004d: ldfld "System.Func System.Runtime.CompilerServices.CallSite>.Target" + IL_0052: ldsfld "System.Runtime.CompilerServices.CallSite> Program.<>o__0.<>p__0" + IL_0057: ldloc.0 + IL_0058: callvirt "string System.Func.Invoke(System.Runtime.CompilerServices.CallSite, dynamic)" + IL_005d: call "void CustomInterpolationHandler.AppendFormatted(string)" + IL_0062: ldloc.1 + IL_0063: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" + IL_0068: call "void System.Console.WriteLine(string)" + IL_006d: ret + } + """, + _ => $$""" + { + // Code size 47 (0x2f) + .maxstack 3 + .locals init (object V_0, //d + CustomInterpolationHandler V_1) + IL_0000: ldstr "Hello world!" + IL_0005: stloc.0 + IL_0006: ldloca.s V_1 + IL_0008: ldc.i4.7 + IL_0009: ldc.i4.1 + IL_000a: call "CustomInterpolationHandler..ctor(int, int)" + IL_000f: ldloca.s V_1 + IL_0011: ldstr "literal" + IL_0016: call "void CustomInterpolationHandler.AppendLiteral(string)" + IL_001b: ldloca.s V_1 + IL_001d: ldloc.0 + IL_001e: call "void CustomInterpolationHandler.AppendFormatted({{type}})" + IL_0023: ldloc.1 + IL_0024: call "string Program.<
$>g__Interpolate|0_0(CustomInterpolationHandler)" + IL_0029: call "void System.Console.WriteLine(string)" + IL_002e: ret + } + """, + }); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction11([CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted(T item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics( + // (7,31): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomInterpolationHandler'. + // Console.WriteLine(Interpolate($"literal" + $"{d}")); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomInterpolationHandler").WithLocation(7, 31) + + ); + } + + [Theory, CombinatorialData, WorkItem("https://github.com/dotnet/roslyn/issues/72606")] + public void DynamicConstruction12([CombinatorialValues(@"$""literal{d}""", @"$""literal"" + $""{d}""")] string expression) + { + var source = $$""" + using System; + using System.Runtime.CompilerServices; + using System.Text; + + dynamic d = "Hello world!"; + + Console.WriteLine(Interpolate({{expression}})); + + static string Interpolate(CustomInterpolationHandler text) + { + return text.ToString(); + } + + [InterpolatedStringHandler] + public ref struct CustomInterpolationHandler + { + private StringBuilder StringBuilder; + + public CustomInterpolationHandler(int literalLength, int formattedCount) + { + StringBuilder = new StringBuilder(); + } + + public void AppendLiteral(string text) + { + StringBuilder.Append(text); + } + + public void AppendFormatted(object item) + { + StringBuilder.Append(item); + } + + public void AppendFormatted(string item) + { + StringBuilder.Append(item); + } + + public override string ToString() + { + return StringBuilder.ToString(); + } + } + """; + + CreateCompilation(source, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics( + // (7,31): error CS9230: Cannot perform a dynamic invocation on an expression with type 'CustomInterpolationHandler'. + // Console.WriteLine(Interpolate($"literal" + $"{d}")); + Diagnostic(ErrorCode.ERR_CannotDynamicInvokeOnExpression, expression).WithArguments("CustomInterpolationHandler").WithLocation(7, 31) + ); + } + [Theory] [InlineData(@"$""{s}""")] [InlineData(@"$""{s}"" + $""""")] diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs index d74969e7e8ad7..22c0f35991cb8 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/ObjectAndCollectionInitializerTests.cs @@ -4158,5 +4158,33 @@ Element Values(2): Assert.Equal(SpecialType.System_Int32, typeInfo.Type.SpecialType); Assert.Equal("I", typeInfo.ConvertedType.ToDisplayString()); } + + [Fact] + public void DynamicInvocationOnRefStructs() + { + var source = """ + using System.Collections; + using System.Collections.Generic; + + dynamic d = null; + S s = new S() { d }; + + ref struct S : IEnumerable + { + public IEnumerator GetEnumerator() => throw null; + IEnumerator IEnumerable.GetEnumerator() => throw null; + public void Add(T t) => throw null; + } + """; + + CreateCompilation(source).VerifyDiagnostics( + // (5,15): error CS1922: Cannot initialize type 'S' with a collection initializer because it does not implement 'System.Collections.IEnumerable' + // S s = new S() { d }; + Diagnostic(ErrorCode.ERR_CollectionInitRequiresIEnumerable, "{ d }").WithArguments("S").WithLocation(5, 15), + // (7,16): error CS8343: 'S': ref structs cannot implement interfaces + // ref struct S : IEnumerable + Diagnostic(ErrorCode.ERR_RefStructInterfaceImpl, "IEnumerable").WithArguments("S").WithLocation(7, 16) + ); + } } }