diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs index e61191861219c..5b8d730023cd8 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Deconstruct.cs @@ -42,10 +42,8 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia case SyntaxKind.ExpressionStatement: if (expression != null) { - // We only allow assignment-only or declaration-only deconstructions at this point. - // Issue https://github.com/dotnet/roslyn/issues/15050 tracks allowing mixed deconstructions. - // For now we give an error when you mix. - Error(diagnostics, ErrorCode.ERR_MixedDeconstructionUnsupported, left); + MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction + .CheckFeatureAvailability(diagnostics, Compilation, node.Location); } break; case SyntaxKind.ForStatement: @@ -53,7 +51,8 @@ internal BoundExpression BindDeconstruction(AssignmentExpressionSyntax node, Dia { if (expression != null) { - Error(diagnostics, ErrorCode.ERR_MixedDeconstructionUnsupported, left); + MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction + .CheckFeatureAvailability(diagnostics, Compilation, node.Location); } } else diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 1c0207a9f16b6..43491c9e711d8 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -5293,9 +5293,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A throw expression is not allowed in this context. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - A declaration is not allowed in this context. @@ -6566,6 +6563,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ discards + + Mixed declarations and expressions in deconstruction + variance safety for static interface members diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 37762fb1e37cf..ab36fdd084732 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1441,7 +1441,7 @@ internal enum ErrorCode ERR_NewWithTupleTypeSyntax = 8181, ERR_PredefinedValueTupleTypeMustBeStruct = 8182, ERR_DiscardTypeInferenceFailed = 8183, - ERR_MixedDeconstructionUnsupported = 8184, + // ERR_MixedDeconstructionUnsupported = 8184, ERR_DeclarationExpressionNotPermitted = 8185, ERR_MustDeclareForeachIteration = 8186, ERR_TupleElementNamesInDeconstruction = 8187, diff --git a/src/Compilers/CSharp/Portable/Errors/MessageID.cs b/src/Compilers/CSharp/Portable/Errors/MessageID.cs index 5a8a263a806e7..944017f26d64d 100644 --- a/src/Compilers/CSharp/Portable/Errors/MessageID.cs +++ b/src/Compilers/CSharp/Portable/Errors/MessageID.cs @@ -215,6 +215,7 @@ internal enum MessageID IDS_Return = MessageBase + 12790, IDS_FeatureVarianceSafetyForStaticInterfaceMembers = MessageBase + 12791, IDS_FeatureConstantInterpolatedStrings = MessageBase + 12792, + IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction = MessageBase + 12793, } // Message IDs may refer to strings that need to be localized. @@ -321,6 +322,9 @@ internal static LanguageVersion RequiredVersion(this MessageID feature) // Checks are in the LanguageParser unless otherwise noted. switch (feature) { + // C# preview features. + case MessageID.IDS_FeatureMixedDeclarationsAndExpressionsInDeconstruction: + return LanguageVersion.Preview; // C# 9.0 features. case MessageID.IDS_FeatureLambdaDiscardParameters: // semantic check case MessageID.IDS_FeatureFunctionPointers: diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 307bef20c80fb..252a58b7e34c8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1002,6 +1002,11 @@ Záznam definuje Equals, ale ne GetHashCode 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Typy a aliasy by neměly mít název record. @@ -10507,11 +10512,6 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference Výraz throw není v tomto kontextu povolený. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Při dekonstrukci nejde na levé straně kombinovat deklarace a výrazy. - - A declaration is not allowed in this context. Deklarace není v tomto kontextu povolená. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 1ed87c1064bd5..4fd387a52e945 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1002,6 +1002,11 @@ Der Datensatz definiert "Equals", aber nicht "GetHashCode". 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Typen und Aliase dürfen nicht den Namen "record" aufweisen. @@ -10507,11 +10512,6 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett Ein throw-Ausdruck ist in diesem Kontext unzulässig. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Eine Dekonstruktion kann Deklarationen und Ausdrücke auf der linken Seite nicht mischen. - - A declaration is not allowed in this context. Eine Deklaration ist in diesem Kontext nicht zulässig. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 7ada2afa60c6d..f078d4123c79d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1002,6 +1002,11 @@ El registro define "Equals", pero no "GetHashCode". 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Los tipos y los alias no deben denominarse "record". @@ -10507,11 +10512,6 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe No se permite una expresión throw en este contexto. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Una deconstrucción no puede mezclar declaraciones y expresiones en el lado izquierdo. - - A declaration is not allowed in this context. No se permite una declaración en este contexto. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index c486848035d4e..360a003df88f0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1002,6 +1002,11 @@ L'enregistrement définit 'Equals' mais pas 'GetHashCode'. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Les types et les alias ne doivent pas porter le nom 'record'. @@ -10507,11 +10512,6 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé Une expression throw n'est pas autorisée dans ce contexte. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Une déconstruction ne peut pas contenir à la fois des déclarations et des expressions à gauche. - - A declaration is not allowed in this context. Une déclaration n'est pas autorisée dans ce contexte. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 77d33f6abbfb9..af452ac989d2f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1002,6 +1002,11 @@ Il record definisce 'Equals' ma non 'GetHashCode'. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Il nome di tipi e alias non deve essere 'record'. @@ -10507,11 +10512,6 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr Un'espressione throw non è consentita in questo contesto. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Nella parte sinistra di una decostruzione non è possibile combinare dichiarazioni ed espressioni. - - A declaration is not allowed in this context. Una dichiarazione non è consentita in questo contesto. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 93f4a0a7e5919..9d28d309308b5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1002,6 +1002,11 @@ レコードでは 'Equals' が定義されていますが、'GetHashCode' は定義されていません。 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. 型およびエイリアスに 'record' という名前を指定することはできません。 @@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ このコンテキストではスロー式は許可されていません。 - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - 分解の左側で宣言と式を混用できません。 - - A declaration is not allowed in this context. 宣言はこのコンテキストでは許可されていません。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 9bb3d82e80eba..0e7c07626a9cc 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1002,6 +1002,11 @@ 레코드가 'Equals'를 정의하지만 'GetHashCode'는 정의하지 않습니다. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. 형식 및 별칭의 이름은 'record'로 지정할 수 없습니다. @@ -10506,11 +10511,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 이 컨텍스트에서는 throw 식을 사용할 수 없습니다. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - 분해는 왼쪽에 선언과 식을 혼합할 수 없습니다. - - A declaration is not allowed in this context. 이 컨텍스트에서 선언을 사용할 수 없습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e3cd3e320102f..7225c0b727253 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1002,6 +1002,11 @@ Rekord definiuje element „Equals”, lecz nie element „GetHashCode”. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Typy i aliasy nie powinny mieć nazwy „record”. @@ -10507,11 +10512,6 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w Wyrażenie throw jest niedozwolone w tym kontekście. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Dekonstrukcja nie może mieszać deklaracji i wyrażeń po lewej stronie. - - A declaration is not allowed in this context. Deklaracja jest niedozwolona w tym kontekście. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7dae52af6425b..23f31b8a765ea 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1002,6 +1002,11 @@ O registro define 'Equals', mas não 'GetHashCode'. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Os tipos e os aliases não devem ser nomeados como 'registro'. @@ -10507,11 +10512,6 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl Uma expressão throw não é permitida neste contexto. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Uma desconstrução não pode mesclar declarações e expressões à esquerda. - - A declaration is not allowed in this context. Uma declaração não é permitida neste contexto. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 9ad683d52827c..a620357f57552 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1002,6 +1002,11 @@ Запись определяет "Equals", но не "GetHashCode". 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Типы и псевдонимы не могут иметь имя "record" @@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Выражение Throw в данном контексте запрещено. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Деконструирование не может содержать объявления и выражения в левой части. - - A declaration is not allowed in this context. Объявление недопустимо в этом контексте. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index cc80f11de03d7..5e83eb4ff9d59 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1002,6 +1002,11 @@ Kayıt, 'GetHashCode' değil, 'Equals' tanımlıyor. 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. Türler ve diğer adlar 'record' olarak adlandırılmamalıdır. @@ -10507,11 +10512,6 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T Throw ifadesine bu bağlamda izin verilmez. - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - Ayrıştırma deyiminin sol tarafında, bildirim ve ifadeler birlikte kullanılamaz. - - A declaration is not allowed in this context. Bu bağlamda bildirime izin verilmez. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index ee48c85f5ac7c..bea3df06d5d63 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1002,6 +1002,11 @@ 记录定义 "Equals" 而不定义 "GetHashCode"。 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. 类型和别名不应命名为 "record"。 @@ -10512,11 +10517,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 此上下文中不允许使用 throw 表达式。 - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - 析构不能在左侧混合声明和表达式。 - - A declaration is not allowed in this context. 此上下文中不允许使用声明。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 5121b00146450..b0a3bd3255948 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1002,6 +1002,11 @@ 記錄會定義 'Equals' 而非 'GetHashCode'。 'GetHashCode' and 'Equals' are not localizable. + + Mixed declarations and expressions in deconstruction + Mixed declarations and expressions in deconstruction + + Types and aliases should not be named 'record'. 類型與別名不應命名為 'record'。 @@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ 此內容不允許 throw 運算式。 - - A deconstruction cannot mix declarations and expressions on the left-hand-side. - 解構不得混合左側的宣告與運算式。 - - A declaration is not allowed in this context. 此內容中不允許宣告。 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index ca8ded1be466f..1c5a6f1e1c391 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -1854,34 +1854,6 @@ public void Deconstruct(out int* a, out int b) ); } - [Fact] - public void MixedDeconstructionCannotBeParsed() - { - string source = @" -class C -{ - public static void Main() - { - int x; - (x, int y) = new C(); - } - - public void Deconstruct(out int a, out int b) - { - a = 1; - b = 2; - } -} -"; - - var comp = CreateCompilation(source); - comp.VerifyDiagnostics( - // (7,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (x, int y) = new C(); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(x, int y)").WithLocation(7, 9) - ); - } - [Fact] public void DeconstructionWithTupleNamesCannotBeParsed() { @@ -6167,14 +6139,11 @@ static void Main() } "; - var comp = CreateCompilation(source, options: TestOptions.DebugExe); + var comp = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics( // (6,10): error CS0103: The name '_' does not exist in the current context // (@_, var x) = (1, 2); - Diagnostic(ErrorCode.ERR_NameNotInContext, "@_").WithArguments("_").WithLocation(6, 10), - // (6,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (@_, var x) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(@_, var x)").WithLocation(6, 9) + Diagnostic(ErrorCode.ERR_NameNotInContext, "@_").WithArguments("_").WithLocation(6, 10) ); var tree = comp.SyntaxTrees.First(); @@ -6184,7 +6153,7 @@ static void Main() } [Fact] - public void UnderscoreLocalInDeconstructDeclaration() + public void EscapedUnderscoreInDeclarationCSharp9() { var source = @" @@ -6192,32 +6161,29 @@ class C { static void Main() { - int _; - (_, var x) = (1, 2); - System.Console.Write(_); + (@_, var x) = (1, 2); } } "; - var comp = CreateCompilation(source, options: TestOptions.DebugExe); + var comp = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular9); comp.VerifyDiagnostics( - // (7,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (_, var x) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(_, var x)").WithLocation(7, 9) + // (6,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (@_, var x) = (1, 2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(@_, var x) = (1, 2)", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(6, 9), + // (6,10): error CS0103: The name '_' does not exist in the current context + // (@_, var x) = (1, 2); + Diagnostic(ErrorCode.ERR_NameNotInContext, "@_", isSuppressed: false).WithArguments("_").WithLocation(6, 10) ); var tree = comp.SyntaxTrees.First(); var model = comp.GetSemanticModel(tree); - var discard = GetDiscardIdentifiers(tree).First(); - Assert.Equal("(_, var x)", discard.Parent.Parent.ToString()); - var symbol = (ILocalSymbol)model.GetSymbolInfo(discard).Symbol; - Assert.Equal("int _", symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); - Assert.Equal("System.Int32", model.GetTypeInfo(discard).Type.ToTestDisplayString()); + Assert.Empty(GetDiscardIdentifiers(tree)); } [Fact] - public void ShortDiscardInDeconstructAssignment() + public void UnderscoreLocalInDeconstructDeclaration() { var source = @" @@ -6225,20 +6191,45 @@ class C { static void Main() { - int x; - (_, _, x) = (1, 2, 3); - System.Console.Write(x); + int _; + (_, var x) = (1, 2); + System.Console.Write(_); } } "; - var comp = CreateCompilation(source, options: TestOptions.DebugExe); + var comp = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics(); - CompileAndVerify(comp, expectedOutput: "3"); + CompileAndVerify(comp, expectedOutput: "1") + .VerifyIL("C.Main", @" +{ + // Code size 13 (0xd) + .maxstack 1 + .locals init (int V_0, //_ + int V_1) //x + IL_0000: nop + IL_0001: ldc.i4.1 + IL_0002: stloc.0 + IL_0003: ldc.i4.2 + IL_0004: stloc.1 + IL_0005: ldloc.0 + IL_0006: call ""void System.Console.Write(int)"" + IL_000b: nop + IL_000c: ret +}"); + + var tree = comp.SyntaxTrees.First(); + var model = comp.GetSemanticModel(tree); + + var discard = GetDiscardIdentifiers(tree).First(); + Assert.Equal("(_, var x)", discard.Parent.Parent.ToString()); + var symbol = (ILocalSymbol)model.GetSymbolInfo(discard).Symbol; + Assert.Equal("int _", symbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat)); + Assert.Equal("System.Int32", model.GetTypeInfo(discard).Type.ToTestDisplayString()); } [Fact] - public void MixedDeconstructionIsBlocked() + public void ShortDiscardInDeconstructAssignment() { var source = @" @@ -6246,18 +6237,16 @@ class C { static void Main() { - int i; - (i, var x) = (1, 2); + int x; + (_, _, x) = (1, 2, 3); + System.Console.Write(x); } } "; var comp = CreateCompilation(source, options: TestOptions.DebugExe); - comp.VerifyDiagnostics( - // (7,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (i, var x) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(i, var x)").WithLocation(7, 9) - ); + comp.VerifyDiagnostics(); + CompileAndVerify(comp, expectedOutput: "3"); } [Fact] @@ -6725,22 +6714,30 @@ static void Main() } } "; + var compCSharp9 = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular9); + compCSharp9.VerifyDiagnostics( + // (6,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (var x, x) = (1, 2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(var x, x) = (1, 2)", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(6, 9), + // (6,17): error CS0841: Cannot use local variable 'x' before it is declared + // (var x, x) = (1, 2); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x", isSuppressed: false).WithArguments("x").WithLocation(6, 17), + // (7,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (y, var y) = (1, 2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(y, var y) = (1, 2)", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(7, 9), + // (7,10): error CS0841: Cannot use local variable 'y' before it is declared + // (y, var y) = (1, 2); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "y", isSuppressed: false).WithArguments("y").WithLocation(7, 10) + ); - var comp = CreateCompilation(source, options: TestOptions.DebugExe); - // mixing declaration and expressions isn't supported yet + var comp = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); comp.VerifyDiagnostics( // (6,17): error CS0841: Cannot use local variable 'x' before it is declared // (var x, x) = (1, 2); Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x").WithArguments("x").WithLocation(6, 17), - // (6,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (var x, x) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(var x, x)").WithLocation(6, 9), // (7,10): error CS0841: Cannot use local variable 'y' before it is declared // (y, var y) = (1, 2); - Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "y").WithArguments("y").WithLocation(7, 10), - // (7,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (y, var y) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(y, var y)").WithLocation(7, 9) + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "y").WithArguments("y").WithLocation(7, 10) ); } @@ -7119,12 +7116,32 @@ static void Main(string[] args) } }"; - var compilation = CreateCompilation(source); - compilation.VerifyDiagnostics( - // (8,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (int x1, z) = t; - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(int x1, z)").WithLocation(8, 9) - ); + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "1") + .VerifyIL("Program.Main", @" +{ + // Code size 32 (0x20) + .maxstack 3 + .locals init (System.ValueTuple V_0, //t + int V_1, //z + int V_2) //x1 + IL_0000: nop + IL_0001: ldloca.s V_0 + IL_0003: ldc.i4.1 + IL_0004: ldc.i4.2 + IL_0005: call ""System.ValueTuple..ctor(int, int)"" + IL_000a: ldloc.0 + IL_000b: dup + IL_000c: ldfld ""int System.ValueTuple.Item1"" + IL_0011: stloc.2 + IL_0012: ldfld ""int System.ValueTuple.Item2"" + IL_0017: stloc.1 + IL_0018: ldloc.2 + IL_0019: call ""void System.Console.WriteLine(int)"" + IL_001e: nop + IL_001f: ret +}"); var tree = compilation.SyntaxTrees.First(); var model = compilation.GetSemanticModel(tree); @@ -7146,16 +7163,78 @@ static void Main(string[] args) for ((int x1, z) = t; ; ) { System.Console.WriteLine(x1); + break; } } }"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + compilation.VerifyDiagnostics(); + CompileAndVerify(compilation, expectedOutput: "1") + .VerifyIL("Program.Main", @" +{ + // Code size 39 (0x27) + .maxstack 3 + .locals init (System.ValueTuple V_0, //t + int V_1, //z + int V_2) //x1 + IL_0000: nop + IL_0001: ldloca.s V_0 + IL_0003: ldc.i4.1 + IL_0004: ldc.i4.2 + IL_0005: call ""System.ValueTuple..ctor(int, int)"" + IL_000a: ldloc.0 + IL_000b: dup + IL_000c: ldfld ""int System.ValueTuple.Item1"" + IL_0011: stloc.2 + IL_0012: ldfld ""int System.ValueTuple.Item2"" + IL_0017: stloc.1 + IL_0018: br.s IL_0024 + IL_001a: nop + IL_001b: ldloc.2 + IL_001c: call ""void System.Console.WriteLine(int)"" + IL_0021: nop + IL_0022: br.s IL_0026 + IL_0024: br.s IL_001a + IL_0026: ret +}"); + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); - var compilation = CreateCompilation(source); + var x1 = GetDeconstructionVariable(tree, "x1"); + var x1Ref = GetReference(tree, "x1"); + VerifyModelForDeconstructionLocal(model, x1, x1Ref); + var symbolInfo = model.GetSymbolInfo(x1Ref); + Assert.Equal(symbolInfo.Symbol, model.GetDeclaredSymbol(x1)); + Assert.Equal(SpecialType.System_Int32, symbolInfo.Symbol.GetTypeOrReturnType().SpecialType); + + var lhs = tree.GetRoot().DescendantNodes().OfType().ElementAt(1); + Assert.Equal(@"(int x1, z)", lhs.ToString()); + Assert.Equal("(System.Int32 x1, System.Int32 z)", model.GetTypeInfo(lhs).Type.ToTestDisplayString()); + Assert.Equal("(System.Int32 x1, System.Int32 z)", model.GetTypeInfo(lhs).ConvertedType.ToTestDisplayString()); + } + + [Fact] + public void MixedDeconstruction_03CSharp9() + { + string source = @" +class Program +{ + static void Main(string[] args) + { + var t = (1, 2); + int z; + for ((int x1, z) = t; ; ) + { + System.Console.WriteLine(x1); + } + } +}"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.Regular9); compilation.VerifyDiagnostics( - // (8,14): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. + // (8,14): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. // for ((int x1, z) = t; ; ) - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(int x1, z)").WithLocation(8, 14) - ); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x1, z) = t", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(8, 14)); + var tree = compilation.SyntaxTrees.First(); var model = compilation.GetSemanticModel(tree); @@ -7332,6 +7411,90 @@ static void Main(string[] args) VerifyModelForLocal(model, x2, LocalDeclarationKind.PatternVariable, x2Ref.ToArray()); } + [Fact] + public void MixedDeconstruction_07() + { + string source = @" +class Program +{ + static void Main(string[] args) + { + var t = (1, ("""", true)); + string y; + for ((int x, (y, var z)) = t; ; ) + { + System.Console.Write(x); + System.Console.Write(z); + break; + } + } +}"; + var compilation = CreateCompilation(source, options: TestOptions.DebugExe, parseOptions: TestOptions.RegularPreview); + CompileAndVerify(compilation, expectedOutput: "1True") + .VerifyIL("Program.Main", @" +{ + // Code size 73 (0x49) + .maxstack 4 + .locals init (System.ValueTuple> V_0, //t + string V_1, //y + int V_2, //x + bool V_3, //z + System.ValueTuple V_4) + IL_0000: nop + IL_0001: ldloca.s V_0 + IL_0003: ldc.i4.1 + IL_0004: ldstr """" + IL_0009: ldc.i4.1 + IL_000a: newobj ""System.ValueTuple..ctor(string, bool)"" + IL_000f: call ""System.ValueTuple>..ctor(int, System.ValueTuple)"" + IL_0014: ldloc.0 + IL_0015: dup + IL_0016: ldfld ""System.ValueTuple System.ValueTuple>.Item2"" + IL_001b: stloc.s V_4 + IL_001d: ldfld ""int System.ValueTuple>.Item1"" + IL_0022: stloc.2 + IL_0023: ldloc.s V_4 + IL_0025: ldfld ""string System.ValueTuple.Item1"" + IL_002a: stloc.1 + IL_002b: ldloc.s V_4 + IL_002d: ldfld ""bool System.ValueTuple.Item2"" + IL_0032: stloc.3 + IL_0033: br.s IL_0046 + IL_0035: nop + IL_0036: ldloc.2 + IL_0037: call ""void System.Console.Write(int)"" + IL_003c: nop + IL_003d: ldloc.3 + IL_003e: call ""void System.Console.Write(bool)"" + IL_0043: nop + IL_0044: br.s IL_0048 + IL_0046: br.s IL_0035 + IL_0048: ret +}"); + compilation.VerifyDiagnostics(); + var tree = compilation.SyntaxTrees.First(); + var model = compilation.GetSemanticModel(tree); + + var x = GetDeconstructionVariable(tree, "x"); + var xRef = GetReference(tree, "x"); + VerifyModelForDeconstructionLocal(model, x, xRef); + var xSymbolInfo = model.GetSymbolInfo(xRef); + Assert.Equal(xSymbolInfo.Symbol, model.GetDeclaredSymbol(x)); + Assert.Equal(SpecialType.System_Int32, xSymbolInfo.Symbol.GetTypeOrReturnType().SpecialType); + + var z = GetDeconstructionVariable(tree, "z"); + var zRef = GetReference(tree, "z"); + VerifyModelForDeconstructionLocal(model, z, zRef); + var zSymbolInfo = model.GetSymbolInfo(zRef); + Assert.Equal(zSymbolInfo.Symbol, model.GetDeclaredSymbol(z)); + Assert.Equal(SpecialType.System_Boolean, zSymbolInfo.Symbol.GetTypeOrReturnType().SpecialType); + + var lhs = tree.GetRoot().DescendantNodes().OfType().ElementAt(2); + Assert.Equal(@"(int x, (y, var z))", lhs.ToString()); + Assert.Equal("(System.Int32 x, (System.String y, System.Boolean z))", model.GetTypeInfo(lhs).Type.ToTestDisplayString()); + Assert.Equal("(System.Int32 x, (System.String y, System.Boolean z))", model.GetTypeInfo(lhs).ConvertedType.ToTestDisplayString()); + } + [Fact] public void IncompleteDeclarationIsSeenAsTupleLiteral() { @@ -9229,5 +9392,689 @@ void M() Diagnostic(ErrorCode.ERR_NameNotInContext, "_").WithArguments("_").WithLocation(9, 17) ); } + + [Fact] + public void MixDeclarationAndAssignmentPermutationsOf2() + { + string source = @" +class C +{ + static void Main() + { + int x1 = 0; + (x1, string y1) = new C(); + System.Console.WriteLine(x1 + "" "" + y1); + int x2; + (x2, var y2) = new C(); + System.Console.WriteLine(x2 + "" "" + y2); + string y3 = """"; + (int x3, y3) = new C(); + System.Console.WriteLine(x3 + "" "" + y3); + string y4; + (var x4, y4) = new C(); + System.Console.WriteLine(x4 + "" "" + y4); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + + var comp = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"1 hello +1 hello +1 hello +1 hello"); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", @" +{ + // Code size 188 (0xbc) + .maxstack 3 + .locals init (int V_0, //x1 + string V_1, //y1 + int V_2, //x2 + string V_3, //y2 + string V_4, //y3 + int V_5, //x3 + string V_6, //y4 + int V_7, //x4 + int V_8, + string V_9) + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: newobj ""C..ctor()"" + IL_0007: ldloca.s V_8 + IL_0009: ldloca.s V_9 + IL_000b: callvirt ""void C.Deconstruct(out int, out string)"" + IL_0010: ldloc.s V_8 + IL_0012: stloc.0 + IL_0013: ldloc.s V_9 + IL_0015: stloc.1 + IL_0016: ldloca.s V_0 + IL_0018: call ""string int.ToString()"" + IL_001d: ldstr "" "" + IL_0022: ldloc.1 + IL_0023: call ""string string.Concat(string, string, string)"" + IL_0028: call ""void System.Console.WriteLine(string)"" + IL_002d: newobj ""C..ctor()"" + IL_0032: ldloca.s V_8 + IL_0034: ldloca.s V_9 + IL_0036: callvirt ""void C.Deconstruct(out int, out string)"" + IL_003b: ldloc.s V_8 + IL_003d: stloc.2 + IL_003e: ldloc.s V_9 + IL_0040: stloc.3 + IL_0041: ldloca.s V_2 + IL_0043: call ""string int.ToString()"" + IL_0048: ldstr "" "" + IL_004d: ldloc.3 + IL_004e: call ""string string.Concat(string, string, string)"" + IL_0053: call ""void System.Console.WriteLine(string)"" + IL_0058: ldstr """" + IL_005d: stloc.s V_4 + IL_005f: newobj ""C..ctor()"" + IL_0064: ldloca.s V_8 + IL_0066: ldloca.s V_9 + IL_0068: callvirt ""void C.Deconstruct(out int, out string)"" + IL_006d: ldloc.s V_8 + IL_006f: stloc.s V_5 + IL_0071: ldloc.s V_9 + IL_0073: stloc.s V_4 + IL_0075: ldloca.s V_5 + IL_0077: call ""string int.ToString()"" + IL_007c: ldstr "" "" + IL_0081: ldloc.s V_4 + IL_0083: call ""string string.Concat(string, string, string)"" + IL_0088: call ""void System.Console.WriteLine(string)"" + IL_008d: newobj ""C..ctor()"" + IL_0092: ldloca.s V_8 + IL_0094: ldloca.s V_9 + IL_0096: callvirt ""void C.Deconstruct(out int, out string)"" + IL_009b: ldloc.s V_8 + IL_009d: stloc.s V_7 + IL_009f: ldloc.s V_9 + IL_00a1: stloc.s V_6 + IL_00a3: ldloca.s V_7 + IL_00a5: call ""string int.ToString()"" + IL_00aa: ldstr "" "" + IL_00af: ldloc.s V_6 + IL_00b1: call ""string string.Concat(string, string, string)"" + IL_00b6: call ""void System.Console.WriteLine(string)"" + IL_00bb: ret +}"); + } + + [Fact] + public void MixDeclarationAndAssignmentPermutationsOf3() + { + string source = @" +class C +{ + static void Main() + { + int x1; + string y1; + (x1, y1, var z1) = new C(); + System.Console.WriteLine(x1 + "" "" + y1 + "" "" + z1); + + int x2; + bool z2; + (x2, var y2, z2) = new C(); + System.Console.WriteLine(x2 + "" "" + y2 + "" "" + z2); + + string y3; + bool z3; + (var x3, y3, z3) = new C(); + System.Console.WriteLine(x3 + "" "" + y3 + "" "" + z3); + + bool z4; + (var x4, var y4, z4) = new C(); + System.Console.WriteLine(x4 + "" "" + y4 + "" "" + z4); + + string y5; + (var x5, y5, var z5) = new C(); + System.Console.WriteLine(x5 + "" "" + y5 + "" "" + z5); + + int x6; + (x6, var y6, var z6) = new C(); + System.Console.WriteLine(x6 + "" "" + y6 + "" "" + z6); + } + + public void Deconstruct(out int a, out string b, out bool c) + { + a = 1; + b = ""hello""; + c = true; + } +} +"; + + var comp = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"1 hello True +1 hello True +1 hello True +1 hello True +1 hello True +1 hello True"); + comp.VerifyDiagnostics(); + } + + [Fact] + public void DontAllowMixedDeclarationAndAssignmentInExpressionContext() + { + string source = @" +class C +{ + static void Main() + { + int x1 = 0; + var z1 = (x1, string y1) = new C(); + string y2 = """"; + var z2 = (int x2, y2) = new C(); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (7,23): error CS8185: A declaration is not allowed in this context. + // var z1 = (x1, string y1) = new C(); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "string y1").WithLocation(7, 23), + // (9,19): error CS8185: A declaration is not allowed in this context. + // var z2 = (int x2, y2) = new C(); + Diagnostic(ErrorCode.ERR_DeclarationExpressionNotPermitted, "int x2").WithLocation(9, 19)); + } + + [Fact] + public void DontAllowMixedDeclarationAndAssignmentInForeachDeclarationVariable() + { + string source = @" +class C +{ + static void Main() + { + int x1; + foreach((x1, string y1) in new C[0]); + string y2; + foreach((int x2, y2) in new C[0]); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + CreateCompilation(source).VerifyDiagnostics( + // (6,13): warning CS0168: The variable 'x1' is declared but never used + // int x1; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "x1").WithArguments("x1").WithLocation(6, 13), + // (7,17): error CS8186: A foreach loop must declare its iteration variables. + // foreach((x1, string y1) in new C[0]); + Diagnostic(ErrorCode.ERR_MustDeclareForeachIteration, "(x1, string y1)").WithLocation(7, 17), + // (8,16): warning CS0168: The variable 'y2' is declared but never used + // string y2; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "y2").WithArguments("y2").WithLocation(8, 16), + // (9,17): error CS8186: A foreach loop must declare its iteration variables. + // foreach((int x2, y2) in new C[0]); + Diagnostic(ErrorCode.ERR_MustDeclareForeachIteration, "(int x2, y2)").WithLocation(9, 17)); + } + + [Fact] + public void DuplicateDeclarationOfVariableDeclaredInMixedDeclarationAndAssignment() + { + string source = @" +class C +{ + static void Main() + { + int x1; + string y1; + (x1, string y1) = new C(); + string y2; + (int x2, y2) = new C(); + int x2; + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (7,16): warning CS0168: The variable 'y1' is declared but never used + // string y1; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "y1").WithArguments("y1").WithLocation(7, 16), + // (8,21): error CS0128: A local variable or function named 'y1' is already defined in this scope + // (x1, string y1) = new C(); + Diagnostic(ErrorCode.ERR_LocalDuplicate, "y1").WithArguments("y1").WithLocation(8, 21), + // (11,13): error CS0128: A local variable or function named 'x2' is already defined in this scope + // int x2; + Diagnostic(ErrorCode.ERR_LocalDuplicate, "x2").WithArguments("x2").WithLocation(11, 13), + // (11,13): warning CS0168: The variable 'x2' is declared but never used + // int x2; + Diagnostic(ErrorCode.WRN_UnreferencedVar, "x2").WithArguments("x2").WithLocation(11, 13)); + } + + [Fact] + public void AssignmentToUndeclaredVariableInMixedDeclarationAndAssignment() + { + string source = @" +class C +{ + static void Main() + { + (x1, string y1) = new C(); + (int x2, y2) = new C(); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics( + // (6,10): error CS0103: The name 'x1' does not exist in the current context + // (x1, string y1) = new C(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "x1").WithArguments("x1").WithLocation(6, 10), + // (7,18): error CS0103: The name 'y2' does not exist in the current context + // (int x2, y2) = new C(); + Diagnostic(ErrorCode.ERR_NameNotInContext, "y2").WithArguments("y2").WithLocation(7, 18)); + } + + [Fact] + public void MixedDeclarationAndAssignmentInForInitialization() + { + string source = @" +class C +{ + static void Main() + { + int x1; + for((x1, string y1) = new C(); x1 < 2; x1++) + System.Console.WriteLine(x1 + "" "" + y1); + string y2; + for((int x2, y2) = new C(); x2 < 2; x2++) + System.Console.WriteLine(x2 + "" "" + y2); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + var comp = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"1 hello +1 hello"); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", @" +{ + // Code size 109 (0x6d) + .maxstack 3 + .locals init (int V_0, //x1 + string V_1, //y2 + string V_2, //y1 + int V_3, + string V_4, + int V_5) //x2 + IL_0000: newobj ""C..ctor()"" + IL_0005: ldloca.s V_3 + IL_0007: ldloca.s V_4 + IL_0009: callvirt ""void C.Deconstruct(out int, out string)"" + IL_000e: ldloc.3 + IL_000f: stloc.0 + IL_0010: ldloc.s V_4 + IL_0012: stloc.2 + IL_0013: br.s IL_0030 + IL_0015: ldloca.s V_0 + IL_0017: call ""string int.ToString()"" + IL_001c: ldstr "" "" + IL_0021: ldloc.2 + IL_0022: call ""string string.Concat(string, string, string)"" + IL_0027: call ""void System.Console.WriteLine(string)"" + IL_002c: ldloc.0 + IL_002d: ldc.i4.1 + IL_002e: add + IL_002f: stloc.0 + IL_0030: ldloc.0 + IL_0031: ldc.i4.2 + IL_0032: blt.s IL_0015 + IL_0034: newobj ""C..ctor()"" + IL_0039: ldloca.s V_3 + IL_003b: ldloca.s V_4 + IL_003d: callvirt ""void C.Deconstruct(out int, out string)"" + IL_0042: ldloc.3 + IL_0043: stloc.s V_5 + IL_0045: ldloc.s V_4 + IL_0047: stloc.1 + IL_0048: br.s IL_0067 + IL_004a: ldloca.s V_5 + IL_004c: call ""string int.ToString()"" + IL_0051: ldstr "" "" + IL_0056: ldloc.1 + IL_0057: call ""string string.Concat(string, string, string)"" + IL_005c: call ""void System.Console.WriteLine(string)"" + IL_0061: ldloc.s V_5 + IL_0063: ldc.i4.1 + IL_0064: add + IL_0065: stloc.s V_5 + IL_0067: ldloc.s V_5 + IL_0069: ldc.i4.2 + IL_006a: blt.s IL_004a + IL_006c: ret +}"); + } + + [Fact] + public void MixDeclarationAndAssignmentInTupleDeconstructPermutationsOf2() + { + string source = @" +class C +{ + static void Main() + { + int x1 = 0; + (x1, string y1) = (1, ""hello""); + System.Console.WriteLine(x1 + "" "" + y1); + int x2; + (x2, var y2) = (1, ""hello""); + System.Console.WriteLine(x2 + "" "" + y2); + string y3 = """"; + (int x3, y3) = (1, ""hello""); + System.Console.WriteLine(x3 + "" "" + y3); + string y4; + (var x4, y4) = (1, ""hello""); + System.Console.WriteLine(x4 + "" "" + y4); + } +} +"; + + var comp = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"1 hello +1 hello +1 hello +1 hello"); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", @" +{ + // Code size 140 (0x8c) + .maxstack 3 + .locals init (int V_0, //x1 + string V_1, //y1 + int V_2, //x2 + string V_3, //y2 + string V_4, //y3 + int V_5, //x3 + string V_6, //y4 + int V_7) //x4 + IL_0000: ldc.i4.0 + IL_0001: stloc.0 + IL_0002: ldc.i4.1 + IL_0003: stloc.0 + IL_0004: ldstr ""hello"" + IL_0009: stloc.1 + IL_000a: ldloca.s V_0 + IL_000c: call ""string int.ToString()"" + IL_0011: ldstr "" "" + IL_0016: ldloc.1 + IL_0017: call ""string string.Concat(string, string, string)"" + IL_001c: call ""void System.Console.WriteLine(string)"" + IL_0021: ldc.i4.1 + IL_0022: stloc.2 + IL_0023: ldstr ""hello"" + IL_0028: stloc.3 + IL_0029: ldloca.s V_2 + IL_002b: call ""string int.ToString()"" + IL_0030: ldstr "" "" + IL_0035: ldloc.3 + IL_0036: call ""string string.Concat(string, string, string)"" + IL_003b: call ""void System.Console.WriteLine(string)"" + IL_0040: ldstr """" + IL_0045: stloc.s V_4 + IL_0047: ldc.i4.1 + IL_0048: stloc.s V_5 + IL_004a: ldstr ""hello"" + IL_004f: stloc.s V_4 + IL_0051: ldloca.s V_5 + IL_0053: call ""string int.ToString()"" + IL_0058: ldstr "" "" + IL_005d: ldloc.s V_4 + IL_005f: call ""string string.Concat(string, string, string)"" + IL_0064: call ""void System.Console.WriteLine(string)"" + IL_0069: ldc.i4.1 + IL_006a: stloc.s V_7 + IL_006c: ldstr ""hello"" + IL_0071: stloc.s V_6 + IL_0073: ldloca.s V_7 + IL_0075: call ""string int.ToString()"" + IL_007a: ldstr "" "" + IL_007f: ldloc.s V_6 + IL_0081: call ""string string.Concat(string, string, string)"" + IL_0086: call ""void System.Console.WriteLine(string)"" + IL_008b: ret +}"); + } + + [Fact] + public void MixedDeclarationAndAssignmentCSharpNine() + { + string source = @" +class Program +{ + static void Main() + { + int x1; + (x1, string y1) = new A(); + string y2; + (int x2, y2) = new A(); + bool z3; + (int x3, (string y3, z3)) = new B(); + int x4; + (x4, var (y4, z4)) = new B(); + } +} + +class A +{ + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} + +class B +{ + public void Deconstruct(out int a, out (string b, bool c) tuple) + { + a = 1; + tuple = (""hello"", true); + } +} +"; + CreateCompilation(source, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (7,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (x1, string y1) = new A(); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(x1, string y1) = new A()", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(7, 9), + // (9,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (int x2, y2) = new A(); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x2, y2) = new A()", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(9, 9), + // (11,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (int x3, (string y3, z3)) = new B(); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x3, (string y3, z3)) = new B()", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(11, 9), + // (13,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (x4, var (y4, z4)) = new B(); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(x4, var (y4, z4)) = new B()", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(13, 9)); + } + + [Fact] + public void NestedMixedDeclarationAndAssignmentPermutations() + { + string source = @" +class C +{ + static void Main() + { + int x1; + string y1; + (x1, (y1, var z1)) = new C(); + System.Console.WriteLine(x1 + "" "" + y1 + "" "" + z1); + + int x2; + bool z2; + (x2, (var y2, z2)) = new C(); + System.Console.WriteLine(x2 + "" "" + y2 + "" "" + z2); + + string y3; + bool z3; + (var x3, (y3, z3)) = new C(); + System.Console.WriteLine(x3 + "" "" + y3 + "" "" + z3); + + bool z4; + (var x4, (var y4, z4)) = new C(); + System.Console.WriteLine(x4 + "" "" + y4 + "" "" + z4); + + string y5; + (var x5, (y5, var z5)) = new C(); + System.Console.WriteLine(x5 + "" "" + y5 + "" "" + z5); + + int x6; + (x6, (var y6, var z6)) = new C(); + System.Console.WriteLine(x6 + "" "" + y6 + "" "" + z6); + + int x7; + (x7, var (y7, z7)) = new C(); + System.Console.WriteLine(x7 + "" "" + y7 + "" "" + z7); + } + + public void Deconstruct(out int a, out (string a, bool b) b) + { + a = 1; + b = (""hello"", true); + } +} +"; + + var comp = CompileAndVerify(source, parseOptions: TestOptions.RegularPreview, expectedOutput: @"1 hello True +1 hello True +1 hello True +1 hello True +1 hello True +1 hello True +1 hello True"); + comp.VerifyDiagnostics(); + } + + [Fact] + public void MixedDeclarationAndAssignmentUseBeforeDeclaration() + { + string source = @" +class Program +{ + static void Main() + { + (x1, string y1) = new A(); + int x1; + (int x2, y2) = new A(); + string y2; + (int x3, (string y3, z3)) = new B(); + bool z3; + (x4, var (y4, z4)) = new B(); + int x4; + } +} + +class A +{ + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} + +class B +{ + public void Deconstruct(out int a, out (string b, bool c) tuple) + { + a = 1; + tuple = (""hello"", true); + } +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview) + .VerifyDiagnostics( + // (6,10): error CS0841: Cannot use local variable 'x1' before it is declared + // (x1, string y1) = new A(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x1", isSuppressed: false).WithArguments("x1").WithLocation(6, 10), + // (8,18): error CS0841: Cannot use local variable 'y2' before it is declared + // (int x2, y2) = new A(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "y2", isSuppressed: false).WithArguments("y2").WithLocation(8, 18), + // (10,30): error CS0841: Cannot use local variable 'z3' before it is declared + // (int x3, (string y3, z3)) = new B(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "z3", isSuppressed: false).WithArguments("z3").WithLocation(10, 30), + // (12,10): error CS0841: Cannot use local variable 'x4' before it is declared + // (x4, var (y4, z4)) = new B(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x4", isSuppressed: false).WithArguments("x4").WithLocation(12, 10)); + } + + [Fact] + public void MixedDeclarationAndAssignmentUseDeclaredVariableInAssignment() + { + string source = @" +class Program +{ + static void Main() + { + (var x1, x1) = new A(); + (x2, var x2) = new A(); + (var x3, (var y3, x3)) = new B(); + (x4, (var y4, var x4)) = new B(); + } +} + +class A +{ + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} + +class B +{ + public void Deconstruct(out int a, out (string b, bool c) tuple) + { + a = 1; + tuple = (""hello"", true); + } +} +"; + CreateCompilation(source, parseOptions: TestOptions.RegularPreview) + .VerifyDiagnostics( + // (6,18): error CS0841: Cannot use local variable 'x1' before it is declared + // (var x1, x1) = new A(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x1").WithArguments("x1").WithLocation(6, 18), + // (7,10): error CS0841: Cannot use local variable 'x2' before it is declared + // (x2, var x2) = new A(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x2").WithArguments("x2").WithLocation(7, 10), + // (8,27): error CS0841: Cannot use local variable 'x3' before it is declared + // (var x3, (var y3, x3)) = new B(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x3").WithArguments("x3").WithLocation(8, 27), + // (9,10): error CS0841: Cannot use local variable 'x4' before it is declared + // (x4, (var y4, var x4)) = new B(); + Diagnostic(ErrorCode.ERR_VariableUsedBeforeDeclaration, "x4").WithArguments("x4").WithLocation(9, 10)); + } } } diff --git a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDeconstructionAssignmentOperation.cs b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDeconstructionAssignmentOperation.cs index d9f1b109a5035..9075638f9a9b9 100644 --- a/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDeconstructionAssignmentOperation.cs +++ b/src/Compilers/CSharp/Test/IOperation/IOperation/IOperationTests_IDeconstructionAssignmentOperation.cs @@ -5,6 +5,7 @@ #nullable disable using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; @@ -363,7 +364,7 @@ public void Deconstruct(out int i1, out int i2) [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] [Fact] - public void DeconstructionFlow_05() + public void MixedDeconstruction() { string source = @" class C @@ -373,26 +374,54 @@ void M(bool b) int i2; (int i1, i2) = b ? (1, 2) : (3, 4); }/**/ +}"; + var expectedDiagnostics = DiagnosticDescription.None; - public void M2(out (int, int) i) - { - i = (0, 0); - } -} - -"; - var expectedDiagnostics = new DiagnosticDescription[] { - // CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (int i2, i1) = b ? (1, 2) : (3, 4); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(int i1, i2)").WithLocation(7, 9) - }; + string expectedOperationTree = @" +IBlockOperation (2 statements, 2 locals) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + Locals: Local_1: System.Int32 i2 + Local_2: System.Int32 i1 + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'int i2;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'int i2') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Int32 i2) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'i2') + Initializer: + null + Initializer: + null + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '(int i1, i2 ... ) : (3, 4);') + Expression: + IDeconstructionAssignmentOperation (OperationKind.DeconstructionAssignment, Type: (System.Int32 i1, System.Int32 i2)) (Syntax: '(int i1, i2 ... 2) : (3, 4)') + Left: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i1, System.Int32 i2)) (Syntax: '(int i1, i2)') + NaturalType: (System.Int32 i1, System.Int32 i2) + Elements(2): + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i1') + ILocalReferenceOperation: i1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') + ILocalReferenceOperation: i2 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i2') + Right: + IConditionalOperation (OperationKind.Conditional, Type: (System.Int32, System.Int32)) (Syntax: 'b ? (1, 2) : (3, 4)') + Condition: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + WhenTrue: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(1, 2)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + WhenFalse: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(3, 4)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3) (Syntax: '3') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 4) (Syntax: '4')"; + VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); string expectedFlowGraph = @" Block[B0] - Entry Statements (0) Next (Regular) Block[B1] Entering: {R1} - .locals {R1} { Locals: [System.Int32 i2] [System.Int32 i1] @@ -400,13 +429,11 @@ public void M2(out (int, int) i) Block[B1] - Block Predecessors: [B0] Statements (1) - IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsInvalid, IsImplicit) (Syntax: 'i2') + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i2') Value: - ILocalReferenceOperation: i2 (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'i2') - + ILocalReferenceOperation: i2 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i2') Jump if False (Regular) to Block[B3] IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') - Next (Regular) Block[B2] Block[B2] - Block Predecessors: [B1] @@ -418,7 +445,6 @@ public void M2(out (int, int) i) Elements(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') - Next (Regular) Block[B4] Block[B3] - Block Predecessors: [B1] @@ -430,33 +456,176 @@ public void M2(out (int, int) i) Elements(2): ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3) (Syntax: '3') ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 4) (Syntax: '4') - Next (Regular) Block[B4] Block[B4] - Block Predecessors: [B2] [B3] Statements (1) - IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null, IsInvalid) (Syntax: '(int i1, i2 ... ) : (3, 4);') + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '(int i1, i2 ... ) : (3, 4);') Expression: - IDeconstructionAssignmentOperation (OperationKind.DeconstructionAssignment, Type: (System.Int32 i1, System.Int32 i2), IsInvalid) (Syntax: '(int i1, i2 ... 2) : (3, 4)') + IDeconstructionAssignmentOperation (OperationKind.DeconstructionAssignment, Type: (System.Int32 i1, System.Int32 i2)) (Syntax: '(int i1, i2 ... 2) : (3, 4)') Left: - ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i1, System.Int32 i2), IsInvalid) (Syntax: '(int i1, i2)') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i1, System.Int32 i2)) (Syntax: '(int i1, i2)') NaturalType: (System.Int32 i1, System.Int32 i2) Elements(2): - IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32, IsInvalid) (Syntax: 'int i1') - ILocalReferenceOperation: i1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32, IsInvalid) (Syntax: 'i1') - IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsInvalid, IsImplicit) (Syntax: 'i2') + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i1') + ILocalReferenceOperation: i1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i2') Right: IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: (System.Int32, System.Int32), IsImplicit) (Syntax: 'b ? (1, 2) : (3, 4)') - Next (Regular) Block[B5] Leaving: {R1} } - Block[B5] - Exit Predecessors: [B4] Statements (0) "; - VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics); + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + } + + [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] + [Fact] + public void MixedNestedDeconstruction() + { + string source = @" +class C +{ + void M(bool b) + /**/{ + int i2; + (int i1, (i2, int i3)) = b ? (1, (2, 3)) : (4, (5, 6)); + }/**/ +}"; + var expectedDiagnostics = DiagnosticDescription.None; + + string expectedOperationTree = @" +IBlockOperation (2 statements, 3 locals) (OperationKind.Block, Type: null) (Syntax: '{ ... }') + Locals: Local_1: System.Int32 i2 + Local_2: System.Int32 i1 + Local_3: System.Int32 i3 + IVariableDeclarationGroupOperation (1 declarations) (OperationKind.VariableDeclarationGroup, Type: null) (Syntax: 'int i2;') + IVariableDeclarationOperation (1 declarators) (OperationKind.VariableDeclaration, Type: null) (Syntax: 'int i2') + Declarators: + IVariableDeclaratorOperation (Symbol: System.Int32 i2) (OperationKind.VariableDeclarator, Type: null) (Syntax: 'i2') + Initializer: + null + Initializer: + null + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '(int i1, (i ... 4, (5, 6));') + Expression: + IDeconstructionAssignmentOperation (OperationKind.DeconstructionAssignment, Type: (System.Int32 i1, (System.Int32 i2, System.Int32 i3))) (Syntax: '(int i1, (i ... (4, (5, 6))') + Left: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i1, (System.Int32 i2, System.Int32 i3))) (Syntax: '(int i1, (i2, int i3))') + NaturalType: (System.Int32 i1, (System.Int32 i2, System.Int32 i3)) + Elements(2): + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i1') + ILocalReferenceOperation: i1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i2, System.Int32 i3)) (Syntax: '(i2, int i3)') + NaturalType: (System.Int32 i2, System.Int32 i3) + Elements(2): + ILocalReferenceOperation: i2 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i2') + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i3') + ILocalReferenceOperation: i3 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i3') + Right: + IConditionalOperation (OperationKind.Conditional, Type: (System.Int32, (System.Int32, System.Int32))) (Syntax: 'b ? (1, (2, ... (4, (5, 6))') + Condition: + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + WhenTrue: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, (System.Int32, System.Int32))) (Syntax: '(1, (2, 3))') + NaturalType: (System.Int32, (System.Int32, System.Int32)) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(2, 3)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3) (Syntax: '3') + WhenFalse: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, (System.Int32, System.Int32))) (Syntax: '(4, (5, 6))') + NaturalType: (System.Int32, (System.Int32, System.Int32)) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 4) (Syntax: '4') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(5, 6)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 6) (Syntax: '6')"; + VerifyOperationTreeAndDiagnosticsForTest(source, expectedOperationTree, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); + + string expectedFlowGraph = @" +Block[B0] - Entry + Statements (0) + Next (Regular) Block[B1] + Entering: {R1} +.locals {R1} +{ + Locals: [System.Int32 i2] [System.Int32 i1] [System.Int32 i3] + CaptureIds: [0] [1] + Block[B1] - Block + Predecessors: [B0] + Statements (1) + IFlowCaptureOperation: 0 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: 'i2') + Value: + ILocalReferenceOperation: i2 (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i2') + Jump if False (Regular) to Block[B3] + IParameterReferenceOperation: b (OperationKind.ParameterReference, Type: System.Boolean) (Syntax: 'b') + Next (Regular) Block[B2] + Block[B2] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '(1, (2, 3))') + Value: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, (System.Int32, System.Int32))) (Syntax: '(1, (2, 3))') + NaturalType: (System.Int32, (System.Int32, System.Int32)) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 1) (Syntax: '1') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(2, 3)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 2) (Syntax: '2') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 3) (Syntax: '3') + Next (Regular) Block[B4] + Block[B3] - Block + Predecessors: [B1] + Statements (1) + IFlowCaptureOperation: 1 (OperationKind.FlowCapture, Type: null, IsImplicit) (Syntax: '(4, (5, 6))') + Value: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, (System.Int32, System.Int32))) (Syntax: '(4, (5, 6))') + NaturalType: (System.Int32, (System.Int32, System.Int32)) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 4) (Syntax: '4') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32, System.Int32)) (Syntax: '(5, 6)') + NaturalType: (System.Int32, System.Int32) + Elements(2): + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 5) (Syntax: '5') + ILiteralOperation (OperationKind.Literal, Type: System.Int32, Constant: 6) (Syntax: '6') + Next (Regular) Block[B4] + Block[B4] - Block + Predecessors: [B2] [B3] + Statements (1) + IExpressionStatementOperation (OperationKind.ExpressionStatement, Type: null) (Syntax: '(int i1, (i ... 4, (5, 6));') + Expression: + IDeconstructionAssignmentOperation (OperationKind.DeconstructionAssignment, Type: (System.Int32 i1, (System.Int32 i2, System.Int32 i3))) (Syntax: '(int i1, (i ... (4, (5, 6))') + Left: + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i1, (System.Int32 i2, System.Int32 i3))) (Syntax: '(int i1, (i2, int i3))') + NaturalType: (System.Int32 i1, (System.Int32 i2, System.Int32 i3)) + Elements(2): + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i1') + ILocalReferenceOperation: i1 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i1') + ITupleOperation (OperationKind.Tuple, Type: (System.Int32 i2, System.Int32 i3)) (Syntax: '(i2, int i3)') + NaturalType: (System.Int32 i2, System.Int32 i3) + Elements(2): + IFlowCaptureReferenceOperation: 0 (OperationKind.FlowCaptureReference, Type: System.Int32, IsImplicit) (Syntax: 'i2') + IDeclarationExpressionOperation (OperationKind.DeclarationExpression, Type: System.Int32) (Syntax: 'int i3') + ILocalReferenceOperation: i3 (IsDeclaration: True) (OperationKind.LocalReference, Type: System.Int32) (Syntax: 'i3') + Right: + IFlowCaptureReferenceOperation: 1 (OperationKind.FlowCaptureReference, Type: (System.Int32, (System.Int32, System.Int32)), IsImplicit) (Syntax: 'b ? (1, (2, ... (4, (5, 6))') + Next (Regular) Block[B5] + Leaving: {R1} +} +Block[B5] - Exit + Predecessors: [B4] + Statements (0)"; + VerifyFlowGraphAndDiagnosticsForTest(source, expectedFlowGraph, expectedDiagnostics, parseOptions: TestOptions.RegularPreview); } [CompilerTrait(CompilerFeature.IOperation, CompilerFeature.Dataflow)] diff --git a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs index c6e30ff307d43..702f3242a7332 100644 --- a/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/FlowAnalysis/RegionAnalysisTests.cs @@ -7140,6 +7140,39 @@ void M() Assert.Equal("this, x, y", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); } + [Fact] + public void AnalysisOfMixedDeconstruction() + { + var analysisResults = CompileAndAnalyzeDataFlowExpression(@" +class A +{ + bool M() + { + int x = 0; + string y; + /**/ + (x, (y, var z)) = (x, ("""", true)) + /**/ + return z; + } +} +"); + var dataFlowAnalysisResults = analysisResults; + Assert.Equal("z", GetSymbolNamesJoined(dataFlowAnalysisResults.VariablesDeclared)); + Assert.Equal("x, y, z", GetSymbolNamesJoined(dataFlowAnalysisResults.AlwaysAssigned)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.Captured)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedInside)); + Assert.Null(GetSymbolNamesJoined(dataFlowAnalysisResults.CapturedOutside)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsIn)); + Assert.Equal("z", GetSymbolNamesJoined(dataFlowAnalysisResults.DataFlowsOut)); + Assert.Equal("this, x", GetSymbolNamesJoined(dataFlowAnalysisResults.DefinitelyAssignedOnEntry)); + Assert.Equal("this, x, y, z", GetSymbolNamesJoined(dataFlowAnalysisResults.DefinitelyAssignedOnExit)); + Assert.Equal("x", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadInside)); + Assert.Equal("z", GetSymbolNamesJoined(dataFlowAnalysisResults.ReadOutside)); + Assert.Equal("x, y, z", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenInside)); + Assert.Equal("this, x", GetSymbolNamesJoined(dataFlowAnalysisResults.WrittenOutside)); + } + [Fact] public void AnalysisOfPropertyGetter_Inside_ReferenceType() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs index 765faae99dce6..212220a0974be 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/DeconstructionTests.cs @@ -4086,7 +4086,8 @@ public void Deconstruct(out dynamic x, out dynamic y) "; var comp = CreateCompilationWithMscorlib40AndSystemCore(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, - options: TestOptions.UnsafeDebugDll); + options: TestOptions.UnsafeDebugDll, + parseOptions: TestOptions.RegularPreview); // The precise diagnostics here are not important, and may be sensitive to parser // adjustments. This is a test that we don't crash. The errors here are likely to @@ -4101,9 +4102,6 @@ public void Deconstruct(out dynamic x, out dynamic y) // (6,19): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) // (int* x1, int y1) = c; Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "int y1").WithArguments("dynamic", "int").WithLocation(6, 19), - // (6,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (int* x1, int y1) = c; - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(int* x1, int y1)").WithLocation(6, 9), // (7,10): error CS0103: The name 'var' does not exist in the current context // (var* x2, int y2) = c; Diagnostic(ErrorCode.ERR_NameNotInContext, "var").WithArguments("var").WithLocation(7, 10), @@ -4113,9 +4111,6 @@ public void Deconstruct(out dynamic x, out dynamic y) // (7,19): error CS0266: Cannot implicitly convert type 'dynamic' to 'int'. An explicit conversion exists (are you missing a cast?) // (var* x2, int y2) = c; Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "int y2").WithArguments("dynamic", "int").WithLocation(7, 19), - // (7,9): error CS8184: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (var* x2, int y2) = c; - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(var* x2, int y2)").WithLocation(7, 9), // (8,10): error CS0266: Cannot implicitly convert type 'dynamic' to 'int*[]'. An explicit conversion exists (are you missing a cast?) // (int*[] x3, int y3) = c; Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "int*[] x3").WithArguments("dynamic", "int*[]").WithLocation(8, 10), diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/DeconstructionTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/DeconstructionTests.cs index 22c8681a3e28b..5abc85ef9d624 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/DeconstructionTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/DeconstructionTests.cs @@ -2079,7 +2079,7 @@ struct ValueTuple } [Fact, WorkItem(12803, "https://github.com/dotnet/roslyn/issues/12803")] - public void BadTupleElementTypeInDeconstruction02() + public void MixedDeclarationAndAssignmentInTupleDeconstruct() { var source = @" @@ -2101,14 +2101,39 @@ struct ValueTuple public ValueTuple(T1 item1, T2 item2) { this.Item1 = item1; this.Item2 = item2; } } }"; - CreateCompilation(source).VerifyDiagnostics( - // (7,9): error CS8183: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (int x1, x2) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(int x1, x2)").WithLocation(7, 9), - // (8,9): error CS8183: A deconstruction cannot mix declarations and expressions on the left-hand-side. - // (x3, int x4) = (1, 2); - Diagnostic(ErrorCode.ERR_MixedDeconstructionUnsupported, "(x3, int x4)").WithLocation(8, 9) - ); + CreateCompilation(source, parseOptions: TestOptions.RegularPreview).VerifyDiagnostics(); + } + + [Fact, WorkItem(12803, "https://github.com/dotnet/roslyn/issues/12803")] + public void MixedDeclarationAndAssignmentInTupleDeconstructCSharp9() + { + var source = +@" +class C +{ + int x2, x3; + void M() + { + (int x1, x2) = (1, 2); + (x3, int x4) = (1, 2); + } +} +namespace System +{ + struct ValueTuple + { + public T1 Item1; + public T2 Item2; + public ValueTuple(T1 item1, T2 item2) { this.Item1 = item1; this.Item2 = item2; } + } +}"; + CreateCompilation(source, parseOptions: TestOptions.Regular9).VerifyDiagnostics( + // (7,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (int x1, x2) = (1, 2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(int x1, x2) = (1, 2)", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(7, 9), + // (8,9): error CS8652: The feature 'Mixed declarations and expressions in deconstruction' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // (x3, int x4) = (1, 2); + Diagnostic(ErrorCode.ERR_FeatureInPreview, "(x3, int x4) = (1, 2)", isSuppressed: false).WithArguments("Mixed declarations and expressions in deconstruction").WithLocation(8, 9)); } [Fact, WorkItem(12803, "https://github.com/dotnet/roslyn/issues/12803")] diff --git a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs index 16d5fcffda2a4..4844e1346be4d 100644 --- a/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs +++ b/src/EditorFeatures/CSharpTest/Completion/CompletionProviders/SymbolCompletionProviderTests.cs @@ -11200,5 +11200,45 @@ await VerifyItemExistsAsync( expectedDescriptionOrNull: $"{targetType} C.Bar({expectedParameterList}) (+{NonBreakingSpaceString}2{NonBreakingSpaceString}{FeaturesResources.overloads_})", matchingFilters: new List { FilterSet.MethodFilter, FilterSet.TargetTypedFilter }); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestTypesNotSuggestedInDeclarationDeconstruction() + { + await VerifyItemIsAbsentAsync(@" +class C +{ + int M() + { + var (x, $$) = (0, 0); + } +}", "C"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestTypesSuggestedInMixedDeclarationAndAssignmentInDeconstruction() + { + await VerifyItemExistsAsync(@" +class C +{ + int M() + { + (x, $$) = (0, 0); + } +}", "C"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestLocalDeclaredBeforeDeconstructionSuggestedInMixedDeclarationAndAssignmentInDeconstruction() + { + await VerifyItemExistsAsync(@" +class C +{ + int M() + { + int y; + (var x, $$) = (0, 0); + } +}", "y"); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs index eeea44c180ddd..7e627a2df922d 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/IntKeywordRecommenderTests.cs @@ -827,5 +827,19 @@ class C { delegate*$$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInDeclarationDeconstruction() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"var (x, $$) = (0, 0);")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInMixedDeclarationAndAssignmentInDeconstruction() + { + await VerifyKeywordAsync(AddInsideMethod( +@"(x, $$) = (0, 0);")); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs index 34be2f83e1750..6607c025f4aa0 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/ObjectKeywordRecommenderTests.cs @@ -761,5 +761,19 @@ class C { delegate*$$"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInDeclarationDeconstruction() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"var (x, $$) = (0, 0);")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInMixedDeclarationAndAssignmentInDeconstruction() + { + await VerifyKeywordAsync(AddInsideMethod( +@"(x, $$) = (0, 0);")); + } } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs index 7f211bc817775..1ac970975992b 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/VarKeywordRecommenderTests.cs @@ -440,5 +440,19 @@ void Goo(object o) } }"); } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestNotInDeclarationDeconstruction() + { + await VerifyAbsenceAsync(AddInsideMethod( +@"var (x, $$) = (0, 0);")); + } + + [Fact, Trait(Traits.Feature, Traits.Features.KeywordRecommending)] + public async Task TestInMixedDeclarationAndAssignmentInDeconstruction() + { + await VerifyKeywordAsync(AddInsideMethod( +@"(x, $$) = (0, 0);")); + } } }