diff --git a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
index f5c5b69c74910..d697c4c2202be 100644
--- a/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
+++ b/src/Compilers/CSharp/Portable/Syntax/SyntaxKind.cs
@@ -699,6 +699,7 @@ public enum SyntaxKind : ushort
NullLiteralExpression = 8754,
DefaultLiteralExpression = 8755,
Utf8StringLiteralExpression = 8756,
+ FieldExpression = 8757,
// primary function expressions
TypeOfExpression = 8760,
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
index 728fafdf945a1..c9ead91a833c5 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
@@ -174,7 +174,7 @@ public void RefReturnStaticProperty()
class Program
{
static int field = 0;
- static ref int P { get { return ref field; } }
+ static ref int P { get { return ref @field; } }
static ref int M()
{
@@ -229,7 +229,7 @@ public void RefReturnClassInstanceProperty()
class Program
{
int field = 0;
- ref int P { get { return ref field; } }
+ ref int P { get { return ref @field; } }
ref int M()
{
diff --git a/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs b/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs
index a1f47885298b6..609973fb80b61 100644
--- a/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs
+++ b/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs
@@ -660,7 +660,7 @@ public dynamic Field
{
get
{
- dynamic d = field + field;
+ dynamic d = @field + @field;
return d;
}
set
@@ -695,7 +695,7 @@ public static void Main(string[] args)
-
+
diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
new file mode 100644
index 0000000000000..92aa16f749918
--- /dev/null
+++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
@@ -0,0 +1,403 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable disable
+
+using Microsoft.CodeAnalysis.CSharp.Symbols;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.CSharp.UnitTests
+{
+ public class FieldKeywordTests : CSharpTestBase
+ {
+ private static string IncludeExpectedOutput(string expectedOutput) => ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null;
+
+ [Fact]
+ public void Field_01()
+ {
+ string source = """
+ using System;
+ using System.Reflection;
+ class C
+ {
+ public object P => field = 1;
+ public static object Q { get => field = 2; }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ Console.WriteLine((new C().P, C.Q));
+ foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
+ Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly);
+ foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Static))
+ Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly);
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(source, expectedOutput: """
+ (1, 2)
+ k__BackingField: False
+ k__BackingField: False
+ """);
+ verifier.VerifyIL("C.P.get", """
+ {
+ // Code size 16 (0x10)
+ .maxstack 3
+ .locals init (object V_0)
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.1
+ IL_0002: box "int"
+ IL_0007: dup
+ IL_0008: stloc.0
+ IL_0009: stfld "object C.k__BackingField"
+ IL_000e: ldloc.0
+ IL_000f: ret
+ }
+ """);
+ verifier.VerifyIL("C.Q.get", """
+ {
+ // Code size 13 (0xd)
+ .maxstack 2
+ IL_0000: ldc.i4.2
+ IL_0001: box "int"
+ IL_0006: dup
+ IL_0007: stsfld "object C.k__BackingField"
+ IL_000c: ret
+ }
+ """);
+ var comp = (CSharpCompilation)verifier.Compilation;
+ var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings();
+ var expectedMembers = new[]
+ {
+ "System.Object C.k__BackingField",
+ "System.Object C.P { get; }",
+ "System.Object C.P.get",
+ "System.Object C.k__BackingField",
+ "System.Object C.Q { get; }",
+ "System.Object C.Q.get",
+ "C..ctor()"
+ };
+ Assert.Equal(expectedMembers, actualMembers);
+ }
+
+ [Fact]
+ public void Field_02()
+ {
+ string source = """
+ using System;
+ class C
+ {
+ public object P => Initialize(out field, 1);
+ public object Q { get => Initialize(out field, 2); }
+ static object Initialize(out object field, object value)
+ {
+ field = value;
+ return field;
+ }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ var c = new C();
+ Console.WriteLine((c.P, c.Q));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(source, expectedOutput: "(1, 2)");
+ verifier.VerifyIL("C.P.get", """
+ {
+ // Code size 18 (0x12)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldflda "object C.k__BackingField"
+ IL_0006: ldc.i4.1
+ IL_0007: box "int"
+ IL_000c: call "object C.Initialize(out object, object)"
+ IL_0011: ret
+ }
+ """);
+ verifier.VerifyIL("C.Q.get", """
+ {
+ // Code size 18 (0x12)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldflda "object C.k__BackingField"
+ IL_0006: ldc.i4.2
+ IL_0007: box "int"
+ IL_000c: call "object C.Initialize(out object, object)"
+ IL_0011: ret
+ }
+ """);
+ var comp = (CSharpCompilation)verifier.Compilation;
+ var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings();
+ var expectedMembers = new[]
+ {
+ "System.Object C.k__BackingField",
+ "System.Object C.P { get; }",
+ "System.Object C.P.get",
+ "System.Object C.k__BackingField",
+ "System.Object C.Q { get; }",
+ "System.Object C.Q.get",
+ "System.Object C.Initialize(out System.Object field, System.Object value)",
+ "C..ctor()"
+ };
+ Assert.Equal(expectedMembers, actualMembers);
+ }
+
+ [Fact]
+ public void Field_03()
+ {
+ string source = """
+ using System;
+ using System.Reflection;
+ class C
+ {
+ public object P { get { return field; } init { field = 1; } }
+ public object Q { init { field = 2; } }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
+ Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly);
+ }
+ }
+ """;
+ CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("""
+ k__BackingField: False
+ k__BackingField: False
+ """));
+ }
+
+ [Fact]
+ public void FieldReference_01()
+ {
+ string source = """
+ class C
+ {
+ static C _other = new();
+ object P
+ {
+ get { return _other.field; }
+ set { _ = field; }
+ }
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ // get { return _other.field; }
+ Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29),
+ // (7,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // set { _ = field; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(7, 19));
+ }
+
+ [Fact]
+ public void FieldReference_02()
+ {
+ string source = """
+ class C
+ {
+ C P
+ {
+ get { return null; }
+ set { field = value.field; }
+ }
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (6,15): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // set { field = value.field; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 15),
+ // (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
+ // set { field = value.field; }
+ Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29));
+ var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings();
+ var expectedMembers = new[]
+ {
+ "C C.k__BackingField",
+ "C C.P { get; set; }",
+ "C C.P.get",
+ "void C.P.set",
+ "C..ctor()"
+ };
+ Assert.Equal(expectedMembers, actualMembers);
+ }
+
+ [Fact]
+ public void FieldReference_03()
+ {
+ string source = """
+ class C
+ {
+ int P
+ {
+ get { return field; }
+ set { _ = this is { field: 0 }; }
+ }
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (5,22): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // get { return field; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 22),
+ // (6,29): error CS0117: 'C' does not contain a definition for 'field'
+ // set { _ = this is { field: 0 }; }
+ Diagnostic(ErrorCode.ERR_NoSuchMember, "field").WithArguments("C", "field").WithLocation(6, 29));
+ }
+
+ [Fact]
+ public void FieldInInitializer_01()
+ {
+ string source = """
+ class C
+ {
+ object P { get; } = F(field);
+ static object F(object value) => value;
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (3,27): error CS0103: The name 'field' does not exist in the current context
+ // object P { get; } = F(field);
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 27));
+ var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings();
+ var expectedMembers = new[]
+ {
+ "System.Object C.k__BackingField",
+ "System.Object C.P { get; }",
+ "System.Object C.P.get",
+ "System.Object C.F(System.Object value)",
+ "C..ctor()"
+ };
+ Assert.Equal(expectedMembers, actualMembers);
+ }
+
+ [Fact]
+ public void FieldInInitializer_02()
+ {
+ string source = """
+ class C
+ {
+ object P { get => field; } = field;
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (3,12): error CS8050: Only auto-implemented properties can have initializers.
+ // object P { get => field; } = field;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12),
+ // (3,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object P { get => field; } = field;
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 23),
+ // (3,34): error CS0103: The name 'field' does not exist in the current context
+ // object P { get => field; } = field;
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 34));
+ }
+
+ [Fact]
+ public void FieldInInitializer_03()
+ {
+ string source = """
+ class C
+ {
+ object P { set { } } = field;
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (3,12): error CS8050: Only auto-implemented properties can have initializers.
+ // object P { set { } } = field;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12),
+ // (3,28): error CS0103: The name 'field' does not exist in the current context
+ // object P { set { } } = field;
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 28));
+ }
+
+ [Fact]
+ public void Attribute_01()
+ {
+ string source = """
+ using System;
+ class A : Attribute
+ {
+ public A(object o) { }
+ }
+ class C
+ {
+ [A(field)] object P1 { get { return null; } set { } }
+ }
+ """;
+
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (8,8): error CS0103: The name 'field' does not exist in the current context
+ // [A(field)] object P1 { get { return null; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(8, 8));
+
+ var tree = comp.SyntaxTrees.Single();
+ var model = comp.GetSemanticModel(tree);
+ var attributeArguments = tree.GetRoot().DescendantNodes().OfType().Select(arg => arg.Expression).ToArray();
+
+ var argument = attributeArguments[0];
+ Assert.IsType(argument);
+ Assert.Null(model.GetSymbolInfo(argument).Symbol);
+ }
+
+ [Fact]
+ public void Attribute_02()
+ {
+ string source = """
+ using System;
+ class A : Attribute
+ {
+ public A(object o) { }
+ }
+ class C
+ {
+ object P2 { [A(field)] get { return null; } set { } }
+ object P3 { get { return null; } [A(field)] set { } }
+ }
+ """;
+
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (8,20): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object P2 { [A(field)] get { return null; } set { } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(8, 20),
+ // (8,20): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
+ // object P2 { [A(field)] get { return null; } set { } }
+ Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(8, 20),
+ // (9,41): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object P3 { get { return null; } [A(field)] set { } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(9, 41),
+ // (9,41): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
+ // object P3 { get { return null; } [A(field)] set { } }
+ Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(9, 41));
+
+ var tree = comp.SyntaxTrees.Single();
+ var model = comp.GetSemanticModel(tree);
+ var attributeArguments = tree.GetRoot().DescendantNodes().OfType().Select(arg => arg.Expression).ToArray();
+
+ var argument = attributeArguments[0];
+ Assert.IsType(argument);
+ Assert.Equal("System.Object C.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
+
+ argument = attributeArguments[1];
+ Assert.IsType(argument);
+ Assert.Equal("System.Object C.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
+ }
+ }
+}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs
index f00e3f59dddbe..2dfa0ee42454f 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs
@@ -1922,7 +1922,7 @@ class Derived : Base
";
CreateCompilationWithMscorlib45(text).VerifyDiagnostics(
// (15,29): error CS8148: 'Derived.Proprty1' must match by reference return of overridden member 'Base.Proprty1'
- // public override ref int Proprty1 { get { return ref field; } }
+ // public override ref int Proprty1 { get { return ref @field; } }
Diagnostic(ErrorCode.ERR_CantChangeRefReturnOnOverride, "Proprty1").WithArguments("Derived.Proprty1", "Base.Proprty1").WithLocation(15, 29),
// (16,25): error CS8148: 'Derived.Property2' must match by reference return of overridden member 'Base.Property2'
// public override int Property2 { get { return 0; } }
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs
index c026c48a2251b..8930d1e1ac959 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs
@@ -546,7 +546,7 @@ public void RefReadonlyReturningExpressionBodiedIndexer()
class C
{
int field = 0;
- public ref readonly int this[in int arg] => ref field;
+ public ref readonly int this[in int arg] => ref @field;
}");
comp.VerifyDiagnostics();
@@ -574,7 +574,7 @@ public void RefReadonlyReturningExpressionBodiedIndexer1()
class C
{
int field = 0;
- public ref readonly int this[in int arg] => ref field;
+ public ref readonly int this[in int arg] => ref @field;
}");
comp.VerifyDiagnostics();
diff --git a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs
index 6edb119c630fa..88f2342f45eec 100644
--- a/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Generated/Syntax.Test.xml.Generated.cs
@@ -121,6 +121,9 @@ private static Syntax.InternalSyntax.BaseExpressionSyntax GenerateBaseExpression
private static Syntax.InternalSyntax.LiteralExpressionSyntax GenerateLiteralExpression()
=> InternalSyntaxFactory.LiteralExpression(SyntaxKind.ArgListExpression, InternalSyntaxFactory.Token(SyntaxKind.ArgListKeyword));
+ private static Syntax.InternalSyntax.FieldExpressionSyntax GenerateFieldExpression()
+ => InternalSyntaxFactory.FieldExpression(InternalSyntaxFactory.Token(SyntaxKind.FieldKeyword));
+
private static Syntax.InternalSyntax.MakeRefExpressionSyntax GenerateMakeRefExpression()
=> InternalSyntaxFactory.MakeRefExpression(InternalSyntaxFactory.Token(SyntaxKind.MakeRefKeyword), InternalSyntaxFactory.Token(SyntaxKind.OpenParenToken), GenerateIdentifierName(), InternalSyntaxFactory.Token(SyntaxKind.CloseParenToken));
@@ -1159,6 +1162,16 @@ public void TestLiteralExpressionFactoryAndProperties()
AttachAndCheckDiagnostics(node);
}
+ [Fact]
+ public void TestFieldExpressionFactoryAndProperties()
+ {
+ var node = GenerateFieldExpression();
+
+ Assert.Equal(SyntaxKind.FieldKeyword, node.Token.Kind);
+
+ AttachAndCheckDiagnostics(node);
+ }
+
[Fact]
public void TestMakeRefExpressionFactoryAndProperties()
{
@@ -4826,6 +4839,32 @@ public void TestLiteralExpressionIdentityRewriter()
Assert.Same(oldNode, newNode);
}
+ [Fact]
+ public void TestFieldExpressionTokenDeleteRewriter()
+ {
+ var oldNode = GenerateFieldExpression();
+ var rewriter = new TokenDeleteRewriter();
+ var newNode = rewriter.Visit(oldNode);
+
+ if(!oldNode.IsMissing)
+ {
+ Assert.NotEqual(oldNode, newNode);
+ }
+
+ Assert.NotNull(newNode);
+ Assert.True(newNode.IsMissing, "No tokens => missing");
+ }
+
+ [Fact]
+ public void TestFieldExpressionIdentityRewriter()
+ {
+ var oldNode = GenerateFieldExpression();
+ var rewriter = new IdentityRewriter();
+ var newNode = rewriter.Visit(oldNode);
+
+ Assert.Same(oldNode, newNode);
+ }
+
[Fact]
public void TestMakeRefExpressionTokenDeleteRewriter()
{
@@ -10298,6 +10337,9 @@ private static BaseExpressionSyntax GenerateBaseExpression()
private static LiteralExpressionSyntax GenerateLiteralExpression()
=> SyntaxFactory.LiteralExpression(SyntaxKind.ArgListExpression, SyntaxFactory.Token(SyntaxKind.ArgListKeyword));
+ private static FieldExpressionSyntax GenerateFieldExpression()
+ => SyntaxFactory.FieldExpression(SyntaxFactory.Token(SyntaxKind.FieldKeyword));
+
private static MakeRefExpressionSyntax GenerateMakeRefExpression()
=> SyntaxFactory.MakeRefExpression(SyntaxFactory.Token(SyntaxKind.MakeRefKeyword), SyntaxFactory.Token(SyntaxKind.OpenParenToken), GenerateIdentifierName(), SyntaxFactory.Token(SyntaxKind.CloseParenToken));
@@ -11336,6 +11378,16 @@ public void TestLiteralExpressionFactoryAndProperties()
Assert.Equal(node, newNode);
}
+ [Fact]
+ public void TestFieldExpressionFactoryAndProperties()
+ {
+ var node = GenerateFieldExpression();
+
+ Assert.Equal(SyntaxKind.FieldKeyword, node.Token.Kind());
+ var newNode = node.WithToken(node.Token);
+ Assert.Equal(node, newNode);
+ }
+
[Fact]
public void TestMakeRefExpressionFactoryAndProperties()
{
@@ -15003,6 +15055,32 @@ public void TestLiteralExpressionIdentityRewriter()
Assert.Same(oldNode, newNode);
}
+ [Fact]
+ public void TestFieldExpressionTokenDeleteRewriter()
+ {
+ var oldNode = GenerateFieldExpression();
+ var rewriter = new TokenDeleteRewriter();
+ var newNode = rewriter.Visit(oldNode);
+
+ if(!oldNode.IsMissing)
+ {
+ Assert.NotEqual(oldNode, newNode);
+ }
+
+ Assert.NotNull(newNode);
+ Assert.True(newNode.IsMissing, "No tokens => missing");
+ }
+
+ [Fact]
+ public void TestFieldExpressionIdentityRewriter()
+ {
+ var oldNode = GenerateFieldExpression();
+ var rewriter = new IdentityRewriter();
+ var newNode = rewriter.Visit(oldNode);
+
+ Assert.Same(oldNode, newNode);
+ }
+
[Fact]
public void TestMakeRefExpressionTokenDeleteRewriter()
{
diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs
new file mode 100644
index 0000000000000..2870420d8fb1f
--- /dev/null
+++ b/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs
@@ -0,0 +1,2008 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable disable
+
+using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.CodeAnalysis.CSharp.UnitTests
+{
+ public class FieldKeywordParsingTests : ParsingTests
+ {
+ public FieldKeywordParsingTests(ITestOutputHelper output) : base(output)
+ {
+ }
+
+ private static bool IsParsedAsToken(LanguageVersion languageVersion, bool escapeIdentifier)
+ {
+ return !escapeIdentifier && languageVersion > LanguageVersion.CSharp12;
+ }
+
+ private void IdentifierNameOrFieldExpression(LanguageVersion languageVersion, bool escapeIdentifier)
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier, IsParsedAsToken(languageVersion, escapeIdentifier));
+ }
+
+ private void IdentifierNameOrFieldExpression(LanguageVersion languageVersion, bool escapeIdentifier, bool isParsedAsToken)
+ {
+ if (isParsedAsToken)
+ {
+ N(SyntaxKind.FieldExpression);
+ {
+ N(SyntaxKind.FieldKeyword);
+ }
+ }
+ else
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, GetFieldIdentifier(escapeIdentifier));
+ }
+ }
+ }
+
+ private static string GetFieldIdentifier(bool escapeIdentifier)
+ {
+ return escapeIdentifier ? "@field" : "field";
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Property_Initializer(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P { get; set; } = field;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.GetAccessorDeclaration);
+ {
+ N(SyntaxKind.GetKeyword);
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EqualsValueClause);
+ {
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Property_ExpressionBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ UsingTree($$"""
+ class C
+ {
+ object P => field;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier: false, expectedParsedAsToken);
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PropertyGet_ExpressionBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ UsingTree($$"""
+ class C
+ {
+ object P { get => field; }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.GetAccessorDeclaration);
+ {
+ N(SyntaxKind.GetKeyword);
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier: false, expectedParsedAsToken);
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PropertyGet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ UsingTree($$"""
+ class C
+ {
+ object P { get { return field; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.GetAccessorDeclaration);
+ {
+ N(SyntaxKind.GetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ReturnStatement);
+ {
+ N(SyntaxKind.ReturnKeyword);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier: false, expectedParsedAsToken);
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PropertySet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool useInit)
+ {
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ UsingTree($$"""
+ class C
+ {
+ object P { {{(useInit ? "init" : "set")}} { field = 0; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(useInit ? SyntaxKind.InitAccessorDeclaration : SyntaxKind.SetAccessorDeclaration);
+ {
+ N(useInit ? SyntaxKind.InitKeyword : SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier: false, expectedParsedAsToken);
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Indexer_ExpressionBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object this[int i] => field;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.IndexerDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ThisKeyword);
+ N(SyntaxKind.BracketedParameterList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "i");
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void IndexerGet_ExpressionBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object this[int i] { get => field; }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.IndexerDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ThisKeyword);
+ N(SyntaxKind.BracketedParameterList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "i");
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.GetAccessorDeclaration);
+ {
+ N(SyntaxKind.GetKeyword);
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void IndexerGet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object this[int i] { get { return field; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.IndexerDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ThisKeyword);
+ N(SyntaxKind.BracketedParameterList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "i");
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.GetAccessorDeclaration);
+ {
+ N(SyntaxKind.GetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ReturnStatement);
+ {
+ N(SyntaxKind.ReturnKeyword);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void IndexerSet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool useInit)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object this[int i] { {{(useInit ? "init" : "set")}} { field = 0; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.IndexerDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ThisKeyword);
+ N(SyntaxKind.BracketedParameterList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "i");
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(useInit ? SyntaxKind.InitAccessorDeclaration : SyntaxKind.SetAccessorDeclaration);
+ {
+ N(useInit ? SyntaxKind.InitKeyword : SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void EventAccessor(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool useRemove)
+ {
+ UsingTree($$"""
+ class C
+ {
+ event EventHandler E { {{(useRemove ? "remove" : "add")}} { field = null; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.EventDeclaration);
+ {
+ N(SyntaxKind.EventKeyword);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "EventHandler");
+ }
+ N(SyntaxKind.IdentifierToken, "E");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(useRemove ? SyntaxKind.RemoveAccessorDeclaration : SyntaxKind.AddAccessorDeclaration);
+ {
+ N(useRemove ? SyntaxKind.RemoveKeyword : SyntaxKind.AddKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NullLiteralExpression);
+ {
+ N(SyntaxKind.NullKeyword);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ExplicitImplementation_PropertySet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool useInit)
+ {
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ UsingTree($$"""
+ class C
+ {
+ object I.P { {{(useInit ? "init" : "set")}} { field = 0; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ExplicitInterfaceSpecifier);
+ {
+ N(SyntaxKind.GenericName);
+ {
+ N(SyntaxKind.IdentifierToken, "I");
+ N(SyntaxKind.TypeArgumentList);
+ {
+ N(SyntaxKind.LessThanToken);
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.GreaterThanToken);
+ }
+ }
+ N(SyntaxKind.DotToken);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(useInit ? SyntaxKind.InitAccessorDeclaration : SyntaxKind.SetAccessorDeclaration);
+ {
+ N(useInit ? SyntaxKind.InitKeyword : SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier: false, expectedParsedAsToken);
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ExplicitImplementation_IndexerSet_BlockBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool useInit)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object I.this[int i] { {{(useInit ? "init" : "set")}} { field = 0; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.IndexerDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.ExplicitInterfaceSpecifier);
+ {
+ N(SyntaxKind.GenericName);
+ {
+ N(SyntaxKind.IdentifierToken, "I");
+ N(SyntaxKind.TypeArgumentList);
+ {
+ N(SyntaxKind.LessThanToken);
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.GreaterThanToken);
+ }
+ }
+ N(SyntaxKind.DotToken);
+ }
+ N(SyntaxKind.ThisKeyword);
+ N(SyntaxKind.BracketedParameterList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.IntKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "i");
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(useInit ? SyntaxKind.InitAccessorDeclaration : SyntaxKind.SetAccessorDeclaration);
+ {
+ N(useInit ? SyntaxKind.InitKeyword : SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Invocation(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}();
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.InvocationExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.ArgumentList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.CloseParenToken);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ElementAccess(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}[0];
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.ElementAccessExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.BracketedArgumentList);
+ {
+ N(SyntaxKind.OpenBracketToken);
+ N(SyntaxKind.Argument);
+ {
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.CloseBracketToken);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PreIncrement(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => ++{{GetFieldIdentifier(escapeIdentifier)}};
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.PreIncrementExpression);
+ {
+ N(SyntaxKind.PlusPlusToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PostIncrement(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}++;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.PostIncrementExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.PlusPlusToken);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PointerIndirection(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => *{{GetFieldIdentifier(escapeIdentifier)}};
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.PointerIndirectionExpression);
+ {
+ N(SyntaxKind.AsteriskToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void PointerMemberAccess(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}->F;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.PointerMemberAccessExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.MinusGreaterThanToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "F");
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ConditionalAccess(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}?.F;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.ConditionalAccessExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.QuestionToken);
+ N(SyntaxKind.MemberBindingExpression);
+ {
+ N(SyntaxKind.DotToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "F");
+ }
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void NullableSuppression(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P => {{GetFieldIdentifier(escapeIdentifier)}}!;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.SuppressNullableWarningExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.ExclamationToken);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Arguments(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P => F({{identifier}}, {{identifier}}, out {{identifier}});
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.InvocationExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "F");
+ }
+ N(SyntaxKind.ArgumentList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.Argument);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ N(SyntaxKind.CommaToken);
+ N(SyntaxKind.Argument);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ N(SyntaxKind.CommaToken);
+ N(SyntaxKind.Argument);
+ {
+ N(SyntaxKind.OutKeyword);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ N(SyntaxKind.CloseParenToken);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void QualifiedName_01(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P => {{identifier}}.B;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.SimpleMemberAccessExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.DotToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "B");
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void QualifiedName_02(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P => A.{{identifier}};
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.SimpleMemberAccessExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "A");
+ }
+ N(SyntaxKind.DotToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, identifier);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void AliasQualifiedName(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P => {{identifier}}::A.B;
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.SimpleMemberAccessExpression);
+ {
+ N(SyntaxKind.AliasQualifiedName);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, identifier);
+ }
+ N(SyntaxKind.ColonColonToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "A");
+ }
+ }
+ N(SyntaxKind.DotToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "B");
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void NameOf(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P { set { _ = nameof({{GetFieldIdentifier(escapeIdentifier)}}); } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "_");
+ }
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.InvocationExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "nameof");
+ }
+ N(SyntaxKind.ArgumentList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.Argument);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ N(SyntaxKind.CloseParenToken);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Lvalue(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ UsingTree($$"""
+ class C
+ {
+ object P { set { {{GetFieldIdentifier(escapeIdentifier)}} = 0; } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.NumericLiteralExpression);
+ {
+ N(SyntaxKind.NumericLiteralToken, "0");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void NewTypeName(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P { set { _ = new {{identifier}}(); } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "_");
+ }
+ N(SyntaxKind.EqualsToken);
+ N(SyntaxKind.ObjectCreationExpression);
+ {
+ N(SyntaxKind.NewKeyword);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, identifier);
+ }
+ N(SyntaxKind.ArgumentList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.CloseParenToken);
+ }
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void LambdaBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P => {{identifier}} => {{identifier}};
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.SimpleLambdaExpression);
+ {
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.IdentifierToken, identifier);
+ }
+ N(SyntaxKind.EqualsGreaterThanToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void LocalFunctionBody(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P { set { void Local(object {{identifier}}) { _ = {{identifier}}; } } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.LocalFunctionStatement);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.VoidKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "Local");
+ N(SyntaxKind.ParameterList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.Parameter);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, identifier);
+ }
+ N(SyntaxKind.CloseParenToken);
+ }
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.ExpressionStatement);
+ {
+ N(SyntaxKind.SimpleAssignmentExpression);
+ {
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "_");
+ }
+ N(SyntaxKind.EqualsToken);
+ IdentifierNameOrFieldExpression(languageVersion, escapeIdentifier);
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void CatchDeclaration(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ bool escapeIdentifier)
+ {
+ string identifier = GetFieldIdentifier(escapeIdentifier);
+ UsingTree($$"""
+ class C
+ {
+ object P { set { try { } catch (Exception {{identifier}}) { } } }
+ }
+ """,
+ TestOptions.Regular.WithLanguageVersion(languageVersion));
+
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "P");
+ N(SyntaxKind.AccessorList);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.SetAccessorDeclaration);
+ {
+ N(SyntaxKind.SetKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.TryStatement);
+ {
+ N(SyntaxKind.TryKeyword);
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.CatchClause);
+ {
+ N(SyntaxKind.CatchKeyword);
+ N(SyntaxKind.CatchDeclaration);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "Exception");
+ }
+ N(SyntaxKind.IdentifierToken, identifier);
+ N(SyntaxKind.CloseParenToken);
+ }
+ N(SyntaxKind.Block);
+ {
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ [Fact]
+ public void Incremental_ChangeBetweenMethodAndProperty()
+ {
+ var tree = ParseTree("""
+ class C
+ {
+ object F() => field;
+ }
+ """,
+ TestOptions.RegularPreview);
+
+ verifyMethod(tree);
+ verifyProperty(tree.WithRemoveFirst("()"));
+
+ tree = ParseTree("""
+ class C
+ {
+ object F => field;
+ }
+ """,
+ TestOptions.RegularPreview);
+
+ verifyProperty(tree);
+ verifyMethod(tree.WithInsertBefore(" =>", "()"));
+
+ void verifyMethod(SyntaxTree tree)
+ {
+ UsingTree(tree);
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.MethodDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "F");
+ N(SyntaxKind.ParameterList);
+ {
+ N(SyntaxKind.OpenParenToken);
+ N(SyntaxKind.CloseParenToken);
+ }
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.IdentifierName);
+ {
+ N(SyntaxKind.IdentifierToken, "field");
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+
+ void verifyProperty(SyntaxTree tree)
+ {
+ UsingTree(tree);
+ N(SyntaxKind.CompilationUnit);
+ {
+ N(SyntaxKind.ClassDeclaration);
+ {
+ N(SyntaxKind.ClassKeyword);
+ N(SyntaxKind.IdentifierToken, "C");
+ N(SyntaxKind.OpenBraceToken);
+ N(SyntaxKind.PropertyDeclaration);
+ {
+ N(SyntaxKind.PredefinedType);
+ {
+ N(SyntaxKind.ObjectKeyword);
+ }
+ N(SyntaxKind.IdentifierToken, "F");
+ N(SyntaxKind.ArrowExpressionClause);
+ {
+ N(SyntaxKind.EqualsGreaterThanToken);
+ N(SyntaxKind.FieldExpression);
+ {
+ N(SyntaxKind.FieldKeyword);
+ }
+ }
+ N(SyntaxKind.SemicolonToken);
+ }
+ N(SyntaxKind.CloseBraceToken);
+ }
+ N(SyntaxKind.EndOfFileToken);
+ }
+ EOF();
+ }
+ }
+ }
+}
diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs
index 521fd20434c66..930ced6a0a2bc 100644
--- a/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Parsing/ParsingTests.cs
@@ -148,9 +148,13 @@ protected SyntaxTree UsingTree(string text, params DiagnosticDescription[] expec
}
protected SyntaxTree UsingTree(string text, CSharpParseOptions? options, params DiagnosticDescription[] expectedErrors)
+ {
+ return UsingTree(ParseTree(text, options), expectedErrors);
+ }
+
+ protected SyntaxTree UsingTree(SyntaxTree tree, params DiagnosticDescription[] expectedErrors)
{
VerifyEnumeratorConsumed();
- var tree = ParseTree(text, options);
_node = tree.GetCompilationUnitRoot();
var actualErrors = _node.GetDiagnostics();
actualErrors.Verify(expectedErrors);
diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
index 33deb66c5bd05..c3f62094f7944 100644
--- a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
@@ -241,15 +241,36 @@ public void IdentifierToken_Invocation(
class C
{
Func field;
- object P1 { get { return field(); } }
- object P2 { get { return @field(); } }
+ Func P1 { get { _ = field(); return null; } }
+ Func P2 { get { _ = @field(); return null; } }
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
comp.VerifyEmitDiagnostics(
- // (6,30): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P1 { get { return field(); } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 30));
+ // (6,33): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // Func P1 { get { _ = field(); return null; } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 33));
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void IdentifierToken_Index(
+ [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ string source = """
+ #pragma warning disable 649
+ class C
+ {
+ object[] field;
+ object[] P1 { get { _ = field[0]; return null; } }
+ object[] P2 { get { _ = @field[0]; return null; } }
+ }
+ """;
+ var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ comp.VerifyEmitDiagnostics(
+ // (5,29): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object[] P1 { get { _ = field[0]; return null; } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 29));
}
[Theory]
@@ -561,6 +582,7 @@ public void Deconstruction(
[CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
string source = """
+ #pragma warning disable 168 // variable is declared but never used
class C
{
void Deconstruct(out object x, out object y) => throw null;
@@ -577,12 +599,12 @@ static object P1
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
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
+ // (10,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),
- // (10,14): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ Diagnostic(ErrorCode.ERR_LocalIllegallyOverrides, "@value").WithArguments("value").WithLocation(10, 20),
+ // (11,14): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
// (field, @value) = new C();
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 14));
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(11, 14));
}
[Theory]
@@ -813,7 +835,26 @@ event EventHandler E
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics();
+ if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp12)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (12,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // [A(nameof(field))] get { return null; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(12, 19),
+ // (12,19): error CS8081: Expression does not have a name.
+ // [A(nameof(field))] get { return null; }
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(12, 19),
+ // (13,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // [A(nameof(field))] set { }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 19),
+ // (13,19): error CS8081: Expression does not have a name.
+ // [A(nameof(field))] set { }
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(13, 19));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
@@ -846,6 +887,16 @@ object P1
{
comp.VerifyEmitDiagnostics();
}
+ else if (languageVersion > LanguageVersion.CSharp12)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (13,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // [A(nameof(field))] void F1(int field) { }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23),
+ // (13,23): error CS8081: Expression does not have a name.
+ // [A(nameof(field))] void F1(int field) { }
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(13, 23));
+ }
else
{
comp.VerifyEmitDiagnostics(
@@ -854,5 +905,43 @@ object P1
Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23));
}
}
+
+ [Fact]
+ public void NameOf_01()
+ {
+ string source = """
+ class C
+ {
+ object P => nameof(field);
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (3,24): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object P => nameof(field);
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 24),
+ // (3,24): error CS8081: Expression does not have a name.
+ // object P => nameof(field);
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(3, 24));
+ }
+
+ [Fact]
+ public void NameOf_02()
+ {
+ string source = """
+ class C
+ {
+ object P { set { _ = nameof(field); } }
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (3,33): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object P { set { _ = nameof(field); } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 33),
+ // (3,33): error CS8081: Expression does not have a name.
+ // object P { set { _ = nameof(field); } }
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(3, 33));
+ }
}
}
diff --git a/src/Compilers/Core/Portable/Syntax/GreenNode.cs b/src/Compilers/Core/Portable/Syntax/GreenNode.cs
index 9ef6a04ef4c59..222883bf0edb8 100644
--- a/src/Compilers/Core/Portable/Syntax/GreenNode.cs
+++ b/src/Compilers/Core/Portable/Syntax/GreenNode.cs
@@ -268,6 +268,7 @@ internal enum NodeFlags : ushort
FactoryContextIsInAsync = 1 << 2,
FactoryContextIsInQuery = 1 << 3,
FactoryContextIsInIterator = FactoryContextIsInQuery, // VB does not use "InQuery", but uses "InIterator" instead
+ FactoryContextIsInFieldKeywordContext = 1 << 4,
// Flags that are inherited upwards when building parent nodes. They should all start with "Contains" to
// indicate that the information could be found on it or anywhere in its children.
@@ -275,15 +276,15 @@ internal enum NodeFlags : ushort
///
/// If this node, or any of its descendants has annotations attached to them.
///
- ContainsAnnotations = 1 << 4,
+ ContainsAnnotations = 1 << 5,
///
/// If this node, or any of its descendants has attributes attached to it.
///
- ContainsAttributes = 1 << 5,
- ContainsDiagnostics = 1 << 6,
- ContainsDirectives = 1 << 7,
- ContainsSkippedText = 1 << 8,
- ContainsStructuredTrivia = 1 << 9,
+ ContainsAttributes = 1 << 6,
+ ContainsDiagnostics = 1 << 7,
+ ContainsDirectives = 1 << 8,
+ ContainsSkippedText = 1 << 9,
+ ContainsStructuredTrivia = 1 << 10,
InheritMask = IsNotMissing | ContainsAnnotations | ContainsAttributes | ContainsDiagnostics | ContainsDirectives | ContainsSkippedText | ContainsStructuredTrivia,
}
@@ -336,6 +337,14 @@ internal bool ParsedInIterator
}
}
+ internal bool ParsedInFieldKeywordContext
+ {
+ get
+ {
+ return (this.Flags & NodeFlags.FactoryContextIsInFieldKeywordContext) != 0;
+ }
+ }
+
public bool ContainsSkippedText
{
get
diff --git a/src/EditorFeatures/Test/Workspaces/ClassificationTypeNamesTests.cs b/src/EditorFeatures/Test/Workspaces/ClassificationTypeNamesTests.cs
index e446a3a33ceac..b5766d5f49881 100644
--- a/src/EditorFeatures/Test/Workspaces/ClassificationTypeNamesTests.cs
+++ b/src/EditorFeatures/Test/Workspaces/ClassificationTypeNamesTests.cs
@@ -23,7 +23,7 @@ public static IEnumerable AllPublicClassificationTypeNames
.Select(f => new[] { f.Name, f.GetRawConstantValue() });
public static IEnumerable AllClassificationTypeNames => typeof(ClassificationTypeNames).GetAllFields().Where(
- field => field.GetValue(null) is string value).Select(field => new[] { field.GetValue(null) });
+ f => f.GetValue(null) is string value).Select(f => new[] { f.GetValue(null) });
[Theory]
[MemberData(nameof(AllPublicClassificationTypeNames))]
From 4111c76dfc45a7fdd7c52e6832bcf197c8b72015 Mon Sep 17 00:00:00 2001
From: Cyrus Najmabadi
Date: Tue, 6 Aug 2024 15:59:59 -0700
Subject: [PATCH 02/18] Update 'use auto property' to support using the `field`
keyword (#74629)
Co-authored-by: Charles Stoner <10732005+cston@users.noreply.github.com>
Co-authored-by: Rikki Gibson
---
.../CSharpUseAutoPropertyAnalyzer.cs | 133 +-
.../Tests/CSharpAnalyzers.UnitTests.projitems | 1 +
.../UseAutoProperty/UseAutoPropertyTests.cs | 23 +-
.../UseAutoPropertyTests_Field.cs | 1379 +++++++++++++++++
.../Core/Analyzers/Analyzers.projitems | 3 +
.../AbstractUseAutoPropertyAnalyzer.cs | 547 +++++--
.../UseAutoProperty/AccessedFields.cs | 34 +
.../UseAutoProperty/AnalysisResult.cs | 40 +
.../UseAutoPropertiesHelpers.cs | 17 +
.../VisualBasicUseAutoPropertyAnalyzer.vb | 27 +-
.../CSharpUseAutoPropertyCodeFixProvider.cs | 268 ++--
.../SingleLinePropertyFormattingRule.cs | 47 +
.../UseAutoPropertyRewriter.cs | 61 +
.../AbstractUseAutoPropertyCodeFixProvider.cs | 58 +-
...sualBasicUseAutoPropertyCodeFixProvider.vb | 28 +-
.../Extensions/ExpressionSyntaxExtensions.cs | 6 -
.../Extensions/ImmutableArrayExtensions.cs | 4 +
17 files changed, 2352 insertions(+), 324 deletions(-)
create mode 100644 src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
create mode 100644 src/Analyzers/Core/Analyzers/UseAutoProperty/AccessedFields.cs
create mode 100644 src/Analyzers/Core/Analyzers/UseAutoProperty/AnalysisResult.cs
create mode 100644 src/Analyzers/Core/Analyzers/UseAutoProperty/UseAutoPropertiesHelpers.cs
create mode 100644 src/Features/CSharp/Portable/UseAutoProperty/SingleLinePropertyFormattingRule.cs
create mode 100644 src/Features/CSharp/Portable/UseAutoProperty/UseAutoPropertyRewriter.cs
diff --git a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
index 4dd854bff2926..334581270c3ab 100644
--- a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -29,6 +30,12 @@ internal sealed class CSharpUseAutoPropertyAnalyzer : AbstractUseAutoPropertyAna
protected override SyntaxKind PropertyDeclarationKind
=> SyntaxKind.PropertyDeclaration;
+ protected override bool CanExplicitInterfaceImplementationsBeFixed
+ => false;
+
+ protected override bool SupportsFieldAttributesOnProperties
+ => true;
+
protected override ISemanticFacts SemanticFacts
=> CSharpSemanticFacts.Instance;
@@ -38,15 +45,26 @@ protected override bool SupportsReadOnlyProperties(Compilation compilation)
protected override bool SupportsPropertyInitializer(Compilation compilation)
=> compilation.LanguageVersion() >= LanguageVersion.CSharp6;
- protected override bool CanExplicitInterfaceImplementationsBeFixed()
- => false;
+ protected override bool SupportsFieldExpression(Compilation compilation)
+ => compilation.LanguageVersion() >= LanguageVersion.CSharp13;
protected override ExpressionSyntax? GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken)
=> variable.Initializer?.Value;
- protected override void RegisterIneligibleFieldsAction(
+ protected override bool ContainsFieldExpression(PropertyDeclarationSyntax propertyDeclaration, CancellationToken cancellationToken)
+ {
+ foreach (var node in propertyDeclaration.DescendantNodes())
+ {
+ if (node.IsKind(SyntaxKind.FieldExpression))
+ return true;
+ }
+
+ return false;
+ }
+
+ protected override void RecordIneligibleFieldLocations(
HashSet fieldNames,
- ConcurrentSet ineligibleFields,
+ ConcurrentDictionary> ineligibleFieldUsageIfOutsideProperty,
SemanticModel semanticModel,
SyntaxNode codeBlock,
CancellationToken cancellationToken)
@@ -54,15 +72,22 @@ protected override void RegisterIneligibleFieldsAction(
foreach (var argument in codeBlock.DescendantNodesAndSelf().OfType())
{
// An argument will disqualify a field if that field is used in a ref/out position.
- // We can't change such field references to be property references in C#.
+ // We can't change such field references to be property references in C#, unless we
+ // are converting to the `field` keyword.
if (argument.RefKindKeyword.Kind() != SyntaxKind.None)
AddIneligibleFieldsForExpression(argument.Expression);
+
+ // Use of a field in a nameof(...) expression can't *ever* be converted to use `field`.
+ // So hard block in this case.
+ if (argument.Expression.IsNameOfArgumentExpression())
+ AddIneligibleFieldsForExpression(argument.Expression, alwaysRestricted: true);
}
foreach (var refExpression in codeBlock.DescendantNodesAndSelf().OfType())
AddIneligibleFieldsForExpression(refExpression.Expression);
- // Can't take the address of an auto-prop. So disallow for fields that we do `&x` on.
+ // Can't take the address of an auto-prop. So disallow for fields that we do `&x` on. Unless we are converting
+ // to the `field` keyword.
foreach (var addressOfExpression in codeBlock.DescendantNodesAndSelf().OfType())
{
if (addressOfExpression.Kind() == SyntaxKind.AddressOfExpression)
@@ -72,9 +97,11 @@ protected override void RegisterIneligibleFieldsAction(
foreach (var memberAccess in codeBlock.DescendantNodesAndSelf().OfType())
{
if (CouldReferenceField(memberAccess))
- AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(semanticModel, memberAccess, ineligibleFields, cancellationToken);
+ AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(memberAccess);
}
+ return;
+
bool CouldReferenceField(ExpressionSyntax expression)
{
// Don't bother binding if the expression isn't even referencing the name of a field we know about.
@@ -82,50 +109,62 @@ bool CouldReferenceField(ExpressionSyntax expression)
return rightmostName != null && fieldNames.Contains(rightmostName);
}
- void AddIneligibleFieldsForExpression(ExpressionSyntax expression)
+ void AddIneligibleFieldsForExpression(ExpressionSyntax expression, bool alwaysRestricted = false)
{
if (!CouldReferenceField(expression))
return;
var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
- AddIneligibleFields(ineligibleFields, symbolInfo);
+ AddIneligibleFields(symbolInfo, expression, alwaysRestricted);
}
- }
- private static void AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(
- SemanticModel semanticModel, MemberAccessExpressionSyntax memberAccess, ConcurrentSet ineligibleFields, CancellationToken cancellationToken)
- {
- // `c.x = ...` can't be converted to `c.X = ...` if `c` is a struct and isn't definitely assigned as that point.
+ void AddIneligibleFieldsIfAccessedOffNotDefinitelyAssignedValue(
+ MemberAccessExpressionSyntax memberAccess)
+ {
+ // `c.x = ...` can't be converted to `c.X = ...` if `c` is a struct and isn't definitely assigned as that point.
- // only care about writes. if this was a read, then it must be def assigned and thus is safe to convert to a prop.
- if (!memberAccess.IsOnlyWrittenTo())
- return;
+ // only care about writes. if this was a read, then it must be def assigned and thus is safe to convert to a prop.
+ if (!memberAccess.IsOnlyWrittenTo())
+ return;
- // this only matters for a field access off of a struct. They can be declared unassigned and have their
- // fields directly written into.
- var symbolInfo = semanticModel.GetSymbolInfo(memberAccess, cancellationToken);
- if (symbolInfo.GetAnySymbol() is not IFieldSymbol { ContainingType.TypeKind: TypeKind.Struct })
- return;
+ // this only matters for a field access off of a struct. They can be declared unassigned and have their
+ // fields directly written into.
+ var symbolInfo = semanticModel.GetSymbolInfo(memberAccess, cancellationToken);
+ if (symbolInfo.GetAnySymbol() is not IFieldSymbol { ContainingType.TypeKind: TypeKind.Struct })
+ return;
- var exprSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken).GetAnySymbol();
- if (exprSymbol is not IParameterSymbol and not ILocalSymbol)
- return;
+ var exprSymbol = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken).GetAnySymbol();
+ if (exprSymbol is not IParameterSymbol and not ILocalSymbol)
+ return;
- var dataFlow = semanticModel.AnalyzeDataFlow(memberAccess.Expression);
- if (dataFlow != null && !dataFlow.DefinitelyAssignedOnEntry.Contains(exprSymbol))
- AddIneligibleFields(ineligibleFields, symbolInfo);
- }
+ var dataFlow = semanticModel.AnalyzeDataFlow(memberAccess.Expression);
+ if (dataFlow != null && !dataFlow.DefinitelyAssignedOnEntry.Contains(exprSymbol))
+ AddIneligibleFields(symbolInfo, memberAccess);
+ }
- private static void AddIneligibleFields(ConcurrentSet ineligibleFields, SymbolInfo symbolInfo)
- {
- AddIneligibleField(symbolInfo.Symbol);
- foreach (var symbol in symbolInfo.CandidateSymbols)
- AddIneligibleField(symbol);
+ void AddIneligibleFields(
+ SymbolInfo symbolInfo,
+ SyntaxNode location,
+ bool alwaysRestricted = false)
+ {
+ AddIneligibleField(symbolInfo.Symbol, location, alwaysRestricted);
+ foreach (var symbol in symbolInfo.CandidateSymbols)
+ AddIneligibleField(symbol, location, alwaysRestricted);
+ }
- void AddIneligibleField(ISymbol? symbol)
+ void AddIneligibleField(
+ ISymbol? symbol,
+ SyntaxNode location,
+ bool alwaysRestricted)
{
+ // If the field is always restricted, then add the compilation unit itself to the ineligibility locations.
+ // that way we never think we can convert this field.
if (symbol is IFieldSymbol field)
- ineligibleFields.Add(field);
+ {
+ AddFieldUsage(ineligibleFieldUsageIfOutsideProperty, field, alwaysRestricted
+ ? location.SyntaxTree.GetRoot(cancellationToken)
+ : location);
+ }
}
}
@@ -181,7 +220,7 @@ private static bool CheckExpressionSyntactically(ExpressionSyntax expression)
=> accessorDeclaration is { Body.Statements: [T statement] } ? statement : null;
protected override ExpressionSyntax? GetSetterExpression(
- IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken)
+ SemanticModel semanticModel, IMethodSymbol setMethod, CancellationToken cancellationToken)
{
// Setter has to be of the form:
//
@@ -213,4 +252,24 @@ protected override SyntaxNode GetFieldNode(
? fieldDeclaration
: variableDeclarator;
}
+
+ protected override void AddAccessedFields(
+ SemanticModel semanticModel,
+ IMethodSymbol accessor,
+ HashSet fieldNames,
+ HashSet result,
+ CancellationToken cancellationToken)
+ {
+ var syntax = accessor.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
+ foreach (var descendant in syntax.DescendantNodesAndSelf())
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ if (descendant is IdentifierNameSyntax identifierName)
+ {
+ result.AddIfNotNull(TryGetDirectlyAccessedFieldSymbol(
+ semanticModel, identifierName, fieldNames, cancellationToken));
+ }
+ }
+ }
}
diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
index 43978f21d34d9..cb7713cf037fb 100644
--- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
+++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
@@ -108,6 +108,7 @@
+
diff --git a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs
index e8fc58427100c..c819c4aad6ad3 100644
--- a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs
+++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests.cs
@@ -18,9 +18,11 @@
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseAutoProperty;
[Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
-public sealed class UseAutoPropertyTests(ITestOutputHelper logger)
+public sealed partial class UseAutoPropertyTests(ITestOutputHelper logger)
: AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest_NoEditor(logger)
{
+ private readonly ParseOptions CSharp12 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp12);
+
internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace)
=> (new CSharpUseAutoPropertyAnalyzer(), GetCSharpUseAutoPropertyCodeFixProvider());
@@ -667,7 +669,7 @@ class Class
}
[Fact]
- public async Task TestGetterWithMutipleStatements()
+ public async Task TestGetterWithMultipleStatements_CSharp12()
{
await TestMissingInRegularAndScriptAsync(
"""
@@ -684,11 +686,11 @@ int P
}
}
}
- """);
+ """, new TestParameters(parseOptions: CSharp12));
}
[Fact]
- public async Task TestSetterWithMutipleStatements()
+ public async Task TestSetterWithMultipleStatements_CSharp12()
{
await TestMissingInRegularAndScriptAsync(
"""
@@ -731,7 +733,7 @@ int P
}
}
}
- """);
+ """, new TestParameters(parseOptions: CSharp12));
}
[Fact]
@@ -1153,9 +1155,9 @@ partial class Class
}
[Fact]
- public async Task TestNotWithFieldWithAttribute()
+ public async Task TestWithFieldWithAttribute()
{
- await TestMissingInRegularAndScriptAsync(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -1170,6 +1172,13 @@ int P
}
}
}
+ """,
+ """
+ class Class
+ {
+ [field: A]
+ int P { get; }
+ }
""");
}
diff --git a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
new file mode 100644
index 0000000000000..4e12a182143d7
--- /dev/null
+++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
@@ -0,0 +1,1379 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseAutoProperty;
+
+[Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
+public sealed partial class UseAutoPropertyTests
+{
+ private readonly ParseOptions CSharp13 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp13);
+ private readonly ParseOptions Preview = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview);
+
+ [Fact]
+ public async Task TestFieldSimplestCase()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ string P
+ {
+ get
+ {
+ return field.Trim();
+ }
+ }
+ }
+ """, parseOptions: CSharp13);
+ }
+
+ [Fact]
+ public async Task TestFieldAccessOffOfThis()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return this.s.Trim();
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ string P
+ {
+ get
+ {
+ return field.Trim();
+ }
+ }
+ }
+ """, parseOptions: CSharp13);
+ }
+
+ [Fact]
+ public async Task TestStaticField()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|static string s|];
+
+ static string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ static string P
+ {
+ get
+ {
+ return field.Trim();
+ }
+ }
+ }
+ """, parseOptions: CSharp13);
+ }
+
+ [Fact]
+ public async Task TestGetterWithMultipleStatements_Field()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ int P
+ {
+ get
+ {
+ ;
+ return i;
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ int P
+ {
+ get
+ {
+ ;
+ return field;
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|int i|];
+
+ int P
+ {
+ get
+ {
+ return i;
+ }
+
+ set
+ {
+ ;
+ i = value;
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ int P
+ {
+ get;
+
+ set
+ {
+ ;
+ field = value;
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field2()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|int i|];
+
+ int P
+ {
+ get => i;
+
+ set
+ {
+ ;
+ i = value;
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ int P
+ {
+ get;
+
+ set
+ {
+ ;
+ field = value;
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSimpleFieldInExpressionBody()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P => s.Trim();
+ }
+ """,
+ """
+ class Class
+ {
+ string P => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestMultipleFields_NoClearChoice()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ int [|x|], y;
+
+ int Total => x + y;
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestMultipleFields_NoClearChoice2()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ int [|x|], y;
+
+ int Total
+ {
+ get => x + y;
+ set
+ {
+ x = value;
+ y = value;
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestMultipleFields_ClearChoice()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ int [|x|], y;
+
+ int Total
+ {
+ get => x + y;
+ set
+ {
+ x = value;
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ int y;
+
+ int Total
+ {
+ get => field + y;
+ set;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestMultipleFields_PickByName1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ int [|x|], y;
+
+ int X => x + y;
+ }
+ """,
+ """
+ class Class
+ {
+ int y;
+
+ int X => field + y;
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestMultipleFields_PickByName2()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ int [|_x|], y;
+
+ int X => _x + y;
+ }
+ """,
+ """
+ class Class
+ {
+ int y;
+
+ int X => field + y;
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWhenAlreadyUsingField()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ var v = field.Trim();
+ return s.Trim();
+ }
+ }
+ }
+ """, new TestParameters(parseOptions: Preview));
+ }
+
+ [Fact]
+ public async Task TestNotWhenUsingNameof1()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ if (s is null)
+ throw new ArgumentNullException(nameof(s));
+ return s.Trim();
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWhenUsingNameof2()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ if (s is null)
+ throw new ArgumentNullException(nameof(this.s));
+ return s.Trim();
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWhenUsingNameof3()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+
+ void M()
+ {
+ if (s is null)
+ throw new ArgumentNullException(nameof(s));
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWhenUsingNameof4()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+
+ void M()
+ {
+ if (s is null)
+ throw new ArgumentNullException(nameof(this.s));
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWhenUsingNameof5()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s = nameof(s)|];
+
+ string P => s;
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestWithRefArgumentUseInside()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P => Init(ref s);
+
+ void Init(ref string s)
+ {
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ string P => Init(ref field);
+
+ void Init(ref string s)
+ {
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWithRefArgumentUseOutside()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P => s.Trim();
+
+ void M()
+ {
+ Init(ref s);
+ }
+
+ void Init(ref string s)
+ {
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestWithRefUseInside()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ ref string s1 = ref s;
+ return s.Trim();
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ string P
+ {
+ get
+ {
+ ref string s1 = ref field;
+ return field.Trim();
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWithRefUseOutside()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+
+ void M()
+ {
+ ref string s1 = ref s;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestWithAddressOfInside()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int s|];
+
+ int P
+ {
+ get
+ {
+ unsafe
+ {
+ int* p = &s;
+ return s;
+ }
+ }
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ int P
+ {
+ get
+ {
+ unsafe
+ {
+ int* p = &field;
+ return field;
+ }
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWithAddressOfOutside()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int s|];
+
+ int P
+ {
+ get
+ {
+ unsafe
+ {
+ return s;
+ }
+ }
+ }
+
+ unsafe void M()
+ {
+ int* p = &s;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotChainedPattern1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Builder
+ {
+ [|private bool _strictMode;|]
+ private Builder _builder;
+
+ public bool StrictMode
+ {
+ get { return _strictMode ?? _builder.StrictMode; }
+ set { this._strictMode = value; }
+ }
+ }
+ """,
+ """
+ class Builder
+ {
+ private Builder _builder;
+
+ public bool StrictMode
+ {
+ get { return field ?? _builder.StrictMode; }
+ set;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestLazyInit1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class Builder
+ {
+ [|private List? _list|]
+
+ public List List => _list ??= new();
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class Builder
+ {
+ public List List => field ??= new();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestRefSetAccessor1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Builder
+ {
+ [|private int prop;|]
+ public int Prop { get => prop; set => Set(ref prop, value); }
+
+ void Set(ref int a, int b) { }
+ }
+ """,
+ """
+ class Builder
+ {
+ public int Prop { get; set => Set(ref field, value); }
+
+ void Set(ref int a, int b) { }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestRefSetAccessor2()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Builder
+ {
+ [|private int prop;|]
+
+ public int Prop
+ {
+ get => prop;
+ set
+ {
+ if (!Set(ref prop, value)) return;
+ OnPropChanged();
+ }
+ }
+
+ void Set(ref int a, int b) { }
+ void OnPropChanged() { }
+ }
+ """,
+ """
+ class Builder
+ {
+ public int Prop
+ {
+ get;
+ set
+ {
+ if (!Set(ref field, value)) return;
+ OnPropChanged();
+ }
+ }
+
+ void Set(ref int a, int b) { }
+ void OnPropChanged() { }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private int prop;|]
+ public int Prop { get => prop; set => prop = value; }
+ }
+ """,
+ """
+ class C
+ {
+ [field: Something]
+ public int Prop { get; set; }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField2()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private string prop;|]
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ [field: Something]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField3()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private string prop;|]
+
+ [PropAttribute]
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ [field: Something]
+ [PropAttribute]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField4()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private string prop;|]
+
+ /// Docs
+ [PropAttribute]
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ /// Docs
+ [field: Something]
+ [PropAttribute]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField5()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private string prop;|]
+
+ /// Docs
+ [PropAttribute][PropAttribute2]
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ /// Docs
+ [field: Something]
+ [PropAttribute][PropAttribute2]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField6()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [Something]
+ [|private string prop;|]
+
+ /// Docs
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ /// Docs
+ [field: Something]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestAttributesOnField7()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ /// FieldDocs
+ [Something]
+ [|private string prop;|]
+
+ /// Docs
+ public string Prop => prop.Trim();
+ }
+ """,
+ """
+ class C
+ {
+ /// Docs
+ [field: Something]
+ public string Prop => field.Trim();
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestFieldUsedInObjectInitializer()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [|private string prop;|]
+
+ public string Prop
+ {
+ get
+ {
+ var v = new C { prop = "" };
+ return prop.Trim();
+ }
+ }
+ }
+ """,
+ """
+ class C
+ {
+ public string Prop
+ {
+ get
+ {
+ var v = new C { Prop = "" };
+ return field.Trim();
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere1()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|string s|];
+
+ public string P => s.Trim();
+
+ void M()
+ {
+ s = "";
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public string P { get => field.Trim(); private set; }
+
+ void M()
+ {
+ P = "";
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere2()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|string s|];
+
+ public string P => s ??= "";
+
+ void M()
+ {
+ s = "";
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public string P { get => field ??= ""; private set; }
+
+ void M()
+ {
+ P = "";
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere3()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|string s|];
+
+ public string P
+ {
+ get => s ??= "";
+ }
+
+ void M()
+ {
+ s = "";
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public string P
+ {
+ get => field ??= ""; private set;
+ }
+
+ void M()
+ {
+ P = "";
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere4()
+ {
+ await TestInRegularAndScript1Async(
+ """
+ class Class
+ {
+ [|string s|];
+
+ public string P
+ {
+ get
+ {
+ return s ??= "";
+ }
+ }
+
+ void M()
+ {
+ s = "";
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public string P
+ {
+ get
+ {
+ return field ??= "";
+ }
+
+ private set;
+ }
+
+ void M()
+ {
+ P = "";
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialGetterWithExternalRead1()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I => i / 2;
+
+ void M()
+ {
+ Console.WriteLine(i);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialGetterWithExternalRead2()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I => i / 2;
+
+ void M()
+ {
+ Console.WriteLine(this.i);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialSetterWithExternalWrite1()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I { get => i; set => value = i / 2; }
+
+ void M()
+ {
+ i = 1;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialSetterWithExternalWrite2()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I { get => i; set => value = i / 2; }
+
+ void M()
+ {
+ this.i = 1;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialSetterWithNoExternalWrite1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I { get => i; set => i = value / 2; }
+ }
+ """,
+ """
+ class Class
+ {
+ public int I { get; set => field = value / 2; }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialGetterWithExternalReadWrite1()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I => i / 2;
+
+ void M()
+ {
+ Console.WriteLine(this.i++);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNonTrivialSetterWithExternalReadWrite1()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I { get => i; set => i = value / 2; }
+
+ void M()
+ {
+ Console.WriteLine(this.i++);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestTrivialGetterWithExternalRead1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I => i;
+
+ void M()
+ {
+ Console.WriteLine(i);
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public int I { get; }
+
+ void M()
+ {
+ Console.WriteLine(I);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNoSetterWithExternalWrite1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|int i|];
+
+ public int I => i;
+
+ void M()
+ {
+ i = 1;
+ }
+ }
+ """,
+ """
+ class Class
+ {
+ public int I { get; private set; }
+
+ void M()
+ {
+ I = 1;
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestFormatString()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [|private string prop;|]
+ public string Prop => $"{prop:prop}";
+ }
+ """,
+ """
+ class C
+ {
+ public string Prop => $"{field:prop}";
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNoSetterButWrittenOutside()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [|private string prop;|]
+ public string Prop => prop ?? "";
+
+ void M() { prop = "..."; }
+ }
+ """,
+ """
+ class C
+ {
+ public string Prop { get => field ?? ""; private set; }
+
+ void M() { Prop = "..."; }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestNotWithNameofInAttribute()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class C
+ {
+ [|private string prop;|]
+ [ThisIsMyBackingField(nameof(prop))]
+ public string Prop { get => prop; set => prop = value; }
+ }
+ """);
+ }
+}
diff --git a/src/Analyzers/Core/Analyzers/Analyzers.projitems b/src/Analyzers/Core/Analyzers/Analyzers.projitems
index bc9457e004538..cd595743cd676 100644
--- a/src/Analyzers/Core/Analyzers/Analyzers.projitems
+++ b/src/Analyzers/Core/Analyzers/Analyzers.projitems
@@ -75,6 +75,9 @@
+
+
+
diff --git a/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs
index 34f7e41e03429..c883b8bb26085 100644
--- a/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs
+++ b/src/Analyzers/Core/Analyzers/UseAutoProperty/AbstractUseAutoPropertyAnalyzer.cs
@@ -6,18 +6,22 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
+using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseAutoProperty;
-internal abstract class AbstractUseAutoPropertyAnalyzer<
+using static UseAutoPropertiesHelpers;
+
+internal abstract partial class AbstractUseAutoPropertyAnalyzer<
TSyntaxKind,
TPropertyDeclaration,
TConstructorDeclaration,
@@ -37,11 +41,9 @@ internal abstract class AbstractUseAutoPropertyAnalyzer<
/// ConcurrentStack as that's the only concurrent collection that supports 'Clear' in netstandard2.
///
private static readonly ObjectPool> s_analysisResultPool = new(() => new());
- private static readonly ObjectPool> s_fieldSetPool = new(() => []);
private static readonly ObjectPool> s_nodeSetPool = new(() => []);
- private static readonly ObjectPool>> s_fieldWriteLocationPool = new(() => []);
- private static readonly Func> s_createFieldWriteNodeSet = _ => s_nodeSetPool.Allocate();
+ private static readonly ObjectPool>> s_fieldToUsageLocationPool = new(() => []);
///
/// Not static as this has different semantics around case sensitivity for C# and VB.
@@ -58,8 +60,16 @@ protected AbstractUseAutoPropertyAnalyzer()
_fieldNamesPool = new(() => new(this.SyntaxFacts.StringComparer));
}
- protected static void AddFieldWrite(ConcurrentDictionary> fieldWrites, IFieldSymbol field, SyntaxNode node)
- => fieldWrites.GetOrAdd(field, s_createFieldWriteNodeSet).Add(node);
+ protected static void AddFieldUsage(ConcurrentDictionary> fieldWrites, IFieldSymbol field, SyntaxNode location)
+ => fieldWrites.GetOrAdd(field, static _ => s_nodeSetPool.Allocate()).Add(location);
+
+ private static void ClearAndFree(ConcurrentDictionary> multiMap)
+ {
+ foreach (var (_, nodeSet) in multiMap)
+ s_nodeSetPool.ClearAndFree(nodeSet);
+
+ s_fieldToUsageLocationPool.ClearAndFree(multiMap);
+ }
///
/// A method body edit anywhere in a type will force us to reanalyze the whole type.
@@ -72,16 +82,25 @@ public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
protected ISyntaxFacts SyntaxFacts => this.SemanticFacts.SyntaxFacts;
protected abstract TSyntaxKind PropertyDeclarationKind { get; }
+
+ protected abstract bool CanExplicitInterfaceImplementationsBeFixed { get; }
+ protected abstract bool SupportsFieldAttributesOnProperties { get; }
+
protected abstract bool SupportsReadOnlyProperties(Compilation compilation);
protected abstract bool SupportsPropertyInitializer(Compilation compilation);
- protected abstract bool CanExplicitInterfaceImplementationsBeFixed();
+ protected abstract bool SupportsFieldExpression(Compilation compilation);
+
+ protected abstract bool ContainsFieldExpression(TPropertyDeclaration propertyDeclaration, CancellationToken cancellationToken);
+
protected abstract TExpression? GetFieldInitializer(TVariableDeclarator variable, CancellationToken cancellationToken);
protected abstract TExpression? GetGetterExpression(IMethodSymbol getMethod, CancellationToken cancellationToken);
- protected abstract TExpression? GetSetterExpression(IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken);
+ protected abstract TExpression? GetSetterExpression(SemanticModel semanticModel, IMethodSymbol setMethod, CancellationToken cancellationToken);
protected abstract SyntaxNode GetFieldNode(TFieldDeclaration fieldDeclaration, TVariableDeclarator variableDeclarator);
+ protected abstract void AddAccessedFields(
+ SemanticModel semanticModel, IMethodSymbol accessor, HashSet fieldNames, HashSet result, CancellationToken cancellationToken);
- protected abstract void RegisterIneligibleFieldsAction(
- HashSet fieldNames, ConcurrentSet ineligibleFields, SemanticModel semanticModel, SyntaxNode codeBlock, CancellationToken cancellationToken);
+ protected abstract void RecordIneligibleFieldLocations(
+ HashSet fieldNames, ConcurrentDictionary> ineligibleFieldUsageIfOutsideProperty, SemanticModel semanticModel, SyntaxNode codeBlock, CancellationToken cancellationToken);
protected sealed override void InitializeWorker(AnalysisContext context)
=> context.RegisterSymbolStartAction(context =>
@@ -90,44 +109,73 @@ protected sealed override void InitializeWorker(AnalysisContext context)
if (!ShouldAnalyze(context, namedType))
return;
- var fieldNames = _fieldNamesPool.Allocate();
+ // Results of our analysis pass that we will use to determine which fields and properties to offer to fixup.
var analysisResults = s_analysisResultPool.Allocate();
- var ineligibleFields = s_fieldSetPool.Allocate();
- var nonConstructorFieldWrites = s_fieldWriteLocationPool.Allocate();
- // Record the names of all the fields in this type. We can use this to greatly reduce the amount of
+ // Fields whose usage may disqualify them from being removed (depending on the usage location). For example,
+ // a field taken by ref normally can't be converted (as a property can't be taken by ref). However, this
+ // doesn't apply within the property itself (as it can refer to `field` after the rewrite).
+ var ineligibleFieldUsageIfOutsideProperty = s_fieldToUsageLocationPool.Allocate();
+
+ // Locations where this field is read or written. If it is read or written outside of hte property being
+ // changed, and the property getter/setter is non-trivial, then we cannot use 'field' for it, as that would
+ // change the semantics in those locations.
+ var fieldReads = s_fieldToUsageLocationPool.Allocate();
+ var fieldWrites = s_fieldToUsageLocationPool.Allocate();
+
+ // Record the names of all the private fields in this type. We can use this to greatly reduce the amount of
// binding we need to perform when looking for restrictions in the type.
+ var fieldNames = _fieldNamesPool.Allocate();
foreach (var member in namedType.GetMembers())
{
- if (member is IFieldSymbol field)
+ if (member is IFieldSymbol
+ {
+ // Can only convert fields that are private, as otherwise we don't know how they may be used
+ // outside of this type.
+ DeclaredAccessibility: Accessibility.Private,
+ // Only care about actual user-defined fields, not compiler generated ones.
+ CanBeReferencedByName: true,
+ // Will never convert a constant into an auto-prop
+ IsConst: false,
+ // Can't preserve volatile semantics on a property.
+ IsVolatile: false,
+ // To make processing later on easier, limit to well-behaved fields (versus having multiple
+ // fields merged together in error recoery scenarios).
+ DeclaringSyntaxReferences.Length: 1,
+ } field)
+ {
fieldNames.Add(field.Name);
+ }
}
- context.RegisterSyntaxNodeAction(context => AnalyzePropertyDeclaration(context, namedType, analysisResults), PropertyDeclarationKind);
+ // Examine each property-declaration we find within this named type to see if it looks like it can be converted.
+ context.RegisterSyntaxNodeAction(
+ context => AnalyzePropertyDeclaration(context, namedType, fieldNames, analysisResults),
+ PropertyDeclarationKind);
+
+ // Concurrently, examine the usages of the fields of this type within itself to see how those may impact if
+ // a field/prop pair can actually be converted.
context.RegisterCodeBlockStartAction(context =>
{
- RegisterIneligibleFieldsAction(fieldNames, ineligibleFields, context.SemanticModel, context.CodeBlock, context.CancellationToken);
- RegisterNonConstructorFieldWrites(fieldNames, nonConstructorFieldWrites, context.SemanticModel, context.CodeBlock, context.CancellationToken);
+ RecordIneligibleFieldLocations(fieldNames, ineligibleFieldUsageIfOutsideProperty, context.SemanticModel, context.CodeBlock, context.CancellationToken);
+ RecordAllFieldReferences(fieldNames, fieldReads, fieldWrites, context.SemanticModel, context.CodeBlock, context.CancellationToken);
});
context.RegisterSymbolEndAction(context =>
{
try
{
- Process(analysisResults, ineligibleFields, nonConstructorFieldWrites, context);
+ Process(analysisResults, ineligibleFieldUsageIfOutsideProperty, fieldReads, fieldWrites, context);
}
finally
{
// Cleanup after doing all our work.
_fieldNamesPool.ClearAndFree(fieldNames);
-
s_analysisResultPool.ClearAndFree(analysisResults);
- s_fieldSetPool.ClearAndFree(ineligibleFields);
- foreach (var (_, nodeSet) in nonConstructorFieldWrites)
- s_nodeSetPool.ClearAndFree(nodeSet);
-
- s_fieldWriteLocationPool.ClearAndFree(nonConstructorFieldWrites);
+ ClearAndFree(ineligibleFieldUsageIfOutsideProperty);
+ ClearAndFree(fieldReads);
+ ClearAndFree(fieldWrites);
}
});
@@ -136,6 +184,11 @@ bool ShouldAnalyze(SymbolStartAnalysisContext context, INamedTypeSymbol namedTyp
if (namedType.TypeKind is not TypeKind.Class and not TypeKind.Struct and not TypeKind.Module)
return false;
+ // Serializable types can depend on fields (and their order). Don't report these
+ // properties in that case.
+ if (namedType.IsSerializable)
+ return false;
+
// Don't bother running on this type unless at least one of its parts has the 'prefer auto props' option
// on, and the diagnostic is not suppressed.
if (!namedType.DeclaringSyntaxReferences.Select(d => d.SyntaxTree).Distinct().Any(tree =>
@@ -171,16 +224,14 @@ bool ShouldAnalyze(SymbolStartAnalysisContext context, INamedTypeSymbol namedTyp
}
}, SymbolKind.NamedType);
- private void RegisterNonConstructorFieldWrites(
+ private void RecordAllFieldReferences(
HashSet fieldNames,
+ ConcurrentDictionary> fieldReads,
ConcurrentDictionary> fieldWrites,
SemanticModel semanticModel,
SyntaxNode codeBlock,
CancellationToken cancellationToken)
{
- if (codeBlock.FirstAncestorOrSelf() != null)
- return;
-
var semanticFacts = this.SemanticFacts;
var syntaxFacts = this.SyntaxFacts;
foreach (var identifierName in codeBlock.DescendantNodesAndSelf().OfType())
@@ -192,16 +243,97 @@ private void RegisterNonConstructorFieldWrites(
if (semanticModel.GetSymbolInfo(identifierName, cancellationToken).Symbol is not IFieldSymbol field)
continue;
- if (!semanticFacts.IsWrittenTo(semanticModel, identifierName, cancellationToken))
- continue;
+ if (semanticFacts.IsOnlyWrittenTo(semanticModel, identifierName, cancellationToken))
+ {
+ AddFieldUsage(fieldWrites, field, identifierName);
+ }
+ else if (semanticFacts.IsWrittenTo(semanticModel, identifierName, cancellationToken))
+ {
+ AddFieldUsage(fieldWrites, field, identifierName);
+ AddFieldUsage(fieldReads, field, identifierName);
+ }
+ else
+ {
+ AddFieldUsage(fieldReads, field, identifierName);
+ }
+ }
+ }
+
+ private AccessedFields GetGetterFields(
+ SemanticModel semanticModel,
+ IMethodSymbol getMethod,
+ HashSet fieldNames,
+ CancellationToken cancellationToken)
+ {
+ var trivialFieldExpression = GetGetterExpression(getMethod, cancellationToken);
+ if (trivialFieldExpression != null)
+ return new(CheckFieldAccessExpression(semanticModel, trivialFieldExpression, fieldNames, cancellationToken));
+
+ if (!this.SupportsFieldExpression(semanticModel.Compilation))
+ return AccessedFields.Empty;
+
+ using var _ = PooledHashSet.GetInstance(out var set);
+ AddAccessedFields(semanticModel, getMethod, fieldNames, set, cancellationToken);
- AddFieldWrite(fieldWrites, field, identifierName);
+ return new(TrivialField: null, set.ToImmutableArray());
+ }
+
+ private AccessedFields GetSetterFields(
+ SemanticModel semanticModel, IMethodSymbol setMethod, HashSet fieldNames, CancellationToken cancellationToken)
+ {
+ var trivialFieldExpression = GetSetterExpression(semanticModel, setMethod, cancellationToken);
+ if (trivialFieldExpression != null)
+ return new(CheckFieldAccessExpression(semanticModel, trivialFieldExpression, fieldNames, cancellationToken));
+
+ if (!this.SupportsFieldExpression(semanticModel.Compilation))
+ return AccessedFields.Empty;
+
+ using var _ = PooledHashSet.GetInstance(out var set);
+ AddAccessedFields(semanticModel, setMethod, fieldNames, set, cancellationToken);
+
+ return new(TrivialField: null, set.ToImmutableArray());
+ }
+
+ private IFieldSymbol? CheckFieldAccessExpression(
+ SemanticModel semanticModel,
+ TExpression? expression,
+ HashSet fieldNames,
+ CancellationToken cancellationToken)
+ {
+ if (expression == null)
+ return null;
+
+ // needs to be of the form `x` or `this.x`.
+ var syntaxFacts = this.SyntaxFacts;
+ var name = expression;
+ if (syntaxFacts.IsMemberAccessExpression(expression))
+ name = (TExpression)SyntaxFacts.GetNameOfMemberAccessExpression(expression);
+
+ return TryGetDirectlyAccessedFieldSymbol(semanticModel, name as TIdentifierName, fieldNames, cancellationToken);
+ }
+
+ private static bool TryGetSyntax(
+ IFieldSymbol field,
+ [NotNullWhen(true)] out TFieldDeclaration? fieldDeclaration,
+ [NotNullWhen(true)] out TVariableDeclarator? variableDeclarator,
+ CancellationToken cancellationToken)
+ {
+ if (field.DeclaringSyntaxReferences is [var fieldReference])
+ {
+ variableDeclarator = fieldReference.GetSyntax(cancellationToken) as TVariableDeclarator;
+ fieldDeclaration = variableDeclarator?.Parent?.Parent as TFieldDeclaration;
+ return fieldDeclaration != null && variableDeclarator != null;
}
+
+ fieldDeclaration = null;
+ variableDeclarator = null;
+ return false;
}
private void AnalyzePropertyDeclaration(
SyntaxNodeAnalysisContext context,
INamedTypeSymbol containingType,
+ HashSet fieldNames,
ConcurrentStack analysisResults)
{
var cancellationToken = context.CancellationToken;
@@ -209,9 +341,15 @@ private void AnalyzePropertyDeclaration(
var compilation = semanticModel.Compilation;
var propertyDeclaration = (TPropertyDeclaration)context.Node;
+
if (semanticModel.GetDeclaredSymbol(propertyDeclaration, cancellationToken) is not IPropertySymbol property)
return;
+ // To make processing later on easier, limit to well-behaved properties (versus having multiple
+ // properties merged together in error recovery scenarios).
+ if (property.DeclaringSyntaxReferences.Length != 1)
+ return;
+
if (!containingType.Equals(property.ContainingType))
return;
@@ -233,12 +371,7 @@ private void AnalyzePropertyDeclaration(
if (property.GetMethod == null)
return;
- if (!CanExplicitInterfaceImplementationsBeFixed() && property.ExplicitInterfaceImplementations.Length != 0)
- return;
-
- // Serializable types can depend on fields (and their order). Don't report these
- // properties in that case.
- if (containingType.IsSerializable)
+ if (!CanExplicitInterfaceImplementationsBeFixed && property.ExplicitInterfaceImplementations.Length != 0)
return;
var preferAutoProps = context.GetAnalyzerOptions().PreferAutoProperties;
@@ -251,106 +384,179 @@ private void AnalyzePropertyDeclaration(
if (notification.Severity == ReportDiagnostic.Suppress)
return;
- var getterField = GetGetterField(semanticModel, property.GetMethod, cancellationToken);
- if (getterField == null)
+ // If the property already contains a `field` expression, then we can't do anything more here.
+ if (SupportsFieldExpression(compilation) && ContainsFieldExpression(propertyDeclaration, cancellationToken))
return;
- // Only support this for private fields. It limits the scope of hte program
- // we have to analyze to make sure this is safe to do.
- if (getterField.DeclaredAccessibility != Accessibility.Private)
- return;
+ var getterFields = GetGetterFields(semanticModel, property.GetMethod, fieldNames, cancellationToken);
+ getterFields = getterFields.Where(
+ static (getterField, args) =>
+ {
+ var (@this, compilation, containingType, property, cancellationToken) = args;
- // If the user made the field readonly, we only want to convert it to a property if we
- // can keep it readonly.
- if (getterField.IsReadOnly && !SupportsReadOnlyProperties(compilation))
- return;
+ // Only support this for private fields. It limits the scope of hte program
+ // we have to analyze to make sure this is safe to do.
+ if (getterField.DeclaredAccessibility != Accessibility.Private)
+ return false;
- // Field and property have to be in the same type.
- if (!containingType.Equals(getterField.ContainingType))
- return;
+ // Don't want to remove constants and volatile fields.
+ if (getterField.IsConst || getterField.IsVolatile)
+ return false;
- // Property and field have to agree on type.
- if (!property.Type.Equals(getterField.Type))
- return;
+ // If the user made the field readonly, we only want to convert it to a property if we
+ // can keep it readonly.
+ if (getterField.IsReadOnly && !@this.SupportsReadOnlyProperties(compilation))
+ return false;
- // Mutable value type fields are mutable unless they are marked read-only
- if (!getterField.IsReadOnly && getterField.Type.IsMutableValueType() != false)
- return;
+ // Mutable value type fields are mutable unless they are marked read-only
+ if (!getterField.IsReadOnly && getterField.Type.IsMutableValueType() != false)
+ return false;
- // Don't want to remove constants and volatile fields.
- if (getterField.IsConst || getterField.IsVolatile)
- return;
+ // Field and property have to be in the same type.
+ if (!containingType.Equals(getterField.ContainingType))
+ return false;
- // Field and property should match in static-ness
- if (getterField.IsStatic != property.IsStatic)
- return;
+ // Field and property should match in static-ness
+ if (getterField.IsStatic != property.IsStatic)
+ return false;
- var fieldReference = getterField.DeclaringSyntaxReferences[0];
- if (fieldReference.GetSyntax(cancellationToken) is not TVariableDeclarator { Parent.Parent: TFieldDeclaration fieldDeclaration } variableDeclarator)
+ // Property and field have to agree on type.
+ if (!property.Type.Equals(getterField.Type))
+ return false;
+
+ if (!TryGetSyntax(getterField, out _, out var variableDeclarator, cancellationToken))
+ return false;
+
+ var initializer = @this.GetFieldInitializer(variableDeclarator, cancellationToken);
+ if (initializer != null && !@this.SupportsPropertyInitializer(compilation))
+ return false;
+
+ if (!@this.CanConvert(property))
+ return false;
+
+ // Can't remove the field if it has attributes on it.
+ var attributes = getterField.GetAttributes();
+ if (attributes.Length > 0 && !@this.SupportsFieldAttributesOnProperties)
+ return false;
+
+ return true;
+ },
+ (this, compilation, containingType, property, cancellationToken));
+
+ if (getterFields.IsEmpty)
return;
+ var isTrivialSetAccessor = false;
+
// A setter is optional though.
- var setMethod = property.SetMethod;
- if (setMethod != null)
+ if (property.SetMethod != null)
{
- var setterField = GetSetterField(semanticModel, setMethod, cancellationToken);
- // If there is a getter and a setter, they both need to agree on which field they are
- // writing to.
- if (setterField != getterField)
+ // Figure out all the fields written to in the setter.
+ var setterFields = GetSetterFields(semanticModel, property.SetMethod, fieldNames, cancellationToken);
+
+ // Intersect these to determine which fields both the getter and setter write to.
+ getterFields = getterFields.Where(
+ static (field, setterFields) => setterFields.Contains(field),
+ setterFields);
+
+ // If there is a getter and a setter, they both need to agree on which field they are writing to.
+ if (getterFields.IsEmpty)
return;
- }
- var initializer = GetFieldInitializer(variableDeclarator, cancellationToken);
- if (initializer != null && !SupportsPropertyInitializer(compilation))
- return;
+ isTrivialSetAccessor = setterFields.TrivialField != null;
+ }
- // Can't remove the field if it has attributes on it.
- var attributes = getterField.GetAttributes();
- var suppressMessageAttributeType = compilation.SuppressMessageAttributeType();
- foreach (var attribute in attributes)
+ if (getterFields.Count > 1)
{
- if (attribute.AttributeClass != suppressMessageAttributeType)
- return;
+ // Multiple fields we could convert here. Check if any of the fields end with the property name. If
+ // so, it's likely that that's the field to use.
+ getterFields = getterFields.Where(
+ static (field, property) => field.Name.EndsWith(property.Name, StringComparison.OrdinalIgnoreCase),
+ property);
}
- if (!CanConvert(property))
+ // If we have multiple fields that could be converted, don't offer. We don't know which field/prop pair would
+ // be best.
+ if (getterFields.Count != 1)
return;
- // Looks like a viable property/field to convert into an auto property.
- analysisResults.Push(new AnalysisResult(property, getterField, propertyDeclaration, fieldDeclaration, variableDeclarator, notification));
+ var getterField = getterFields.TrivialField ?? getterFields.NonTrivialFields.Single();
+ var isTrivialGetAccessor = getterFields.TrivialField == getterField;
+
+ Contract.ThrowIfFalse(TryGetSyntax(getterField, out var fieldDeclaration, out var variableDeclarator, cancellationToken));
+
+ analysisResults.Push(new AnalysisResult(
+ property, getterField,
+ propertyDeclaration, fieldDeclaration, variableDeclarator,
+ notification,
+ isTrivialGetAccessor,
+ isTrivialSetAccessor));
}
protected virtual bool CanConvert(IPropertySymbol property)
=> true;
- private IFieldSymbol? GetSetterField(SemanticModel semanticModel, IMethodSymbol setMethod, CancellationToken cancellationToken)
- => CheckFieldAccessExpression(semanticModel, GetSetterExpression(setMethod, semanticModel, cancellationToken), cancellationToken);
+ protected IFieldSymbol? TryGetDirectlyAccessedFieldSymbol(
+ SemanticModel semanticModel,
+ TIdentifierName? identifierName,
+ HashSet fieldNames,
+ CancellationToken cancellationToken)
+ {
+ if (identifierName is null)
+ return null;
- private IFieldSymbol? GetGetterField(SemanticModel semanticModel, IMethodSymbol getMethod, CancellationToken cancellationToken)
- => CheckFieldAccessExpression(semanticModel, GetGetterExpression(getMethod, cancellationToken), cancellationToken);
+ var syntaxFacts = this.SyntaxFacts;
- private static IFieldSymbol? CheckFieldAccessExpression(SemanticModel semanticModel, TExpression? expression, CancellationToken cancellationToken)
- {
- if (expression == null)
+ // Quick check to avoid costly binding. Only look at identifiers that match the name of a private field in
+ // the containing type.
+ if (!fieldNames.Contains(syntaxFacts.GetIdentifierOfIdentifierName(identifierName).ValueText))
+ return null;
+
+ TExpression expression = identifierName;
+ if (this.SyntaxFacts.IsNameOfAnyMemberAccessExpression(expression))
+ expression = (TExpression)expression.GetRequiredParent();
+
+ var operation = semanticModel.GetOperation(expression, cancellationToken);
+ if (operation is not IFieldReferenceOperation
+ {
+ // Instance has to be 'null' (a static reference) or through `this.` Anything else is not a direct
+ // reference that can be updated to `field`.
+ Instance: null or IInstanceReferenceOperation
+ {
+ ReferenceKind: InstanceReferenceKind.ContainingTypeInstance,
+ },
+ Field.DeclaringSyntaxReferences.Length: 1,
+ } fieldReference)
+ {
return null;
+ }
- var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken);
- return symbolInfo.Symbol is IFieldSymbol { DeclaringSyntaxReferences.Length: 1 } field
- ? field
- : null;
+ return fieldReference.Field;
}
private void Process(
ConcurrentStack analysisResults,
- ConcurrentSet ineligibleFields,
- ConcurrentDictionary> nonConstructorFieldWrites,
+ ConcurrentDictionary> ineligibleFieldUsageIfOutsideProperty,
+ ConcurrentDictionary> fieldReads,
+ ConcurrentDictionary> fieldWrites,
SymbolAnalysisContext context)
{
+ using var _1 = PooledHashSet.GetInstance(out var reportedFields);
+ using var _2 = PooledHashSet.GetInstance(out var reportedProperties);
+
foreach (var result in analysisResults)
{
- // C# specific check.
- if (ineligibleFields.Contains(result.Field))
- continue;
+ // Check If we had any invalid field usage outside of the property we're converting.
+ if (ineligibleFieldUsageIfOutsideProperty.TryGetValue(result.Field, out var ineligibleFieldUsages))
+ {
+ if (!ineligibleFieldUsages.All(loc => loc.Ancestors().Contains(result.PropertyDeclaration)))
+ continue;
+
+ // All the usages were inside the property. This is ok if we support the `field` keyword as those
+ // usages will be updated to that form.
+ if (!this.SupportsFieldExpression(context.Compilation))
+ continue;
+ }
// VB specific check.
//
@@ -362,65 +568,110 @@ private void Process(
{
if (result.Property.DeclaredAccessibility != Accessibility.Private &&
result.Property.SetMethod is null &&
- nonConstructorFieldWrites.TryGetValue(result.Field, out var writeLocations1) &&
- writeLocations1.Any(loc => !loc.Ancestors().Contains(result.PropertyDeclaration)))
+ fieldWrites.TryGetValue(result.Field, out var writeLocations1) &&
+ NonConstructorLocations(writeLocations1).Any(loc => !loc.Ancestors().Contains(result.PropertyDeclaration)))
{
continue;
}
}
- // If this was an `init` property, and there was a write to the field, then we can't support this.
- // That's because we can't still keep this `init` as that write will not be allowed, and we can't make
- // it a `setter` as that would allow arbitrary writing outside the type, despite the original `init`
- // semantics.
+ // C# specific check.
+ //
+ // If this was an `init` property, and there was a write to the field, then we can't support this. That's
+ // because we can't still keep this `init` as that write will not be allowed, and we can't make it a
+ // `setter` as that would allow arbitrary writing outside the type, despite the original `init` semantics.
if (result.Property.SetMethod is { IsInitOnly: true } &&
- nonConstructorFieldWrites.TryGetValue(result.Field, out var writeLocations2) &&
- writeLocations2.Any(loc => !loc.Ancestors().Contains(result.PropertyDeclaration)))
+ fieldWrites.TryGetValue(result.Field, out var writeLocations2) &&
+ NonConstructorLocations(writeLocations2).Any(loc => !loc.Ancestors().Contains(result.PropertyDeclaration)))
+ {
+ continue;
+ }
+
+ // If we have a non-trivial getter, then we can't convert this if the field is read outside of the property.
+ // The read will go through the property getter now, which may change semantics.
+ if (!result.IsTrivialGetAccessor &&
+ fieldReads.TryGetValue(result.Field, out var specificFieldReads) &&
+ NotWithinProperty(specificFieldReads, result.PropertyDeclaration))
+ {
+ continue;
+ }
+
+ // If we have a non-trivial getter, then we can't convert this if the field is written outside of the
+ // property. The write will go through the property setter now, which may change semantics.
+ if (result.Property.SetMethod != null &&
+ !result.IsTrivialSetAccessor &&
+ fieldWrites.TryGetValue(result.Field, out var specificFieldWrites) &&
+ NotWithinProperty(specificFieldWrites, result.PropertyDeclaration))
{
continue;
}
- Process(result, context);
+ // Only report a use-auto-prop message at most once for any field or property. Note: we could be smarter
+ // here. The set of fields and properties form a bipartite graph. In an ideal world, we'd determine the
+ // maximal matching between those two bipartite sets (see
+ // https://en.wikipedia.org/wiki/Hopcroft%E2%80%93Karp_algorithm) and use that to offer the most matches as
+ // possible.
+ //
+ // We can see if the simple greedy approach of just taking the matches as we find them and returning those
+ // is insufficient in the future.
+ if (reportedFields.Contains(result.Field) || reportedProperties.Contains(result.Property))
+ continue;
+
+ reportedFields.Add(result.Field);
+ reportedProperties.Add(result.Property);
+
+ ReportDiagnostics(result);
}
- }
- private void Process(AnalysisResult result, SymbolAnalysisContext context)
- {
- var propertyDeclaration = result.PropertyDeclaration;
- var variableDeclarator = result.VariableDeclarator;
- var fieldNode = GetFieldNode(result.FieldDeclaration, variableDeclarator);
-
- // Now add diagnostics to both the field and the property saying we can convert it to
- // an auto property. For each diagnostic store both location so we can easily retrieve
- // them when performing the code fix.
- var additionalLocations = ImmutableArray.Create(
- propertyDeclaration.GetLocation(),
- variableDeclarator.GetLocation());
-
- // Place the appropriate marker on the field depending on the user option.
- var diagnostic1 = DiagnosticHelper.Create(
- Descriptor,
- fieldNode.GetLocation(),
- result.Notification,
- context.Options,
- additionalLocations: additionalLocations,
- properties: null);
-
- // Also, place a hidden marker on the property. If they bring up a lightbulb
- // there, they'll be able to see that they can convert it to an auto-prop.
- var diagnostic2 = Diagnostic.Create(
- Descriptor, propertyDeclaration.GetLocation(),
- additionalLocations: additionalLocations);
-
- context.ReportDiagnostic(diagnostic1);
- context.ReportDiagnostic(diagnostic2);
- }
+ static bool NotWithinProperty(IEnumerable nodes, TPropertyDeclaration propertyDeclaration)
+ {
+ foreach (var node in nodes)
+ {
+ if (!node.AncestorsAndSelf().Contains(propertyDeclaration))
+ return true;
+ }
+
+ return false;
+ }
- private sealed record AnalysisResult(
- IPropertySymbol Property,
- IFieldSymbol Field,
- TPropertyDeclaration PropertyDeclaration,
- TFieldDeclaration FieldDeclaration,
- TVariableDeclarator VariableDeclarator,
- NotificationOption2 Notification);
+ static IEnumerable NonConstructorLocations(IEnumerable nodes)
+ => nodes.Where(n => n.FirstAncestorOrSelf() is null);
+
+ void ReportDiagnostics(AnalysisResult result)
+ {
+ var propertyDeclaration = result.PropertyDeclaration;
+ var variableDeclarator = result.VariableDeclarator;
+ var fieldNode = GetFieldNode(result.FieldDeclaration, variableDeclarator);
+
+ // Now add diagnostics to both the field and the property saying we can convert it to
+ // an auto property. For each diagnostic store both location so we can easily retrieve
+ // them when performing the code fix.
+ var additionalLocations = ImmutableArray.Create(
+ propertyDeclaration.GetLocation(),
+ variableDeclarator.GetLocation());
+
+ var properties = ImmutableDictionary.Empty;
+ if (result.IsTrivialGetAccessor)
+ properties = properties.Add(IsTrivialGetAccessor, IsTrivialGetAccessor);
+
+ if (result.IsTrivialSetAccessor)
+ properties = properties.Add(IsTrivialSetAccessor, IsTrivialSetAccessor);
+
+ // Place the appropriate marker on the field depending on the user option.
+ context.ReportDiagnostic(DiagnosticHelper.Create(
+ Descriptor,
+ fieldNode.GetLocation(),
+ result.Notification,
+ context.Options,
+ additionalLocations,
+ properties));
+
+ // Also, place a hidden marker on the property. If they bring up a lightbulb there, they'll be able to see that
+ // they can convert it to an auto-prop.
+ context.ReportDiagnostic(Diagnostic.Create(
+ Descriptor, propertyDeclaration.GetLocation(),
+ additionalLocations,
+ properties));
+ }
+ }
}
diff --git a/src/Analyzers/Core/Analyzers/UseAutoProperty/AccessedFields.cs b/src/Analyzers/Core/Analyzers/UseAutoProperty/AccessedFields.cs
new file mode 100644
index 0000000000000..ce4fa40d42f0e
--- /dev/null
+++ b/src/Analyzers/Core/Analyzers/UseAutoProperty/AccessedFields.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Immutable;
+
+namespace Microsoft.CodeAnalysis.UseAutoProperty;
+
+/// The single field accessed, when the get/set-accessor is of a trivial form similar to
+/// get => fieldName; or set => fieldName = value; . If we see these forms, we'll want to convert them to
+/// get;/set; .
+/// Any fields we saw accessed in more complex expressions. These can be converted to use
+/// the field expression form if we think we can still convert this field/property pair to an auto-prop.
+internal readonly record struct AccessedFields(
+ IFieldSymbol? TrivialField,
+ ImmutableArray NonTrivialFields)
+{
+ public static readonly AccessedFields Empty = new(null, []);
+
+ public AccessedFields(IFieldSymbol? trivialField) : this(trivialField, [])
+ {
+ }
+
+ public int Count => (TrivialField != null ? 1 : 0) + NonTrivialFields.Length;
+ public bool IsEmpty => Count == 0;
+
+ public AccessedFields Where(Func predicate, TArg arg)
+ => new(TrivialField != null && predicate(TrivialField, arg) ? TrivialField : null,
+ NonTrivialFields.WhereAsArray(predicate, arg));
+
+ public bool Contains(IFieldSymbol field)
+ => Equals(TrivialField, field) || NonTrivialFields.Contains(field);
+}
diff --git a/src/Analyzers/Core/Analyzers/UseAutoProperty/AnalysisResult.cs b/src/Analyzers/Core/Analyzers/UseAutoProperty/AnalysisResult.cs
new file mode 100644
index 0000000000000..e38aa38ee311e
--- /dev/null
+++ b/src/Analyzers/Core/Analyzers/UseAutoProperty/AnalysisResult.cs
@@ -0,0 +1,40 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using Microsoft.CodeAnalysis.CodeStyle;
+
+namespace Microsoft.CodeAnalysis.UseAutoProperty;
+
+internal abstract partial class AbstractUseAutoPropertyAnalyzer<
+ TSyntaxKind,
+ TPropertyDeclaration,
+ TConstructorDeclaration,
+ TFieldDeclaration,
+ TVariableDeclarator,
+ TExpression,
+ TIdentifierName>
+{
+ /// The property we will make into an auto-property.
+ /// The field we are removing.
+ /// The single declaration that has.
+ /// The single containing declaration that has.
+ /// The single containing declarator that has.
+ /// The option value/severity at this particular analysis location.
+ /// If the get-accessor is of a trivial form like get => fieldName; . Such
+ /// an accessor is a simple 'read through to the field' accessor. As such, reads of the field can be replaced with
+ /// calls to this accessor as it will have the same semantics.
+ /// Same as . Such an accessor is a simple
+ /// 'write through to the field' accessor. As such, writes of the field can be replaced with calls to this accessor
+ /// as it will have the same semantics.
+ internal sealed record AnalysisResult(
+ IPropertySymbol Property,
+ IFieldSymbol Field,
+ TPropertyDeclaration PropertyDeclaration,
+ TFieldDeclaration FieldDeclaration,
+ TVariableDeclarator VariableDeclarator,
+ NotificationOption2 Notification,
+ bool IsTrivialGetAccessor,
+ bool IsTrivialSetAccessor);
+}
diff --git a/src/Analyzers/Core/Analyzers/UseAutoProperty/UseAutoPropertiesHelpers.cs b/src/Analyzers/Core/Analyzers/UseAutoProperty/UseAutoPropertiesHelpers.cs
new file mode 100644
index 0000000000000..19679570faf23
--- /dev/null
+++ b/src/Analyzers/Core/Analyzers/UseAutoProperty/UseAutoPropertiesHelpers.cs
@@ -0,0 +1,17 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.CodeAnalysis.UseAutoProperty;
+
+internal static class UseAutoPropertiesHelpers
+{
+ public const string IsTrivialGetAccessor = nameof(IsTrivialGetAccessor);
+ public const string IsTrivialSetAccessor = nameof(IsTrivialSetAccessor);
+}
diff --git a/src/Analyzers/VisualBasic/Analyzers/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb
index 39a420c3c7ff0..044d82e42b2f8 100644
--- a/src/Analyzers/VisualBasic/Analyzers/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb
+++ b/src/Analyzers/VisualBasic/Analyzers/UseAutoProperty/VisualBasicUseAutoPropertyAnalyzer.vb
@@ -2,6 +2,8 @@
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
+Imports System.Collections.Concurrent
+Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.LanguageService
@@ -32,11 +34,24 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty
Return DirectCast(compilation, VisualBasicCompilation).LanguageVersion >= LanguageVersion.VisualBasic10
End Function
- Protected Overrides Function CanExplicitInterfaceImplementationsBeFixed() As Boolean
- Return True
+ Protected Overrides Function SupportsFieldExpression(compilation As Compilation) As Boolean
+ ' 'field' keyword not supported in VB.
+ Return False
+ End Function
+
+ Protected Overrides ReadOnly Property CanExplicitInterfaceImplementationsBeFixed As Boolean = True
+ Protected Overrides ReadOnly Property SupportsFieldAttributesOnProperties As Boolean = False
+
+ Protected Overrides Function ContainsFieldExpression(propertyDeclaration As PropertyBlockSyntax, cancellationToken As CancellationToken) As Boolean
+ Return False
End Function
- Protected Overrides Sub RegisterIneligibleFieldsAction(fieldNames As HashSet(Of String), ineligibleFields As ConcurrentSet(Of IFieldSymbol), semanticModel As SemanticModel, codeBlock As SyntaxNode, cancellationToken As CancellationToken)
+ Protected Overrides Sub RecordIneligibleFieldLocations(
+ fieldNames As HashSet(Of String),
+ ineligibleFieldUsageIfOutsideProperty As ConcurrentDictionary(Of IFieldSymbol, ConcurrentSet(Of SyntaxNode)),
+ semanticModel As SemanticModel,
+ codeBlock As SyntaxNode,
+ cancellationToken As CancellationToken)
' There are no syntactic constructs that make a field ineligible to be replaced with
' a property. In C# you can't use a property in a ref/out position. But that restriction
' doesn't apply to VB.
@@ -100,7 +115,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty
Return Nothing
End Function
- Protected Overrides Function GetSetterExpression(setMethod As IMethodSymbol, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ExpressionSyntax
+ Protected Overrides Function GetSetterExpression(semanticModel As SemanticModel, setMethod As IMethodSymbol, cancellationToken As CancellationToken) As ExpressionSyntax
' Setter has to be of the form:
'
' Set(value)
@@ -132,5 +147,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty
Protected Overrides Function GetFieldNode(fieldDeclaration As FieldDeclarationSyntax, identifier As ModifiedIdentifierSyntax) As SyntaxNode
Return GetNodeToRemove(identifier)
End Function
+
+ Protected Overrides Sub AddAccessedFields(semanticModel As SemanticModel, accessor As IMethodSymbol, fieldNames As HashSet(Of String), result As HashSet(Of IFieldSymbol), cancellationToken As CancellationToken)
+ Throw ExceptionUtilities.Unreachable()
+ End Sub
End Class
End Namespace
diff --git a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs
index adf0adae32d83..28991bf64838a 100644
--- a/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs
+++ b/src/Features/CSharp/Portable/UseAutoProperty/CSharpUseAutoPropertyCodeFixProvider.cs
@@ -2,8 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-#nullable disable
-
+using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
@@ -17,7 +16,11 @@
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Formatting.Rules;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Rename;
+using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.UseAutoProperty;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseAutoProperty;
@@ -25,159 +28,222 @@ namespace Microsoft.CodeAnalysis.CSharp.UseAutoProperty;
using static SyntaxFactory;
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseAutoProperty), Shared]
-internal class CSharpUseAutoPropertyCodeFixProvider
- : AbstractUseAutoPropertyCodeFixProvider
+[method: ImportingConstructor]
+[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
+internal sealed partial class CSharpUseAutoPropertyCodeFixProvider()
+ : AbstractUseAutoPropertyCodeFixProvider<
+ TypeDeclarationSyntax,
+ PropertyDeclarationSyntax,
+ VariableDeclaratorSyntax,
+ ConstructorDeclarationSyntax,
+ ExpressionSyntax>
{
- [ImportingConstructor]
- [SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
- public CSharpUseAutoPropertyCodeFixProvider()
- {
- }
-
protected override PropertyDeclarationSyntax GetPropertyDeclaration(SyntaxNode node)
=> (PropertyDeclarationSyntax)node;
+ private static bool SupportsReadOnlyProperties(Compilation compilation)
+ => compilation.LanguageVersion() >= LanguageVersion.CSharp6;
+
+ private static bool IsSetOrInitAccessor(AccessorDeclarationSyntax accessor)
+ => accessor.Kind() is SyntaxKind.SetAccessorDeclaration or SyntaxKind.InitAccessorDeclaration;
+
+ private static FieldDeclarationSyntax GetFieldDeclaration(VariableDeclaratorSyntax declarator)
+ => (FieldDeclarationSyntax)declarator.GetRequiredParent().GetRequiredParent();
+
protected override SyntaxNode GetNodeToRemove(VariableDeclaratorSyntax declarator)
{
- var fieldDeclaration = (FieldDeclarationSyntax)declarator.Parent.Parent;
- var nodeToRemove = fieldDeclaration.Declaration.Variables.Count > 1 ? declarator : (SyntaxNode)fieldDeclaration;
- return nodeToRemove;
+ var fieldDeclaration = GetFieldDeclaration(declarator);
+ return fieldDeclaration.Declaration.Variables.Count > 1 ? declarator : fieldDeclaration;
}
- protected override async Task UpdatePropertyAsync(
- Document propertyDocument, Compilation compilation, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol,
- PropertyDeclarationSyntax propertyDeclaration, bool isWrittenOutsideOfConstructor, CancellationToken cancellationToken)
+ protected override PropertyDeclarationSyntax RewriteFieldReferencesInProperty(
+ PropertyDeclarationSyntax property,
+ LightweightRenameLocations fieldLocations,
+ CancellationToken cancellationToken)
+ {
+ // We're going to walk this property body, converting most reference of the field to use the `field` keyword
+ // instead. However, not all reference can be updated. For example, reference through another instance. Those
+ // we update to point at the property instead. So we grab that property name here to use in the rewriter.
+ var propertyIdentifier = property.Identifier.WithoutTrivia();
+ var propertyIdentifierName = IdentifierName(propertyIdentifier);
+
+ var identifierNames = fieldLocations.Locations
+ .Select(loc => loc.Location.FindNode(cancellationToken) as IdentifierNameSyntax)
+ .WhereNotNull()
+ .ToSet();
+
+ var rewriter = new UseAutoPropertyRewriter(propertyIdentifierName, identifierNames);
+ return (PropertyDeclarationSyntax)rewriter.Visit(property);
+ }
+
+ protected override Task UpdatePropertyAsync(
+ Document propertyDocument,
+ Compilation compilation,
+ IFieldSymbol fieldSymbol,
+ IPropertySymbol propertySymbol,
+ VariableDeclaratorSyntax fieldDeclarator,
+ PropertyDeclarationSyntax propertyDeclaration,
+ bool isWrittenOutsideOfConstructor,
+ bool isTrivialGetAccessor,
+ bool isTrivialSetAccessor,
+ CancellationToken cancellationToken)
{
var project = propertyDocument.Project;
- var trailingTrivia = propertyDeclaration.GetTrailingTrivia();
+ var generator = SyntaxGenerator.GetGenerator(project);
- var updatedProperty = propertyDeclaration.WithAccessorList(UpdateAccessorList(propertyDeclaration.AccessorList))
- .WithExpressionBody(null)
- .WithSemicolonToken(Token(SyntaxKind.None));
+ // Ensure that any attributes on the field are moved over to the property.
+ propertyDeclaration = MoveAttributes(propertyDeclaration, GetFieldDeclaration(fieldDeclarator));
// We may need to add a setter if the field is written to outside of the constructor
// of it's class.
- if (NeedsSetter(compilation, propertyDeclaration, isWrittenOutsideOfConstructor))
+ var needsSetter = NeedsSetter(compilation, propertyDeclaration, isWrittenOutsideOfConstructor);
+ var fieldInitializer = fieldDeclarator.Initializer?.Value;
+
+ if (!isTrivialGetAccessor && !isTrivialSetAccessor && !needsSetter && fieldInitializer == null)
{
- var accessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
- .WithSemicolonToken(SemicolonToken);
- var generator = SyntaxGenerator.GetGenerator(project);
+ // Nothing to actually do. We're not changing the accessors to `get;set;` accessors, and we didn't have to
+ // add an setter. We also had no field initializer to move over. This can happen when we're converting to
+ // using `field` and that rewrite already happened.
+ return Task.FromResult(propertyDeclaration);
+ }
+
+ // 1. If we have a trivial getters/setter then we want to convert to an accessor list to have `get;set;`
+ // 2. If we need a setter, we have to convert to having an accessor list to place the setter in.
+ // 3. If we have a field initializer, we need to convert to an accessor list to add the initializer expression after.
+ var updatedProperty = propertyDeclaration
+ .WithExpressionBody(null)
+ .WithSemicolonToken(default)
+ .WithAccessorList(ConvertToAccessorList(
+ propertyDeclaration, isTrivialGetAccessor, isTrivialSetAccessor));
+
+ if (needsSetter)
+ {
+ var accessor = AccessorDeclaration(SyntaxKind.SetAccessorDeclaration).WithSemicolonToken(SemicolonToken);
if (fieldSymbol.DeclaredAccessibility != propertySymbol.DeclaredAccessibility)
- {
accessor = (AccessorDeclarationSyntax)generator.WithAccessibility(accessor, fieldSymbol.DeclaredAccessibility);
- }
-
- var modifiers = TokenList(
- updatedProperty.Modifiers.Where(token => !token.IsKind(SyntaxKind.ReadOnlyKeyword)));
- updatedProperty = updatedProperty.WithModifiers(modifiers)
- .AddAccessorListAccessors(accessor);
+ updatedProperty = updatedProperty
+ .AddAccessorListAccessors(accessor)
+ .WithModifiers(TokenList(updatedProperty.Modifiers.Where(token => !token.IsKind(SyntaxKind.ReadOnlyKeyword))));
}
- var fieldInitializer = await GetFieldInitializerAsync(fieldSymbol, cancellationToken).ConfigureAwait(false);
+ // Move any field initializer over to the property as well.
if (fieldInitializer != null)
{
- updatedProperty = updatedProperty.WithInitializer(EqualsValueClause(fieldInitializer))
- .WithSemicolonToken(SemicolonToken);
+ updatedProperty = updatedProperty
+ .WithInitializer(EqualsValueClause(fieldInitializer))
+ .WithSemicolonToken(SemicolonToken);
}
- return updatedProperty.WithTrailingTrivia(trailingTrivia).WithAdditionalAnnotations(SpecializedFormattingAnnotation);
- }
+ var finalProperty = updatedProperty
+ .WithTrailingTrivia(propertyDeclaration.GetTrailingTrivia())
+ .WithAdditionalAnnotations(SpecializedFormattingAnnotation);
+ return Task.FromResult(finalProperty);
- protected override ImmutableArray GetFormattingRules(Document document)
- => [new SingleLinePropertyFormattingRule(), .. Formatter.GetDefaultFormattingRules(document)];
-
- private class SingleLinePropertyFormattingRule : AbstractFormattingRule
- {
- private static bool ForceSingleSpace(SyntaxToken previousToken, SyntaxToken currentToken)
+ static PropertyDeclarationSyntax MoveAttributes(
+ PropertyDeclarationSyntax property,
+ FieldDeclarationSyntax field)
{
- if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AccessorList))
- {
- return true;
- }
+ var fieldAttributes = field.AttributeLists;
+ if (fieldAttributes.Count == 0)
+ return property;
- if (previousToken.IsKind(SyntaxKind.OpenBraceToken) && previousToken.Parent.IsKind(SyntaxKind.AccessorList))
- {
- return true;
- }
+ var leadingTrivia = property.GetLeadingTrivia();
+ var indentation = leadingTrivia is [.., (kind: SyntaxKind.WhitespaceTrivia) whitespaceTrivia]
+ ? whitespaceTrivia
+ : default;
- if (currentToken.IsKind(SyntaxKind.CloseBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AccessorList))
+ using var _ = ArrayBuilder.GetInstance(out var finalAttributes);
+ foreach (var attributeList in fieldAttributes)
{
- return true;
+ // Change any field attributes to be `[field: ...]` attributes. Take the property's trivia and place it
+ // on the first field attribute we move over.
+ var converted = ConvertAttributeList(attributeList);
+ finalAttributes.Add(attributeList == fieldAttributes[0]
+ ? converted.WithLeadingTrivia(leadingTrivia)
+ : converted);
}
- return false;
- }
-
- public override AdjustNewLinesOperation GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation)
- {
- if (ForceSingleSpace(previousToken, currentToken))
+ foreach (var attributeList in property.AttributeLists)
{
- return null;
+ // Remove the leading trivia off of the first attribute. We're going to move it before all the new
+ // field attributes we're adding.
+ finalAttributes.Add(attributeList == property.AttributeLists[0]
+ ? attributeList.WithLeadingTrivia(indentation)
+ : attributeList);
}
- return base.GetAdjustNewLinesOperation(in previousToken, in currentToken, in nextOperation);
+ return property
+ .WithAttributeLists([])
+ .WithLeadingTrivia(indentation)
+ .WithAttributeLists(List(finalAttributes));
}
- public override AdjustSpacesOperation GetAdjustSpacesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustSpacesOperation nextOperation)
- {
- if (ForceSingleSpace(previousToken, currentToken))
- {
- return new AdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces);
- }
+ static AttributeListSyntax ConvertAttributeList(AttributeListSyntax attributeList)
+ => attributeList.WithTarget(AttributeTargetSpecifier(Identifier(SyntaxFacts.GetText(SyntaxKind.FieldKeyword)), ColonToken.WithTrailingTrivia(Space)));
- return base.GetAdjustSpacesOperation(in previousToken, in currentToken, in nextOperation);
+ static AccessorListSyntax ConvertToAccessorList(
+ PropertyDeclarationSyntax propertyDeclaration,
+ bool isTrivialGetAccessor,
+ bool isTrivialSetAccessor)
+ {
+ // If we don't have an accessor list at all, convert the property's expr body to a `get => ...` accessor.
+ var accessorList = propertyDeclaration.AccessorList ?? AccessorList(SingletonList(
+ AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
+ .WithExpressionBody(propertyDeclaration.ExpressionBody)
+ .WithSemicolonToken(SemicolonToken)));
+
+ // Now that we have an accessor list, convert the getter/setter to `get;`/`set;` form if requested.
+ return accessorList.WithAccessors(List(accessorList.Accessors.Select(
+ accessor =>
+ {
+ var convert =
+ (isTrivialGetAccessor && accessor.Kind() is SyntaxKind.GetAccessorDeclaration) ||
+ (isTrivialSetAccessor && IsSetOrInitAccessor(accessor));
+
+ if (convert)
+ {
+ if (accessor.ExpressionBody != null)
+ return accessor.WithExpressionBody(null).WithKeyword(accessor.Keyword.WithoutTrailingTrivia());
+
+ if (accessor.Body != null)
+ return accessor.WithBody(null).WithSemicolonToken(SemicolonToken.WithTrailingTrivia(accessor.Body.CloseBraceToken.TrailingTrivia));
+ }
+
+ return accessor;
+ })));
}
}
- private static async Task GetFieldInitializerAsync(IFieldSymbol fieldSymbol, CancellationToken cancellationToken)
+ protected override ImmutableArray GetFormattingRules(
+ Document document,
+ SyntaxNode propertyDeclaration)
{
- var variableDeclarator = (VariableDeclaratorSyntax)await fieldSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
- return variableDeclarator.Initializer?.Value;
+ // If the final property is only simple `get;set;` accessors, then reformat the property to be on a single line.
+ if (propertyDeclaration is PropertyDeclarationSyntax { AccessorList.Accessors: var accessors } &&
+ accessors.All(a => a is { ExpressionBody: null, Body: null }))
+ {
+ return [new SingleLinePropertyFormattingRule(), .. Formatter.GetDefaultFormattingRules(document)];
+ }
+
+ return default;
}
private static bool NeedsSetter(Compilation compilation, PropertyDeclarationSyntax propertyDeclaration, bool isWrittenOutsideOfConstructor)
{
- if (propertyDeclaration.AccessorList?.Accessors.Any(SyntaxKind.SetAccessorDeclaration) == true)
+ // Don't need to add if we already have a setter.
+ if (propertyDeclaration.AccessorList != null &&
+ propertyDeclaration.AccessorList.Accessors.Any(IsSetOrInitAccessor))
{
- // Already has a setter.
return false;
}
+ // If the language doesn't have readonly properties, then we'll need a setter here.
if (!SupportsReadOnlyProperties(compilation))
- {
- // If the language doesn't have readonly properties, then we'll need a
- // setter here.
return true;
- }
// If we're written outside a constructor we need a setter.
return isWrittenOutsideOfConstructor;
}
-
- private static bool SupportsReadOnlyProperties(Compilation compilation)
- => compilation.LanguageVersion() >= LanguageVersion.CSharp6;
-
- private static AccessorListSyntax UpdateAccessorList(AccessorListSyntax accessorList)
- {
- if (accessorList == null)
- {
- var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
- .WithSemicolonToken(SemicolonToken);
- return AccessorList([getter]);
- }
-
- return accessorList.WithAccessors([.. GetAccessors(accessorList.Accessors)]);
- }
-
- private static IEnumerable GetAccessors(SyntaxList accessors)
- {
- foreach (var accessor in accessors)
- {
- yield return accessor.WithBody(null)
- .WithExpressionBody(null)
- .WithSemicolonToken(SemicolonToken);
- }
- }
}
diff --git a/src/Features/CSharp/Portable/UseAutoProperty/SingleLinePropertyFormattingRule.cs b/src/Features/CSharp/Portable/UseAutoProperty/SingleLinePropertyFormattingRule.cs
new file mode 100644
index 0000000000000..889d73f575ec3
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseAutoProperty/SingleLinePropertyFormattingRule.cs
@@ -0,0 +1,47 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Formatting.Rules;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseAutoProperty;
+
+internal sealed partial class CSharpUseAutoPropertyCodeFixProvider
+{
+ private sealed class SingleLinePropertyFormattingRule : AbstractFormattingRule
+ {
+ private static bool ForceSingleSpace(SyntaxToken previousToken, SyntaxToken currentToken)
+ {
+ if (currentToken.IsKind(SyntaxKind.OpenBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AccessorList))
+ return true;
+
+ if (previousToken.IsKind(SyntaxKind.OpenBraceToken) && previousToken.Parent.IsKind(SyntaxKind.AccessorList))
+ return true;
+
+ if (currentToken.IsKind(SyntaxKind.CloseBraceToken) && currentToken.Parent.IsKind(SyntaxKind.AccessorList))
+ return true;
+
+ if (previousToken.IsKind(SyntaxKind.SemicolonToken) && currentToken.Parent is AccessorDeclarationSyntax)
+ return true;
+
+ return false;
+ }
+
+ public override AdjustNewLinesOperation? GetAdjustNewLinesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation)
+ {
+ if (ForceSingleSpace(previousToken, currentToken))
+ return null;
+
+ return base.GetAdjustNewLinesOperation(in previousToken, in currentToken, in nextOperation);
+ }
+
+ public override AdjustSpacesOperation? GetAdjustSpacesOperation(in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustSpacesOperation nextOperation)
+ {
+ if (ForceSingleSpace(previousToken, currentToken))
+ return new AdjustSpacesOperation(1, AdjustSpacesOption.ForceSpaces);
+
+ return base.GetAdjustSpacesOperation(in previousToken, in currentToken, in nextOperation);
+ }
+ }
+}
diff --git a/src/Features/CSharp/Portable/UseAutoProperty/UseAutoPropertyRewriter.cs b/src/Features/CSharp/Portable/UseAutoProperty/UseAutoPropertyRewriter.cs
new file mode 100644
index 0000000000000..672bf3cb19c38
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseAutoProperty/UseAutoPropertyRewriter.cs
@@ -0,0 +1,61 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Generic;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseAutoProperty;
+
+using static SyntaxFactory;
+
+internal sealed partial class CSharpUseAutoPropertyCodeFixProvider
+{
+ private sealed class UseAutoPropertyRewriter(
+ IdentifierNameSyntax propertyIdentifierName,
+ ISet identifierNames) : CSharpSyntaxRewriter
+ {
+ private readonly IdentifierNameSyntax _propertyIdentifierName = propertyIdentifierName;
+ private readonly ISet _identifierNames = identifierNames;
+
+ public override SyntaxNode? VisitMemberAccessExpression(MemberAccessExpressionSyntax node)
+ {
+ if (node.Name is IdentifierNameSyntax identifierName &&
+ _identifierNames.Contains(identifierName))
+ {
+ if (node.Expression.IsKind(SyntaxKind.ThisExpression))
+ {
+ // `this.fieldName` gets rewritten to `field`.
+ return FieldExpression().WithTriviaFrom(node);
+ }
+ else
+ {
+ // `obj.fieldName` gets rewritten to `obj.PropName`
+ return node.WithName(_propertyIdentifierName.WithTriviaFrom(identifierName));
+ }
+ }
+
+ return base.VisitMemberAccessExpression(node);
+ }
+
+ public override SyntaxNode? VisitIdentifierName(IdentifierNameSyntax node)
+ {
+ if (_identifierNames.Contains(node))
+ {
+ if (node.Parent is AssignmentExpressionSyntax
+ {
+ Parent: InitializerExpressionSyntax { RawKind: (int)SyntaxKind.ObjectInitializerExpression }
+ } assignment && assignment.Left == node)
+ {
+ // `new X { fieldName = ... }` gets rewritten to `new X { propName = ... }`
+ return _propertyIdentifierName.WithTriviaFrom(node);
+ }
+
+ // Any other naked reference to fieldName within the property gets updated to `field`.
+ return FieldExpression().WithTriviaFrom(node);
+ }
+
+ return base.VisitIdentifierName(node);
+ }
+ }
+}
diff --git a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs
index 08bd17a7fad6e..1c8118dd50c9a 100644
--- a/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs
+++ b/src/Features/Core/Portable/UseAutoProperty/AbstractUseAutoPropertyCodeFixProvider.cs
@@ -23,6 +23,8 @@
namespace Microsoft.CodeAnalysis.UseAutoProperty;
+using static UseAutoPropertiesHelpers;
+
internal abstract class AbstractUseAutoPropertyCodeFixProvider : CodeFixProvider
where TTypeDeclarationSyntax : SyntaxNode
where TPropertyDeclaration : SyntaxNode
@@ -39,12 +41,23 @@ public sealed override ImmutableArray FixableDiagnosticIds
protected abstract TPropertyDeclaration GetPropertyDeclaration(SyntaxNode node);
protected abstract SyntaxNode GetNodeToRemove(TVariableDeclarator declarator);
+ protected abstract TPropertyDeclaration RewriteFieldReferencesInProperty(
+ TPropertyDeclaration property, LightweightRenameLocations fieldLocations, CancellationToken cancellationToken);
- protected abstract ImmutableArray GetFormattingRules(Document document);
+ protected abstract ImmutableArray GetFormattingRules(
+ Document document, SyntaxNode finalPropertyDeclaration);
protected abstract Task UpdatePropertyAsync(
- Document propertyDocument, Compilation compilation, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol,
- TPropertyDeclaration propertyDeclaration, bool isWrittenOutsideConstructor, CancellationToken cancellationToken);
+ Document propertyDocument,
+ Compilation compilation,
+ IFieldSymbol fieldSymbol,
+ IPropertySymbol propertySymbol,
+ TVariableDeclarator fieldDeclarator,
+ TPropertyDeclaration propertyDeclaration,
+ bool isWrittenOutsideConstructor,
+ bool isTrivialGetAccessor,
+ bool isTrivialSetAccessor,
+ CancellationToken cancellationToken);
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
@@ -56,7 +69,7 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
context.RegisterCodeFix(CodeAction.SolutionChangeAction.Create(
AnalyzersResources.Use_auto_property,
- c => ProcessResultAsync(context, diagnostic, c),
+ cancellationToken => ProcessResultAsync(context, diagnostic, cancellationToken),
equivalenceKey: nameof(AnalyzersResources.Use_auto_property),
priority),
diagnostic);
@@ -68,6 +81,7 @@ public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
private async Task ProcessResultAsync(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var locations = diagnostic.AdditionalLocations;
+
var propertyLocation = locations[0];
var declaratorLocation = locations[1];
@@ -82,6 +96,9 @@ private async Task ProcessResultAsync(CodeFixContext context, Diagnost
var propertySemanticModel = await propertyDocument.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var propertySymbol = (IPropertySymbol)propertySemanticModel.GetRequiredDeclaredSymbol(property, cancellationToken);
+ var isTrivialGetAccessor = diagnostic.Properties.ContainsKey(IsTrivialGetAccessor);
+ var isTrivialSetAccessor = diagnostic.Properties.ContainsKey(IsTrivialSetAccessor);
+
Debug.Assert(fieldDocument.Project == propertyDocument.Project);
var project = fieldDocument.Project;
var compilation = await project.GetRequiredCompilationAsync(cancellationToken).ConfigureAwait(false);
@@ -92,10 +109,23 @@ private async Task ProcessResultAsync(CodeFixContext context, Diagnost
solution, fieldSymbol, renameOptions, cancellationToken).ConfigureAwait(false);
// First, create the updated property we want to replace the old property with
- var isWrittenToOutsideOfConstructor = IsWrittenToOutsideOfConstructorOrProperty(fieldSymbol, fieldLocations, property, cancellationToken);
+ var isWrittenToOutsideOfConstructor = IsWrittenToOutsideOfConstructorOrProperty(
+ fieldSymbol, fieldLocations, property, cancellationToken);
+
+ if (!isTrivialGetAccessor ||
+ (propertySymbol.SetMethod != null && !isTrivialSetAccessor))
+ {
+ // We have at least a non-trivial getter/setter. Those will not be rewritten to `get;/set;`. As such, we
+ // need to update the property to reference `field` or itself instead of the actual field.
+ property = RewriteFieldReferencesInProperty(property, fieldLocations, cancellationToken);
+ }
+
var updatedProperty = await UpdatePropertyAsync(
- propertyDocument, compilation, fieldSymbol, propertySymbol, property,
- isWrittenToOutsideOfConstructor, cancellationToken).ConfigureAwait(false);
+ propertyDocument, compilation,
+ fieldSymbol, propertySymbol,
+ declarator, property,
+ isWrittenToOutsideOfConstructor, isTrivialGetAccessor, isTrivialSetAccessor,
+ cancellationToken).ConfigureAwait(false);
// Note: rename will try to update all the references in linked files as well. However,
// this can lead to some very bad behavior as we will change the references in linked files
@@ -211,7 +241,7 @@ private async Task ProcessResultAsync(CodeFixContext context, Diagnost
editor.RemoveNode(nodeToRemove, syntaxRemoveOptions);
var newRoot = editor.GetChangedRoot();
- newRoot = await FormatAsync(newRoot, fieldDocument, cancellationToken).ConfigureAwait(false);
+ newRoot = await FormatAsync(newRoot, fieldDocument, updatedProperty, cancellationToken).ConfigureAwait(false);
return solution.WithDocumentSyntaxRoot(fieldDocument.Id, newRoot);
}
@@ -225,8 +255,8 @@ private async Task ProcessResultAsync(CodeFixContext context, Diagnost
Contract.ThrowIfNull(newFieldTreeRoot);
var newPropertyTreeRoot = propertyTreeRoot.ReplaceNode(property, updatedProperty);
- newFieldTreeRoot = await FormatAsync(newFieldTreeRoot, fieldDocument, cancellationToken).ConfigureAwait(false);
- newPropertyTreeRoot = await FormatAsync(newPropertyTreeRoot, propertyDocument, cancellationToken).ConfigureAwait(false);
+ newFieldTreeRoot = await FormatAsync(newFieldTreeRoot, fieldDocument, updatedProperty, cancellationToken).ConfigureAwait(false);
+ newPropertyTreeRoot = await FormatAsync(newPropertyTreeRoot, propertyDocument, updatedProperty, cancellationToken).ConfigureAwait(false);
var updatedSolution = solution.WithDocumentSyntaxRoot(fieldDocument.Id, newFieldTreeRoot);
updatedSolution = updatedSolution.WithDocumentSyntaxRoot(propertyDocument.Id, newPropertyTreeRoot);
@@ -277,9 +307,13 @@ private static bool CanEditDocument(
return canEditDocument;
}
- private async Task FormatAsync(SyntaxNode newRoot, Document document, CancellationToken cancellationToken)
+ private async Task FormatAsync(
+ SyntaxNode newRoot,
+ Document document,
+ SyntaxNode finalPropertyDeclaration,
+ CancellationToken cancellationToken)
{
- var formattingRules = GetFormattingRules(document);
+ var formattingRules = GetFormattingRules(document, finalPropertyDeclaration);
if (formattingRules.IsDefault)
return newRoot;
diff --git a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb
index 244c4a39c372c..0ba154e235d0f 100644
--- a/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb
+++ b/src/Features/VisualBasic/Portable/UseAutoProperty/VisualBasicUseAutoPropertyCodeFixProvider.vb
@@ -9,12 +9,13 @@ Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.Formatting.Rules
+Imports Microsoft.CodeAnalysis.Rename
Imports Microsoft.CodeAnalysis.UseAutoProperty
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty
- Friend Class VisualBasicUseAutoPropertyCodeFixProvider
+ Friend NotInheritable Class VisualBasicUseAutoPropertyCodeFixProvider
Inherits AbstractUseAutoPropertyCodeFixProvider(Of TypeBlockSyntax, PropertyBlockSyntax, ModifiedIdentifierSyntax, ConstructorBlockSyntax, ExpressionSyntax)
@@ -34,17 +35,26 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseAutoProperty
Return Utilities.GetNodeToRemove(identifier)
End Function
- Protected Overrides Function GetFormattingRules(document As Document) As ImmutableArray(Of AbstractFormattingRule)
+ Protected Overrides Function GetFormattingRules(document As Document, finalProperty As SyntaxNode) As ImmutableArray(Of AbstractFormattingRule)
Return Nothing
End Function
- Protected Overrides Async Function UpdatePropertyAsync(propertyDocument As Document,
- compilation As Compilation,
- fieldSymbol As IFieldSymbol,
- propertySymbol As IPropertySymbol,
- propertyDeclaration As PropertyBlockSyntax,
- isWrittenToOutsideOfConstructor As Boolean,
- cancellationToken As CancellationToken) As Task(Of SyntaxNode)
+ Protected Overrides Function RewriteFieldReferencesInProperty([property] As PropertyBlockSyntax, fieldLocations As LightweightRenameLocations, cancellationToken As CancellationToken) As PropertyBlockSyntax
+ ' Only called to rewrite to `field` (which VB does not support).
+ Return [property]
+ End Function
+
+ Protected Overrides Async Function UpdatePropertyAsync(
+ propertyDocument As Document,
+ compilation As Compilation,
+ fieldSymbol As IFieldSymbol,
+ propertySymbol As IPropertySymbol,
+ fieldDeclarator As ModifiedIdentifierSyntax,
+ propertyDeclaration As PropertyBlockSyntax,
+ isWrittenToOutsideOfConstructor As Boolean,
+ isTrivialGetAccessor As Boolean,
+ isTrivialSetAccessor As Boolean,
+ cancellationToken As CancellationToken) As Task(Of SyntaxNode)
Dim statement = propertyDeclaration.PropertyStatement
Dim generator = SyntaxGenerator.GetGenerator(propertyDocument.Project)
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs
index ae491e6c78840..5f5a71587dcfa 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Extensions/ExpressionSyntaxExtensions.cs
@@ -243,21 +243,15 @@ public static bool IsOnlyWrittenTo([NotNullWhen(true)] this ExpressionSyntax? ex
if (expression != null)
{
if (expression.IsInOutContext())
- {
return true;
- }
if (expression.Parent != null)
{
if (expression.IsLeftSideOfAssignExpression())
- {
return true;
- }
if (expression.IsAttributeNamedArgumentIdentifier())
- {
return true;
- }
}
if (IsExpressionOfArgumentInDeconstruction(expression))
diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs
index e0f157f5b4333..127ecf928a193 100644
--- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs
+++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ImmutableArrayExtensions.cs
@@ -11,6 +11,10 @@ internal static partial class ImmutableArrayExtensions
{
public static ImmutableArray ToImmutableArray(this HashSet set)
{
+ // [.. set] currently allocates, even for the empty case. Workaround that until that is solved by the compiler.
+ if (set.Count == 0)
+ return [];
+
return [.. set];
}
From 2397d0d15d89373910f7901944962fee10ea64b9 Mon Sep 17 00:00:00 2001
From: Charles Stoner <10732005+cston@users.noreply.github.com>
Date: Mon, 12 Aug 2024 14:15:29 -0700
Subject: [PATCH 03/18] Handle various cases for field-backed properties
(#74641)
---
.../Portable/Binder/Binder_Statements.cs | 2 +-
.../Portable/Compiler/MethodCompiler.cs | 3 +-
.../Portable/FlowAnalysis/NullableWalker.cs | 2 +-
.../LocalRewriter_AssignmentOperator.cs | 2 +-
.../Source/SourcePropertyAccessorSymbol.cs | 2 +
.../Symbols/Source/SourcePropertySymbol.cs | 67 +-
.../Source/SourcePropertySymbolBase.cs | 61 +-
...nthesizedRecordEqualityContractProperty.cs | 3 +-
.../SynthesizedRecordPropertySymbol.cs | 3 +-
.../SynthesizedBackingFieldSymbol.cs | 2 +-
.../Test/Emit/Emit/EmitMetadataTests.cs | 2 +-
.../CSharp/Test/Emit3/FieldKeywordTests.cs | 810 +++++++++++++++++-
.../Semantic/Semantics/InitOnlyMemberTests.cs | 13 +-
.../Semantics/NullableReferenceTypesTests.cs | 3 +
.../UninitializedNonNullableFieldTests.cs | 3 +
.../DefaultInterfaceImplementationTests.cs | 76 +-
.../Symbol/Symbols/Source/PropertyTests.cs | 46 +-
.../Test/Symbol/Symbols/SymbolErrorTests.cs | 27 +-
18 files changed, 1022 insertions(+), 105 deletions(-)
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index b0f799d6ae6c3..c2c03dddb53f8 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -1765,7 +1765,7 @@ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receive
var propertyIsStatic = propertySymbol.IsStatic;
return (object)sourceProperty != null &&
- sourceProperty.IsAutoPropertyWithGetAccessor &&
+ sourceProperty.IsAutoProperty &&
TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) &&
IsConstructorOrField(fromMember, isStatic: propertyIsStatic) &&
(propertyIsStatic || receiver.Kind == BoundKind.ThisReference);
diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
index 7940f2df63ec4..d889b8a84621e 100644
--- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
+++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs
@@ -1879,8 +1879,7 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax &&
}
else
{
- var property = sourceMethod.AssociatedSymbol as SourcePropertySymbolBase;
- if (property is not null && property.IsAutoPropertyWithGetAccessor)
+ if (sourceMethod is SourcePropertyAccessorSymbol { IsAutoPropertyAccessor: true })
{
return MethodBodySynthesizer.ConstructAutoPropertyAccessorBody(sourceMethod);
}
diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
index cb06ce1c35d30..ad012a565cfee 100644
--- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
+++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs
@@ -1075,7 +1075,7 @@ static IEnumerable getAllMembersToBeDefaulted(Symbol requiredMember)
}
static Symbol getFieldSymbolToBeInitialized(Symbol requiredMember)
- => requiredMember is SourcePropertySymbol { IsAutoPropertyWithGetAccessor: true } prop ? prop.BackingField : requiredMember;
+ => requiredMember is SourcePropertySymbol { IsAutoProperty: true } prop ? prop.BackingField : requiredMember;
}
}
}
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
index 75c23b3592248..5ff4636c6c674 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
@@ -281,7 +281,7 @@ private BoundExpression MakePropertyAssignment(
if (setMethod is null)
{
var autoProp = (SourcePropertySymbolBase)property.OriginalDefinition;
- Debug.Assert(autoProp.IsAutoPropertyWithGetAccessor,
+ Debug.Assert(autoProp.IsAutoProperty,
"only autoproperties can be assignable without having setters");
Debug.Assert(property.Equals(autoProp, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs
index dc1f91d9fe98d..d57ad4859e972 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertyAccessorSymbol.cs
@@ -485,6 +485,8 @@ internal sealed override bool IsDeclaredReadOnly
}
}
+ internal bool IsAutoPropertyAccessor => _isAutoPropertyAccessor;
+
internal sealed override bool IsInitOnly => !IsStatic && _usesInit;
private static DeclarationModifiers MakeModifiers(NamedTypeSymbol containingType, SyntaxTokenList modifiers, bool isExplicitInterfaceImplementation,
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs
index e575d7840340a..64eb737684431 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs
@@ -40,13 +40,16 @@ private static SourcePropertySymbol Create(
GetAccessorDeclarations(
syntax,
diagnostics,
- out bool hasAccessorList,
- out bool accessorsHaveImplementation,
+ out bool isExpressionBodied,
+ out bool hasGetAccessorImplementation,
+ out bool hasSetAccessorImplementation,
out bool usesFieldKeyword,
out bool isInitOnly,
out var getSyntax,
out var setSyntax);
+ bool accessorsHaveImplementation = hasGetAccessorImplementation || hasSetAccessorImplementation;
+
var explicitInterfaceSpecifier = GetExplicitInterfaceSpecifier(syntax);
SyntaxTokenList modifiersTokenList = GetModifierTokensSyntax(syntax);
bool isExplicitInterfaceImplementation = explicitInterfaceSpecifier is object;
@@ -60,8 +63,10 @@ private static SourcePropertySymbol Create(
diagnostics,
out _);
- bool isAutoProperty = (modifiers & DeclarationModifiers.Partial) == 0 && !accessorsHaveImplementation;
- bool isExpressionBodied = !hasAccessorList && GetArrowExpression(syntax) != null;
+ bool allowAutoPropertyAccessors = (modifiers & (DeclarationModifiers.Partial | DeclarationModifiers.Abstract | DeclarationModifiers.Extern | DeclarationModifiers.Indexer)) == 0 &&
+ (!containingType.IsInterface || (modifiers & DeclarationModifiers.Static) != 0);
+ bool hasAutoPropertyGet = allowAutoPropertyAccessors && getSyntax != null && !hasGetAccessorImplementation;
+ bool hasAutoPropertySet = allowAutoPropertyAccessors && setSyntax != null && !hasSetAccessorImplementation;
binder = binder.SetOrClearUnsafeRegionIfNecessary(modifiersTokenList);
TypeSymbol? explicitInterfaceType;
@@ -78,7 +83,8 @@ private static SourcePropertySymbol Create(
aliasQualifierOpt,
modifiers,
hasExplicitAccessMod: hasExplicitAccessMod,
- isAutoProperty: isAutoProperty,
+ hasAutoPropertyGet: hasAutoPropertyGet,
+ hasAutoPropertySet: hasAutoPropertySet,
isExpressionBodied: isExpressionBodied,
isInitOnly: isInitOnly,
accessorsHaveImplementation: accessorsHaveImplementation,
@@ -98,7 +104,8 @@ private SourcePropertySymbol(
string? aliasQualifierOpt,
DeclarationModifiers modifiers,
bool hasExplicitAccessMod,
- bool isAutoProperty,
+ bool hasAutoPropertyGet,
+ bool hasAutoPropertySet,
bool isExpressionBodied,
bool isInitOnly,
bool accessorsHaveImplementation,
@@ -109,15 +116,16 @@ private SourcePropertySymbol(
: base(
containingType,
syntax,
- hasGetAccessor,
- hasSetAccessor,
+ hasGetAccessor: hasGetAccessor,
+ hasSetAccessor: hasSetAccessor,
isExplicitInterfaceImplementation,
explicitInterfaceType,
aliasQualifierOpt,
modifiers,
hasInitializer: HasInitializer(syntax),
hasExplicitAccessMod: hasExplicitAccessMod,
- isAutoProperty: isAutoProperty,
+ hasAutoPropertyGet: hasAutoPropertyGet,
+ hasAutoPropertySet: hasAutoPropertySet,
isExpressionBodied: isExpressionBodied,
isInitOnly: isInitOnly,
accessorsHaveImplementation: accessorsHaveImplementation,
@@ -130,11 +138,13 @@ private SourcePropertySymbol(
{
Debug.Assert(syntax.Type is not ScopedTypeSyntax);
- if (IsAutoProperty)
+ if (hasAutoPropertyGet || hasAutoPropertySet)
{
Binder.CheckFeatureAvailability(
syntax,
- (hasGetAccessor && !hasSetAccessor) ? MessageID.IDS_FeatureReadonlyAutoImplementedProperties : MessageID.IDS_FeatureAutoImplementedProperties,
+ hasGetAccessor && hasSetAccessor ?
+ (hasAutoPropertyGet && hasAutoPropertySet ? MessageID.IDS_FeatureAutoImplementedProperties : MessageID.IDS_FeatureFieldKeyword) :
+ (hasAutoPropertyGet ? MessageID.IDS_FeatureReadonlyAutoImplementedProperties : MessageID.IDS_FeatureAutoImplementedProperties),
diagnostics,
location);
}
@@ -202,23 +212,25 @@ public override OneOrMany> GetAttributeDeclarati
private static void GetAccessorDeclarations(
CSharpSyntaxNode syntaxNode,
BindingDiagnosticBag diagnostics,
- out bool hasAccessorList,
- out bool accessorsHaveImplementation,
+ out bool isExpressionBodied,
+ out bool hasGetAccessorImplementation,
+ out bool hasSetAccessorImplementation,
out bool usesFieldKeyword,
out bool isInitOnly,
- out CSharpSyntaxNode? getSyntax,
- out CSharpSyntaxNode? setSyntax)
+ out AccessorDeclarationSyntax? getSyntax,
+ out AccessorDeclarationSyntax? setSyntax)
{
var syntax = (BasePropertyDeclarationSyntax)syntaxNode;
- hasAccessorList = syntax.AccessorList != null;
+ isExpressionBodied = syntax.AccessorList is null;
getSyntax = null;
setSyntax = null;
isInitOnly = false;
- if (hasAccessorList)
+ if (!isExpressionBodied)
{
usesFieldKeyword = false;
- accessorsHaveImplementation = false;
+ hasGetAccessorImplementation = false;
+ hasSetAccessorImplementation = false;
foreach (var accessor in syntax.AccessorList!.Accessors)
{
switch (accessor.Kind())
@@ -227,6 +239,7 @@ private static void GetAccessorDeclarations(
if (getSyntax == null)
{
getSyntax = accessor;
+ hasGetAccessorImplementation = hasImplementation(accessor);
}
else
{
@@ -238,6 +251,7 @@ private static void GetAccessorDeclarations(
if (setSyntax == null)
{
setSyntax = accessor;
+ hasSetAccessorImplementation = hasImplementation(accessor);
if (accessor.Keyword.IsKind(SyntaxKind.InitKeyword))
{
isInitOnly = true;
@@ -260,21 +274,22 @@ private static void GetAccessorDeclarations(
throw ExceptionUtilities.UnexpectedValue(accessor.Kind());
}
- var body = (SyntaxNode?)accessor.Body ?? accessor.ExpressionBody;
- if (body != null)
- {
- accessorsHaveImplementation = true;
- }
-
usesFieldKeyword = usesFieldKeyword || containsFieldKeyword(accessor);
}
}
else
{
var body = GetArrowExpression(syntax);
- accessorsHaveImplementation = body is object;
+ hasGetAccessorImplementation = body is object;
+ hasSetAccessorImplementation = false;
usesFieldKeyword = body is { } && containsFieldKeyword(body);
- Debug.Assert(accessorsHaveImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression.
+ Debug.Assert(hasGetAccessorImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression.
+ }
+
+ static bool hasImplementation(AccessorDeclarationSyntax accessor)
+ {
+ var body = (SyntaxNode?)accessor.Body ?? accessor.ExpressionBody;
+ return body != null;
}
static bool containsFieldKeyword(SyntaxNode syntax)
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
index 2ba54ae1a0b8e..95fa2f85e9da4 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
@@ -29,11 +29,13 @@ internal abstract class SourcePropertySymbolBase : PropertySymbol, IAttributeTar
private enum Flags : byte
{
IsExpressionBodied = 1 << 0,
- IsAutoProperty = 1 << 1,
- IsExplicitInterfaceImplementation = 1 << 2,
- HasInitializer = 1 << 3,
- AccessorsHaveImplementation = 1 << 4,
- HasExplicitAccessModifier = 1 << 5,
+ HasAutoPropertyGet = 1 << 1,
+ HasAutoPropertySet = 1 << 2,
+ UsesFieldKeyword = 1 << 3,
+ IsExplicitInterfaceImplementation = 1 << 4,
+ HasInitializer = 1 << 5,
+ AccessorsHaveImplementation = 1 << 6,
+ HasExplicitAccessModifier = 1 << 7,
}
// TODO (tomat): consider splitting into multiple subclasses/rare data.
@@ -80,7 +82,8 @@ protected SourcePropertySymbolBase(
DeclarationModifiers modifiers,
bool hasInitializer,
bool hasExplicitAccessMod,
- bool isAutoProperty,
+ bool hasAutoPropertyGet,
+ bool hasAutoPropertySet,
bool isExpressionBodied,
bool isInitOnly,
bool accessorsHaveImplementation,
@@ -91,7 +94,7 @@ protected SourcePropertySymbolBase(
Location location,
BindingDiagnosticBag diagnostics)
{
- Debug.Assert(!isExpressionBodied || !isAutoProperty);
+ Debug.Assert(!isExpressionBodied || !(hasAutoPropertyGet || hasAutoPropertySet));
Debug.Assert(!isExpressionBodied || !hasInitializer);
Debug.Assert(!isExpressionBodied || accessorsHaveImplementation);
Debug.Assert((modifiers & DeclarationModifiers.Required) == 0 || this is SourcePropertySymbol);
@@ -112,17 +115,24 @@ protected SourcePropertySymbolBase(
_lazyExplicitInterfaceImplementations = ImmutableArray.Empty;
}
- bool isIndexer = IsIndexer;
- isAutoProperty = isAutoProperty && !(containingType.IsInterface && !IsStatic) && !IsAbstract && !IsExtern && !isIndexer;
-
if (hasExplicitAccessMod)
{
_propertyFlags |= Flags.HasExplicitAccessModifier;
}
- if (isAutoProperty)
+ if (hasAutoPropertyGet)
{
- _propertyFlags |= Flags.IsAutoProperty;
+ _propertyFlags |= Flags.HasAutoPropertyGet;
+ }
+
+ if (hasAutoPropertySet)
+ {
+ _propertyFlags |= Flags.HasAutoPropertySet;
+ }
+
+ if (usesFieldKeyword)
+ {
+ _propertyFlags |= Flags.UsesFieldKeyword;
}
if (hasInitializer)
@@ -140,7 +150,7 @@ protected SourcePropertySymbolBase(
_propertyFlags |= Flags.AccessorsHaveImplementation;
}
- if (isIndexer)
+ if (IsIndexer)
{
if (indexerNameAttributeLists.Count == 0 || isExplicitInterfaceImplementation)
{
@@ -158,7 +168,7 @@ protected SourcePropertySymbolBase(
_name = _lazySourceName = memberName;
}
- if (usesFieldKeyword || (isAutoProperty && hasGetAccessor) || hasInitializer)
+ if (usesFieldKeyword || hasAutoPropertyGet || hasAutoPropertySet || hasInitializer)
{
Debug.Assert(!IsIndexer);
string fieldName = GeneratedNames.MakeBackingFieldName(_name);
@@ -175,12 +185,12 @@ protected SourcePropertySymbolBase(
if (hasGetAccessor)
{
- _getMethod = CreateGetAccessorSymbol(isAutoPropertyAccessor: isAutoProperty, diagnostics);
+ _getMethod = CreateGetAccessorSymbol(hasAutoPropertyGet, diagnostics);
}
if (hasSetAccessor)
{
- _setMethod = CreateSetAccessorSymbol(isAutoPropertyAccessor: isAutoProperty, diagnostics);
+ _setMethod = CreateSetAccessorSymbol(hasAutoPropertySet, diagnostics);
}
}
@@ -640,14 +650,17 @@ public bool HasSkipLocalsInitAttribute
}
}
- internal bool IsAutoPropertyWithGetAccessor
- => IsAutoProperty && _getMethod is object;
+ internal bool IsAutoPropertyOrUsesFieldKeyword
+ => IsAutoProperty || UsesFieldKeyword;
+
+ protected bool UsesFieldKeyword
+ => (_propertyFlags & Flags.UsesFieldKeyword) != 0;
protected bool HasExplicitAccessModifier
=> (_propertyFlags & Flags.HasExplicitAccessModifier) != 0;
- protected bool IsAutoProperty
- => (_propertyFlags & Flags.IsAutoProperty) != 0;
+ internal bool IsAutoProperty
+ => (_propertyFlags & (Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet)) != 0;
protected bool AccessorsHaveImplementation
=> (_propertyFlags & Flags.AccessorsHaveImplementation) != 0;
@@ -702,10 +715,8 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
diagnostics.Add(ErrorCode.ERR_RefReturningPropertiesCannotBeRequired, Location);
}
- if (IsAutoPropertyWithGetAccessor)
+ if (IsAutoProperty)
{
- Debug.Assert(GetMethod is object);
-
if (!IsStatic && SetMethod is { IsInitOnly: false })
{
if (ContainingType.IsReadOnly)
@@ -1084,7 +1095,7 @@ private SynthesizedSealedPropertyAccessor MakeSynthesizedSealedAccessor()
AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Property;
AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations
- => IsAutoPropertyWithGetAccessor
+ => IsAutoPropertyOrUsesFieldKeyword
? AttributeLocation.Property | AttributeLocation.Field
: AttributeLocation.Property;
@@ -1649,7 +1660,7 @@ protected virtual void ValidatePropertyType(BindingDiagnosticBag diagnostics)
{
diagnostics.Add(ErrorCode.ERR_FieldCantBeRefAny, TypeLocation, type);
}
- else if (this.IsAutoPropertyWithGetAccessor && type.IsRefLikeOrAllowsRefLikeType() && (this.IsStatic || !this.ContainingType.IsRefLikeType))
+ else if (this.IsAutoPropertyOrUsesFieldKeyword && type.IsRefLikeOrAllowsRefLikeType() && (this.IsStatic || !this.ContainingType.IsRefLikeType))
{
diagnostics.Add(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, TypeLocation, type);
}
diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs
index 6fab1931f2763..af8a830d393fc 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs
@@ -32,7 +32,8 @@ public SynthesizedRecordEqualityContractProperty(SourceMemberContainerTypeSymbol
},
hasInitializer: false,
hasExplicitAccessMod: false,
- isAutoProperty: false,
+ hasAutoPropertyGet: false,
+ hasAutoPropertySet: false,
isExpressionBodied: false,
isInitOnly: false,
accessorsHaveImplementation: true,
diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs
index 798028c8b916a..14ef25bb0fa59 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs
@@ -30,7 +30,8 @@ public SynthesizedRecordPropertySymbol(
modifiers: DeclarationModifiers.Public | (isOverride ? DeclarationModifiers.Override : DeclarationModifiers.None),
hasInitializer: true, // Synthesized record properties always have a synthesized initializer
hasExplicitAccessMod: false,
- isAutoProperty: true,
+ hasAutoPropertyGet: true,
+ hasAutoPropertySet: true,
isExpressionBodied: false,
isInitOnly: ShouldUseInit(containingType),
accessorsHaveImplementation: true,
diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
index 323447d1aa226..a56bf57992fb0 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs
@@ -149,7 +149,7 @@ internal override void PostDecodeWellKnownAttributes(ImmutableArrayk__BackingField"
+ IL_0005: ret
+ }
+ """);
+ verifier.VerifyIL("A.P2.set", """
+ {
+ // Code size 7 (0x7)
+ .maxstack 1
+ IL_0000: ldarg.0
+ IL_0001: stsfld "object A.k__BackingField"
+ IL_0006: ret
+ }
+ """);
+ verifier.VerifyIL("A.Q1.get", """
+ {
+ // Code size 7 (0x7)
+ .maxstack 1
+ IL_0000: ldarg.0
+ IL_0001: ldfld "object A.k__BackingField"
+ IL_0006: ret
+ }
+ """);
+ verifier.VerifyIL("A.Q2.set", """
+ {
+ // Code size 8 (0x8)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld "object A.k__BackingField"
+ IL_0007: ret
+ }
+ """);
+ verifier.VerifyIL("A.Q3.init", """
+ {
+ // Code size 8 (0x8)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldarg.1
+ IL_0002: stfld "object A.k__BackingField"
+ IL_0007: ret
+ }
+ """);
+ }
+
+ if (!typeKind.StartsWith("record"))
+ {
+ var actualMembers = comp.GetMember("A").GetMembers().ToTestDisplayStrings();
+ string readonlyQualifier = typeKind.EndsWith("struct") ? "readonly " : "";
+ var expectedMembers = new[]
+ {
+ "System.Object A.k__BackingField",
+ "System.Object A.P1 { get; set; }",
+ "System.Object A.P1.get",
+ "void A.P1.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.P2 { get; set; }",
+ "System.Object A.P2.get",
+ "void A.P2.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.P3 { get; set; }",
+ "System.Object A.P3.get",
+ "void A.P3.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.Q1 { get; set; }",
+ readonlyQualifier + "System.Object A.Q1.get",
+ "void A.Q1.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.Q2 { get; set; }",
+ "System.Object A.Q2.get",
+ "void A.Q2.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.Q3 { get; init; }",
+ "System.Object A.Q3.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.Q3.init",
+ "System.Object A.k__BackingField",
+ "System.Object A.Q4 { get; set; }",
+ readonlyQualifier + "System.Object A.Q4.get",
+ "void A.Q4.set",
+ "System.Object A.k__BackingField",
+ "System.Object A.Q5 { get; init; }",
+ readonlyQualifier + "System.Object A.Q5.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.Q5.init",
+ "A..ctor()"
+ };
+ AssertEx.Equal(expectedMembers, actualMembers);
+ }
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ImplicitAccessorBody_02(
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ string source = """
+ interface I
+ {
+ static object P1 { get; set { _ = field; } }
+ static object P2 { get { return field; } set; }
+ }
+ """;
+
+ var comp = CreateCompilation(
+ source,
+ parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion),
+ targetFramework: TargetFramework.Net80);
+
+ if (languageVersion == LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // static object P1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 19),
+ // (3,39): error CS0103: The name 'field' does not exist in the current context
+ // static object P1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 39),
+ // (4,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // static object P2 { get { return field; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 19),
+ // (4,37): error CS0103: The name 'field' does not exist in the current context
+ // static object P2 { get { return field; } set; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(4, 37));
+ }
+ else
+ {
+ var verifier = CompileAndVerify(comp, verify: Verification.Skipped);
+ verifier.VerifyIL("I.P1.get", """
+ {
+ // Code size 6 (0x6)
+ .maxstack 1
+ IL_0000: ldsfld "object I.k__BackingField"
+ IL_0005: ret
+ }
+ """);
+ verifier.VerifyIL("I.P2.set", """
+ {
+ // Code size 7 (0x7)
+ .maxstack 1
+ IL_0000: ldarg.0
+ IL_0001: stsfld "object I.k__BackingField"
+ IL_0006: ret
+ }
+ """);
+ }
+
+ var actualMembers = comp.GetMember("I").GetMembers().ToTestDisplayStrings();
+ var expectedMembers = new[]
+ {
+ "System.Object I.k__BackingField",
+ "System.Object I.P1 { get; set; }",
+ "System.Object I.P1.get",
+ "void I.P1.set",
+ "System.Object I.k__BackingField",
+ "System.Object I.P2 { get; set; }",
+ "System.Object I.P2.get",
+ "void I.P2.set",
+ };
+ AssertEx.Equal(expectedMembers, actualMembers);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ImplicitAccessorBody_03(
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ string source = """
+ interface I
+ {
+ object Q1 { get; set { _ = field; } }
+ object Q2 { get { return field; } set; }
+ object Q3 { get { return field; } init; }
+ }
+ """;
+
+ var comp = CreateCompilation(
+ source,
+ parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion),
+ targetFramework: TargetFramework.Net80);
+
+ if (languageVersion == LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,17): error CS0501: 'I.Q1.get' must declare a body because it is not marked abstract, extern, or partial
+ // object Q1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I.Q1.get").WithLocation(3, 17),
+ // (3,32): error CS0103: The name 'field' does not exist in the current context
+ // object Q1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 32),
+ // (4,30): error CS0103: The name 'field' does not exist in the current context
+ // object Q2 { get { return field; } set; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(4, 30),
+ // (4,39): error CS0501: 'I.Q2.set' must declare a body because it is not marked abstract, extern, or partial
+ // object Q2 { get { return field; } set; }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I.Q2.set").WithLocation(4, 39),
+ // (5,30): error CS0103: The name 'field' does not exist in the current context
+ // object Q3 { get { return field; } init; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 30),
+ // (5,39): error CS0501: 'I.Q3.init' must declare a body because it is not marked abstract, extern, or partial
+ // object Q3 { get { return field; } init; }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "init").WithArguments("I.Q3.init").WithLocation(5, 39));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,17): error CS0501: 'I.Q1.get' must declare a body because it is not marked abstract, extern, or partial
+ // object Q1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I.Q1.get").WithLocation(3, 17),
+ // (3,32): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object Q1 { get; set { _ = field; } }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 32),
+ // (4,30): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object Q2 { get { return field; } set; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 30),
+ // (4,39): error CS0501: 'I.Q2.set' must declare a body because it is not marked abstract, extern, or partial
+ // object Q2 { get { return field; } set; }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I.Q2.set").WithLocation(4, 39),
+ // (5,30): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // object Q3 { get { return field; } init; }
+ Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 30),
+ // (5,39): error CS0501: 'I.Q3.init' must declare a body because it is not marked abstract, extern, or partial
+ // object Q3 { get { return field; } init; }
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "init").WithArguments("I.Q3.init").WithLocation(5, 39));
+ }
+
+ var actualMembers = comp.GetMember("I").GetMembers().ToTestDisplayStrings();
+ string[] expectedMembers;
+ if (languageVersion == LanguageVersion.CSharp13)
+ {
+ expectedMembers = new[]
+ {
+ "System.Object I.Q1 { get; set; }",
+ "System.Object I.Q1.get",
+ "void I.Q1.set",
+ "System.Object I.Q2 { get; set; }",
+ "System.Object I.Q2.get",
+ "void I.Q2.set",
+ "System.Object I.Q3 { get; init; }",
+ "System.Object I.Q3.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) I.Q3.init",
+ };
+ }
+ else
+ {
+ expectedMembers = new[]
+ {
+ "System.Object I.k__BackingField",
+ "System.Object I.Q1 { get; set; }",
+ "System.Object I.Q1.get",
+ "void I.Q1.set",
+ "System.Object I.k__BackingField",
+ "System.Object I.Q2 { get; set; }",
+ "System.Object I.Q2.get",
+ "void I.Q2.set",
+ "System.Object I.k__BackingField",
+ "System.Object I.Q3 { get; init; }",
+ "System.Object I.Q3.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) I.Q3.init",
+ };
+ }
+ AssertEx.Equal(expectedMembers, actualMembers);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ImplicitAccessorBody_04(
+ [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ string source = $$"""
+ {{typeKind}} A
+ {
+ public static int P1 { get; set { } }
+ public static int P2 { get { return -2; } set; }
+ public int P3 { get; set { } }
+ public int P4 { get { return -4; } set; }
+ public int P5 { get; init { } }
+ public int P6 { get { return -6; } init; }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ A.P1 = 1;
+ A.P2 = 2;
+ var a = new A() { P3 = 3, P4 = 4, P5 = 5, P6 = 6 };
+ System.Console.WriteLine((A.P1, A.P2, a.P3, a.P4, a.P5, a.P6));
+ }
+ }
+ """;
+
+ var comp = CreateCompilation(
+ source,
+ parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion),
+ options: TestOptions.ReleaseExe,
+ targetFramework: TargetFramework.Net80);
+
+ if (languageVersion == LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,23): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public static int P1 { get; set { } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 23),
+ // (4,23): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public static int P2 { get { return -2; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 23),
+ // (5,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P3 { get; set { } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P3").WithArguments("field keyword").WithLocation(5, 16),
+ // (6,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P4 { get { return -4; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P4").WithArguments("field keyword").WithLocation(6, 16),
+ // (7,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P5 { get; init { } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P5").WithArguments("field keyword").WithLocation(7, 16),
+ // (8,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P6 { get { return -6; } init; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P6").WithArguments("field keyword").WithLocation(8, 16));
+ }
+ else
+ {
+ CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("(0, -2, 0, -4, 0, -6)"));
+ }
+
+ if (!typeKind.StartsWith("record"))
+ {
+ var actualMembers = comp.GetMember("A").GetMembers().ToTestDisplayStrings();
+ string readonlyQualifier = typeKind.EndsWith("struct") ? "readonly " : "";
+ var expectedMembers = new[]
+ {
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P1 { get; set; }",
+ "System.Int32 A.P1.get",
+ "void A.P1.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P2 { get; set; }",
+ "System.Int32 A.P2.get",
+ "void A.P2.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P3 { get; set; }",
+ readonlyQualifier + "System.Int32 A.P3.get",
+ "void A.P3.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P4 { get; set; }",
+ "System.Int32 A.P4.get",
+ "void A.P4.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P5 { get; init; }",
+ readonlyQualifier + "System.Int32 A.P5.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.P5.init",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P6 { get; init; }",
+ "System.Int32 A.P6.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.P6.init",
+ "A..ctor()",
+ };
+ AssertEx.Equal(expectedMembers, actualMembers);
+ }
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ImplicitAccessorBody_05(
+ [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ {
+ string source = $$"""
+ {{typeKind}} A
+ {
+ public static int P1 { get; set { field = value * 2; } }
+ public static int P2 { get { return field * -1; } set; }
+ public int P3 { get; set { field = value * 2; } }
+ public int P4 { get { return field * -1; } set; }
+ public int P5 { get; init { field = value * 2; } }
+ public int P6 { get { return field * -1; } init; }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ A.P1 = 1;
+ A.P2 = 2;
+ var a = new A() { P3 = 3, P4 = 4, P5 = 5, P6 = 6 };
+ System.Console.WriteLine((A.P1, A.P2, a.P3, a.P4, a.P5, a.P6));
+ }
+ }
+ """;
+
+ var comp = CreateCompilation(
+ source,
+ parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion),
+ options: TestOptions.ReleaseExe,
+ targetFramework: TargetFramework.Net80);
+
+ if (languageVersion == LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,23): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public static int P1 { get; set { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 23),
+ // (3,39): error CS0103: The name 'field' does not exist in the current context
+ // public static int P1 { get; set { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 39),
+ // (4,23): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public static int P2 { get { return field * -1; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 23),
+ // (4,41): error CS0103: The name 'field' does not exist in the current context
+ // public static int P2 { get { return field * -1; } set; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(4, 41),
+ // (5,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P3 { get; set { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P3").WithArguments("field keyword").WithLocation(5, 16),
+ // (5,32): error CS0103: The name 'field' does not exist in the current context
+ // public int P3 { get; set { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 32),
+ // (6,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P4 { get { return field * -1; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P4").WithArguments("field keyword").WithLocation(6, 16),
+ // (6,34): error CS0103: The name 'field' does not exist in the current context
+ // public int P4 { get { return field * -1; } set; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(6, 34),
+ // (7,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P5 { get; init { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P5").WithArguments("field keyword").WithLocation(7, 16),
+ // (7,33): error CS0103: The name 'field' does not exist in the current context
+ // public int P5 { get; init { field = value * 2; } }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(7, 33),
+ // (8,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P6 { get { return field * -1; } init; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P6").WithArguments("field keyword").WithLocation(8, 16),
+ // (8,34): error CS0103: The name 'field' does not exist in the current context
+ // public int P6 { get { return field * -1; } init; }
+ Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(8, 34));
+ }
+ else
+ {
+ CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("(2, -2, 6, -4, 10, -6)"));
+ }
+
+ if (!typeKind.StartsWith("record"))
+ {
+ var actualMembers = comp.GetMember("A").GetMembers().ToTestDisplayStrings();
+ string readonlyQualifier = typeKind.EndsWith("struct") ? "readonly " : "";
+ var expectedMembers = new[]
+ {
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P1 { get; set; }",
+ "System.Int32 A.P1.get",
+ "void A.P1.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P2 { get; set; }",
+ "System.Int32 A.P2.get",
+ "void A.P2.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P3 { get; set; }",
+ readonlyQualifier + "System.Int32 A.P3.get",
+ "void A.P3.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P4 { get; set; }",
+ "System.Int32 A.P4.get",
+ "void A.P4.set",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P5 { get; init; }",
+ readonlyQualifier + "System.Int32 A.P5.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.P5.init",
+ "System.Int32 A.k__BackingField",
+ "System.Int32 A.P6 { get; init; }",
+ "System.Int32 A.P6.get",
+ "void modreq(System.Runtime.CompilerServices.IsExternalInit) A.P6.init",
+ "A..ctor()",
+ };
+ AssertEx.Equal(expectedMembers, actualMembers);
+ }
+ }
+
[Fact]
public void Attribute_01()
{
@@ -399,5 +953,247 @@ class C
Assert.IsType(argument);
Assert.Equal("System.Object C.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
}
+
+ [Fact]
+ public void Attribute_03()
+ {
+ string source = $$"""
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ using System;
+ using System.Reflection;
+
+ [AttributeUsage(AttributeTargets.All, AllowMultiple=true)]
+ class A : Attribute
+ {
+ private readonly object _obj;
+ public A(object obj) { _obj = obj; }
+ public override string ToString() => $"A({_obj})";
+ }
+
+ class B
+ {
+ [A(0)][field: A(1)] public object P1 { get; }
+ [field: A(2)][field: A(-2)] public static object P2 { get; set; }
+ [field: A(3)] public object P3 { get; init; }
+ public object P4 { [field: A(4)] get; }
+ public static object P5 { get; [field: A(5)] set; }
+ [A(0)][field: A(1)] public object Q1 => field;
+ [field: A(2)][field: A(-2)] public static object Q2 { get { return field; } set { } }
+ [field: A(3)] public object Q3 { get { return field; } init { } }
+ public object Q4 { [field: A(4)] get => field; }
+ public static object Q5 { get { return field; } [field: A(5)] set { } }
+ [field: A(6)] public static object Q6 { set { _ = field; } }
+ [field: A(7)] public object Q7 { init { _ = field; } }
+ }
+
+ class Program
+ {
+ static void Main()
+ {
+ foreach (var field in typeof(B).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
+ ReportField(field);
+ }
+
+ static void ReportField(FieldInfo field)
+ {
+ Console.Write("{0}.{1}:", field.DeclaringType.Name, field.Name);
+ foreach (var obj in field.GetCustomAttributes())
+ Console.Write(" {0},", obj.ToString());
+ Console.WriteLine();
+ }
+ }
+ """;
+
+ var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (18,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
+ // public object P4 { [field: A(4)] get; }
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(18, 25),
+ // (19,37): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
+ // public static object P5 { get; [field: A(5)] set; }
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(19, 37),
+ // (23,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
+ // public object Q4 { [field: A(4)] get => field; }
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(23, 25),
+ // (24,54): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
+ // public static object Q5 { get { return field; } [field: A(5)] set { } }
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(24, 54));
+
+ CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("""
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(1),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(3),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute,
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(1),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(3),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute,
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(7),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(2), A(-2),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute,
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(2), A(-2),
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute,
+ B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(6),
+ """));
+ }
+
+ [Fact]
+ public void RestrictedTypes()
+ {
+ string source = """
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ using System;
+ class C
+ {
+ static TypedReference P1 { get; }
+ ArgIterator P2 { get; set; }
+ static TypedReference Q1 => field;
+ ArgIterator Q2 { get { return field; } set { } }
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (5,12): error CS0610: Field or property cannot be of type 'TypedReference'
+ // static TypedReference P1 { get; }
+ Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "TypedReference").WithArguments("System.TypedReference").WithLocation(5, 12),
+ // (6,5): error CS0610: Field or property cannot be of type 'ArgIterator'
+ // ArgIterator P2 { get; set; }
+ Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "ArgIterator").WithArguments("System.ArgIterator").WithLocation(6, 5),
+ // (7,12): error CS0610: Field or property cannot be of type 'TypedReference'
+ // static TypedReference Q1 => field;
+ Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "TypedReference").WithArguments("System.TypedReference").WithLocation(7, 12),
+ // (8,5): error CS0610: Field or property cannot be of type 'ArgIterator'
+ // ArgIterator Q2 { get { return field; } set { } }
+ Diagnostic(ErrorCode.ERR_FieldCantBeRefAny, "ArgIterator").WithArguments("System.ArgIterator").WithLocation(8, 5));
+ }
+
+ [Theory]
+ [InlineData("class", false)]
+ [InlineData("struct", false)]
+ [InlineData("ref struct", true)]
+ [InlineData("record", false)]
+ [InlineData("record struct", false)]
+ public void ByRefLikeType_01(string typeKind, bool allow)
+ {
+ string source = $$"""
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ ref struct R
+ {
+ }
+ {{typeKind}} C
+ {
+ R P1 { get; }
+ R P2 { get; set; }
+ R Q1 => field;
+ R Q2 { get => field; }
+ R Q3 { set { _ = field; } }
+ public override string ToString() => "C";
+ }
+ class Program
+ {
+ static void Main()
+ {
+ var c = new C();
+ System.Console.WriteLine("{0}", c.ToString());
+ }
+ }
+ """;
+ var comp = CreateCompilation(source, options: TestOptions.ReleaseExe);
+ if (allow)
+ {
+ CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: "C");
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics(
+ // (7,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R P1 { get; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(7, 5),
+ // (8,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R P2 { get; set; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(8, 5),
+ // (9,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R Q1 => field;
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(9, 5),
+ // (10,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R Q2 { get => field; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(10, 5),
+ // (11,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R Q3 { set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(11, 5));
+ }
+ }
+
+ [Theory]
+ [InlineData("class")]
+ [InlineData("struct")]
+ [InlineData("ref struct")]
+ [InlineData("record")]
+ [InlineData("record struct")]
+ public void ByRefLikeType_02(string typeKind)
+ {
+ string source = $$"""
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ ref struct R
+ {
+ }
+ {{typeKind}} C
+ {
+ static R P1 { get; }
+ static R P2 { get; set; }
+ static R Q1 => field;
+ static R Q2 { get => field; }
+ static R Q3 { set { _ = field; } }
+ }
+ """;
+ var comp = CreateCompilation(source);
+ comp.VerifyEmitDiagnostics(
+ // (7,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R P1 { get; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(7, 12),
+ // (8,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R P2 { get; set; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(8, 12),
+ // (9,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R Q1 => field;
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(9, 12),
+ // (10,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R Q2 { get => field; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(10, 12),
+ // (11,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R Q3 { set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(11, 12));
+ }
+
+ [Fact]
+ public void ByRefLikeType_03()
+ {
+ string source = """
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ ref struct R
+ {
+ }
+ interface I
+ {
+ static R P1 { get; }
+ R P2 { get; set; }
+ static R Q1 => field;
+ R Q2 { get => field; }
+ R Q3 { set { _ = field; } }
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (7,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R P1 { get; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(7, 12),
+ // (9,12): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // static R Q1 => field;
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(9, 12),
+ // (10,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R Q2 { get => field; }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(10, 5),
+ // (11,5): error CS8345: Field or auto-implemented property cannot be of type 'R' unless it is an instance member of a ref struct.
+ // R Q3 { set { _ = field; } }
+ Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(11, 5));
+ }
}
}
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs
index 2b262e99295e7..c123b2a332829 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs
@@ -1407,19 +1407,22 @@ public class C
";
var comp = CreateCompilation(new[] { source, IsExternalInitTypeDefinition }, parseOptions: TestOptions.Regular9);
comp.VerifyEmitDiagnostics(
- // (4,13): error CS8145: Auto-implemented properties cannot return by reference
+ // 0.cs(4,13): error CS8145: Auto-implemented properties cannot return by reference
// ref int Property1 { get; init; }
Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "Property1").WithLocation(4, 13),
- // (4,30): error CS8147: Properties which return by reference cannot have set accessors
+ // 0.cs(4,30): error CS8147: Properties which return by reference cannot have set accessors
// ref int Property1 { get; init; }
Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(4, 30),
- // (5,13): error CS8146: Properties which return by reference must have a get accessor
+ // 0.cs(5,13): error CS8145: Auto-implemented properties cannot return by reference
+ // ref int Property2 { init; }
+ Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "Property2").WithLocation(5, 13),
+ // 0.cs(5,13): error CS8146: Properties which return by reference must have a get accessor
// ref int Property2 { init; }
Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "Property2").WithLocation(5, 13),
- // (6,44): error CS8147: Properties which return by reference cannot have set accessors
+ // 0.cs(6,44): error CS8147: Properties which return by reference cannot have set accessors
// ref int Property3 { get => throw null; init => throw null; }
Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(6, 44),
- // (7,13): error CS8146: Properties which return by reference must have a get accessor
+ // 0.cs(7,13): error CS8146: Properties which return by reference must have a get accessor
// ref int Property4 { init => throw null; }
Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "Property4").WithLocation(7, 13)
);
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
index 1de85b8adc7bc..1f13aa457cc29 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs
@@ -15264,6 +15264,9 @@ public override Func P1 { set {} } // warn
// (15,39): warning CS8767: Nullability of reference types in type of parameter 'value' of 'void C.P1.set' doesn't match implicitly implemented member 'void A.P1.set' (possibly because of nullability attributes).
// public override Func P1 { set {} } // warn
Diagnostic(ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnImplicitImplementation, "set").WithArguments("value", "void C.P1.set", "void A.P1.set").WithLocation(15, 39),
+ // (16,34): warning CS8618: Non-nullable property 'P2' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable.
+ // public override Func P2 { set; } // warn
+ Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P2").WithArguments("property", "P2").WithLocation(16, 34),
// (16,39): error CS8051: Auto-implemented properties must have get accessors.
// public override Func P2 { set; } // warn
Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithLocation(16, 39),
diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs
index cdc7c38eca163..44ff7a3e97c93 100644
--- a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs
+++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs
@@ -2631,6 +2631,9 @@ public C()
// (5,19): error CS0548: 'C.P': property or indexer must have at least one accessor
// public string P { }
Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P").WithArguments("C.P").WithLocation(5, 19),
+ // (7,19): error CS8050: Only auto-implemented properties can have initializers.
+ // public string P3 { } = string.Empty;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(7, 19),
// (7,19): error CS0548: 'C.P3': property or indexer must have at least one accessor
// public string P3 { } = string.Empty;
Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P3").WithArguments("C.P3").WithLocation(7, 19),
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs
index 6466391693427..9e8c95afbbc13 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs
@@ -3245,14 +3245,17 @@ public interface I1
targetFramework: TargetFramework.Net60);
Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation);
compilation1.VerifyEmitDiagnostics(
- // (4,13): error CS1014: A get, set or init accessor expected
- // int P1 {add; remove;} = 0;
+ // (4,28): error CS1014: A get or set accessor expected
+ // static virtual int P1 {add; remove;} = 0;
Diagnostic(ErrorCode.ERR_GetOrSetExpected, "add").WithLocation(4, 28),
- // (4,18): error CS1014: A get, set or init accessor expected
- // int P1 {add; remove;} = 0;
+ // (4,33): error CS1014: A get or set accessor expected
+ // static virtual int P1 {add; remove;} = 0;
Diagnostic(ErrorCode.ERR_GetOrSetExpected, "remove").WithLocation(4, 33),
- // (4,9): error CS0548: 'I1.P1': property or indexer must have at least one accessor
- // int P1 {add; remove;} = 0;
+ // (4,24): error CS8050: Only auto-implemented properties can have initializers.
+ // static virtual int P1 {add; remove;} = 0;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(4, 24),
+ // (4,24): error CS0548: 'I1.P1': property or indexer must have at least one accessor
+ // static virtual int P1 {add; remove;} = 0;
Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P1").WithArguments("I1.P1").WithLocation(4, 24)
);
@@ -3343,7 +3346,7 @@ public interface I1
[Theory]
[CombinatorialData]
- public void PropertyImplementation_109(bool isStatic)
+ public void PropertyImplementation_109(bool isStatic, bool useCSharp13)
{
string declModifiers = isStatic ? "static virtual " : "";
@@ -3366,17 +3369,32 @@ class Test1 : I1
{}
";
var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll,
- parseOptions: TestOptions.RegularPreview,
+ parseOptions: useCSharp13 ? TestOptions.Regular13 : TestOptions.RegularPreview,
targetFramework: TargetFramework.Net60);
Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation);
+ // PROTOTYPE: Confirm that we now allow one accessor to have an implementation.
// According to LDM decision captured at https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-04-18.md,
// we don't want to allow only one accessor to have an implementation.
- compilation1.VerifyDiagnostics(
- // (11,9): error CS0501: 'I1.P1.set' must declare a body because it is not marked abstract, extern, or partial
- // set;
- Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I1.P1.set").WithLocation(11, 9)
- );
+ if (isStatic && useCSharp13)
+ {
+ compilation1.VerifyDiagnostics(
+ // (4,24): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // static virtual int P1
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 24));
+ }
+ else if (isStatic)
+ {
+ compilation1.VerifyDiagnostics();
+ }
+ else
+ {
+ compilation1.VerifyDiagnostics(
+ // (11,9): error CS0501: 'I1.P1.set' must declare a body because it is not marked abstract, extern, or partial
+ // set;
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I1.P1.set").WithLocation(11, 9)
+ );
+ }
var p1 = compilation1.GetMember("I1.P1");
var getP1 = p1.GetMethod;
@@ -3403,7 +3421,7 @@ class Test1 : I1
[Theory]
[CombinatorialData]
- public void PropertyImplementation_110(bool isStatic)
+ public void PropertyImplementation_110(bool isStatic, bool useCSharp13)
{
string declModifiers = isStatic ? "static virtual " : "";
@@ -3422,17 +3440,32 @@ class Test1 : I1
{}
";
var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll,
- parseOptions: TestOptions.RegularPreview,
+ parseOptions: useCSharp13 ? TestOptions.Regular13 : TestOptions.RegularPreview,
targetFramework: TargetFramework.Net60);
Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation);
+ // PROTOTYPE: Confirm that we now allow one accessor to have an implementation.
// According to LDM decision captured at https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-04-18.md,
// we don't want to allow only one accessor to have an implementation.
- compilation1.VerifyDiagnostics(
- // (6,9): error CS0501: 'I1.P1.get' must declare a body because it is not marked abstract, extern, or partial
- // get;
- Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I1.P1.get").WithLocation(6, 9)
- );
+ if (isStatic && useCSharp13)
+ {
+ compilation1.VerifyDiagnostics(
+ // (4,24): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // static virtual int P1
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 24));
+ }
+ else if (isStatic)
+ {
+ compilation1.VerifyDiagnostics();
+ }
+ else
+ {
+ compilation1.VerifyDiagnostics(
+ // (6,9): error CS0501: 'I1.P1.get' must declare a body because it is not marked abstract, extern, or partial
+ // get;
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I1.P1.get").WithLocation(6, 9)
+ );
+ }
var p1 = compilation1.GetMember("I1.P1");
var getP1 = p1.GetMethod;
@@ -67575,6 +67608,9 @@ interface IC
// (9,30): error CS8147: Properties which return by reference cannot have set accessors
// static ref int PB { get; set;}
Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(9, 30),
+ // (14,20): error CS8145: Auto-implemented properties cannot return by reference
+ // static ref int PC { set;}
+ Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PC").WithLocation(14, 20),
// (14,20): error CS8146: Properties which return by reference must have a get accessor
// static ref int PC { set;}
Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "PC").WithLocation(14, 20)
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs
index 9cb17023809c6..168ce8917b0c3 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs
@@ -2933,7 +2933,7 @@ class C
}
[Fact, WorkItem(4696, "https://github.com/dotnet/roslyn/issues/4696")]
- public void LangVersionAndReadonlyAutoProperty()
+ public void LangVersionAndReadonlyAutoProperty_01()
{
var source = @"
public class Class1
@@ -2957,12 +2957,48 @@ interface I1
}
";
- var comp = CreateCompilation(source, parseOptions: CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp5));
+ var comp = CreateCompilation(source, parseOptions: TestOptions.Regular5);
comp.GetDeclarationDiagnostics().Verify(
- // (9,19): error CS8026: Feature 'readonly automatically implemented properties' is not available in C# 5. Please use language version 6 or greater.
- // public string Prop1 { get; }
- Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion5, "Prop1").WithArguments("readonly automatically implemented properties", "6").WithLocation(9, 19)
+ // (9,19): error CS8026: Feature 'readonly automatically implemented properties' is not available in C# 5. Please use language version 6 or greater.
+ // public string Prop1 { get; }
+ Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion5, "Prop1").WithArguments("readonly automatically implemented properties", "6").WithLocation(9, 19)
);
+
+ comp = CreateCompilation(source, parseOptions: TestOptions.Regular6);
+ comp.GetDeclarationDiagnostics().Verify();
+ }
+
+ [Fact]
+ public void LangVersionAndReadonlyAutoProperty_02()
+ {
+ var source = @"
+public class Class1
+{
+ public string Prop1 { set; }
+}
+
+abstract class Class2
+{
+ public abstract string Prop2 { set; }
+}
+
+interface I1
+{
+ string Prop3 { set; }
+}
+";
+
+ var comp = CreateCompilation(source, parseOptions: TestOptions.Regular5);
+ comp.GetDeclarationDiagnostics().Verify(
+ // (4,27): error CS8051: Auto-implemented properties must have get accessors.
+ // public string Prop1 { set; }
+ Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithLocation(4, 27));
+
+ comp = CreateCompilation(source, parseOptions: TestOptions.RegularPreview);
+ comp.GetDeclarationDiagnostics().Verify(
+ // (4,27): error CS8051: Auto-implemented properties must have get accessors.
+ // public string Prop1 { set; }
+ Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithLocation(4, 27));
}
[Fact]
diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
index 67598be201e58..31be06d2e2ace 100644
--- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
+++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs
@@ -1760,13 +1760,13 @@ internal int P2 { static set { } }
}
";
CreateCompilation(text, parseOptions: TestOptions.Regular7, targetFramework: TargetFramework.NetCoreApp).VerifyDiagnostics(
- // (3,23): error CS8503: The modifier 'static' is not valid for this item in C# 7. Please use language version '8.0' or greater.
+ // (3,23): error CS8703: The modifier 'static' is not valid for this item in C# 7.0. Please use language version '8.0' or greater.
// public static int P1 { get; }
Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("static", "7.0", "8.0").WithLocation(3, 23),
- // (3,23): error CS8503: The modifier 'public' is not valid for this item in C# 7. Please use language version '8.0' or greater.
+ // (3,23): error CS8703: The modifier 'public' is not valid for this item in C# 7.0. Please use language version '8.0' or greater.
// public static int P1 { get; }
Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("public", "7.0", "8.0").WithLocation(3, 23),
- // (4,18): error CS8503: The modifier 'abstract' is not valid for this item in C# 7. Please use language version '8.0' or greater.
+ // (4,18): error CS8703: The modifier 'abstract' is not valid for this item in C# 7.0. Please use language version '8.0' or greater.
// abstract int P2 { static set; }
Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P2").WithArguments("abstract", "7.0", "8.0").WithLocation(4, 18),
// (4,30): error CS0106: The modifier 'static' is not valid for this item
@@ -1796,6 +1796,9 @@ internal int P2 { static set { } }
// (14,21): error CS0106: The modifier 'sealed' is not valid for this item
// int P4 { sealed get { return 0; } }
Diagnostic(ErrorCode.ERR_BadMemberFlag, "get").WithArguments("sealed").WithLocation(14, 21),
+ // (15,31): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // protected internal object P5 { get { return null; } extern set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P5").WithArguments("field keyword").WithLocation(15, 31),
// (15,64): error CS0106: The modifier 'extern' is not valid for this item
// protected internal object P5 { get { return null; } extern set; }
Diagnostic(ErrorCode.ERR_BadMemberFlag, "set").WithArguments("extern").WithLocation(15, 64),
@@ -8125,13 +8128,21 @@ public void CS0501ERR_ConcreteMissingBody02()
protected abstract object S { set; } // no error
}
";
+ CreateCompilation(text, parseOptions: TestOptions.Regular13).VerifyDiagnostics(
+ // (3,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int P { get; set { } }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "P").WithArguments("field keyword").WithLocation(3, 16),
+ // (4,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version.
+ // public int Q { get { return 0; } set; }
+ Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q").WithArguments("field keyword").WithLocation(4, 16),
+ // (5,30): warning CS0626: Method, operator, or accessor 'C.R.get' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
+ // public extern object R { get; } // no error
+ Diagnostic(ErrorCode.WRN_ExternMethodNoImplementation, "get").WithArguments("C.R.get").WithLocation(5, 30));
+
CreateCompilation(text).VerifyDiagnostics(
- // (3,20): error CS0501: 'C.P.get' must declare a body because it is not marked abstract, extern, or partial
- Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("C.P.get"),
- // (4,38): error CS0501: 'C.Q.set' must declare a body because it is not marked abstract, extern, or partial
- Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("C.Q.set"),
// (5,30): warning CS0626: Method, operator, or accessor 'C.R.get' is marked external and has no attributes on it. Consider adding a DllImport attribute to specify the external implementation.
- Diagnostic(ErrorCode.WRN_ExternMethodNoImplementation, "get").WithArguments("C.R.get"));
+ // public extern object R { get; } // no error
+ Diagnostic(ErrorCode.WRN_ExternMethodNoImplementation, "get").WithArguments("C.R.get").WithLocation(5, 30));
}
[Fact]
From 2b39f490c1871b25cfd8b8b6d5a6460b454118b6 Mon Sep 17 00:00:00 2001
From: Charles Stoner <10732005+cston@users.noreply.github.com>
Date: Fri, 16 Aug 2024 12:35:45 -0700
Subject: [PATCH 04/18] Report field diagnostic as warning, only report when
ambiguous, and only with preview language version (#74729)
---
.../Portable/Binder/Binder_Expressions.cs | 46 +-
.../CSharp/Portable/CSharpResources.resx | 7 +-
.../CSharp/Portable/Errors/ErrorCode.cs | 2 +-
.../CSharp/Portable/Errors/ErrorFacts.cs | 4 +-
.../Generated/ErrorFacts.Generated.cs | 2 +-
.../Portable/xlf/CSharpResources.cs.xlf | 15 +-
.../Portable/xlf/CSharpResources.de.xlf | 15 +-
.../Portable/xlf/CSharpResources.es.xlf | 15 +-
.../Portable/xlf/CSharpResources.fr.xlf | 15 +-
.../Portable/xlf/CSharpResources.it.xlf | 15 +-
.../Portable/xlf/CSharpResources.ja.xlf | 15 +-
.../Portable/xlf/CSharpResources.ko.xlf | 15 +-
.../Portable/xlf/CSharpResources.pl.xlf | 15 +-
.../Portable/xlf/CSharpResources.pt-BR.xlf | 15 +-
.../Portable/xlf/CSharpResources.ru.xlf | 15 +-
.../Portable/xlf/CSharpResources.tr.xlf | 15 +-
.../Portable/xlf/CSharpResources.zh-Hans.xlf | 15 +-
.../Portable/xlf/CSharpResources.zh-Hant.xlf | 15 +-
.../CSharp/Test/Emit3/FieldKeywordTests.cs | 93 ++--
.../Test/Syntax/Diagnostics/DiagnosticTest.cs | 2 +-
.../Parsing/FieldKeywordParsingTests.cs | 72 +--
.../Syntax/FieldAndValueKeywordTests.cs | 436 +++++++++++++-----
22 files changed, 559 insertions(+), 300 deletions(-)
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
index b51735f28319b..8e14810c77d5e 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
@@ -1440,7 +1440,10 @@ private BoundExpression BindFieldExpression(FieldExpressionSyntax node, BindingD
Debug.Assert(ContainingType is { });
SynthesizedBackingFieldSymbolBase? field = null;
- ReportFieldContextualKeywordConflict(node, node.Token, diagnostics);
+ if (hasOtherFieldSymbolInScope())
+ {
+ diagnostics.Add(ErrorCode.WRN_FieldIsAmbiguous, node, Compilation.LanguageVersion.ToDisplayString());
+ }
switch (ContainingMember())
{
@@ -1469,6 +1472,17 @@ private BoundExpression BindFieldExpression(FieldExpressionSyntax node, BindingD
var implicitReceiver = field.IsStatic ? null : ThisReference(node, field.ContainingType, wasCompilerGenerated: true);
return new BoundFieldAccess(node, implicitReceiver, field, constantValueOpt: null);
+
+ bool hasOtherFieldSymbolInScope()
+ {
+ var lookupResult = LookupResult.GetInstance();
+ var useSiteInfo = CompoundUseSiteInfo.Discarded;
+ this.LookupIdentifier(lookupResult, name: "field", arity: 0, invoked: false, ref useSiteInfo);
+ bool result = lookupResult.Kind != LookupResultKind.Empty;
+ Debug.Assert(!result || lookupResult.Symbols.Count > 0);
+ lookupResult.Free();
+ return result;
+ }
}
/// true if managed type-related errors were found, otherwise false.
@@ -1587,8 +1601,6 @@ private BoundExpression BindIdentifier(
var members = ArrayBuilder.GetInstance();
Symbol symbol = GetSymbolOrMethodOrPropertyGroup(lookupResult, node, name, node.Arity, members, diagnostics, out isError, qualifierOpt: null); // reports diagnostics in result.
- ReportFieldContextualKeywordConflictIfAny(node, node.Identifier, diagnostics);
-
if ((object)symbol == null)
{
Debug.Assert(members.Count > 0);
@@ -1771,32 +1783,12 @@ void reportPrimaryConstructorParameterShadowing(SimpleNameSyntax node, Symbol sy
}
}
-#nullable enable
- ///
- /// Report a diagnostic for a 'field' identifier that the meaning will
- /// change when the identifier is considered a contextual keyword.
- ///
- internal void ReportFieldContextualKeywordConflictIfAny(SyntaxNode syntax, SyntaxToken identifier, BindingDiagnosticBag diagnostics)
- {
- string name = identifier.Text;
- if (name == "field" &&
- ContainingMember() is MethodSymbol { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet, AssociatedSymbol: PropertySymbol { IsIndexer: false } })
- {
- ReportFieldContextualKeywordConflict(syntax, identifier, diagnostics);
- }
- }
-
- private static void ReportFieldContextualKeywordConflict(SyntaxNode syntax, SyntaxToken identifier, BindingDiagnosticBag diagnostics)
+ private void LookupIdentifier(LookupResult lookupResult, SimpleNameSyntax node, bool invoked, ref CompoundUseSiteInfo useSiteInfo)
{
- // PROTOTYPE: Should this diagnostic be dropped when compiling with the latest language version
- // when 'field' would not otherwise bind to a different symbol?
- string name = identifier.Text;
- var requiredVersion = MessageID.IDS_FeatureFieldKeyword.RequiredVersion();
- diagnostics.Add(ErrorCode.INF_IdentifierConflictWithContextualKeyword, syntax, name, requiredVersion.ToDisplayString());
+ LookupIdentifier(lookupResult, name: node.Identifier.ValueText, arity: node.Arity, invoked, useSiteInfo: ref useSiteInfo);
}
-#nullable disable
- private void LookupIdentifier(LookupResult lookupResult, SimpleNameSyntax node, bool invoked, ref CompoundUseSiteInfo useSiteInfo)
+ private void LookupIdentifier(LookupResult lookupResult, string name, int arity, bool invoked, ref CompoundUseSiteInfo useSiteInfo)
{
LookupOptions options = LookupOptions.AllMethodsOnArityZero;
if (invoked)
@@ -1810,7 +1802,7 @@ private void LookupIdentifier(LookupResult lookupResult, SimpleNameSyntax node,
options |= LookupOptions.MustNotBeMethodTypeParameter;
}
- this.LookupSymbolsWithFallback(lookupResult, node.Identifier.ValueText, arity: node.Arity, useSiteInfo: ref useSiteInfo, options: options);
+ this.LookupSymbolsWithFallback(lookupResult, name, arity, useSiteInfo: ref useSiteInfo, options: options);
}
///
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index 283e8657cfaac..e5af9ff4a7b96 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -6868,8 +6868,11 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Compiling requires binding the lambda expression many times. Consider declaring the lambda expression with explicit parameter types, or if the containing method call is generic, consider using explicit type arguments.
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
Identifier is a contextual keyword, with a specific meaning, in a later language version.
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
index 350d7a829cf11..22f200a0dac99 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
@@ -2336,7 +2336,7 @@ internal enum ErrorCode
WRN_PartialPropertySignatureDifference = 9256,
ERR_PartialPropertyRequiredDifference = 9257,
- INF_IdentifierConflictWithContextualKeyword = 9258,
+ WRN_FieldIsAmbiguous = 9258,
ERR_InlineArrayAttributeOnRecord = 9259,
ERR_FeatureNotAvailableInVersion13 = 9260,
diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
index 619d08c98c3e8..66d6ee6d41efc 100644
--- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
+++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
@@ -555,7 +555,7 @@ internal static int GetWarningLevel(ErrorCode code)
case ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate:
case ErrorCode.WRN_ConvertingLock:
case ErrorCode.WRN_PartialPropertySignatureDifference:
-
+ case ErrorCode.WRN_FieldIsAmbiguous:
return 1;
default:
return 0;
@@ -2449,7 +2449,7 @@ or ErrorCode.ERR_PartialPropertyInitMismatch
or ErrorCode.ERR_PartialPropertyTypeDifference
or ErrorCode.WRN_PartialPropertySignatureDifference
or ErrorCode.ERR_PartialPropertyRequiredDifference
- or ErrorCode.INF_IdentifierConflictWithContextualKeyword
+ or ErrorCode.WRN_FieldIsAmbiguous
or ErrorCode.ERR_InlineArrayAttributeOnRecord
or ErrorCode.ERR_FeatureNotAvailableInVersion13
or ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToOverride
diff --git a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs
index 23ef65e70688e..6bb0ac80239ba 100644
--- a/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs
+++ b/src/Compilers/CSharp/Portable/Generated/ErrorFacts.Generated.cs
@@ -339,6 +339,7 @@ public static bool IsWarning(ErrorCode code)
case ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate:
case ErrorCode.WRN_ConvertingLock:
case ErrorCode.WRN_PartialPropertySignatureDifference:
+ case ErrorCode.WRN_FieldIsAmbiguous:
return true;
default:
return false;
@@ -368,7 +369,6 @@ public static bool IsInfo(ErrorCode code)
{
case ErrorCode.INF_UnableToLoadSomeTypesInAnalyzer:
case ErrorCode.INF_TooManyBoundLambdas:
- case ErrorCode.INF_IdentifierConflictWithContextualKeyword:
return true;
default:
return false;
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index b1dea486f3bfe..c070c270fb61b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -2632,11 +2632,6 @@
ukazatel
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- {0} je kontextové klíčové slovo v přístupových objektech vlastností počínaje jazykovou verzí {1}. Místo toho použijte @{0}.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identifikátor je kontextové klíčové slovo se specifickým významem v novější jazykové verzi.
@@ -2927,6 +2922,16 @@
Použití proměnné v tomto kontextu může vystavit odkazované proměnné mimo rozsah jejich oboru.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
Operátor převodu vloženého pole se nepoužije pro převod z výrazu deklarujícího typu.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index 9b85a9f212ea1..0078527e65793 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -2632,11 +2632,6 @@
Zeiger
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- "{0}" ist ein kontextbezogenes Schlüsselwort in Eigenschaftenaccessoren ab Sprachversion {1}. Verwenden Sie stattdessen "@{0}".
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Der Bezeichner ist ein kontextbezogenes Schlüsselwort mit einer bestimmten Bedeutung in einer späteren Sprachversion.
@@ -2927,6 +2922,16 @@
Die Verwendung der Variablen in diesem Kontext kann dazu führen, dass referenzierte Variablen außerhalb ihres Deklarationsbereichs verfügbar gemacht werden.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
Der Inlinearray-Konvertierungsoperator wird nicht für die Konvertierung aus einem Ausdruck des deklarierenden Typs verwendet.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index 9f5094c9c1f6d..fe8b9998c951b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -2632,11 +2632,6 @@
puntero
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' es una palabra clave contextual en descriptores de acceso de propiedad a partir de la versión de idioma {1}. Use '@{0}' en su lugar.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
El identificador es una palabra clave contextual, con un significado específico, en una versión posterior del lenguaje.
@@ -2927,6 +2922,16 @@
Usar la variable en este contexto puede exponer variables a las que se hace referencia fuera de su ámbito de declaración
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
El operador de conversión de matriz en línea no se utilizará para la conversión de una expresión del tipo declarante.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index 0556de0512f26..0d55d57e5c894 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -2632,11 +2632,6 @@
aiguille
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- « {0} » est un mot clé contextuel dans les accesseurs de propriété commençant dans la version de langage {1}. Utilisez {0} à la place.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
L’identificateur est un mot clé contextuel, avec une signification spécifique, dans une version de langage ultérieure.
@@ -2927,6 +2922,16 @@
Utiliser la variable dans ce contexte peut exposer des variables de référence en dehors de leur étendue de déclaration
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
L’opérateur de conversion de tableau inlined ne sera pas utilisé pour la conversion à partir de l’expression du type déclarant.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index 482486f7b95b8..e0d85c231f8e7 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -2632,11 +2632,6 @@
indicatore di misura
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identifier is a contextual keyword, with a specific meaning, in a later language version.
@@ -2927,6 +2922,16 @@
L'uso di variabili in questo contesto potrebbe esporre le variabili a cui si fa riferimento al di fuori dell'ambito della dichiarazione
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
L'operatore di conversione della matrice inline non verrà usato per la conversione dell’espressione del tipo dichiarante.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index 757d82e765440..8ecfc63478d6c 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -2632,11 +2632,6 @@
ポインター
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' は、言語バージョン {1} 以降のプロパティ アクセサーのコンテキスト キーワードです。代わりに '@{0}' を使用してください。
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
識別子は、後の言語バージョンでは、特定の意味を持つコンテキスト キーワードです。
@@ -2927,6 +2922,16 @@
このコンテキストでの変数の使用は、参照される変数が宣言のスコープ外に公開される可能性があります
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
インライン配列変換演算子は、宣言型の式からの変換には使用されません。
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index 5997a9a70af03..ae92ac759f259 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -2632,11 +2632,6 @@
포인터
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}'은(는) 언어 버전 {1} 속성 접근자의 컨텍스트 키워드 대신 '@{0}'을(를) 사용하세요.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
식별자는 이후 언어 버전에서 특정 의미를 가진 컨텍스트 키워드입니다.
@@ -2927,6 +2922,16 @@
이 컨텍스트에서 변수를 사용하면 선언 범위 외부에서 참조된 변수가 노출될 수 있습니다.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
인라인 배열 변환 연산자는 선언 형식의 식에서 변환하는 데 사용되지 않습니다.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index 841c0f7a71798..0f18b1d6b3ad5 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -2632,11 +2632,6 @@
wskaźnik
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identifier is a contextual keyword, with a specific meaning, in a later language version.
@@ -2927,6 +2922,16 @@
Nie można używać zmiennej w tym kontekście, ponieważ może uwidaczniać odwoływane zmienne poza ich zakresem deklaracji
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
Operator konwersji tablicy wbudowanej nie będzie używany do konwersji z wyrażenia typu deklarującego.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index a6579e5a2a266..5b182e2af6227 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -2632,11 +2632,6 @@
ponteiro
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- "{0}" é uma palavra-chave contextual em acessadores de propriedade a partir da versão de linguagem {1}. Em vez disso, use "@{0}".
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identificador é uma palavra-chave contextual, com um significado específico, em uma versão de linguagem posterior.
@@ -2927,6 +2922,16 @@
O uso de variável neste contexto pode expor variáveis referenciadas fora de seu escopo de declaração
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
O operador de conversão de matriz em linha não será usado para a conversão da expressão do tipo declarativo.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index e50f92ee9ab29..f660d123cf6ef 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -2632,11 +2632,6 @@
указатель
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identifier is a contextual keyword, with a specific meaning, in a later language version.
@@ -2927,6 +2922,16 @@
Использование переменной в этом контексте может представить ссылочные переменные за пределами области их объявления.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
Оператор преобразования встроенного массива не будет использоваться для преобразования из выражения объявляющего типа.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index ef7f39cd069dd..b7ccaf7545791 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -2632,11 +2632,6 @@
işaretçi
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}', özellik erişimcilerinde {1} dil sürümünden başlayan bağlamsal bir anahtar sözcüktür. Bunun yerine '@{0}' kullanın.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Tanımlayıcı, daha sonraki bir dil sürümünde belirli bir anlamı olan bağlamsal bir anahtar sözcüktür.
@@ -2927,6 +2922,16 @@
Bu bağlamda değişken kullanımı, başvurulan değişkenleri bildirim kapsamının dışında gösterebilir.
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
Bildirim türündeki ifadeden dönüştürme için satır içi dizi dönüştürme işleci kullanılmaz.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index c3da2594a2438..ac1f52a36f53b 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -2632,11 +2632,6 @@
指针
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
Identifier is a contextual keyword, with a specific meaning, in a later language version.
@@ -2927,6 +2922,16 @@
在此上下文中使用变量可能会在变量声明范围以外公开所引用的变量
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
内联数组转换运算符将不会用于从声明类型的表达式进行转换。
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index 0d5ff8e552a73..b3f556c0b0476 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -2632,11 +2632,6 @@
指標
-
- '{0}' is a contextual keyword in property accessors starting in language version {1}. Use '@{0}' instead.
- '{0}' 是從語言版本 {1} 開始之屬性存取子中的內容相關關鍵字。請改用 '@{0}'。
-
-
Identifier is a contextual keyword, with a specific meaning, in a later language version.
識別碼是在較新語言版本中具有特定意義的內容相關關鍵字。
@@ -2927,6 +2922,16 @@
在此內容中使用變數,可能會將參考的變數公開在其宣告範圍外
+
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ In language version {0}, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+
+
+
+ The 'field' keyword binds to a synthesized backing field for the property.
+ The 'field' keyword binds to a synthesized backing field for the property.
+
+
Inline array conversion operator will not be used for conversion from expression of the declaring type.
內嵌陣列轉換運算子不會用於從宣告類型的運算式進行轉換。
diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
index cfdee2f546876..0fc3ee90cf8ac 100644
--- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
+++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
@@ -194,10 +194,7 @@ object P
comp.VerifyEmitDiagnostics(
// (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
// get { return _other.field; }
- Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29),
- // (7,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // set { _ = field; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(7, 19));
+ Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29));
}
[Fact]
@@ -215,9 +212,6 @@ C P
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (6,15): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // set { field = value.field; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 15),
// (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?)
// set { field = value.field; }
Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29));
@@ -248,9 +242,6 @@ int P
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (5,22): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // get { return field; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 22),
// (6,29): error CS0117: 'C' does not contain a definition for 'field'
// set { _ = this is { field: 0 }; }
Diagnostic(ErrorCode.ERR_NoSuchMember, "field").WithArguments("C", "field").WithLocation(6, 29));
@@ -297,9 +288,6 @@ class C
// (3,12): error CS8050: Only auto-implemented properties can have initializers.
// object P { get => field; } = field;
Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12),
- // (3,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P { get => field; } = field;
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 23),
// (3,34): error CS0103: The name 'field' does not exist in the current context
// object P { get => field; } = field;
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 34));
@@ -613,18 +601,9 @@ interface I
// (3,17): error CS0501: 'I.Q1.get' must declare a body because it is not marked abstract, extern, or partial
// object Q1 { get; set { _ = field; } }
Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I.Q1.get").WithLocation(3, 17),
- // (3,32): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object Q1 { get; set { _ = field; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 32),
- // (4,30): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object Q2 { get { return field; } set; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 30),
// (4,39): error CS0501: 'I.Q2.set' must declare a body because it is not marked abstract, extern, or partial
// object Q2 { get { return field; } set; }
Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I.Q2.set").WithLocation(4, 39),
- // (5,30): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object Q3 { get { return field; } init; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 30),
// (5,39): error CS0501: 'I.Q3.init' must declare a body because it is not marked abstract, extern, or partial
// object Q3 { get { return field; } init; }
Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "init").WithArguments("I.Q3.init").WithLocation(5, 39));
@@ -889,10 +868,15 @@ class A : Attribute
{
public A(object o) { }
}
- class C
+ class B
{
[A(field)] object P1 { get { return null; } set { } }
}
+ class C
+ {
+ const object field = null;
+ [A(field)] object P2 { get { return null; } set { } }
+ }
""";
var comp = CreateCompilation(source);
@@ -908,6 +892,10 @@ class C
var argument = attributeArguments[0];
Assert.IsType(argument);
Assert.Null(model.GetSymbolInfo(argument).Symbol);
+
+ argument = attributeArguments[1];
+ Assert.IsType(argument);
+ Assert.Equal("System.Object C.field", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
}
[Fact]
@@ -919,27 +907,32 @@ class A : Attribute
{
public A(object o) { }
}
+ class B
+ {
+ object P1 { [A(field)] get { return null; } set { } }
+ object P2 { get { return null; } [A(field)] set { } }
+ }
class C
{
- object P2 { [A(field)] get { return null; } set { } }
- object P3 { get { return null; } [A(field)] set { } }
+ const object field = null;
+ object P3 { [A(field)] get { return null; } set { } }
}
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (8,20): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P2 { [A(field)] get { return null; } set { } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(8, 20),
// (8,20): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
- // object P2 { [A(field)] get { return null; } set { } }
+ // object P1 { [A(field)] get { return null; } set { } }
Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(8, 20),
- // (9,41): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P3 { get { return null; } [A(field)] set { } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(9, 41),
// (9,41): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
- // object P3 { get { return null; } [A(field)] set { } }
- Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(9, 41));
+ // object P2 { get { return null; } [A(field)] set { } }
+ Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(9, 41),
+ // (14,20): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P3 { [A(field)] get { return null; } set { } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(14, 20),
+ // (14,20): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
+ // object P3 { [A(field)] get { return null; } set { } }
+ Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(14, 20));
var tree = comp.SyntaxTrees.Single();
var model = comp.GetSemanticModel(tree);
@@ -947,10 +940,14 @@ class C
var argument = attributeArguments[0];
Assert.IsType(argument);
- Assert.Equal("System.Object C.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
+ Assert.Equal("System.Object B.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
argument = attributeArguments[1];
Assert.IsType(argument);
+ Assert.Equal("System.Object B.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
+
+ argument = attributeArguments[2];
+ Assert.IsType(argument);
Assert.Equal("System.Object C.k__BackingField", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString());
}
@@ -958,7 +955,6 @@ class C
public void Attribute_03()
{
string source = $$"""
- #pragma warning disable 9258 // 'field' is a contextual keyword
using System;
using System.Reflection;
@@ -1006,18 +1002,18 @@ static void ReportField(FieldInfo field)
var comp = CreateCompilation(source, options: TestOptions.ReleaseExe, targetFramework: TargetFramework.Net80);
comp.VerifyEmitDiagnostics(
- // (18,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
+ // (17,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
// public object P4 { [field: A(4)] get; }
- Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(18, 25),
- // (19,37): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(17, 25),
+ // (18,37): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
// public static object P5 { get; [field: A(5)] set; }
- Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(19, 37),
- // (23,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(18, 37),
+ // (22,25): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, return'. All attributes in this block will be ignored.
// public object Q4 { [field: A(4)] get => field; }
- Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(23, 25),
- // (24,54): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, return").WithLocation(22, 25),
+ // (23,54): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'method, param, return'. All attributes in this block will be ignored.
// public static object Q5 { get { return field; } [field: A(5)] set { } }
- Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(24, 54));
+ Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "method, param, return").WithLocation(23, 54));
CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("""
B.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(1),
@@ -1039,8 +1035,8 @@ static void ReportField(FieldInfo field)
public void RestrictedTypes()
{
string source = """
- #pragma warning disable 9258 // 'field' is a contextual keyword
using System;
+
class C
{
static TypedReference P1 { get; }
@@ -1074,10 +1070,10 @@ class C
public void ByRefLikeType_01(string typeKind, bool allow)
{
string source = $$"""
- #pragma warning disable 9258 // 'field' is a contextual keyword
ref struct R
{
}
+
{{typeKind}} C
{
R P1 { get; }
@@ -1087,6 +1083,7 @@ ref struct R
R Q3 { set { _ = field; } }
public override string ToString() => "C";
}
+
class Program
{
static void Main()
@@ -1131,10 +1128,10 @@ static void Main()
public void ByRefLikeType_02(string typeKind)
{
string source = $$"""
- #pragma warning disable 9258 // 'field' is a contextual keyword
ref struct R
{
}
+
{{typeKind}} C
{
static R P1 { get; }
@@ -1167,10 +1164,10 @@ ref struct R
public void ByRefLikeType_03()
{
string source = """
- #pragma warning disable 9258 // 'field' is a contextual keyword
ref struct R
{
}
+
interface I
{
static R P1 { get; }
diff --git a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
index a196defc664ea..b4e747af6f74a 100644
--- a/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Diagnostics/DiagnosticTest.cs
@@ -432,7 +432,7 @@ public void WarningLevel_2()
case ErrorCode.WRN_CollectionExpressionRefStructMayAllocate:
case ErrorCode.WRN_CollectionExpressionRefStructSpreadMayAllocate:
case ErrorCode.INF_TooManyBoundLambdas:
- case ErrorCode.INF_IdentifierConflictWithContextualKeyword:
+ case ErrorCode.WRN_FieldIsAmbiguous:
Assert.Equal(1, ErrorFacts.GetWarningLevel(errorCode));
break;
case ErrorCode.WRN_InvalidVersionFormat:
diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs
index 2870420d8fb1f..9492d589e19da 100644
--- a/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs
@@ -18,7 +18,7 @@ public FieldKeywordParsingTests(ITestOutputHelper output) : base(output)
private static bool IsParsedAsToken(LanguageVersion languageVersion, bool escapeIdentifier)
{
- return !escapeIdentifier && languageVersion > LanguageVersion.CSharp12;
+ return !escapeIdentifier && languageVersion > LanguageVersion.CSharp13;
}
private void IdentifierNameOrFieldExpression(LanguageVersion languageVersion, bool escapeIdentifier)
@@ -52,7 +52,7 @@ private static string GetFieldIdentifier(bool escapeIdentifier)
[Theory]
[CombinatorialData]
public void Property_Initializer(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
UsingTree($$"""
class C
@@ -111,9 +111,9 @@ class C
[Theory]
[CombinatorialData]
public void Property_ExpressionBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
- bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13;
UsingTree($$"""
class C
{
@@ -153,9 +153,9 @@ class C
[Theory]
[CombinatorialData]
public void PropertyGet_ExpressionBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
- bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13;
UsingTree($$"""
class C
{
@@ -204,9 +204,9 @@ class C
[Theory]
[CombinatorialData]
public void PropertyGet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
- bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13;
UsingTree($$"""
class C
{
@@ -260,10 +260,10 @@ class C
[Theory]
[CombinatorialData]
public void PropertySet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool useInit)
{
- bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13;
UsingTree($$"""
class C
{
@@ -324,7 +324,7 @@ class C
[Theory]
[CombinatorialData]
public void Indexer_ExpressionBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
UsingTree($$"""
class C
@@ -381,7 +381,7 @@ class C
[Theory]
[CombinatorialData]
public void IndexerGet_ExpressionBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
UsingTree($$"""
class C
@@ -447,7 +447,7 @@ class C
[Theory]
[CombinatorialData]
public void IndexerGet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion)
{
UsingTree($$"""
class C
@@ -518,7 +518,7 @@ class C
[Theory]
[CombinatorialData]
public void IndexerSet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool useInit)
{
UsingTree($$"""
@@ -597,7 +597,7 @@ class C
[Theory]
[CombinatorialData]
public void EventAccessor(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool useRemove)
{
UsingTree($$"""
@@ -664,10 +664,10 @@ class C
[Theory]
[CombinatorialData]
public void ExplicitImplementation_PropertySet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool useInit)
{
- bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp12;
+ bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13;
UsingTree($$"""
class C
{
@@ -745,7 +745,7 @@ class C
[Theory]
[CombinatorialData]
public void ExplicitImplementation_IndexerSet_BlockBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool useInit)
{
UsingTree($$"""
@@ -841,7 +841,7 @@ class C
[Theory]
[CombinatorialData]
public void Invocation(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -891,7 +891,7 @@ class C
[Theory]
[CombinatorialData]
public void ElementAccess(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -948,7 +948,7 @@ class C
[Theory]
[CombinatorialData]
public void PreIncrement(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -994,7 +994,7 @@ class C
[Theory]
[CombinatorialData]
public void PostIncrement(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1040,7 +1040,7 @@ class C
[Theory]
[CombinatorialData]
public void PointerIndirection(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1086,7 +1086,7 @@ class C
[Theory]
[CombinatorialData]
public void PointerMemberAccess(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1136,7 +1136,7 @@ class C
[Theory]
[CombinatorialData]
public void ConditionalAccess(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1190,7 +1190,7 @@ class C
[Theory]
[CombinatorialData]
public void NullableSuppression(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1236,7 +1236,7 @@ class C
[Theory]
[CombinatorialData]
public void Arguments(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1305,7 +1305,7 @@ class C
[Theory]
[CombinatorialData]
public void QualifiedName_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1356,7 +1356,7 @@ class C
[Theory]
[CombinatorialData]
public void QualifiedName_02(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1410,7 +1410,7 @@ class C
[Theory]
[CombinatorialData]
public void AliasQualifiedName(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1472,7 +1472,7 @@ class C
[Theory]
[CombinatorialData]
public void NameOf(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1550,7 +1550,7 @@ class C
[Theory]
[CombinatorialData]
public void Lvalue(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
UsingTree($$"""
@@ -1613,7 +1613,7 @@ class C
[Theory]
[CombinatorialData]
public void NewTypeName(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1689,7 +1689,7 @@ class C
[Theory]
[CombinatorialData]
public void LambdaBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1740,7 +1740,7 @@ class C
[Theory]
[CombinatorialData]
public void LocalFunctionBody(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
@@ -1830,7 +1830,7 @@ class C
[Theory]
[CombinatorialData]
public void CatchDeclaration(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = GetFieldIdentifier(escapeIdentifier);
diff --git a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
index c3f62094f7944..d70cc8725e2dc 100644
--- a/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
+++ b/src/Compilers/CSharp/Test/Syntax/Syntax/FieldAndValueKeywordTests.cs
@@ -4,7 +4,12 @@
#nullable disable
+using System.Linq;
+using Microsoft.CodeAnalysis.CSharp.Symbols;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.CSharp.UnitTests
@@ -14,7 +19,7 @@ public class FieldKeywordTests : CSharpTestBase
[Theory]
[CombinatorialData]
public void Field_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
@@ -33,25 +38,25 @@ 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)
- {
- comp.VerifyEmitDiagnostics();
- }
- else
+ if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp13)
{
comp.VerifyEmitDiagnostics(
- // (4,28): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // (4,28): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// class C1 : A { object P => field; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 28),
- // (5,34): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 28),
+ // (5,34): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// class C2 : A { object P { get => field; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 34),
- // (6,40): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(5, 34),
+ // (6,40): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// class C3 : A { object P { get { return field; } } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 40),
- // (7,33): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(6, 40),
+ // (7,33): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// class C4 : A { object P { set { field = 0; } } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(7, 33));
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(7, 33));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
}
}
@@ -84,7 +89,7 @@ object this[int @field]
[Theory]
[CombinatorialData]
public void Event_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = $$"""
#pragma warning disable 649
@@ -106,7 +111,7 @@ event EventHandler E1
[Theory]
[CombinatorialData]
public void ExplicitImplementation_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
@@ -125,26 +130,26 @@ class C : I
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- if (escapeIdentifier)
- {
- comp.VerifyEmitDiagnostics();
- }
- else
+ if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp13)
{
comp.VerifyEmitDiagnostics(
- // (10,25): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // (10,25): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// object I.P { get => field; set { _ = field; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 25),
- // (10,42): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(10, 25),
+ // (10,42): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// object I.P { get => field; set { _ = field; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 42));
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(10, 42));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
}
}
[Theory]
[CombinatorialData]
public void ExplicitImplementation_02(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
@@ -169,7 +174,7 @@ class C : I
[Theory]
[CombinatorialData]
public void ExplicitImplementation_04(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion,
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion,
bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
@@ -197,7 +202,7 @@ event EventHandler I.E
[Theory]
[CombinatorialData]
public void IdentifierToken_IdentifierNameSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 8981
@@ -215,7 +220,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_GenericNameSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 8981
@@ -233,7 +238,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_Invocation(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 649
@@ -246,16 +251,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (6,33): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // Func P1 { get { _ = field(); return null; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 33));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (6,33): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // Func P1 { get { _ = field(); return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(6, 33));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_Index(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 649
@@ -267,16 +279,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (5,29): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object[] P1 { get { _ = field[0]; return null; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 29));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (5,29): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object[] P1 { get { _ = field[0]; return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(5, 29));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_TupleElementSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 219
@@ -293,7 +312,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_FromClauseSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
using System.Linq;
@@ -304,16 +323,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,59): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P1 { get { _ = from field in new int[0] select field; return null; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 59));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,59): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P1 { get { _ = from field in new int[0] select field; return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 59));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_LetClauseSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
using System.Linq;
@@ -324,16 +350,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,69): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // 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));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,69): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P1 { get { _ = from i in new int[0] let field = i select field; return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 69));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_JoinClauseSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
using System.Linq;
@@ -344,16 +377,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,85): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // 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));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,85): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // 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.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 85));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_JoinIntoClauseSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
using System.Linq;
@@ -364,16 +404,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,101): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // 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));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,101): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // 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.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 101));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_QueryContinuationSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
using System.Linq;
@@ -384,16 +431,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,75): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // 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));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,75): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P1 { get { _ = from x in new int[0] select x into field select field; return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 75));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_LocalFunctionStatementSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 8321
@@ -410,7 +464,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_VariableDeclaratorSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 219
@@ -427,7 +481,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_SingleVariableDesignationSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
class C
@@ -444,7 +498,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_LabeledStatementSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 164
@@ -461,7 +515,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_ForEachStatementSyntax_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
class C
@@ -477,7 +531,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_ForEachStatementSyntax_02(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
class C
@@ -499,7 +553,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_CatchDeclarationSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 168
@@ -517,7 +571,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_TypeParameterSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 8321, 8981
@@ -534,7 +588,7 @@ class C
[Theory]
[CombinatorialData]
public void IdentifierToken_ParameterSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 8321
@@ -545,16 +599,23 @@ class C
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (4,50): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P1 { get { object F1(object field) => field; return null; } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(4, 50));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (4,50): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P1 { get { object F1(object field) => field; return null; } }
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 50));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void IdentifierToken_AttributeTargetSpecifierSyntax(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = $$"""
#pragma warning disable 657
@@ -579,7 +640,7 @@ class C
[Theory]
[CombinatorialData]
public void Deconstruction(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 168 // variable is declared but never used
@@ -598,19 +659,29 @@ static object P1
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (10,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(10, 20),
- // (11,14): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // (field, @value) = new C();
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(11, 14));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (10,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(10, 20),
+ // (11,14): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // (field, @value) = new C();
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(11, 14));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics(
+ // (10,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(10, 20));
+ }
}
[Theory]
[CombinatorialData]
public void Lambda_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 649
@@ -632,16 +703,23 @@ object P
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (11,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // f = () => field;
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(11, 23));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (11,23): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // f = () => field;
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(11, 23));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Theory]
[CombinatorialData]
public void LocalFunction_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
{
string source = """
#pragma warning disable 649, 8321
@@ -661,10 +739,17 @@ object P
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- comp.VerifyEmitDiagnostics(
- // (9,28): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object F1() => field;
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(9, 28));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (9,28): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object F1() => field;
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(9, 28));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
}
[Fact]
@@ -808,7 +893,7 @@ object P
[Theory]
[CombinatorialData]
public void Attribute_01(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion, bool escapeIdentifier)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
string source = $$"""
@@ -835,18 +920,18 @@ event EventHandler E
}
""";
var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
- if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp12)
+ if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp13)
{
comp.VerifyEmitDiagnostics(
- // (12,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // (12,19): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// [A(nameof(field))] get { return null; }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(12, 19),
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(12, 19),
// (12,19): error CS8081: Expression does not have a name.
// [A(nameof(field))] get { return null; }
Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(12, 19),
- // (13,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // (13,19): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// [A(nameof(field))] set { }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 19),
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(13, 19),
// (13,19): error CS8081: Expression does not have a name.
// [A(nameof(field))] set { }
Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(13, 19));
@@ -860,7 +945,7 @@ event EventHandler E
[Theory]
[CombinatorialData]
public void Attribute_LocalFunction(
- [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.Preview)] LanguageVersion languageVersion, bool escapeIdentifier)
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, bool escapeIdentifier)
{
string identifier = escapeIdentifier ? "@field" : "field";
string source = $$"""
@@ -887,22 +972,19 @@ object P1
{
comp.VerifyEmitDiagnostics();
}
- else if (languageVersion > LanguageVersion.CSharp12)
+ else if (languageVersion > LanguageVersion.CSharp13)
{
comp.VerifyEmitDiagnostics(
- // (13,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
+ // (13,23): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
// [A(nameof(field))] void F1(int field) { }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23),
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(13, 23),
// (13,23): error CS8081: Expression does not have a name.
// [A(nameof(field))] void F1(int field) { }
Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(13, 23));
}
else
{
- comp.VerifyEmitDiagnostics(
- // (13,23): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // [A(nameof(field))] void F1(int field) { }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23));
+ comp.VerifyEmitDiagnostics();
}
}
@@ -917,9 +999,6 @@ class C
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (3,24): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P => nameof(field);
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 24),
// (3,24): error CS8081: Expression does not have a name.
// object P => nameof(field);
Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(3, 24));
@@ -936,12 +1015,135 @@ class C
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (3,33): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead.
- // object P { set { _ = nameof(field); } }
- Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(3, 33),
// (3,33): error CS8081: Expression does not have a name.
// object P { set { _ = nameof(field); } }
Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(3, 33));
}
+
+ [Theory]
+ [CombinatorialData]
+ public void NameOf_03(
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
+ {
+ string source = """
+ class C
+ {
+ static int field;
+ object P => nameof(field);
+ }
+ """;
+ var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,16): warning CS0169: The field 'C.field' is never used
+ // static int field;
+ Diagnostic(ErrorCode.WRN_UnreferencedField, "field").WithArguments("C.field").WithLocation(3, 16),
+ // (4,24): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // object P => nameof(field);
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(4, 24),
+ // (4,24): error CS8081: Expression does not have a name.
+ // object P => nameof(field);
+ Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(4, 24));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,16): warning CS0649: Field 'C.field' is never assigned to, and will always have its default value 0
+ // static int field;
+ Diagnostic(ErrorCode.WRN_UnassignedInternalField, "field").WithArguments("C.field", "0").WithLocation(3, 16));
+ }
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void BaseClassMember(
+ [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion)
+ {
+ string sourceA = """
+ public class Base
+ {
+ protected string field;
+ }
+ """;
+ var comp = CreateCompilation(sourceA);
+ var refA = comp.EmitToImageReference();
+
+ string sourceB1 = """
+ class Derived : Base
+ {
+ string P => field; // synthesized backing field
+ }
+ """;
+ comp = CreateCompilation(sourceB1, references: [refA], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ if (languageVersion > LanguageVersion.CSharp13)
+ {
+ comp.VerifyEmitDiagnostics(
+ // (3,17): warning CS9258: In language version preview, the 'field' keyword binds to a synthesized backing field for the property. To avoid generating a synthesized backing field, and to refer to the existing member, use 'this.field' or '@field' instead.
+ // string P => field;
+ Diagnostic(ErrorCode.WRN_FieldIsAmbiguous, "field").WithArguments("preview").WithLocation(3, 17));
+ }
+ else
+ {
+ comp.VerifyEmitDiagnostics();
+ }
+ verify(comp, synthesizeField: languageVersion > LanguageVersion.CSharp13);
+
+ string sourceB2 = """
+ class Derived : Base
+ {
+ string P => @field; // Base.field
+ }
+ """;
+ comp = CreateCompilation(sourceB2, references: [refA], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ comp.VerifyEmitDiagnostics();
+ verify(comp, synthesizeField: false);
+
+ string sourceB3 = """
+ class Derived : Base
+ {
+ string P => this.field; // Base.field
+ }
+ """;
+ comp = CreateCompilation(sourceB3, references: [refA], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ comp.VerifyEmitDiagnostics();
+ verify(comp, synthesizeField: false);
+
+ string sourceB4 = """
+ class Derived : Base
+ {
+ string P => base.field; // Base.field
+ }
+ """;
+ comp = CreateCompilation(sourceB4, references: [refA], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ comp.VerifyEmitDiagnostics();
+ verify(comp, synthesizeField: false);
+
+ string sourceB5 = """
+ class Derived : Base
+ {
+ #pragma warning disable 9258 // 'field' is a contextual keyword
+ string P => field; // synthesized backing field
+ }
+ """;
+ comp = CreateCompilation(sourceB5, references: [refA], parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion));
+ comp.VerifyEmitDiagnostics();
+ verify(comp, synthesizeField: languageVersion > LanguageVersion.CSharp13);
+
+ static void verify(CSharpCompilation comp, bool synthesizeField)
+ {
+ var syntaxTree = comp.SyntaxTrees[0];
+ var model = comp.GetSemanticModel(syntaxTree);
+ var expr = syntaxTree.GetRoot().DescendantNodes().OfType().Single().Expression;
+
+ var symbolInfo = model.GetSymbolInfo(expr);
+ string expectedSymbol = synthesizeField ? "System.String Derived.k__BackingField" : "System.String Base.field";
+ Assert.Equal(expectedSymbol, symbolInfo.Symbol.ToTestDisplayString());
+
+ var actualFields = comp.GetMember("Derived").GetMembers().Where(m => m.Kind == SymbolKind.Field).ToTestDisplayStrings();
+ string[] expectedFields = synthesizeField ? ["System.String Derived.k__BackingField"] : [];
+ AssertEx.Equal(expectedFields, actualFields);
+ }
+ }
}
}
From a878dd235e5da507b5ec3234e4237a2d7f98123a Mon Sep 17 00:00:00 2001
From: Cyrus Najmabadi
Date: Fri, 23 Aug 2024 16:11:25 -0700
Subject: [PATCH 05/18] Move feature to only working when in 'Preview'
---
.../Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
index 334581270c3ab..ac65a9e833852 100644
--- a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs
@@ -46,7 +46,7 @@ protected override bool SupportsPropertyInitializer(Compilation compilation)
=> compilation.LanguageVersion() >= LanguageVersion.CSharp6;
protected override bool SupportsFieldExpression(Compilation compilation)
- => compilation.LanguageVersion() >= LanguageVersion.CSharp13;
+ => compilation.LanguageVersion() >= LanguageVersion.Preview;
protected override ExpressionSyntax? GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken)
=> variable.Initializer?.Value;
From c01a6bc316d29437a80670f9015a185bf0e093ff Mon Sep 17 00:00:00 2001
From: Cyrus Najmabadi
Date: Fri, 23 Aug 2024 16:27:33 -0700
Subject: [PATCH 06/18] Update tests
---
.../UseAutoPropertyTests_Field.cs | 138 ++++++++++--------
1 file changed, 79 insertions(+), 59 deletions(-)
diff --git a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
index 4e12a182143d7..41b95d6f61dd5 100644
--- a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
+++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs
@@ -15,6 +15,26 @@ public sealed partial class UseAutoPropertyTests
private readonly ParseOptions CSharp13 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp13);
private readonly ParseOptions Preview = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview);
+ [Fact]
+ public async Task TestNotInCSharp13()
+ {
+ await TestMissingInRegularAndScriptAsync(
+ """
+ class Class
+ {
+ [|string s|];
+
+ string P
+ {
+ get
+ {
+ return s.Trim();
+ }
+ }
+ }
+ """, new(parseOptions: CSharp13));
+ }
+
[Fact]
public async Task TestFieldSimplestCase()
{
@@ -44,7 +64,7 @@ string P
}
}
}
- """, parseOptions: CSharp13);
+ """, parseOptions: Preview);
}
[Fact]
@@ -76,7 +96,7 @@ string P
}
}
}
- """, parseOptions: CSharp13);
+ """, parseOptions: Preview);
}
[Fact]
@@ -108,7 +128,7 @@ static string P
}
}
}
- """, parseOptions: CSharp13);
+ """, parseOptions: Preview);
}
[Fact]
@@ -142,13 +162,13 @@ int P
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -183,13 +203,13 @@ int P
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field2()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -221,13 +241,13 @@ int P
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSimpleFieldInExpressionBody()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -241,7 +261,7 @@ class Class
{
string P => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -255,7 +275,7 @@ class Class
int Total => x + y;
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -277,7 +297,7 @@ int Total
}
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -310,7 +330,7 @@ int Total
set;
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -332,7 +352,7 @@ class Class
int X => field + y;
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -354,7 +374,7 @@ class Class
int X => field + y;
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -375,7 +395,7 @@ string P
}
}
}
- """, new TestParameters(parseOptions: Preview));
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -397,7 +417,7 @@ string P
}
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -419,7 +439,7 @@ string P
}
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -445,7 +465,7 @@ void M()
throw new ArgumentNullException(nameof(s));
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -471,7 +491,7 @@ void M()
throw new ArgumentNullException(nameof(this.s));
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -485,7 +505,7 @@ class Class
string P => s;
}
- """);
+ """, new(parseOptions: CSharp13));
}
[Fact]
@@ -513,7 +533,7 @@ void Init(ref string s)
{
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -536,7 +556,7 @@ void Init(ref string s)
{
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -570,7 +590,7 @@ string P
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -595,7 +615,7 @@ void M()
ref string s1 = ref s;
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -635,7 +655,7 @@ int P
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -663,7 +683,7 @@ unsafe void M()
int* p = &s;
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -694,7 +714,7 @@ public bool StrictMode
set;
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -718,7 +738,7 @@ class Builder
{
public List List => field ??= new();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -741,7 +761,7 @@ class Builder
void Set(ref int a, int b) { }
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -783,7 +803,7 @@ public int Prop
void Set(ref int a, int b) { }
void OnPropChanged() { }
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -804,7 +824,7 @@ class C
[field: Something]
public int Prop { get; set; }
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -825,7 +845,7 @@ class C
[field: Something]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -849,7 +869,7 @@ class C
[PropAttribute]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -875,7 +895,7 @@ class C
[PropAttribute]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -901,7 +921,7 @@ class C
[PropAttribute][PropAttribute2]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -925,7 +945,7 @@ class C
[field: Something]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -950,7 +970,7 @@ class C
[field: Something]
public string Prop => field.Trim();
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -984,13 +1004,13 @@ public string Prop
}
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere1()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -1014,13 +1034,13 @@ void M()
P = "";
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere2()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -1044,13 +1064,13 @@ void M()
P = "";
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere3()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -1080,13 +1100,13 @@ void M()
P = "";
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere4()
{
- await TestInRegularAndScript1Async(
+ await TestInRegularAndScriptAsync(
"""
class Class
{
@@ -1124,7 +1144,7 @@ void M()
P = "";
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1143,7 +1163,7 @@ void M()
Console.WriteLine(i);
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1162,7 +1182,7 @@ void M()
Console.WriteLine(this.i);
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1181,7 +1201,7 @@ void M()
i = 1;
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1200,7 +1220,7 @@ void M()
this.i = 1;
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1220,7 +1240,7 @@ class Class
{
public int I { get; set => field = value / 2; }
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1239,7 +1259,7 @@ void M()
Console.WriteLine(this.i++);
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1258,7 +1278,7 @@ void M()
Console.WriteLine(this.i++);
}
}
- """);
+ """, new(parseOptions: Preview));
}
[Fact]
@@ -1288,7 +1308,7 @@ void M()
Console.WriteLine(I);
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1318,7 +1338,7 @@ void M()
I = 1;
}
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1337,7 +1357,7 @@ class C
{
public string Prop => $"{field:prop}";
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1360,7 +1380,7 @@ class C
void M() { Prop = "..."; }
}
- """);
+ """, parseOptions: Preview);
}
[Fact]
@@ -1374,6 +1394,6 @@ class C
[ThisIsMyBackingField(nameof(prop))]
public string Prop { get => prop; set => prop = value; }
}
- """);
+ """, new(parseOptions: Preview));
}
}
From b8c1ea0b6d6d888be3bd832dfc64b375b295d0c2 Mon Sep 17 00:00:00 2001
From: Charles Stoner <10732005+cston@users.noreply.github.com>
Date: Tue, 27 Aug 2024 12:58:44 -0700
Subject: [PATCH 07/18] Field-backed properties: update uses of IsAutoProperty
(#74798)
Co-authored-by: Fred Silberberg
---
.../Portable/Binder/Binder.ValueChecks.cs | 2 +-
.../Portable/Binder/Binder_Statements.cs | 5 +-
.../CSharp/Portable/CSharpResources.resx | 2 +-
.../LocalRewriter_AssignmentOperator.cs | 3 +-
.../Source/SourcePropertySymbolBase.cs | 27 +-
.../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 +-
.../Test/Emit/CodeGen/CodeGenRefLocalTests.cs | 4 +-
.../Emit/CodeGen/CodeGenRefReturnTests.cs | 4 +-
.../CSharp/Test/Emit3/FieldKeywordTests.cs | 2212 ++++++++++++++++-
.../Semantics/NullableReferenceTypesTests.cs | 3 +
.../Semantic/Semantics/RecordStructTests.cs | 4 +-
.../Test/Semantic/Semantics/StructsTests.cs | 4 +-
.../UninitializedNonNullableFieldTests.cs | 2 +-
.../DefaultInterfaceImplementationTests.cs | 158 +-
.../Symbol/Symbols/PartialPropertiesTests.cs | 24 +-
.../Test/Symbol/Symbols/SymbolErrorTests.cs | 6 +-
28 files changed, 2430 insertions(+), 82 deletions(-)
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
index a23da4551f2f3..f550f1c8f3f2f 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
@@ -1694,7 +1694,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV
if (setMethod is null)
{
var containing = this.ContainingMemberOrLambda;
- if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing)
+ if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing, allowFieldKeyword: true)
&& !isAllowedDespiteReadonly(receiver))
{
Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol);
diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
index c2c03dddb53f8..65ec316dad808 100644
--- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
+++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
@@ -1754,7 +1754,8 @@ internal static bool AccessingAutoPropertyFromConstructor(BoundPropertyAccess pr
return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember);
}
- private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember)
+ // PROTOTYPE: Review all callers for allowFieldKeyword.
+ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember, bool allowFieldKeyword = false)
{
if (!propertySymbol.IsDefinition && propertySymbol.ContainingType.Equals(propertySymbol.ContainingType.OriginalDefinition, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes))
{
@@ -1765,7 +1766,7 @@ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receive
var propertyIsStatic = propertySymbol.IsStatic;
return (object)sourceProperty != null &&
- sourceProperty.IsAutoProperty &&
+ (allowFieldKeyword ? sourceProperty.IsAutoPropertyOrUsesFieldKeyword : sourceProperty.IsAutoProperty) &&
TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) &&
IsConstructorOrField(fromMember, isStatic: propertyIsStatic) &&
(propertyIsStatic || receiver.Kind == BoundKind.ThisReference);
diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx
index e5af9ff4a7b96..d85e31bbcd749 100644
--- a/src/Compilers/CSharp/Portable/CSharpResources.resx
+++ b/src/Compilers/CSharp/Portable/CSharpResources.resx
@@ -4891,7 +4891,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
Expected identifier or numeric literal
- Only auto-implemented properties can have initializers.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
Instance properties in interfaces cannot have initializers.
diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
index 5ff4636c6c674..9a2d1ec5b9caa 100644
--- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
+++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs
@@ -281,8 +281,9 @@ private BoundExpression MakePropertyAssignment(
if (setMethod is null)
{
var autoProp = (SourcePropertySymbolBase)property.OriginalDefinition;
- Debug.Assert(autoProp.IsAutoProperty,
+ Debug.Assert(autoProp.IsAutoPropertyOrUsesFieldKeyword,
"only autoproperties can be assignable without having setters");
+ Debug.Assert(_factory.CurrentFunction.IsConstructor());
Debug.Assert(property.Equals(autoProp, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes));
var backingField = autoProp.BackingField;
diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
index 95fa2f85e9da4..85b0273488847 100644
--- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs
@@ -302,7 +302,7 @@ protected void CheckInitializerIfNeeded(BindingDiagnosticBag diagnostics)
{
diagnostics.Add(ErrorCode.ERR_InstancePropertyInitializerInInterface, Location);
}
- else if (!IsAutoProperty)
+ else if (!IsAutoPropertyOrUsesFieldKeyword)
{
diagnostics.Add(ErrorCode.ERR_InitializerOnNonAutoProperty, Location);
}
@@ -653,7 +653,7 @@ public bool HasSkipLocalsInitAttribute
internal bool IsAutoPropertyOrUsesFieldKeyword
=> IsAutoProperty || UsesFieldKeyword;
- protected bool UsesFieldKeyword
+ private bool UsesFieldKeyword
=> (_propertyFlags & Flags.UsesFieldKeyword) != 0;
protected bool HasExplicitAccessModifier
@@ -666,8 +666,9 @@ protected bool AccessorsHaveImplementation
=> (_propertyFlags & Flags.AccessorsHaveImplementation) != 0;
///
- /// Backing field for automatically implemented property, or
- /// for a property with an initializer.
+ /// Backing field for an automatically implemented property, or
+ /// a property with an accessor using the 'field' keyword, or
+ /// a property with an initializer.
///
internal SynthesizedBackingFieldSymbol BackingField { get; }
@@ -715,9 +716,9 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
diagnostics.Add(ErrorCode.ERR_RefReturningPropertiesCannotBeRequired, Location);
}
- if (IsAutoProperty)
+ if (IsAutoPropertyOrUsesFieldKeyword)
{
- if (!IsStatic && SetMethod is { IsInitOnly: false })
+ if (!IsStatic && ((_propertyFlags & Flags.HasAutoPropertySet) != 0) && SetMethod is { IsInitOnly: false })
{
if (ContainingType.IsReadOnly)
{
@@ -738,10 +739,15 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
diagnostics.Add(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, Location);
}
- // get-only auto property should not override settable properties
- if (this.IsOverride && SetMethod is null && !this.IsReadOnly)
+ // Auto property should override both accessors.
+ if (this.IsOverride)
{
- diagnostics.Add(ErrorCode.ERR_AutoPropertyMustOverrideSet, Location);
+ var overriddenProperty = (PropertySymbol)this.GetLeastOverriddenMember(this.ContainingType);
+ if ((overriddenProperty.GetMethod is { } && GetMethod is null) ||
+ (overriddenProperty.SetMethod is { } && SetMethod is null))
+ {
+ diagnostics.Add(ErrorCode.ERR_AutoPropertyMustOverrideSet, Location);
+ }
}
}
@@ -791,6 +797,9 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions,
}
else if (!hasGetAccessor && IsAutoProperty)
{
+ // The only forms of auto-property that are disallowed are { set; } and { init; }.
+ // Other forms of auto- or manually-implemented accessors are allowed
+ // including equivalent field cases such as { set { field = value; } }.
diagnostics.Add(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, _setMethod!.GetFirstLocation());
}
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
index c070c270fb61b..3e3c96569ce32 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf
@@ -12160,8 +12160,8 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference
- Only auto-implemented properties can have initializers.
- Jenom automaticky implementované vlastnosti můžou mít inicializátory.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Jenom automaticky implementované vlastnosti můžou mít inicializátory.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
index 0078527e65793..e0c3e7f6cdf2f 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf
@@ -12160,8 +12160,8 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett
- Only auto-implemented properties can have initializers.
- Nur automatisch implementierte Eigenschaften können Initialisierer aufweisen.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Nur automatisch implementierte Eigenschaften können Initialisierer aufweisen.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
index fe8b9998c951b..d14b5b75cece9 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf
@@ -12160,8 +12160,8 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe
- Only auto-implemented properties can have initializers.
- Solo las propiedades implementadas automáticamente pueden tener inicializadores.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Solo las propiedades implementadas automáticamente pueden tener inicializadores.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
index 0d55d57e5c894..2979a2f6128f6 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf
@@ -12160,8 +12160,8 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé
- Only auto-implemented properties can have initializers.
- Seules les propriétés implémentées automatiquement peuvent avoir des initialiseurs.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Seules les propriétés implémentées automatiquement peuvent avoir des initialiseurs.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
index e0d85c231f8e7..81fe93c7f4b76 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf
@@ -12160,8 +12160,8 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr
- Only auto-implemented properties can have initializers.
- Solo le proprietà implementate automaticamente possono avere inizializzatori.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Solo le proprietà implementate automaticamente possono avere inizializzatori.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
index 8ecfc63478d6c..056d3ac1f3559 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf
@@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
- Only auto-implemented properties can have initializers.
- 自動実装プロパティのみが初期化子を持つことができます。
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ 自動実装プロパティのみが初期化子を持つことができます。
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
index ae92ac759f259..709344ded6891 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf
@@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
- Only auto-implemented properties can have initializers.
- 자동 구현 속성만 이니셜라이저를 사용할 수 있습니다.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ 자동 구현 속성만 이니셜라이저를 사용할 수 있습니다.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
index 0f18b1d6b3ad5..0daffc73f4fd7 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf
@@ -12160,8 +12160,8 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w
- Only auto-implemented properties can have initializers.
- Tylko właściwości zaimplementowane automatycznie mogą mieć inicjatory.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Tylko właściwości zaimplementowane automatycznie mogą mieć inicjatory.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
index 5b182e2af6227..41e99905d18d0 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf
@@ -12160,8 +12160,8 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl
- Only auto-implemented properties can have initializers.
- Somente propriedades implementadas automaticamente podem ter inicializadores.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Somente propriedades implementadas automaticamente podem ter inicializadores.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
index f660d123cf6ef..03bcaab1d8307 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf
@@ -12161,8 +12161,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
- Only auto-implemented properties can have initializers.
- Инициализаторы могут иметь только автоматически реализованные свойства.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Инициализаторы могут иметь только автоматически реализованные свойства.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
index b7ccaf7545791..baadb43153104 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf
@@ -12160,8 +12160,8 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T
- Only auto-implemented properties can have initializers.
- Yalnızca otomatik uygulanan özelliklerin başlatıcıları olabilir.
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ Yalnızca otomatik uygulanan özelliklerin başlatıcıları olabilir.
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
index ac1f52a36f53b..2423d50dbab33 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf
@@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
- Only auto-implemented properties can have initializers.
- 只有自动实现的属性才能具有初始值设定项。
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ 只有自动实现的属性才能具有初始值设定项。
diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
index b3f556c0b0476..b8e18bf92daa3 100644
--- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
+++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf
@@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
- Only auto-implemented properties can have initializers.
- 只有自動實作的屬性可以有初始設定式。
+ Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ 只有自動實作的屬性可以有初始設定式。
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs
index 23cf311d431af..70450c3879552 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs
@@ -1761,7 +1761,7 @@ public void RefAssignStaticProperty()
class Program
{
static int field = 0;
- static ref int P { get { return ref field; } }
+ static ref int P { get { return ref @field; } }
static void M()
{
@@ -1789,7 +1789,7 @@ public void RefAssignClassInstanceProperty()
class Program
{
int field = 0;
- ref int P { get { return ref field; } }
+ ref int P { get { return ref @field; } }
void M()
{
diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
index c9ead91a833c5..82cd6e6b97d32 100644
--- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
+++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs
@@ -1258,7 +1258,7 @@ class Program
{
int field = 0;
- ref int P { get { return ref field; } }
+ ref int P { get { return ref @field; } }
ref int this[int i] { get { return ref field; } }
@@ -1455,7 +1455,7 @@ class Program
{
int field = 0;
- ref int P { get { return ref field; } }
+ ref int P { get { return ref @field; } }
ref int this[int i] { get { return ref field; } }
diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
index 0fc3ee90cf8ac..92745d17f1cd2 100644
--- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
+++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs
@@ -285,9 +285,6 @@ class C
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (3,12): error CS8050: Only auto-implemented properties can have initializers.
- // object P { get => field; } = field;
- Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12),
// (3,34): error CS0103: The name 'field' does not exist in the current context
// object P { get => field; } = field;
Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 34));
@@ -304,7 +301,7 @@ class C
""";
var comp = CreateCompilation(source);
comp.VerifyEmitDiagnostics(
- // (3,12): error CS8050: Only auto-implemented properties can have initializers.
+ // (3,12): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
// object P { set { } } = field;
Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12),
// (3,28): error CS0103: The name 'field' does not exist in the current context
@@ -1031,6 +1028,2213 @@ static void ReportField(FieldInfo field)
"""));
}
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_01A([CombinatorialValues("class", "struct", "ref struct", "interface")] string typeKind)
+ {
+ string source = $$"""
+ using System;
+ {{typeKind}} C
+ {
+ public static int P1 { get; } = 1;
+ public static int P2 { get => field; } = 2;
+ public static int P3 { get => field; set; } = 3;
+ public static int P4 { get => field; set { } } = 4;
+ public static int P5 { get => 0; set; } = 5;
+ public static int P6 { get; set; } = 6;
+ public static int P7 { get; set { } } = 7;
+ public static int P8 { set { field = value; } } = 8;
+ public static int P9 { get { return field; } set { field = value; } } = 9;
+ }
+ class Program
+ {
+ static void Main()
+ {
+ Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(
+ source,
+ targetFramework: TargetFramework.Net80,
+ verify: Verification.Skipped,
+ expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)"));
+ verifier.VerifyDiagnostics();
+ verifier.VerifyIL("C..cctor", """
+ {
+ // Code size 56 (0x38)
+ .maxstack 1
+ IL_0000: ldc.i4.1
+ IL_0001: stsfld "int C.k__BackingField"
+ IL_0006: ldc.i4.2
+ IL_0007: stsfld "int C.k__BackingField"
+ IL_000c: ldc.i4.3
+ IL_000d: stsfld "int C.k__BackingField"
+ IL_0012: ldc.i4.4
+ IL_0013: stsfld "int C.k__BackingField"
+ IL_0018: ldc.i4.5
+ IL_0019: stsfld "int C.k__BackingField"
+ IL_001e: ldc.i4.6
+ IL_001f: stsfld "int C.k__BackingField"
+ IL_0024: ldc.i4.7
+ IL_0025: stsfld "int C.k__BackingField"
+ IL_002a: ldc.i4.8
+ IL_002b: stsfld "int C.k__BackingField"
+ IL_0030: ldc.i4.s 9
+ IL_0032: stsfld "int C.k__BackingField"
+ IL_0037: ret
+ }
+ """);
+ }
+
+ [Fact]
+ public void Initializer_01B()
+ {
+ string source = """
+ using System;
+ interface C
+ {
+ public static int P1 { get; } = 1;
+ public static int P2 { get => field; } = 2;
+ public static int P3 { get => field; set; } = 3;
+ public static int P4 { get => field; set { } } = 4;
+ public static int P5 { get => 0; set; } = 5;
+ public static int P6 { get; set; } = 6;
+ public static int P7 { get; set { } } = 7;
+ public static int P8 { set { field = value; } } = 8;
+ public static int P9 { get { return field; } set { field = value; } } = 9;
+ }
+ class Program
+ {
+ static void Main()
+ {
+ Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(
+ source,
+ targetFramework: TargetFramework.Net80,
+ verify: Verification.Skipped,
+ expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)"));
+ verifier.VerifyDiagnostics();
+ verifier.VerifyIL("C..cctor", """
+ {
+ // Code size 56 (0x38)
+ .maxstack 1
+ IL_0000: ldc.i4.1
+ IL_0001: stsfld "int C.k__BackingField"
+ IL_0006: ldc.i4.2
+ IL_0007: stsfld "int C.k__BackingField"
+ IL_000c: ldc.i4.3
+ IL_000d: stsfld "int C.k__BackingField"
+ IL_0012: ldc.i4.4
+ IL_0013: stsfld "int C.k__BackingField"
+ IL_0018: ldc.i4.5
+ IL_0019: stsfld "int C.k__BackingField"
+ IL_001e: ldc.i4.6
+ IL_001f: stsfld "int C.k__BackingField"
+ IL_0024: ldc.i4.7
+ IL_0025: stsfld "int C.k__BackingField"
+ IL_002a: ldc.i4.8
+ IL_002b: stsfld "int C.k__BackingField"
+ IL_0030: ldc.i4.s 9
+ IL_0032: stsfld "int C.k__BackingField"
+ IL_0037: ret
+ }
+ """);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_02A(bool useInit)
+ {
+ string setter = useInit ? "init" : "set";
+ string source = $$"""
+ using System;
+ class C
+ {
+ public int P1 { get; } = 1;
+ public int P2 { get => field; } = 2;
+ public int P3 { get => field; {{setter}}; } = 3;
+ public int P4 { get => field; {{setter}} { } } = 4;
+ public int P5 { get => 0; {{setter}}; } = 5;
+ public int P6 { get; {{setter}}; } = 6;
+ public int P7 { get; {{setter}} { } } = 7;
+ public int P8 { {{setter}} { field = value; } } = 8;
+ public int P9 { get { return field; } {{setter}} { field = value; } } = 9;
+ }
+ class Program
+ {
+ static void Main()
+ {
+ var c = new C();
+ Console.WriteLine((c.P1, c.P2, c.P3, c.P4, c.P5, c.P6, c.P7, c.P9));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(
+ source,
+ targetFramework: TargetFramework.Net80,
+ verify: Verification.Skipped,
+ expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)"));
+ verifier.VerifyDiagnostics();
+ verifier.VerifyIL("C..ctor", """
+ {
+ // Code size 71 (0x47)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.1
+ IL_0002: stfld "int C.k__BackingField"
+ IL_0007: ldarg.0
+ IL_0008: ldc.i4.2
+ IL_0009: stfld "int C.k__BackingField"
+ IL_000e: ldarg.0
+ IL_000f: ldc.i4.3
+ IL_0010: stfld "int C.k__BackingField"
+ IL_0015: ldarg.0
+ IL_0016: ldc.i4.4
+ IL_0017: stfld "int C.k__BackingField"
+ IL_001c: ldarg.0
+ IL_001d: ldc.i4.5
+ IL_001e: stfld "int C.k__BackingField"
+ IL_0023: ldarg.0
+ IL_0024: ldc.i4.6
+ IL_0025: stfld "int C.k__BackingField"
+ IL_002a: ldarg.0
+ IL_002b: ldc.i4.7
+ IL_002c: stfld "int C.k__BackingField"
+ IL_0031: ldarg.0
+ IL_0032: ldc.i4.8
+ IL_0033: stfld "int C.k__BackingField"
+ IL_0038: ldarg.0
+ IL_0039: ldc.i4.s 9
+ IL_003b: stfld "int C.k__BackingField"
+ IL_0040: ldarg.0
+ IL_0041: call "object..ctor()"
+ IL_0046: ret
+ }
+ """);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_02B(bool useRefStruct, bool useInit)
+ {
+ string setter = useInit ? "init" : "set";
+ string typeKind = useRefStruct ? "ref struct" : "struct";
+ string source = $$"""
+ using System;
+ {{typeKind}} C
+ {
+ public int P1 { get; } = 1;
+ public int P2 { get => field; } = 2;
+ public int P3 { get => field; {{setter}}; } = 3;
+ public int P4 { get => field; {{setter}} { } } = 4;
+ public int P5 { get => 0; {{setter}}; } = 5;
+ public int P6 { get; {{setter}}; } = 6;
+ public int P7 { get; {{setter}} { } } = 7;
+ public int P8 { {{setter}} { field = value; } } = 8;
+ public int P9 { get { return field; } {{setter}} { field = value; } } = 9;
+ public C() { }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ var c = new C();
+ Console.WriteLine((c.P1, c.P2, c.P3, c.P4, c.P5, c.P6, c.P7, c.P9));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(
+ source,
+ targetFramework: TargetFramework.Net80,
+ verify: Verification.Skipped,
+ expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)"));
+ verifier.VerifyDiagnostics();
+ verifier.VerifyIL("C..ctor", """
+ {
+ // Code size 65 (0x41)
+ .maxstack 2
+ IL_0000: ldarg.0
+ IL_0001: ldc.i4.1
+ IL_0002: stfld "int C.k__BackingField"
+ IL_0007: ldarg.0
+ IL_0008: ldc.i4.2
+ IL_0009: stfld "int C.k__BackingField"
+ IL_000e: ldarg.0
+ IL_000f: ldc.i4.3
+ IL_0010: stfld "int C.k__BackingField"
+ IL_0015: ldarg.0
+ IL_0016: ldc.i4.4
+ IL_0017: stfld "int C.k__BackingField"
+ IL_001c: ldarg.0
+ IL_001d: ldc.i4.5
+ IL_001e: stfld "int C.k__BackingField"
+ IL_0023: ldarg.0
+ IL_0024: ldc.i4.6
+ IL_0025: stfld "int C.k__BackingField"
+ IL_002a: ldarg.0
+ IL_002b: ldc.i4.7
+ IL_002c: stfld "int C.k__BackingField"
+ IL_0031: ldarg.0
+ IL_0032: ldc.i4.8
+ IL_0033: stfld "int C.k__BackingField"
+ IL_0038: ldarg.0
+ IL_0039: ldc.i4.s 9
+ IL_003b: stfld "int C.k__BackingField"
+ IL_0040: ret
+ }
+ """);
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_02C(bool useInit)
+ {
+ string setter = useInit ? "init" : "set";
+ string source = $$"""
+ using System;
+ interface C
+ {
+ public int P1 { get; } = 1;
+ public int P2 { get => field; } = 2;
+ public int P3 { get => field; {{setter}}; } = 3;
+ public int P4 { get => field; {{setter}} { } } = 4;
+ public int P5 { get => 0; {{setter}}; } = 5;
+ public int P6 { get; {{setter}}; } = 6;
+ public int P7 { get; {{setter}} { } } = 7;
+ public int P8 { {{setter}} { field = value; } } = 8;
+ public int P9 { get { return field; } {{setter}} { field = value; } } = 9;
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (4,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P1 { get; } = 1;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P1").WithLocation(4, 16),
+ // (5,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P2 { get => field; } = 2;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P2").WithLocation(5, 16),
+ // (6,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P3 { get => field; set; } = 3;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P3").WithLocation(6, 16),
+ // (6,35): error CS0501: 'C.P3.set' must declare a body because it is not marked abstract, extern, or partial
+ // public int P3 { get => field; set; } = 3;
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P3.{setter}").WithLocation(6, 35),
+ // (7,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P4 { get => field; set { } } = 4;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P4").WithLocation(7, 16),
+ // (8,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P5 { get => 0; set; } = 5;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P5").WithLocation(8, 16),
+ // (8,31): error CS0501: 'C.P5.set' must declare a body because it is not marked abstract, extern, or partial
+ // public int P5 { get => 0; set; } = 5;
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P5.{setter}").WithLocation(8, 31),
+ // (9,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P6 { get; set; } = 6;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P6").WithLocation(9, 16),
+ // (10,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P7 { get; set { } } = 7;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P7").WithLocation(10, 16),
+ // (10,21): error CS0501: 'C.P7.get' must declare a body because it is not marked abstract, extern, or partial
+ // public int P7 { get; set { } } = 7;
+ Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("C.P7.get").WithLocation(10, 21),
+ // (11,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P8 { set { field = value; } } = 8;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P8").WithLocation(11, 16),
+ // (12,16): error CS8053: Instance properties in interfaces cannot have initializers.
+ // public int P9 { get { return field; } set { field = value; } } = 9;
+ Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P9").WithLocation(12, 16));
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_02D(bool useRefStruct, bool useInit)
+ {
+ string setter = useInit ? "init" : "set";
+ string typeKind = useRefStruct ? "ref struct" : " struct";
+ string source = $$"""
+ {{typeKind}} S1
+ {
+ public int P1 { get; } = 1;
+ }
+ {{typeKind}} S2
+ {
+ public int P2 { get => field; } = 2;
+ }
+ {{typeKind}} S3
+ {
+ public int P3 { get => field; {{setter}}; } = 3;
+ }
+ {{typeKind}} S6
+ {
+ public int P6 { get; {{setter}}; } = 6;
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (1,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
+ // struct S1
+ Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S1").WithLocation(1, 12),
+ // (5,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
+ // struct S2
+ Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S2").WithLocation(5, 12),
+ // (9,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
+ // struct S3
+ Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S3").WithLocation(9, 12),
+ // (13,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor.
+ // struct S6
+ Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S6").WithLocation(13, 12));
+ }
+
+ [Fact]
+ public void Initializer_03()
+ {
+ string source = """
+ class C
+ {
+ public static int PA { get => 0; } = 10;
+ public static int PB { get => 0; set { } } = 11;
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (3,23): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ // public static int PA { get => 0; } = 10;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PA").WithLocation(3, 23),
+ // (4,23): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ // public static int PB { get => 0; set { } } = 11;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PB").WithLocation(4, 23));
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void Initializer_04(bool useInit)
+ {
+ string setter = useInit ? "init" : "set";
+ string source = $$"""
+ class C
+ {
+ public int PA { get => 0; } = 10;
+ public int PB { get => 0; {{setter}} { } } = 11;
+ }
+ """;
+ var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80);
+ comp.VerifyEmitDiagnostics(
+ // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ // public int PA { get => 0; } = 10;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PA").WithLocation(3, 16),
+ // (4,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers.
+ // public int PB { get => 0; set { } } = 11;
+ Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PB").WithLocation(4, 16));
+ }
+
+ [Theory]
+ [CombinatorialData]
+ public void ConstructorAssignment_01([CombinatorialValues("class", "struct", "ref struct", "interface")] string typeKind)
+ {
+ string source = $$"""
+ using System;
+ {{typeKind}} C
+ {
+ public static int P1 { get; }
+ public static int P2 { get => field; }
+ public static int P3 { get => field; set; }
+ public static int P4 { get => field; set { } }
+ public static int P5 { get => 0; set; }
+ public static int P6 { get; set; }
+ public static int P7 { get; set { } }
+ public static int P8 { set { field = value; } }
+ public static int P9 { get { return field; } set { field = value; } }
+ static C()
+ {
+ P1 = 1;
+ P2 = 2;
+ P3 = 3;
+ P4 = 4;
+ P5 = 5;
+ P6 = 6;
+ P7 = 7;
+ P8 = 8;
+ P9 = 9;
+ }
+ }
+ class Program
+ {
+ static void Main()
+ {
+ Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9));
+ }
+ }
+ """;
+ var verifier = CompileAndVerify(
+ source,
+ targetFramework: TargetFramework.Net80,
+ verify: Verification.Skipped,
+ expectedOutput: IncludeExpectedOutput("(1, 2, 3, 0, 0, 6, 0, 9)"));
+ verifier.VerifyDiagnostics();
+ verifier.VerifyIL("C..cctor", """
+ {
+ // Code size 56 (0x38)
+ .maxstack 1
+ IL_0000: ldc.i4.1
+ IL_0001: stsfld "int C.k__BackingField"
+ IL_0006: ldc.i4.2
+ IL_0007: stsfld "int C.