From 9f69b45a36886d11df54bfb4780c1a1a4889800a Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 11 Jun 2024 23:36:50 -0700 Subject: [PATCH] Report diagnostic for field regardless of language version --- .../Portable/Binder/Binder_Expressions.cs | 7 +- .../Syntax/FieldAndValueKeywordTests.cs | 118 ++++++++++++++++-- 2 files changed, 112 insertions(+), 13 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 19e3505db39f..ab1b8a044624 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -1750,7 +1750,12 @@ internal void ReportFieldOrValueContextualKeywordConflictIfAny(SyntaxNode syntax case "value" when ContainingMember() is MethodSymbol { MethodKind: MethodKind.PropertySet or MethodKind.EventAdd or MethodKind.EventRemove }: { var requiredVersion = MessageID.IDS_FeatureFieldAndValueKeywords.RequiredVersion(); - if (Compilation.LanguageVersion < requiredVersion) + // For "field", report the diagnostic regardless of language version because the meaning will + // definitely change to refer to the backing field. For "value", there's less chance the meaning + // will change since the "value" parameter was in scope previously. Moreover, when compiling + // with later language versions, we won't bind both ways to check if the meaning is different. + // So for "value", we only report the diagnostic for earlier language versions. + if (name == "field" || Compilation.LanguageVersion < requiredVersion) { diagnostics.Add(ErrorCode.INF_IdentifierConflictWithContextualKeyword, syntax, name, requiredVersion.ToDisplayString()); } diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs index a3c0e188d7ce..6a01732ed73c 100644 --- a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs @@ -34,7 +34,7 @@ class D2 : A { object this[int i] { get => {{identifier}}; } } class D4 : A { object this[int i] { set { {{identifier}} = 0; } } } """; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); - if (escapeIdentifier || languageVersion > LanguageVersion.CSharp12) + if (escapeIdentifier) { comp.VerifyEmitDiagnostics(); } @@ -270,7 +270,7 @@ class C : I } """; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); - if (escapeIdentifier || languageVersion > LanguageVersion.CSharp12) + if (escapeIdentifier) { comp.VerifyEmitDiagnostics(); } @@ -386,7 +386,10 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (6,34): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { return new field(); } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 34)); } else { @@ -420,7 +423,10 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (6,34): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { return new field(); } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 34)); } else { @@ -450,7 +456,10 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (4,24): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P3 { set { (int field, int value) t = default; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "int field").WithArguments("field", "preview").WithLocation(4, 24)); } else { @@ -483,6 +492,12 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,27): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from field in new int[0] select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "from field in new int[0]").WithArguments("field", "preview").WithLocation(4, 27), + // (4,59): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from field in new int[0] select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 59), // (6,32): error CS1931: The range variable 'value' conflicts with a previous declaration of 'value' // object P3 { set { _ = from value in new int[0] select value; } } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "value").WithArguments("value").WithLocation(6, 32), @@ -533,6 +548,12 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,48): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from i in new int[0] let field = i select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "let field = i").WithArguments("field", "preview").WithLocation(4, 48), + // (4,69): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from i in new int[0] let field = i select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 69), // (6,52): error CS1931: The range variable 'value' conflicts with a previous declaration of 'value' // object P3 { set { _ = from i in new int[0] let value = i select value; } } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "value").WithArguments("value").WithLocation(6, 52), @@ -583,6 +604,12 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,48): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] join field in new int[0] on x equals field select x; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "join field in new int[0] on x equals field").WithArguments("field", "preview").WithLocation(4, 48), + // (4,85): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] join field in new int[0] on x equals field select x; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 85), // (6,53): error CS1931: The range variable 'value' conflicts with a previous declaration of 'value' // object P3 { set { _ = from x in new int[0] join value in new int[0] on x equals value select x; } } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "value").WithArguments("value").WithLocation(6, 53), @@ -633,6 +660,12 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,83): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] join y in new int[0] on x equals y into field select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "into field").WithArguments("field", "preview").WithLocation(4, 83), + // (4,101): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] join y in new int[0] on x equals y into field select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 101), // (6,88): error CS1931: The range variable 'value' conflicts with a previous declaration of 'value' // object P3 { set { _ = from x in new int[0] join y in new int[0] on x equals y into value select value; } } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "value").WithArguments("value").WithLocation(6, 88), @@ -683,6 +716,12 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,57): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] select x into field select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "into field select field").WithArguments("field", "preview").WithLocation(4, 57), + // (4,75): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { _ = from x in new int[0] select x into field select field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 75), // (6,62): error CS1931: The range variable 'value' conflicts with a previous declaration of 'value' // object P3 { set { _ = from x in new int[0] select x into value select value; } } Diagnostic(ErrorCode.ERR_QueryRangeVariableOverrides, "value").WithArguments("value").WithLocation(6, 62), @@ -733,6 +772,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,23): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { object field() => null; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "object field() => null;").WithArguments("field", "preview").WithLocation(4, 23), // (6,28): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P3 { set { void value() { } } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "value").WithArguments("value").WithLocation(6, 28), @@ -777,6 +819,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,27): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { int field = 0; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field = 0").WithArguments("field", "preview").WithLocation(4, 27), // (6,27): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P3 { set { int value = 0; } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "value").WithArguments("value").WithLocation(6, 27), @@ -823,6 +868,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (4,33): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { F(out var field); return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 33), // (6,33): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P3 { set { F(out var value); } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "value").WithArguments("value").WithLocation(6, 33), @@ -866,7 +914,10 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (4,23): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { field: return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field: return null;").WithArguments("field", "preview").WithLocation(4, 23)); } else { @@ -898,6 +949,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (3,23): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { foreach (var field in new int[0]) { } return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "foreach (var field in new int[0]) { }").WithArguments("field", "preview").WithLocation(3, 23), // (5,36): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P3 { set { foreach (var value in new int[0]) { } } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "value").WithArguments("value").WithLocation(5, 36), @@ -939,6 +993,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (3,37): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { set { foreach (var (field, @value) in new (int, int)[0]) { } } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 37), // (3,44): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P1 { set { foreach (var (field, @value) in new (int, int)[0]) { } } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "@value").WithArguments("value").WithLocation(3, 44), @@ -984,6 +1041,9 @@ class C if (languageVersion > LanguageVersion.CSharp12) { comp.VerifyEmitDiagnostics( + // (5,37): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { try { } catch (Exception field) { } return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "(Exception field)").WithArguments("field", "preview").WithLocation(5, 37), // (7,48): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object P3 { set { try { } catch (Exception value) { } } } Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "value").WithArguments("value").WithLocation(7, 48), @@ -1027,7 +1087,10 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (4,31): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { void F1() { } return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 31)); } else { @@ -1059,7 +1122,13 @@ class C var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (4,33): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { object F1(object field) => field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "object field").WithArguments("field", "preview").WithLocation(4, 33), + // (4,50): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object P1 { get { object F1(object field) => field; return null; } } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 50)); } else { @@ -1138,7 +1207,10 @@ static object P2 comp.VerifyEmitDiagnostics( // (9,20): error CS0136: A local or parameter named 'value' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter // object @value; - Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "@value").WithArguments("value").WithLocation(9, 20)); + Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "@value").WithArguments("value").WithLocation(9, 20), + // (10,14): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // (field, @value) = new C(); + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 14)); } else { @@ -1187,7 +1259,13 @@ object P var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (12,23): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // f = () => field; + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(12, 23), + // (14,25): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // f = () => C.field; + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(14, 25)); } else { @@ -1234,7 +1312,13 @@ object P var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); if (languageVersion > LanguageVersion.CSharp12) { - comp.VerifyEmitDiagnostics(); + comp.VerifyEmitDiagnostics( + // (10,28): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object F1() => field; + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 28), + // (12,30): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // object F3() => C.field; + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(12, 30)); } else { @@ -1584,10 +1668,20 @@ object P3 } """; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); - if (languageVersion > LanguageVersion.CSharp12 || escapeIdentifier) + if (escapeIdentifier) { comp.VerifyEmitDiagnostics(); } + else if (languageVersion > LanguageVersion.CSharp12) + { + comp.VerifyEmitDiagnostics( + // (13,23): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // [A(nameof(field))] void F1(int field) { } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23), + // (13,40): info CS9258: 'field' is a contextual keyword, with a specific meaning, in language version preview or later. Use '@field' to avoid a breaking change when compiling with different language versions. + // [A(nameof(field))] void F1(int field) { } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "int field").WithArguments("field", "preview").WithLocation(13, 40)); + } else { comp.VerifyEmitDiagnostics(