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
+
+
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ý.
-
-
- Při dekonstrukci nejde na levé straně kombinovat deklarace a výrazy.
-
-
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
+
+
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.
-
-
- Eine Dekonstruktion kann Deklarationen und Ausdrücke auf der linken Seite nicht mischen.
-
-
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
+
+
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.
-
-
- Una deconstrucción no puede mezclar declaraciones y expresiones en el lado izquierdo.
-
-
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
+
+
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.
-
-
- Une déconstruction ne peut pas contenir à la fois des déclarations et des expressions à gauche.
-
-
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
+
+
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.
-
-
- Nella parte sinistra di una decostruzione non è possibile combinare dichiarazioni ed espressioni.
-
-
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
+
+
型およびエイリアスに 'record' という名前を指定することはできません。
@@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
このコンテキストではスロー式は許可されていません。
-
-
- 分解の左側で宣言と式を混用できません。
-
-
宣言はこのコンテキストでは許可されていません。
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
+
+
형식 및 별칭의 이름은 'record'로 지정할 수 없습니다.
@@ -10506,11 +10511,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
이 컨텍스트에서는 throw 식을 사용할 수 없습니다.
-
-
- 분해는 왼쪽에 선언과 식을 혼합할 수 없습니다.
-
-
이 컨텍스트에서 선언을 사용할 수 없습니다.
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
+
+
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.
-
-
- Dekonstrukcja nie może mieszać deklaracji i wyrażeń po lewej stronie.
-
-
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
+
+
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.
-
-
- Uma desconstrução não pode mesclar declarações e expressões à esquerda.
-
-
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
+
+
Типы и псевдонимы не могут иметь имя "record"
@@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Выражение Throw в данном контексте запрещено.
-
-
- Деконструирование не может содержать объявления и выражения в левой части.
-
-
Объявление недопустимо в этом контексте.
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
+
+
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.
-
-
- Ayrıştırma deyiminin sol tarafında, bildirim ve ifadeler birlikte kullanılamaz.
-
-
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
+
+
类型和别名不应命名为 "record"。
@@ -10512,11 +10517,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
此上下文中不允许使用 throw 表达式。
-
-
- 析构不能在左侧混合声明和表达式。
-
-
此上下文中不允许使用声明。
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
+
+
類型與別名不應命名為 'record'。
@@ -10507,11 +10512,6 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
此內容不允許 throw 運算式。
-
-
- 解構不得混合左側的宣告與運算式。
-
-
此內容中不允許宣告。
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);"));
+ }
}
}