From 5fd73c502789746e1c20b12a5696f38748da7629 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Wed, 15 Dec 2021 20:54:06 -0800 Subject: [PATCH 1/3] Report error if 'record struct' constructor calls default parameterless constructor (#58339) --- .../Portable/Binder/Binder_Statements.cs | 11 +- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Semantic/Semantics/RecordStructTests.cs | 344 ++++++++++++++++++ 17 files changed, 423 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index bf9d9b659e10d..31869e224e126 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3420,7 +3420,7 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, bool thisInitializer = initializer?.IsKind(SyntaxKind.ThisConstructorInitializer) == true; if (!thisInitializer && - ContainingType.GetMembersUnordered().OfType().Any()) + hasAnyRecordConstructors()) { var constructorSymbol = (MethodSymbol)this.ContainingMember(); if (!constructorSymbol.IsStatic && @@ -3437,6 +3437,12 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, && thisInitializer && ContainingType.IsDefaultValueTypeConstructor(initializer); + if (skipInitializer && + hasAnyRecordConstructors()) + { + Error(diagnostics, ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, initializer.ThisOrBaseKeyword); + } + // Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel return new BoundConstructorMethodBody(constructor, bodyBinder.GetDeclaredLocalsForScope(constructor), @@ -3447,6 +3453,9 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, null : bodyBinder.BindExpressionBodyAsBlock(constructor.ExpressionBody, constructor.Body == null ? diagnostics : BindingDiagnosticBag.Discarded)); + + bool hasAnyRecordConstructors() => + ContainingType.GetMembersUnordered().OfType().Any(); } internal virtual BoundExpressionStatement BindConstructorInitializer(ConstructorInitializerSyntax initializer, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index d3cdb466a752d..c257fea3b7bd1 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6866,6 +6866,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A lambda expression with attributes cannot be converted to an expression tree + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + The operation may overflow '{0}' at runtime (use 'unchecked' syntax to override) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 21d55131f01bf..964b50453d0ca 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1999,6 +1999,7 @@ internal enum ErrorCode WRN_CompileTimeCheckedOverflow = 8973, WRN_MethGrpToNonDel = 8974, + ERR_RecordStructConstructorCallsDefaultConstructor = 8982, #endregion diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 0fc0af7751f66..d334071e2d152 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1022,6 +1022,11 @@ Primární konstruktor je v konfliktu se syntetizovaně zkopírovaným konstruktorem. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Přiřazení odkazu {1} k {0} nelze provést, protože {1} má užší řídicí obor než {0}. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index fab99a6112ed4..358f243c0c509 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1022,6 +1022,11 @@ Der primäre Konstruktor verursacht einen Konflikt mit dem synthetisierten Kopierkonstruktor. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. ref-assign von "{1}" zu "{0}" ist nicht möglich, weil "{1}" einen geringeren Escapebereich als "{0}" aufweist. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index e61b82ee62bd9..c029628899a86 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1022,6 +1022,11 @@ El constructor principal está en conflicto con el constructor de copia sintetizado. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. No se puede asignar referencia "{1}" a "{0}" porque "{1}" tiene un ámbito de escape más limitado que "{0}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index d3e10858683d6..54f9dfccbeb5c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1022,6 +1022,11 @@ Le constructeur principal est en conflit avec le constructeur de copie synthétisée. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Impossible d'effectuer une assignation par référence de '{1}' vers '{0}', car '{1}' a une portée de sortie plus limitée que '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index ae09f873ca7a5..9884be5d98434 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1022,6 +1022,11 @@ Il costruttore primario è in conflitto con il costruttore di copia sintetizzato. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Non è possibile assegnare '{1}' a '{0}' come ref perché l'ambito di escape di '{1}' è ridotto rispetto a quello di '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 78f8ef3c14a9d..832e8afce59f6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1022,6 +1022,11 @@ プライマリ コンストラクターが、合成されたコピー コンストラクターと競合しています。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}' を '{0}' に ref 割り当てすることはできません。'{1}' のエスケープ スコープが '{0}' より狭いためです。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index f44cf74c02950..0980e056007d9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1022,6 +1022,11 @@ 기본 생성자가 합성된 복사 생성자와 충돌합니다. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}'을(를) '{0}'에 참조 할당할 수 없습니다. '{1}'이(가) '{0}'보다 이스케이프 범위가 좁기 때문입니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 6d5b28c290a26..e790851286280 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1022,6 +1022,11 @@ Konstruktor podstawowy powoduje konflikt z konstruktorem syntetyzowanej kopii. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Nie można przypisać odwołania elementu „{1}” do elementu „{0}”, ponieważ element „{1}” ma węższy zakres wyjścia niż element „{0}”. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 34597e7def371..781f858282a8c 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1022,6 +1022,11 @@ O construtor primário entra em conflito com o construtor de cópia sintetizado. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Não é possível atribuir ref '{1}' a '{0}' porque '{1}' tem um escopo de escape mais limitado que '{0}'. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index ab7c1967c0b75..90dbd691b6a59 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1022,6 +1022,11 @@ Первичный конструктор конфликтует с синтезированным конструктором копий. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. Не удается присвоить по ссылке "{1}" для "{0}", так как escape-область у "{1}" уже, чем у "{0}". diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 5160e9e0f8812..c3bdceded066e 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1022,6 +1022,11 @@ Birincil oluşturucu, sentezlenmiş kopya oluşturucusuyla çakışıyor. + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. '{1}', '{0}' öğesinden daha dar bir kaçış kapsamı içerdiğinden '{0}' öğesine '{1}' ref ataması yapılamıyor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 8dd99d7d68f16..13572a101628a 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1022,6 +1022,11 @@ 主构造函数与合成的复制构造函数冲突。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. 无法将“{1}”重新赋值为“{0}”,因为“{1}”具有比“{0}”更窄的转义范围。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 15fb46f8f8213..06d23b0f04b18 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1022,6 +1022,11 @@ 主要建構函式與合成的複製建構函式相衝突。 + + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + + Cannot ref-assign '{1}' to '{0}' because '{1}' has a narrower escape scope than '{0}'. 不能將 '{1}' 參考指派至 '{0}',因為 '{1}' 的逸出範圍比 '{0}' 還要窄。 diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index e5ae5daaf864a..fa11e107a789a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -10616,5 +10616,349 @@ public Value(Value original) : this(42) { } var comp = CreateCompilation(src); CompileAndVerify(comp, expectedOutput: "Value { I = 42 }"); } + + [Fact] + public void ExplicitConstructors_01() + { + var source = +@"using static System.Console; +record struct S1 +{ +} +record struct S2 +{ + public S2() { } +} +record struct S3 +{ + public S3(object o) { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S2()); + WriteLine(new S3()); + WriteLine(new S3(null)); + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: +@"S1 { } +S2 { } +S3 { } +S3 { } +"); + verifier.VerifyMissing("S1..ctor()"); + verifier.VerifyIL("S2..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyMissing("S3..ctor()"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + } + + [Fact] + public void ExplicitConstructors_02() + { + var source = +@"record struct S1 +{ + public S1(object o) { } +} +record struct S2() +{ + public S2(object o) { } +} +record struct S3(char A) +{ + public S3(object o) { } +} +record struct S4(char A, char B) +{ + public S4(object o) { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (7,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S2").WithLocation(7, 12), + // (11,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S3(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S3").WithLocation(11, 12), + // (15,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S4(object o) { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S4").WithLocation(15, 12)); + } + + [Fact] + public void ExplicitConstructors_03() + { + var source = +@"using static System.Console; +record struct S1 +{ + public S1(object o) : this() { } +} +record struct S2() +{ + public S2(object o) : this() { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S2()); + } +}"; + CompileAndVerify(source, expectedOutput: +@"S1 { } +S2 { } +"); + } + + [Fact] + public void ExplicitConstructors_04() + { + var source = +@"using static System.Console; +record struct S0 +{ + internal object F = 0; + public S0() { } +} +record struct S1 +{ + internal object F = 1; + public S1(object o) : this() { F = o; } +} +record struct S2() +{ + internal object F = 2; + public S2(object o) : this() { F = o; } +} +class Program +{ + static void Main() + { + WriteLine(new S0().F); + WriteLine(new S1().F); + WriteLine(new S1(-1).F); + WriteLine(new S2().F); + WriteLine(new S2(-2).F); + } +}"; + CompileAndVerify(source, expectedOutput: +@"0 + +-1 +2 +-2 +"); + } + + [Fact] + [WorkItem(58328, "https://github.com/dotnet/roslyn/issues/58328")] + public void ExplicitConstructors_05() + { + var source = +@"record struct S3(char A) +{ + public S3(object o) : this() { } +} +record struct S4(char A, char B) +{ + public S4(object o) : this() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S3(object o) : this() { } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(3, 27), + // (7,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S4(object o) : this() { } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(7, 27)); + } + + [Fact] + [WorkItem(58328, "https://github.com/dotnet/roslyn/issues/58328")] + public void ExplicitConstructors_06() + { + var source = +@"record struct S3(char A) +{ + internal object F = 3; + public S3(object o) : this() { F = o; } +} +record struct S4(char A, char B) +{ + internal object F = 4; + public S4(object o) : this() { F = o; } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S3(object o) : this() { F = o; } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(4, 27), + // (9,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // public S4(object o) : this() { F = o; } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(9, 27)); + } + + [Fact] + public void ExplicitConstructors_07() + { + var source = +@"using static System.Console; +record struct S1 +{ + public S1(object o) : this() { } + public S1() { } +} +record struct S3(char A) +{ + public S3(object o) : this() { } + public S3() : this('a') { } +} +record struct S4(char A, char B) +{ + public S4(object o) : this() { } + public S4() : this('a', 'b') { } +} +class Program +{ + static void Main() + { + WriteLine(new S1()); + WriteLine(new S1(1)); + WriteLine(new S3()); + WriteLine(new S3(3)); + WriteLine(new S4()); + WriteLine(new S4(4)); + } +}"; + var verifier = CompileAndVerify(source, expectedOutput: +@"S1 { } +S1 { } +S3 { A = a } +S3 { A = a } +S4 { A = a, B = b } +S4 { A = a, B = b } +"); + verifier.VerifyIL("S1..ctor()", +@"{ + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret +}"); + verifier.VerifyIL("S1..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S1..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S3..ctor()", +@"{ + // Code size 9 (0x9) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 97 + IL_0003: call ""S3..ctor(char)"" + IL_0008: ret +}"); + verifier.VerifyIL("S3..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S3..ctor()"" + IL_0006: ret +}"); + verifier.VerifyIL("S4..ctor()", +@"{ + // Code size 11 (0xb) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.s 97 + IL_0003: ldc.i4.s 98 + IL_0005: call ""S4..ctor(char, char)"" + IL_000a: ret +}"); + verifier.VerifyIL("S4..ctor(object)", +@"{ + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: call ""S4..ctor()"" + IL_0006: ret +}"); + } + + [Fact] + public void ExplicitConstructors_08() + { + var source = +@"record struct S2() +{ + public S2(object o) : this() { } + public S2() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,27): error CS2121: The call is ambiguous between the following methods or properties: 'S2.S2()' and 'S2.S2()' + // public S2(object o) : this() { } + Diagnostic(ErrorCode.ERR_AmbigCall, "this").WithArguments("S2.S2()", "S2.S2()").WithLocation(3, 27), + // (4,12): error CS2111: Type 'S2' already defines a member called 'S2' with the same parameter types + // public S2() { } + Diagnostic(ErrorCode.ERR_MemberAlreadyExists, "S2").WithArguments("S2", "S2").WithLocation(4, 12), + // (4,12): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "S2").WithLocation(4, 12)); + } + + [Fact] + public void ExplicitConstructors_09() + { + var source = +@"record struct S1 +{ + public S1(object o) : base() { } +} +record struct S2() +{ + public S2(object o) : base() { } +} +record struct S3(char A) +{ + public S3(object o) : base() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (3,12): error CS0522: 'S1': structs cannot call base class constructors + // public S1(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S1").WithArguments("S1").WithLocation(3, 12), + // (7,12): error CS0522: 'S2': structs cannot call base class constructors + // public S2(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S2").WithArguments("S2").WithLocation(7, 12), + // (7,27): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S2(object o) : base() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "base").WithLocation(7, 27), + // (11,12): error CS0522: 'S3': structs cannot call base class constructors + // public S3(object o) : base() { } + Diagnostic(ErrorCode.ERR_StructWithBaseConstructorCall, "S3").WithArguments("S3").WithLocation(11, 12), + // (11,27): error CS8862: A constructor declared in a record with parameter list must have 'this' constructor initializer. + // public S3(object o) : base() { } + Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "base").WithLocation(11, 27)); + } } } From 588f8672fcd1e8b134818c32f656fb0bc0176d95 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 3 Jan 2022 09:37:22 -0800 Subject: [PATCH 2/3] Improve error reporting for 'this()' initializer from 'record struct' constructor --- .../Portable/Binder/Binder_Statements.cs | 33 +++++++++---- .../CSharp/Portable/CSharpResources.resx | 2 +- .../Portable/xlf/CSharpResources.cs.xlf | 4 +- .../Portable/xlf/CSharpResources.de.xlf | 4 +- .../Portable/xlf/CSharpResources.es.xlf | 4 +- .../Portable/xlf/CSharpResources.fr.xlf | 4 +- .../Portable/xlf/CSharpResources.it.xlf | 4 +- .../Portable/xlf/CSharpResources.ja.xlf | 4 +- .../Portable/xlf/CSharpResources.ko.xlf | 4 +- .../Portable/xlf/CSharpResources.pl.xlf | 4 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 4 +- .../Portable/xlf/CSharpResources.ru.xlf | 4 +- .../Portable/xlf/CSharpResources.tr.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 4 +- .../Semantic/Semantics/RecordStructTests.cs | 47 +++++++++++++++++-- 16 files changed, 93 insertions(+), 41 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 31869e224e126..5354029158f1a 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -3420,10 +3420,9 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, bool thisInitializer = initializer?.IsKind(SyntaxKind.ThisConstructorInitializer) == true; if (!thisInitializer && - hasAnyRecordConstructors()) + hasRecordPrimaryConstructor()) { - var constructorSymbol = (MethodSymbol)this.ContainingMember(); - if (!constructorSymbol.IsStatic && + if (isInstanceConstructor(out MethodSymbol constructorSymbol) && !SynthesizedRecordCopyCtor.IsCopyConstructor(constructorSymbol)) { // Note: we check the constructor initializer of copy constructors elsewhere @@ -3431,18 +3430,21 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, } } - // The `: this()` initializer is ignored when it is a default value type constructor - // and we need to include field initializers into the constructor. - bool skipInitializer = includesFieldInitializers - && thisInitializer + bool isDefaultValueTypeInitializer = thisInitializer && ContainingType.IsDefaultValueTypeConstructor(initializer); - if (skipInitializer && - hasAnyRecordConstructors()) + if (isDefaultValueTypeInitializer && + isInstanceConstructor(out _) && + hasRecordPrimaryConstructor()) { Error(diagnostics, ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, initializer.ThisOrBaseKeyword); } + // The `: this()` initializer is ignored when it is a default value type constructor + // and we need to include field initializers into the constructor. + bool skipInitializer = includesFieldInitializers + && isDefaultValueTypeInitializer; + // Using BindStatement to bind block to make sure we are reusing results of partial binding in SemanticModel return new BoundConstructorMethodBody(constructor, bodyBinder.GetDeclaredLocalsForScope(constructor), @@ -3454,8 +3456,19 @@ private BoundNode BindConstructorBody(ConstructorDeclarationSyntax constructor, bodyBinder.BindExpressionBodyAsBlock(constructor.ExpressionBody, constructor.Body == null ? diagnostics : BindingDiagnosticBag.Discarded)); - bool hasAnyRecordConstructors() => + bool hasRecordPrimaryConstructor() => ContainingType.GetMembersUnordered().OfType().Any(); + + bool isInstanceConstructor(out MethodSymbol constructorSymbol) + { + if (this.ContainingMember() is MethodSymbol { IsStatic: false } method) + { + constructorSymbol = method; + return true; + } + constructorSymbol = null; + return false; + } } internal virtual BoundExpressionStatement BindConstructorInitializer(ConstructorInitializerSyntax initializer, BindingDiagnosticBag diagnostics) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index c257fea3b7bd1..0eeff0bf52101 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -6867,7 +6867,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ A lambda expression with attributes cannot be converted to an expression tree - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. The operation may overflow '{0}' at runtime (use 'unchecked' syntax to override) diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index d334071e2d152..7d7fd7db14c55 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 358f243c0c509..109a6b64113d2 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index c029628899a86..c6db9fa9c1157 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 54f9dfccbeb5c..1796b2525a7c0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 9884be5d98434..2e11b9d9be67f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 832e8afce59f6..524f7a5924738 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 0980e056007d9..3401653261d80 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index e790851286280..fb76111aa5253 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 781f858282a8c..e1a3fff80b7ff 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 90dbd691b6a59..668c43448ae91 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index c3bdceded066e..71993a37a5ec9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index 13572a101628a..581791125b476 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 06d23b0f04b18..fb39ce0ab12fa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1023,8 +1023,8 @@ - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. - A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index fa11e107a789a..6b887a5cad92b 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -10780,10 +10780,10 @@ public S4(object o) : this() { } }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (3,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // (3,27): error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. // public S3(object o) : this() { } Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(3, 27), - // (7,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // (7,27): error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. // public S4(object o) : this() { } Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(7, 27)); } @@ -10805,10 +10805,10 @@ record struct S4(char A, char B) }"; var comp = CreateCompilation(source); comp.VerifyDiagnostics( - // (4,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // (4,27): error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. // public S3(object o) : this() { F = o; } Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(4, 27), - // (9,27): error CS8982: A 'this' initializer for a 'record struct' constructor must call the primary constructor or an explicitly declared constructor. + // (9,27): error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. // public S4(object o) : this() { F = o; } Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(9, 27)); } @@ -10960,5 +10960,44 @@ public S3(object o) : base() { } // public S3(object o) : base() { } Diagnostic(ErrorCode.ERR_UnexpectedOrMissingConstructorInitializerInRecord, "base").WithLocation(11, 27)); } + + [Fact] + [WorkItem(58328, "https://github.com/dotnet/roslyn/issues/58328")] + public void ExplicitConstructors_10() + { + var source = +@"record struct S(object F) +{ + public object F; + public S(int i) : this() { F = i; } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (1,15): error CS0171: Field 'S.F' must be fully assigned before control is returned to the caller + // record struct S(object F) + Diagnostic(ErrorCode.ERR_UnassignedThis, "S").WithArguments("S.F").WithLocation(1, 15), + // (1,24): warning CS8907: Parameter 'F' is unread. Did you forget to use it to initialize the property with that name? + // record struct S(object F) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "F").WithArguments("F").WithLocation(1, 24), + // (4,23): error CS8982: A constructor declared in a 'record struct' with parameter list must have a 'this' initializer that calls the primary constructor or an explicitly declared constructor. + // public S(int i) : this() { F = i; } + Diagnostic(ErrorCode.ERR_RecordStructConstructorCallsDefaultConstructor, "this").WithLocation(4, 23)); + } + + [Fact] + public void ExplicitConstructors_11() + { + var source = +@"record struct S(int X) +{ + static internal int F = 1; + static S() : this() { } +}"; + var comp = CreateCompilation(source); + comp.VerifyDiagnostics( + // (4,18): error CS0514: 'S': static constructor cannot have an explicit 'this' or 'base' constructor call + // static S() : this() { } + Diagnostic(ErrorCode.ERR_StaticConstructorWithExplicitConstructorCall, "this").WithArguments("S").WithLocation(4, 18)); + } } } From 9d2819a5e53041b6df71aec7c251288509b505f6 Mon Sep 17 00:00:00 2001 From: Charles Stoner Date: Mon, 29 Nov 2021 14:11:23 -0800 Subject: [PATCH 3/3] Require definite assignment of all fields if struct includes any field initializers (#57925) --- .../Portable/FlowAnalysis/FlowAnalysisPass.cs | 2 +- .../EditAndContinue/EditAndContinueTests.cs | 6 +- .../Semantics/StructConstructorTests.cs | 697 +++++++++++++----- 3 files changed, 529 insertions(+), 176 deletions(-) diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs index cb6480c0e4ef7..cb74697895015 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs @@ -43,7 +43,7 @@ public static BoundBlock Rewrite( if (method.ReturnsVoid || method.IsIterator || method.IsAsyncEffectivelyReturningTask(compilation)) { // we don't analyze synthesized void methods. - if ((method.IsImplicitlyDeclared && !method.IsScriptInitializer) || + if ((method.IsImplicitlyDeclared && !method.IsScriptInitializer && !(method.ContainingType.IsStructType() && method.IsParameterlessConstructor() && !method.IsDefaultValueTypeConstructor(requireZeroInit: true))) || Analyze(compilation, method, block, diagnostics)) { block = AppendImplicitReturn(block, method, originalBodyNested); diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs index 8620daf21a198..a46b3a689be66 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueTests.cs @@ -4932,7 +4932,7 @@ public void Struct_ImplementSynthesizedConstructor() struct S { int a = 1; - int b; + int b = 2; } "; var source1 = @@ -4940,11 +4940,11 @@ struct S struct S { int a = 1; - int b; + int b = 2; public S() { - b = 2; + b = 3; } } "; diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs index 27977bcea55be..0b959997e1537 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructConstructorTests.cs @@ -899,12 +899,6 @@ public void FieldInitializers_01() var source = @"#pragma warning disable 649 using System; -struct S1 -{ - object X = null; - object Y; - public override string ToString() => (X, Y).ToString(); -} struct S2 { object X = null; @@ -923,7 +917,6 @@ class Program { static void Main() { - Console.WriteLine(new S1()); Console.WriteLine(new S2()); Console.WriteLine(new S3()); } @@ -934,49 +927,33 @@ static void Main() // (5,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // object X = null; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(5, 12), - // (11,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. - // object X = null; - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(11, 12), - // (13,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // (7,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. // public S2() { Y = 1; } - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(13, 12), - // (19,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(7, 12), + // (13,12): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // object Y = null; - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(19, 12)); + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(13, 12)); comp = CreateCompilation(source, options: TestOptions.ReleaseExe); comp.VerifyDiagnostics(); var verifier = CompileAndVerify(comp, expectedOutput: -@"(, ) -(, 1) +@"(, 1) (, )"); verifier.VerifyIL("Program.Main", @"{ - // Code size 50 (0x32) + // Code size 35 (0x23) .maxstack 1 .locals init (S3 V_0) - IL_0000: newobj ""S1..ctor()"" - IL_0005: box ""S1"" + IL_0000: newobj ""S2..ctor()"" + IL_0005: box ""S2"" IL_000a: call ""void System.Console.WriteLine(object)"" - IL_000f: newobj ""S2..ctor()"" - IL_0014: box ""S2"" - IL_0019: call ""void System.Console.WriteLine(object)"" - IL_001e: ldloca.s V_0 - IL_0020: initobj ""S3"" - IL_0026: ldloc.0 - IL_0027: box ""S3"" - IL_002c: call ""void System.Console.WriteLine(object)"" - IL_0031: ret -}"); - verifier.VerifyIL("S1..ctor()", -@"{ - // Code size 8 (0x8) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldnull - IL_0002: stfld ""object S1.X"" - IL_0007: ret + IL_000f: ldloca.s V_0 + IL_0011: initobj ""S3"" + IL_0017: ldloc.0 + IL_0018: box ""S3"" + IL_001d: call ""void System.Console.WriteLine(object)"" + IL_0022: ret }"); verifier.VerifyIL("S2..ctor()", @"{ @@ -1012,12 +989,6 @@ public void FieldInitializers_02() var source = @"#pragma warning disable 649 using System; -struct S1 -{ - internal object X = 1; - internal object Y; - public override string ToString() => (X, Y).ToString(); -} struct S2 { internal object X = 2; @@ -1036,95 +1007,63 @@ class Program { static void Main() { - Console.WriteLine(new S1()); Console.WriteLine(new S2()); Console.WriteLine(new S3()); - Console.WriteLine(new S1 { }); Console.WriteLine(new S2 { }); Console.WriteLine(new S3 { }); - Console.WriteLine(new S1 { Y = 2 }); Console.WriteLine(new S2 { Y = 4 }); Console.WriteLine(new S3 { Y = 6 }); } }"; var verifier = CompileAndVerify(source, options: TestOptions.ReleaseExe, expectedOutput: -@"(1, ) -(2, 2) +@"(2, 2) (, ) -(1, ) (2, 2) (, ) -(1, 2) (2, 4) (, 6)"); verifier.VerifyIL("Program.Main", @"{ - // Code size 193 (0xc1) + // Code size 132 (0x84) .maxstack 2 .locals init (S3 V_0, - S1 V_1, - S2 V_2) - IL_0000: newobj ""S1..ctor()"" - IL_0005: box ""S1"" + S2 V_1) + IL_0000: newobj ""S2..ctor()"" + IL_0005: box ""S2"" IL_000a: call ""void System.Console.WriteLine(object)"" - IL_000f: newobj ""S2..ctor()"" - IL_0014: box ""S2"" - IL_0019: call ""void System.Console.WriteLine(object)"" - IL_001e: ldloca.s V_0 - IL_0020: initobj ""S3"" - IL_0026: ldloc.0 - IL_0027: box ""S3"" + IL_000f: ldloca.s V_0 + IL_0011: initobj ""S3"" + IL_0017: ldloc.0 + IL_0018: box ""S3"" + IL_001d: call ""void System.Console.WriteLine(object)"" + IL_0022: newobj ""S2..ctor()"" + IL_0027: box ""S2"" IL_002c: call ""void System.Console.WriteLine(object)"" - IL_0031: newobj ""S1..ctor()"" - IL_0036: box ""S1"" - IL_003b: call ""void System.Console.WriteLine(object)"" - IL_0040: newobj ""S2..ctor()"" - IL_0045: box ""S2"" - IL_004a: call ""void System.Console.WriteLine(object)"" - IL_004f: ldloca.s V_0 - IL_0051: initobj ""S3"" - IL_0057: ldloc.0 - IL_0058: box ""S3"" - IL_005d: call ""void System.Console.WriteLine(object)"" - IL_0062: ldloca.s V_1 - IL_0064: call ""S1..ctor()"" - IL_0069: ldloca.s V_1 - IL_006b: ldc.i4.2 - IL_006c: box ""int"" - IL_0071: stfld ""object S1.Y"" - IL_0076: ldloc.1 - IL_0077: box ""S1"" - IL_007c: call ""void System.Console.WriteLine(object)"" - IL_0081: ldloca.s V_2 - IL_0083: call ""S2..ctor()"" - IL_0088: ldloca.s V_2 - IL_008a: ldc.i4.4 - IL_008b: box ""int"" - IL_0090: stfld ""object S2.Y"" - IL_0095: ldloc.2 - IL_0096: box ""S2"" - IL_009b: call ""void System.Console.WriteLine(object)"" - IL_00a0: ldloca.s V_0 - IL_00a2: initobj ""S3"" - IL_00a8: ldloca.s V_0 - IL_00aa: ldc.i4.6 - IL_00ab: box ""int"" - IL_00b0: stfld ""object S3.Y"" - IL_00b5: ldloc.0 - IL_00b6: box ""S3"" - IL_00bb: call ""void System.Console.WriteLine(object)"" - IL_00c0: ret -}"); - verifier.VerifyIL("S1..ctor()", -@"{ - // Code size 13 (0xd) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.1 - IL_0002: box ""int"" - IL_0007: stfld ""object S1.X"" - IL_000c: ret + IL_0031: ldloca.s V_0 + IL_0033: initobj ""S3"" + IL_0039: ldloc.0 + IL_003a: box ""S3"" + IL_003f: call ""void System.Console.WriteLine(object)"" + IL_0044: ldloca.s V_1 + IL_0046: call ""S2..ctor()"" + IL_004b: ldloca.s V_1 + IL_004d: ldc.i4.4 + IL_004e: box ""int"" + IL_0053: stfld ""object S2.Y"" + IL_0058: ldloc.1 + IL_0059: box ""S2"" + IL_005e: call ""void System.Console.WriteLine(object)"" + IL_0063: ldloca.s V_0 + IL_0065: initobj ""S3"" + IL_006b: ldloca.s V_0 + IL_006d: ldc.i4.6 + IL_006e: box ""int"" + IL_0073: stfld ""object S3.Y"" + IL_0078: ldloc.0 + IL_0079: box ""S3"" + IL_007e: call ""void System.Console.WriteLine(object)"" + IL_0083: ret }"); verifier.VerifyIL("S2..ctor()", @"{ @@ -1164,12 +1103,6 @@ public void FieldInitializers_03() var source = @"#pragma warning disable 649 using System; -struct S1 -{ - internal object X { get; } = 1; - internal object Y { get; } - public override string ToString() => (X, Y).ToString(); -} struct S2 { internal object X { get; init; } = 2; @@ -1188,10 +1121,8 @@ class Program { static void Main() { - Console.WriteLine(new S1()); Console.WriteLine(new S2()); Console.WriteLine(new S3()); - Console.WriteLine(new S1 { }); Console.WriteLine(new S2 { }); Console.WriteLine(new S3 { }); Console.WriteLine(new S2 { Y = 4 }); @@ -1200,71 +1131,53 @@ static void Main() }"; var verifier = CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, options: TestOptions.ReleaseExe, verify: Verification.Skipped, expectedOutput: -@"(1, ) -(2, 2) +@"(2, 2) (, ) -(1, ) (2, 2) (, ) (2, 4) (, 6)"); verifier.VerifyIL("Program.Main", @"{ - // Code size 162 (0xa2) + // Code size 132 (0x84) .maxstack 2 .locals init (S3 V_0, S2 V_1) - IL_0000: newobj ""S1..ctor()"" - IL_0005: box ""S1"" + IL_0000: newobj ""S2..ctor()"" + IL_0005: box ""S2"" IL_000a: call ""void System.Console.WriteLine(object)"" - IL_000f: newobj ""S2..ctor()"" - IL_0014: box ""S2"" - IL_0019: call ""void System.Console.WriteLine(object)"" - IL_001e: ldloca.s V_0 - IL_0020: initobj ""S3"" - IL_0026: ldloc.0 - IL_0027: box ""S3"" + IL_000f: ldloca.s V_0 + IL_0011: initobj ""S3"" + IL_0017: ldloc.0 + IL_0018: box ""S3"" + IL_001d: call ""void System.Console.WriteLine(object)"" + IL_0022: newobj ""S2..ctor()"" + IL_0027: box ""S2"" IL_002c: call ""void System.Console.WriteLine(object)"" - IL_0031: newobj ""S1..ctor()"" - IL_0036: box ""S1"" - IL_003b: call ""void System.Console.WriteLine(object)"" - IL_0040: newobj ""S2..ctor()"" - IL_0045: box ""S2"" - IL_004a: call ""void System.Console.WriteLine(object)"" - IL_004f: ldloca.s V_0 - IL_0051: initobj ""S3"" - IL_0057: ldloc.0 - IL_0058: box ""S3"" - IL_005d: call ""void System.Console.WriteLine(object)"" - IL_0062: ldloca.s V_1 - IL_0064: call ""S2..ctor()"" - IL_0069: ldloca.s V_1 - IL_006b: ldc.i4.4 - IL_006c: box ""int"" - IL_0071: call ""void S2.Y.init"" - IL_0076: ldloc.1 - IL_0077: box ""S2"" - IL_007c: call ""void System.Console.WriteLine(object)"" - IL_0081: ldloca.s V_0 - IL_0083: initobj ""S3"" - IL_0089: ldloca.s V_0 - IL_008b: ldc.i4.6 - IL_008c: box ""int"" - IL_0091: call ""void S3.Y.set"" - IL_0096: ldloc.0 - IL_0097: box ""S3"" - IL_009c: call ""void System.Console.WriteLine(object)"" - IL_00a1: ret -}"); - verifier.VerifyIL("S1..ctor()", -@"{ - // Code size 13 (0xd) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.1 - IL_0002: box ""int"" - IL_0007: stfld ""object S1.k__BackingField"" - IL_000c: ret + IL_0031: ldloca.s V_0 + IL_0033: initobj ""S3"" + IL_0039: ldloc.0 + IL_003a: box ""S3"" + IL_003f: call ""void System.Console.WriteLine(object)"" + IL_0044: ldloca.s V_1 + IL_0046: call ""S2..ctor()"" + IL_004b: ldloca.s V_1 + IL_004d: ldc.i4.4 + IL_004e: box ""int"" + IL_0053: call ""void S2.Y.init"" + IL_0058: ldloc.1 + IL_0059: box ""S2"" + IL_005e: call ""void System.Console.WriteLine(object)"" + IL_0063: ldloca.s V_0 + IL_0065: initobj ""S3"" + IL_006b: ldloca.s V_0 + IL_006d: ldc.i4.6 + IL_006e: box ""int"" + IL_0073: call ""void S3.Y.set"" + IL_0078: ldloc.0 + IL_0079: box ""S3"" + IL_007e: call ""void System.Console.WriteLine(object)"" + IL_0083: ret }"); verifier.VerifyIL("S2..ctor()", @"{ @@ -1419,6 +1332,446 @@ .maxstack 2 }"); } + [WorkItem(57870, "https://github.com/dotnet/roslyn/issues/57870")] + [Fact] + public void FieldInitializers_06() + { + var source = +@"#pragma warning disable 649 +struct S1 +{ + internal object X = null; + internal object Y; +} +struct S2 +{ + internal object X = 2; + internal object Y; + public S2() { } +} +struct S3 +{ + internal object X; + internal object Y = 3; + public S3() { Y = 3; } +} +struct S4 +{ + internal object X; + internal object Y = 4; + public S4() { X = 4; } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (2,8): error CS0171: Field 'S1.Y' must be fully assigned before control is returned to the caller + // struct S1 + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.Y").WithLocation(2, 8), + // (4,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object X = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(4, 21), + // (9,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object X = 2; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(9, 21), + // (11,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S2() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(11, 12), + // (11,12): error CS0171: Field 'S2.Y' must be fully assigned before control is returned to the caller + // public S2() { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.Y").WithLocation(11, 12), + // (16,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object Y = 3; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(16, 21), + // (17,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S3").WithArguments("parameterless struct constructors", "10.0").WithLocation(17, 12), + // (17,12): error CS0171: Field 'S3.X' must be fully assigned before control is returned to the caller + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S3").WithArguments("S3.X").WithLocation(17, 12), + // (22,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object Y = 4; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(22, 21), + // (23,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S4() { X = 4; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S4").WithArguments("parameterless struct constructors", "10.0").WithLocation(23, 12)); + + var expectedDiagnostics = new[] + { + // (2,8): error CS0171: Field 'S1.Y' must be fully assigned before control is returned to the caller + // struct S1 + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.Y").WithLocation(2, 8), + // (11,12): error CS0171: Field 'S2.Y' must be fully assigned before control is returned to the caller + // public S2() { } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S2").WithArguments("S2.Y").WithLocation(11, 12), + // (17,12): error CS0171: Field 'S3.X' must be fully assigned before control is returned to the caller + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_UnassignedThis, "S3").WithArguments("S3.X").WithLocation(17, 12), + }; + + comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [WorkItem(57870, "https://github.com/dotnet/roslyn/issues/57870")] + [Fact] + public void FieldInitializers_07() + { + var source = +@"#pragma warning disable 649 +struct S1 +{ + internal object X { get; } = null; + internal object Y { get; } +} +struct S2 +{ + internal object X { get; } = 2; + internal object Y { get; } + public S2() { } +} +struct S3 +{ + internal object X { get; } + internal object Y { get; } = 3; + public S3() { Y = 3; } +} +struct S4 +{ + internal object X { get; } + internal object Y { get; } = 4; + public S4() { X = 4; } +}"; + + var comp = CreateCompilation(source, parseOptions: TestOptions.Regular9); + comp.VerifyDiagnostics( + // (2,8): error CS0843: Auto-implemented property 'S1.Y' must be fully assigned before control is returned to the caller. + // struct S1 + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S1").WithArguments("S1.Y").WithLocation(2, 8), + // (4,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object X { get; } = null; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(4, 21), + // (9,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object X { get; } = 2; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "X").WithArguments("struct field initializers", "10.0").WithLocation(9, 21), + // (11,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S2() { } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S2").WithArguments("parameterless struct constructors", "10.0").WithLocation(11, 12), + // (11,12): error CS0843: Auto-implemented property 'S2.Y' must be fully assigned before control is returned to the caller. + // public S2() { } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.Y").WithLocation(11, 12), + // (16,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object Y { get; } = 3; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(16, 21), + // (17,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S3").WithArguments("parameterless struct constructors", "10.0").WithLocation(17, 12), + // (17,12): error CS0843: Auto-implemented property 'S3.X' must be fully assigned before control is returned to the caller. + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.X").WithLocation(17, 12), + // (22,21): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. + // internal object Y { get; } = 4; + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "Y").WithArguments("struct field initializers", "10.0").WithLocation(22, 21), + // (23,12): error CS8773: Feature 'parameterless struct constructors' is not available in C# 9.0. Please use language version 10.0 or greater. + // public S4() { X = 4; } + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "S4").WithArguments("parameterless struct constructors", "10.0").WithLocation(23, 12)); + + var expectedDiagnostics = new[] + { + // (2,8): error CS0843: Auto-implemented property 'S1.Y' must be fully assigned before control is returned to the caller. + // struct S1 + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S1").WithArguments("S1.Y").WithLocation(2, 8), + // (11,12): error CS0843: Auto-implemented property 'S2.Y' must be fully assigned before control is returned to the caller. + // public S2() { } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.Y").WithLocation(11, 12), + // (17,12): error CS0843: Auto-implemented property 'S3.X' must be fully assigned before control is returned to the caller. + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.X").WithLocation(17, 12), + }; + + comp = CreateCompilation(source, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(source); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [WorkItem(57870, "https://github.com/dotnet/roslyn/issues/57870")] + [Fact] + public void FieldInitializers_08() + { + var source = +@"#pragma warning disable 649 +record struct S1 +{ + internal object X = 1; + internal object Y; +} +record struct S2 +{ + internal object X { get; } = 2; + internal object Y { get; } + public S2() { } +} +record struct S3 +{ + internal object X { get; init; } + internal object Y { get; init; } = 3; + public S3() { Y = 3; } +} +record struct S4 +{ + internal object X { get; init; } + internal object Y { get; init; } = 4; + public S4() { X = 4; } +} +"; + + var expectedDiagnostics = new[] + { + // (2,15): error CS0171: Field 'S1.Y' must be fully assigned before control is returned to the caller + // record struct S1 + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.Y").WithLocation(2, 15), + // (11,12): error CS0843: Auto-implemented property 'S2.Y' must be fully assigned before control is returned to the caller. + // public S2() { } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.Y").WithLocation(11, 12), + // (17,12): error CS0843: Auto-implemented property 'S3.X' must be fully assigned before control is returned to the caller. + // public S3() { Y = 3; } + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.X").WithLocation(17, 12) + }; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void FieldInitializers_09() + { + var source = +@"#pragma warning disable 649 +record struct S1() +{ + internal object X = 1; + internal object Y; +} +record struct S2() +{ + internal object X { get; } = 2; + internal object Y { get; } +} +record struct S3() +{ + internal object X { get; init; } + internal object Y { get; init; } = 3; +} +"; + + var expectedDiagnostics = new[] + { + // (2,15): error CS0171: Field 'S1.Y' must be fully assigned before control is returned to the caller + // record struct S1() + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.Y").WithLocation(2, 15), + // (7,15): error CS0843: Auto-implemented property 'S2.Y' must be fully assigned before control is returned to the caller. + // record struct S2() + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.Y").WithLocation(7, 15), + // (12,15): error CS0843: Auto-implemented property 'S3.X' must be fully assigned before control is returned to the caller. + // record struct S3() + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.X").WithLocation(12, 15) + }; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void FieldInitializers_10() + { + var source = +@"#pragma warning disable 649 +record struct S1(object X) +{ + internal object X = 1; + internal object Y; +} +record struct S2(object X) +{ + internal object X { get; } = 2; + internal object Y { get; } +} +record struct S3(object Y) +{ + internal object X { get; init; } + internal object Y { get; init; } = 3; +} +"; + + var expectedDiagnostics = new[] + { + // (2,15): error CS0171: Field 'S1.Y' must be fully assigned before control is returned to the caller + // record struct S1(object X) + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.Y").WithLocation(2, 15), + // (2,25): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct S1(object X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 25), + // (7,15): error CS0843: Auto-implemented property 'S2.Y' must be fully assigned before control is returned to the caller. + // record struct S2(object X) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.Y").WithLocation(7, 15), + // (7,25): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct S2(object X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(7, 25), + // (12,15): error CS0843: Auto-implemented property 'S3.X' must be fully assigned before control is returned to the caller. + // record struct S3(object Y) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.X").WithLocation(12, 15), + // (12,25): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? + // record struct S3(object Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "Y").WithArguments("Y").WithLocation(12, 25) + }; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void FieldInitializers_11() + { + var source = +@"#pragma warning disable 649 +record struct S1(object X) +{ + internal object X; + internal object Y = 1; +} +record struct S2(object X) +{ + internal object X { get; } + internal object Y { get; } = 2; +} +record struct S3(object Y) +{ + internal object X { get; init; } = 3; + internal object Y { get; init; } +} +"; + + var expectedDiagnostics = new[] + { + // (2,15): error CS0171: Field 'S1.X' must be fully assigned before control is returned to the caller + // record struct S1(object X) + Diagnostic(ErrorCode.ERR_UnassignedThis, "S1").WithArguments("S1.X").WithLocation(2, 15), + // (2,25): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct S1(object X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(2, 25), + // (7,15): error CS0843: Auto-implemented property 'S2.X' must be fully assigned before control is returned to the caller. + // record struct S2(object X) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S2").WithArguments("S2.X").WithLocation(7, 15), + // (7,25): warning CS8907: Parameter 'X' is unread. Did you forget to use it to initialize the property with that name? + // record struct S2(object X) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "X").WithArguments("X").WithLocation(7, 25), + // (12,15): error CS0843: Auto-implemented property 'S3.Y' must be fully assigned before control is returned to the caller. + // record struct S3(object Y) + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S3").WithArguments("S3.Y").WithLocation(12, 15), + // (12,25): warning CS8907: Parameter 'Y' is unread. Did you forget to use it to initialize the property with that name? + // record struct S3(object Y) + Diagnostic(ErrorCode.WRN_UnreadRecordParameter, "Y").WithArguments("Y").WithLocation(12, 25) + }; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + + [Fact] + public void FieldInitializers_12() + { + var source = +@"#pragma warning disable 649 +using System; +record struct S1(object X) +{ + internal object Y = 1; +} +record struct S2(object X) +{ + internal object Y { get; } = 2; +} +record struct S3(object X) +{ + internal object Y { get; init; } = 3; +} +class Program +{ + static void Main() + { + Console.WriteLine(new S1()); + Console.WriteLine(new S1(10)); + Console.WriteLine(new S2()); + Console.WriteLine(new S2(20)); + Console.WriteLine(new S3()); + Console.WriteLine(new S3(30)); + } +} +"; + + var expectedOutput = +@"S1 { X = } +S1 { X = 10 } +S2 { X = } +S2 { X = 20 } +S3 { X = } +S3 { X = 30 } +"; + + CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10, verify: Verification.Skipped, expectedOutput: expectedOutput); + CompileAndVerify(new[] { source, IsExternalInitTypeDefinition }, verify: Verification.Skipped, expectedOutput: expectedOutput); + } + + [WorkItem(57870, "https://github.com/dotnet/roslyn/issues/57870")] + [Fact] + public void FieldInitializers_13() + { + var source = +@"#nullable enable + +using System; + +var x = new S { P1 = ""x1"", P2 = ""x2"" }; +var y = new S { P2 = ""y2"" }; + +Console.WriteLine(y.P1); + +record struct S +{ + public string? P1 { get; init; } + public string? P2 { get; init; } = """"; +}"; + + var expectedDiagnostics = new[] + { + // (10,15): error CS0843: Auto-implemented property 'S.P1' must be fully assigned before control is returned to the caller. + // record struct S + Diagnostic(ErrorCode.ERR_UnassignedThisAutoProperty, "S").WithArguments("S.P1").WithLocation(10, 15) + }; + + var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular10); + comp.VerifyDiagnostics(expectedDiagnostics); + + comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }); + comp.VerifyDiagnostics(expectedDiagnostics); + } + [Fact] public void ExpressionTrees() {