diff --git a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs index 4dd854bff2926..ac65a9e833852 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.Preview; 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..41b95d6f61dd5 --- /dev/null +++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs @@ -0,0 +1,1399 @@ +// 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 TestNotInCSharp13() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + string P + { + get + { + return s.Trim(); + } + } + } + """, new(parseOptions: CSharp13)); + } + + [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: Preview); + } + + [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: Preview); + } + + [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: Preview); + } + + [Fact] + public async Task TestGetterWithMultipleStatements_Field() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + int P + { + get + { + ; + return i; + } + } + } + """, + """ + class Class + { + int P + { + get + { + ; + return field; + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + int P + { + get + { + return i; + } + + set + { + ; + i = value; + } + } + } + """, + """ + class Class + { + int P + { + get; + + set + { + ; + field = value; + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field2() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + int P + { + get => i; + + set + { + ; + i = value; + } + } + } + """, + """ + class Class + { + int P + { + get; + + set + { + ; + field = value; + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSimpleFieldInExpressionBody() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + string P => s.Trim(); + } + """, + """ + class Class + { + string P => field.Trim(); + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestMultipleFields_NoClearChoice() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + int [|x|], y; + + int Total => x + y; + } + """, new(parseOptions: Preview)); + } + + [Fact] + public async Task TestMultipleFields_NoClearChoice2() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + int [|x|], y; + + int Total + { + get => x + y; + set + { + x = value; + y = value; + } + } + } + """, new(parseOptions: Preview)); + } + + [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; + } + } + """, parseOptions: Preview); + } + + [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; + } + """, parseOptions: Preview); + } + + [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; + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNotWhenAlreadyUsingField() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + string P + { + get + { + var v = field.Trim(); + return s.Trim(); + } + } + } + """, new(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(); + } + } + } + """, new(parseOptions: Preview)); + } + + [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(); + } + } + } + """, new(parseOptions: Preview)); + } + + [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)); + } + } + """, new(parseOptions: Preview)); + } + + [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)); + } + } + """, new(parseOptions: Preview)); + } + + [Fact] + public async Task TestNotWhenUsingNameof5() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|string s = nameof(s)|]; + + string P => s; + } + """, new(parseOptions: CSharp13)); + } + + [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) + { + } + } + """, parseOptions: Preview); + } + + [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) + { + } + } + """, new(parseOptions: Preview)); + } + + [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(); + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNotWithRefUseOutside() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + string P + { + get + { + return s.Trim(); + } + } + + void M() + { + ref string s1 = ref s; + } + } + """, new(parseOptions: Preview)); + } + + [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; + } + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNotWithAddressOfOutside() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|int s|]; + + int P + { + get + { + unsafe + { + return s; + } + } + } + + unsafe void M() + { + int* p = &s; + } + } + """, new(parseOptions: Preview)); + } + + [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; + } + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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) { } + } + """, parseOptions: Preview); + } + + [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() { } + } + """, parseOptions: Preview); + } + + [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; } + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + """, parseOptions: Preview); + } + + [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(); + } + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere1() + { + await TestInRegularAndScriptAsync( + """ + 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 = ""; + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere2() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + public string P => s ??= ""; + + void M() + { + s = ""; + } + } + """, + """ + class Class + { + public string P { get => field ??= ""; private set; } + + void M() + { + P = ""; + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere3() + { + await TestInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + public string P + { + get => s ??= ""; + } + + void M() + { + s = ""; + } + } + """, + """ + class Class + { + public string P + { + get => field ??= ""; private set; + } + + void M() + { + P = ""; + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere4() + { + await TestInRegularAndScriptAsync( + """ + 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 = ""; + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNonTrivialGetterWithExternalRead1() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + public int I => i / 2; + + void M() + { + Console.WriteLine(i); + } + } + """, new(parseOptions: Preview)); + } + + [Fact] + public async Task TestNonTrivialGetterWithExternalRead2() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + public int I => i / 2; + + void M() + { + Console.WriteLine(this.i); + } + } + """, new(parseOptions: Preview)); + } + + [Fact] + public async Task TestNonTrivialSetterWithExternalWrite1() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + public int I { get => i; set => value = i / 2; } + + void M() + { + i = 1; + } + } + """, new(parseOptions: Preview)); + } + + [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; + } + } + """, new(parseOptions: Preview)); + } + + [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; } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNonTrivialGetterWithExternalReadWrite1() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|int i|]; + + public int I => i / 2; + + void M() + { + Console.WriteLine(this.i++); + } + } + """, new(parseOptions: Preview)); + } + + [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++); + } + } + """, new(parseOptions: Preview)); + } + + [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); + } + } + """, parseOptions: Preview); + } + + [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; + } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestFormatString() + { + await TestInRegularAndScriptAsync( + """ + class C + { + [|private string prop;|] + public string Prop => $"{prop:prop}"; + } + """, + """ + class C + { + public string Prop => $"{field:prop}"; + } + """, parseOptions: Preview); + } + + [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 = "..."; } + } + """, parseOptions: Preview); + } + + [Fact] + public async Task TestNotWithNameofInAttribute() + { + await TestMissingInRegularAndScriptAsync( + """ + class C + { + [|private string prop;|] + [ThisIsMyBackingField(nameof(prop))] + public string Prop { get => prop; set => prop = value; } + } + """, new(parseOptions: Preview)); + } +} 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 203f9d8ac17b4..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); } }); @@ -176,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()) @@ -197,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); + } + } + } - AddFieldWrite(fieldWrites, 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); + + 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; @@ -214,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; @@ -238,7 +371,7 @@ private void AnalyzePropertyDeclaration( if (property.GetMethod == null) return; - if (!CanExplicitInterfaceImplementationsBeFixed() && property.ExplicitInterfaceImplementations.Length != 0) + 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; + + // Property and field have to agree on type. + if (!property.Type.Equals(getterField.Type)) + return false; - var fieldReference = getterField.DeclaringSyntaxReferences[0]; - if (fieldReference.GetSyntax(cancellationToken) is not TVariableDeclarator { Parent.Parent: TFieldDeclaration fieldDeclaration } variableDeclarator) + 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; + + var syntaxFacts = this.SyntaxFacts; - private IFieldSymbol? GetGetterField(SemanticModel semanticModel, IMethodSymbol getMethod, CancellationToken cancellationToken) - => CheckFieldAccessExpression(semanticModel, GetGetterExpression(getMethod, cancellationToken), cancellationToken); + // 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; - private static IFieldSymbol? CheckFieldAccessExpression(SemanticModel semanticModel, TExpression? expression, CancellationToken cancellationToken) - { - if (expression == 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; } - Process(result, context); + // 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; + } + + // 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/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index 9d4d3a1bb41a7..d1cc8b15db4f5 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -388,6 +388,11 @@ private static AccessorKind GetIndexerAccessorKind(BoundIndexerAccess indexerAcc return AccessorKind.Get; } + return GetAccessorKind(valueKind); + } + + private static AccessorKind GetAccessorKind(BindValueKind valueKind) + { var coreValueKind = valueKind & ValueKindSignificantBitsMask; return coreValueKind switch { @@ -523,6 +528,28 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind Debug.Assert(valueKind is (BindValueKind.Assignable or BindValueKind.RefOrOut or BindValueKind.RefAssignable) || diagnostics.DiagnosticBag is null || diagnostics.HasAnyResolvedErrors()); return expr; + case BoundKind.PropertyAccess: + if (!InAttributeArgument) + { + // If the property has a synthesized backing field, record the accessor kind of the property + // access for determining whether the property access can use the backing field directly. + var propertyAccess = (BoundPropertyAccess)expr; + if (HasSynthesizedBackingField(propertyAccess.PropertySymbol, out _)) + { + expr = propertyAccess.Update( + propertyAccess.ReceiverOpt, + propertyAccess.InitialBindingReceiverIsSubjectToCloning, + propertyAccess.PropertySymbol, + autoPropertyAccessorKind: GetAccessorKind(valueKind), + propertyAccess.ResultKind, + propertyAccess.Type); + } + } +#if DEBUG + expr.WasPropertyBackingFieldAccessChecked = true; +#endif + break; + case BoundKind.IndexerAccess: expr = BindIndexerDefaultArgumentsAndParamsCollection((BoundIndexerAccess)expr, valueKind, diagnostics); break; @@ -1707,7 +1734,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, AccessorKind.Set) && !isAllowedDespiteReadonly(receiver)) { Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs index 122cbd5940eba..601bcdd9c92b2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Attributes.cs @@ -578,7 +578,7 @@ private BoundAssignmentOperator BindNamedAttributeArgument(AttributeArgumentSynt var propertySymbol = namedArgumentNameSymbol as PropertySymbol; if (propertySymbol is object) { - lvalue = new BoundPropertyAccess(nameSyntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, resultKind, namedArgumentType); + lvalue = new BoundPropertyAccess(nameSyntax, receiverOpt: null, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, namedArgumentType); } else { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 5c2fc193e7f42..136cf4a4ece19 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -598,6 +598,8 @@ BoundExpression bindExpressionInternal(ExpressionSyntax node, BindingDiagnosticB return BindThis((ThisExpressionSyntax)node, diagnostics); case SyntaxKind.BaseExpression: return BindBase((BaseExpressionSyntax)node, diagnostics); + case SyntaxKind.FieldExpression: + return BindFieldExpression((FieldExpressionSyntax)node, diagnostics); case SyntaxKind.InvocationExpression: return BindInvocationExpression((InvocationExpressionSyntax)node, diagnostics); case SyntaxKind.ArrayInitializerExpression: @@ -1433,6 +1435,56 @@ private BoundExpression BindSizeOf(SizeOfExpressionSyntax node, BindingDiagnosti this.GetSpecialType(SpecialType.System_Int32, diagnostics, node), hasErrors); } + private BoundExpression BindFieldExpression(FieldExpressionSyntax node, BindingDiagnosticBag diagnostics) + { + Debug.Assert(ContainingType is { }); + SynthesizedBackingFieldSymbolBase? field = null; + + if (hasOtherFieldSymbolInScope()) + { + diagnostics.Add(ErrorCode.WRN_FieldIsAmbiguous, node, Compilation.LanguageVersion.ToDisplayString()); + } + + switch (ContainingMember()) + { + case SynthesizedBackingFieldSymbolBase backingField: + field = backingField; + break; + case MethodSymbol { AssociatedSymbol: SourcePropertySymbol property }: + field = property.BackingField; + break; + default: + { + Debug.Assert((this.Flags & BinderFlags.InContextualAttributeBinder) != 0); + var contextualAttributeBinder = TryGetContextualAttributeBinder(this); + if (contextualAttributeBinder is { AttributeTarget: MethodSymbol { AssociatedSymbol: SourcePropertySymbol property } }) + { + field = property.BackingField; + } + break; + } + } + + if (field is null) + { + throw ExceptionUtilities.UnexpectedValue(ContainingMember()); + } + + 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. internal static bool CheckManagedAddr(CSharpCompilation compilation, TypeSymbol type, Location location, BindingDiagnosticBag diagnostics, bool errorForManaged = false) { @@ -1549,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); @@ -1733,24 +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) + private void LookupIdentifier(LookupResult lookupResult, SimpleNameSyntax node, bool invoked, ref CompoundUseSiteInfo useSiteInfo) { - string name = identifier.Text; - if (name == "field" && - ContainingMember() is MethodSymbol { MethodKind: MethodKind.PropertyGet or MethodKind.PropertySet, AssociatedSymbol: PropertySymbol { IsIndexer: false } }) - { - 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) @@ -1764,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); } /// @@ -8588,7 +8626,7 @@ private BoundExpression BindPropertyAccess( WarnOnAccessOfOffDefault(node, receiver, diagnostics); } - return new BoundPropertyAccess(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, propertySymbol), propertySymbol, lookupResult, propertySymbol.Type, hasErrors: (hasErrors || hasError)); + return new BoundPropertyAccess(node, receiver, initialBindingReceiverIsSubjectToCloning: ReceiverIsSubjectToCloning(receiver, propertySymbol), propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, lookupResult, propertySymbol.Type, hasErrors: (hasErrors || hasError)); } #nullable disable diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs index 8f75c4fcfce84..6940809a886e1 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Patterns.cs @@ -363,7 +363,7 @@ private bool BindLengthAndIndexerForListPattern(SyntaxNode node, TypeSymbol inpu hasErrors |= !TryGetSpecialTypeMember(Compilation, SpecialMember.System_Array__Length, node, diagnostics, out PropertySymbol lengthProperty); if (lengthProperty is not null) { - lengthAccess = new BoundPropertyAccess(node, receiverPlaceholder, initialBindingReceiverIsSubjectToCloning: ThreeState.False, lengthProperty, LookupResultKind.Viable, lengthProperty.Type) { WasCompilerGenerated = true }; + lengthAccess = new BoundPropertyAccess(node, receiverPlaceholder, initialBindingReceiverIsSubjectToCloning: ThreeState.False, lengthProperty, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, lengthProperty.Type) { WasCompilerGenerated = true }; } else { diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 607f7b90e2f1b..8ed826f6b9d83 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Immutable; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -1749,26 +1750,43 @@ private DiagnosticInfo GetBadEventUsageDiagnosticInfo(EventSymbol eventSymbol) new CSDiagnosticInfo(ErrorCode.ERR_BadEventUsageNoField, leastOverridden); } +#nullable enable internal static bool AccessingAutoPropertyFromConstructor(BoundPropertyAccess propertyAccess, Symbol fromMember) { - return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember); + return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember, propertyAccess.AutoPropertyAccessorKind); } - private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember) + private static bool AccessingAutoPropertyFromConstructor(BoundExpression? receiver, PropertySymbol propertySymbol, Symbol fromMember, AccessorKind accessorKind) + { + if (!HasSynthesizedBackingField(propertySymbol, out var sourceProperty)) + { + return false; + } + + var propertyIsStatic = propertySymbol.IsStatic; + + return sourceProperty is { } && + sourceProperty.CanUseBackingFieldDirectlyInConstructor(useAsLvalue: accessorKind != AccessorKind.Get) && + TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) && + IsConstructorOrField(fromMember, isStatic: propertyIsStatic) && + (propertyIsStatic || receiver?.Kind == BoundKind.ThisReference); + } + + private static bool HasSynthesizedBackingField(PropertySymbol propertySymbol, [NotNullWhen(true)] out SourcePropertySymbolBase? sourcePropertyDefinition) { if (!propertySymbol.IsDefinition && propertySymbol.ContainingType.Equals(propertySymbol.ContainingType.OriginalDefinition, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { propertySymbol = propertySymbol.OriginalDefinition; } - var sourceProperty = propertySymbol as SourcePropertySymbolBase; - var propertyIsStatic = propertySymbol.IsStatic; + if (propertySymbol is SourcePropertySymbolBase { BackingField: { } } sourceProperty) + { + sourcePropertyDefinition = sourceProperty; + return true; + } - return (object)sourceProperty != null && - sourceProperty.IsAutoPropertyWithGetAccessor && - TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) && - IsConstructorOrField(fromMember, isStatic: propertyIsStatic) && - (propertyIsStatic || receiver.Kind == BoundKind.ThisReference); + sourcePropertyDefinition = null; + return false; } private static bool IsConstructorOrField(Symbol member, bool isStatic) @@ -1778,6 +1796,7 @@ private static bool IsConstructorOrField(Symbol member, bool isStatic) MethodKind.Constructor) || (member as FieldSymbol)?.IsStatic == isStatic; } +#nullable disable private TypeSymbol GetAccessThroughType(BoundExpression receiver) { diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs index 4e521c6d83c8d..a121970dfa9d9 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNode.cs @@ -55,6 +55,11 @@ private enum BoundNodeAttributes : short ParamsArrayOrCollection = 1 << 9, + /// + /// Set after checking if the property access should use the backing field directly. + /// + WasPropertyBackingFieldAccessChecked = 1 << 10, + AttributesPreservedInClone = HasErrors | CompilerGenerated | IsSuppressed | WasConverted | ParamsArrayOrCollection, } @@ -325,6 +330,22 @@ protected set } } } + + public bool WasPropertyBackingFieldAccessChecked + { + get + { + return (_attributes & BoundNodeAttributes.WasPropertyBackingFieldAccessChecked) != 0; + } + set + { + Debug.Assert((_attributes & BoundNodeAttributes.WasPropertyBackingFieldAccessChecked) == 0, "should not be set twice or reset"); + if (value) + { + _attributes |= BoundNodeAttributes.WasPropertyBackingFieldAccessChecked; + } + } + } #endif public bool IsParamsArrayOrCollection diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 7b42e948312aa..28e5f8a9cc8c0 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -2194,6 +2194,8 @@ + + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index 8a0d1e3b5ea9e..d98f42f18fa32 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4888,7 +4888,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. @@ -6865,8 +6865,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. @@ -7974,6 +7977,9 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Both partial property declarations must be required or neither may be required + + A partial property cannot have an initializer on both the definition and implementation. + allows ref struct constraint diff --git a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index b67875f9192bb..80d3e83214591 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1833,6 +1833,7 @@ syntaxNode is ConstructorDeclarationSyntax constructorSyntax && #if DEBUG Debug.Assert(IsEmptyRewritePossible(methodBody)); + Debug.Assert(WasPropertyBackingFieldAccessChecked.FindUncheckedAccess(methodBody) is null); #endif RefSafetyAnalysis.Analyze(compilation, method, methodBody, diagnostics); @@ -1884,8 +1885,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); } @@ -2227,6 +2227,94 @@ public static bool FoundInUnboundLambda(BoundNode methodBody, IdentifierNameSynt return base.Visit(node); } } + + private sealed class WasPropertyBackingFieldAccessChecked : BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator + { + public static BoundPropertyAccess? FindUncheckedAccess(BoundNode node) + { + var walker = new WasPropertyBackingFieldAccessChecked(); + walker.Visit(node); + return walker._found; + } + + private BoundPropertyAccess? _found; + private bool _suppressChecking; + + private WasPropertyBackingFieldAccessChecked() + { + } + + public override BoundNode? Visit(BoundNode? node) + { + if (_found is { }) + { + return null; + } + + return base.Visit(node); + } + + public override BoundNode? VisitPropertyAccess(BoundPropertyAccess node) + { + if (!_suppressChecking && + !node.WasPropertyBackingFieldAccessChecked) + { + _found = node; + } + + return base.VisitPropertyAccess(node); + } + + public override BoundNode? VisitRangeVariable(BoundRangeVariable node) + { + using (new ChangeSuppression(this, suppressChecking: true)) + { + return base.VisitRangeVariable(node); + } + } + + public override BoundNode? VisitAssignmentOperator(BoundAssignmentOperator node) + { + using (new ChangeSuppression(this, suppressChecking: false)) + { + return base.VisitAssignmentOperator(node); + } + } + + public override BoundNode? VisitNameOfOperator(BoundNameOfOperator node) + { + using (new ChangeSuppression(this, suppressChecking: true)) + { + return base.VisitNameOfOperator(node); + } + } + + public override BoundNode? VisitBadExpression(BoundBadExpression node) + { + using (new ChangeSuppression(this, suppressChecking: true)) + { + return base.VisitBadExpression(node); + } + } + + private struct ChangeSuppression : IDisposable + { + private readonly WasPropertyBackingFieldAccessChecked _walker; + private readonly bool _previousValue; + + internal ChangeSuppression(WasPropertyBackingFieldAccessChecked walker, bool suppressChecking) + { + _walker = walker; + _previousValue = walker._suppressChecking; + walker._suppressChecking = suppressChecking; + } + + public void Dispose() + { + _walker._suppressChecking = _previousValue; + } + } + } #endif #nullable disable diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index f9a267abd3075..2e1cb1212f272 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2336,13 +2336,14 @@ internal enum ErrorCode WRN_PartialPropertySignatureDifference = 9256, ERR_PartialPropertyRequiredDifference = 9257, - INF_IdentifierConflictWithContextualKeyword = 9258, + WRN_FieldIsAmbiguous = 9258, ERR_InlineArrayAttributeOnRecord = 9259, ERR_FeatureNotAvailableInVersion13 = 9260, ERR_CannotApplyOverloadResolutionPriorityToOverride = 9261, ERR_CannotApplyOverloadResolutionPriorityToMember = 9262, + ERR_PartialPropertyDuplicateInitializer = 9263, // Note: you will need to do the following after adding errors: // 1) Update ErrorFacts.IsBuildOnlyDiagnostic (src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs) diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs index 85737ac0e7568..78f82f4704736 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -556,7 +556,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; @@ -2450,11 +2450,12 @@ 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 or ErrorCode.ERR_CannotApplyOverloadResolutionPriorityToMember + or ErrorCode.ERR_PartialPropertyDuplicateInitializer => false, }; #pragma warning restore CS8524 // The switch expression does not handle some values of its input type (it is not exhaustive) involving an unnamed enum value. diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 9eaac42526bd5..bb3ee78c6dd05 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -1074,7 +1074,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; } } } @@ -9771,8 +9771,8 @@ private void VisitThisOrBaseReference(BoundExpression node) { // when binding initializers, we treat assignments to auto-properties or field-like events as direct assignments to the underlying field. // in order to track member state based on these initializers, we need to see the assignment in terms of the associated member - case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol autoProperty } } fieldAccess: - left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, autoProperty, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); + case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: PropertySymbol autoProperty }, Syntax: not FieldExpressionSyntax } fieldAccess: + left = new BoundPropertyAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, autoProperty, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, autoProperty.Type, fieldAccess.HasErrors); break; case BoundFieldAccess { ExpressionSymbol: FieldSymbol { AssociatedSymbol: EventSymbol @event } } fieldAccess: left = new BoundEventAccess(fieldAccess.Syntax, fieldAccess.ReceiverOpt, @event, isUsableAsField: true, LookupResultKind.Viable, @event.Type, fieldAccess.HasErrors); diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 4f80b423d41e8..3cbe110e05414 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -7334,7 +7334,7 @@ public BoundHoistedFieldAccess Update(FieldSymbol fieldSymbol, TypeSymbol type) internal sealed partial class BoundPropertyAccess : BoundExpression { - public BoundPropertyAccess(SyntaxNode syntax, BoundExpression? receiverOpt, ThreeState initialBindingReceiverIsSubjectToCloning, PropertySymbol propertySymbol, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) + public BoundPropertyAccess(SyntaxNode syntax, BoundExpression? receiverOpt, ThreeState initialBindingReceiverIsSubjectToCloning, PropertySymbol propertySymbol, AccessorKind autoPropertyAccessorKind, LookupResultKind resultKind, TypeSymbol type, bool hasErrors = false) : base(BoundKind.PropertyAccess, syntax, type, hasErrors || receiverOpt.HasErrors()) { @@ -7344,6 +7344,7 @@ public BoundPropertyAccess(SyntaxNode syntax, BoundExpression? receiverOpt, Thre this.ReceiverOpt = receiverOpt; this.InitialBindingReceiverIsSubjectToCloning = initialBindingReceiverIsSubjectToCloning; this.PropertySymbol = propertySymbol; + this.AutoPropertyAccessorKind = autoPropertyAccessorKind; this.ResultKind = resultKind; } @@ -7351,16 +7352,17 @@ public BoundPropertyAccess(SyntaxNode syntax, BoundExpression? receiverOpt, Thre public BoundExpression? ReceiverOpt { get; } public ThreeState InitialBindingReceiverIsSubjectToCloning { get; } public PropertySymbol PropertySymbol { get; } + public AccessorKind AutoPropertyAccessorKind { get; } public override LookupResultKind ResultKind { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitPropertyAccess(this); - public BoundPropertyAccess Update(BoundExpression? receiverOpt, ThreeState initialBindingReceiverIsSubjectToCloning, PropertySymbol propertySymbol, LookupResultKind resultKind, TypeSymbol type) + public BoundPropertyAccess Update(BoundExpression? receiverOpt, ThreeState initialBindingReceiverIsSubjectToCloning, PropertySymbol propertySymbol, AccessorKind autoPropertyAccessorKind, LookupResultKind resultKind, TypeSymbol type) { - if (receiverOpt != this.ReceiverOpt || initialBindingReceiverIsSubjectToCloning != this.InitialBindingReceiverIsSubjectToCloning || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(propertySymbol, this.PropertySymbol) || resultKind != this.ResultKind || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (receiverOpt != this.ReceiverOpt || initialBindingReceiverIsSubjectToCloning != this.InitialBindingReceiverIsSubjectToCloning || !Symbols.SymbolEqualityComparer.ConsiderEverything.Equals(propertySymbol, this.PropertySymbol) || autoPropertyAccessorKind != this.AutoPropertyAccessorKind || resultKind != this.ResultKind || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundPropertyAccess(this.Syntax, receiverOpt, initialBindingReceiverIsSubjectToCloning, propertySymbol, resultKind, type, this.HasErrors); + var result = new BoundPropertyAccess(this.Syntax, receiverOpt, initialBindingReceiverIsSubjectToCloning, propertySymbol, autoPropertyAccessorKind, resultKind, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -11938,7 +11940,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor { BoundExpression? receiverOpt = (BoundExpression?)this.Visit(node.ReceiverOpt); TypeSymbol? type = this.VisitType(node.Type); - return node.Update(receiverOpt, node.InitialBindingReceiverIsSubjectToCloning, node.PropertySymbol, node.ResultKind, type); + return node.Update(receiverOpt, node.InitialBindingReceiverIsSubjectToCloning, node.PropertySymbol, node.AutoPropertyAccessorKind, node.ResultKind, type); } public override BoundNode? VisitEventAccess(BoundEventAccess node) { @@ -14450,12 +14452,12 @@ public NullabilityRewriter(ImmutableDictionary new LiteralExpressionSyntax(this.Kind, this.token, GetDiagnostics(), annotations); } +/// Class which represents the syntax node for a field expression. +internal sealed partial class FieldExpressionSyntax : ExpressionSyntax +{ + internal readonly SyntaxToken token; + + internal FieldExpressionSyntax(SyntaxKind kind, SyntaxToken token, DiagnosticInfo[]? diagnostics, SyntaxAnnotation[]? annotations) + : base(kind, diagnostics, annotations) + { + this.SlotCount = 1; + this.AdjustFlagsAndWidth(token); + this.token = token; + } + + internal FieldExpressionSyntax(SyntaxKind kind, SyntaxToken token, SyntaxFactoryContext context) + : base(kind) + { + this.SetFactoryContext(context); + this.SlotCount = 1; + this.AdjustFlagsAndWidth(token); + this.token = token; + } + + internal FieldExpressionSyntax(SyntaxKind kind, SyntaxToken token) + : base(kind) + { + this.SlotCount = 1; + this.AdjustFlagsAndWidth(token); + this.token = token; + } + + /// SyntaxToken representing the field keyword. + public SyntaxToken Token => this.token; + + internal override GreenNode? GetSlot(int index) + => index == 0 ? this.token : null; + + internal override SyntaxNode CreateRed(SyntaxNode? parent, int position) => new CSharp.Syntax.FieldExpressionSyntax(this, parent, position); + + public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitFieldExpression(this); + public override TResult Accept(CSharpSyntaxVisitor visitor) => visitor.VisitFieldExpression(this); + + public FieldExpressionSyntax Update(SyntaxToken token) + { + if (token != this.Token) + { + var newNode = SyntaxFactory.FieldExpression(token); + var diags = GetDiagnostics(); + if (diags?.Length > 0) + newNode = newNode.WithDiagnosticsGreen(diags); + var annotations = GetAnnotations(); + if (annotations?.Length > 0) + newNode = newNode.WithAnnotationsGreen(annotations); + return newNode; + } + + return this; + } + + internal override GreenNode SetDiagnostics(DiagnosticInfo[]? diagnostics) + => new FieldExpressionSyntax(this.Kind, this.token, diagnostics, GetAnnotations()); + + internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) + => new FieldExpressionSyntax(this.Kind, this.token, GetDiagnostics(), annotations); +} + /// Class which represents the syntax node for MakeRef expression. internal sealed partial class MakeRefExpressionSyntax : ExpressionSyntax { @@ -26456,6 +26521,7 @@ internal partial class CSharpSyntaxVisitor public virtual TResult VisitThisExpression(ThisExpressionSyntax node) => this.DefaultVisit(node); public virtual TResult VisitBaseExpression(BaseExpressionSyntax node) => this.DefaultVisit(node); public virtual TResult VisitLiteralExpression(LiteralExpressionSyntax node) => this.DefaultVisit(node); + public virtual TResult VisitFieldExpression(FieldExpressionSyntax node) => this.DefaultVisit(node); public virtual TResult VisitMakeRefExpression(MakeRefExpressionSyntax node) => this.DefaultVisit(node); public virtual TResult VisitRefTypeExpression(RefTypeExpressionSyntax node) => this.DefaultVisit(node); public virtual TResult VisitRefValueExpression(RefValueExpressionSyntax node) => this.DefaultVisit(node); @@ -26703,6 +26769,7 @@ internal partial class CSharpSyntaxVisitor public virtual void VisitThisExpression(ThisExpressionSyntax node) => this.DefaultVisit(node); public virtual void VisitBaseExpression(BaseExpressionSyntax node) => this.DefaultVisit(node); public virtual void VisitLiteralExpression(LiteralExpressionSyntax node) => this.DefaultVisit(node); + public virtual void VisitFieldExpression(FieldExpressionSyntax node) => this.DefaultVisit(node); public virtual void VisitMakeRefExpression(MakeRefExpressionSyntax node) => this.DefaultVisit(node); public virtual void VisitRefTypeExpression(RefTypeExpressionSyntax node) => this.DefaultVisit(node); public virtual void VisitRefValueExpression(RefValueExpressionSyntax node) => this.DefaultVisit(node); @@ -27024,6 +27091,9 @@ public override CSharpSyntaxNode VisitBaseExpression(BaseExpressionSyntax node) public override CSharpSyntaxNode VisitLiteralExpression(LiteralExpressionSyntax node) => node.Update((SyntaxToken)Visit(node.Token)); + public override CSharpSyntaxNode VisitFieldExpression(FieldExpressionSyntax node) + => node.Update((SyntaxToken)Visit(node.Token)); + public override CSharpSyntaxNode VisitMakeRefExpression(MakeRefExpressionSyntax node) => node.Update((SyntaxToken)Visit(node.Keyword), (SyntaxToken)Visit(node.OpenParenToken), (ExpressionSyntax)Visit(node.Expression), (SyntaxToken)Visit(node.CloseParenToken)); @@ -28620,6 +28690,26 @@ public LiteralExpressionSyntax LiteralExpression(SyntaxKind kind, SyntaxToken to return result; } + public FieldExpressionSyntax FieldExpression(SyntaxToken token) + { +#if DEBUG + if (token == null) throw new ArgumentNullException(nameof(token)); + if (token.Kind != SyntaxKind.FieldKeyword) throw new ArgumentException(nameof(token)); +#endif + + int hash; + var cached = CSharpSyntaxNodeCache.TryGetNode((int)SyntaxKind.FieldExpression, token, this.context, out hash); + if (cached != null) return (FieldExpressionSyntax)cached; + + var result = new FieldExpressionSyntax(SyntaxKind.FieldExpression, token, this.context); + if (hash >= 0) + { + SyntaxNodeCache.AddNode(result, hash); + } + + return result; + } + public MakeRefExpressionSyntax MakeRefExpression(SyntaxToken keyword, SyntaxToken openParenToken, ExpressionSyntax expression, SyntaxToken closeParenToken) { #if DEBUG @@ -33868,6 +33958,26 @@ public static LiteralExpressionSyntax LiteralExpression(SyntaxKind kind, SyntaxT return result; } + public static FieldExpressionSyntax FieldExpression(SyntaxToken token) + { +#if DEBUG + if (token == null) throw new ArgumentNullException(nameof(token)); + if (token.Kind != SyntaxKind.FieldKeyword) throw new ArgumentException(nameof(token)); +#endif + + int hash; + var cached = SyntaxNodeCache.TryGetNode((int)SyntaxKind.FieldExpression, token, out hash); + if (cached != null) return (FieldExpressionSyntax)cached; + + var result = new FieldExpressionSyntax(SyntaxKind.FieldExpression, token); + if (hash >= 0) + { + SyntaxNodeCache.AddNode(result, hash); + } + + return result; + } + public static MakeRefExpressionSyntax MakeRefExpression(SyntaxToken keyword, SyntaxToken openParenToken, ExpressionSyntax expression, SyntaxToken closeParenToken) { #if DEBUG diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs index fff130e4907ad..0b31bde257994 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Main.Generated.cs @@ -126,6 +126,9 @@ public partial class CSharpSyntaxVisitor /// Called when the visitor visits a LiteralExpressionSyntax node. public virtual TResult? VisitLiteralExpression(LiteralExpressionSyntax node) => this.DefaultVisit(node); + /// Called when the visitor visits a FieldExpressionSyntax node. + public virtual TResult? VisitFieldExpression(FieldExpressionSyntax node) => this.DefaultVisit(node); + /// Called when the visitor visits a MakeRefExpressionSyntax node. public virtual TResult? VisitMakeRefExpression(MakeRefExpressionSyntax node) => this.DefaultVisit(node); @@ -858,6 +861,9 @@ public partial class CSharpSyntaxVisitor /// Called when the visitor visits a LiteralExpressionSyntax node. public virtual void VisitLiteralExpression(LiteralExpressionSyntax node) => this.DefaultVisit(node); + /// Called when the visitor visits a FieldExpressionSyntax node. + public virtual void VisitFieldExpression(FieldExpressionSyntax node) => this.DefaultVisit(node); + /// Called when the visitor visits a MakeRefExpressionSyntax node. public virtual void VisitMakeRefExpression(MakeRefExpressionSyntax node) => this.DefaultVisit(node); @@ -1590,6 +1596,9 @@ public partial class CSharpSyntaxRewriter : CSharpSyntaxVisitor public override SyntaxNode? VisitLiteralExpression(LiteralExpressionSyntax node) => node.Update(VisitToken(node.Token)); + public override SyntaxNode? VisitFieldExpression(FieldExpressionSyntax node) + => node.Update(VisitToken(node.Token)); + public override SyntaxNode? VisitMakeRefExpression(MakeRefExpressionSyntax node) => node.Update(VisitToken(node.Keyword), VisitToken(node.OpenParenToken), (ExpressionSyntax?)Visit(node.Expression) ?? throw new ArgumentNullException("expression"), VisitToken(node.CloseParenToken)); @@ -2936,6 +2945,17 @@ public static LiteralExpressionSyntax LiteralExpression(SyntaxKind kind, SyntaxT return (LiteralExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.LiteralExpression(kind, (Syntax.InternalSyntax.SyntaxToken)token.Node!).CreateRed(); } + /// Creates a new FieldExpressionSyntax instance. + public static FieldExpressionSyntax FieldExpression(SyntaxToken token) + { + if (token.Kind() != SyntaxKind.FieldKeyword) throw new ArgumentException(nameof(token)); + return (FieldExpressionSyntax)Syntax.InternalSyntax.SyntaxFactory.FieldExpression((Syntax.InternalSyntax.SyntaxToken)token.Node!).CreateRed(); + } + + /// Creates a new FieldExpressionSyntax instance. + public static FieldExpressionSyntax FieldExpression() + => SyntaxFactory.FieldExpression(SyntaxFactory.Token(SyntaxKind.FieldKeyword)); + /// Creates a new MakeRefExpressionSyntax instance. public static MakeRefExpressionSyntax MakeRefExpression(SyntaxToken keyword, SyntaxToken openParenToken, ExpressionSyntax expression, SyntaxToken closeParenToken) { diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs index dadef45b3bf3d..0c97b7c25c0a7 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Syntax.Generated.cs @@ -2032,6 +2032,46 @@ public LiteralExpressionSyntax Update(SyntaxToken token) public LiteralExpressionSyntax WithToken(SyntaxToken token) => Update(token); } +/// Class which represents the syntax node for a field expression. +/// +/// This node is associated with the following syntax kinds: +/// +/// +/// +/// +public sealed partial class FieldExpressionSyntax : ExpressionSyntax +{ + + internal FieldExpressionSyntax(InternalSyntax.CSharpSyntaxNode green, SyntaxNode? parent, int position) + : base(green, parent, position) + { + } + + /// SyntaxToken representing the field keyword. + public SyntaxToken Token => new SyntaxToken(this, ((InternalSyntax.FieldExpressionSyntax)this.Green).token, Position, 0); + + internal override SyntaxNode? GetNodeSlot(int index) => null; + + internal override SyntaxNode? GetCachedSlot(int index) => null; + + public override void Accept(CSharpSyntaxVisitor visitor) => visitor.VisitFieldExpression(this); + public override TResult? Accept(CSharpSyntaxVisitor visitor) where TResult : default => visitor.VisitFieldExpression(this); + + public FieldExpressionSyntax Update(SyntaxToken token) + { + if (token != this.Token) + { + var newNode = SyntaxFactory.FieldExpression(token); + var annotations = GetAnnotations(); + return annotations?.Length > 0 ? newNode.WithAnnotations(annotations) : newNode; + } + + return this; + } + + public FieldExpressionSyntax WithToken(SyntaxToken token) => Update(token); +} + /// Class which represents the syntax node for MakeRef expression. /// /// This node is associated with the following syntax kinds: 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/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 75c23b3592248..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.IsAutoPropertyWithGetAccessor, + 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/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs index 9cf7b4f388044..6b87177c37ffb 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_CompoundAssignmentOperator.cs @@ -599,7 +599,7 @@ private BoundExpression TransformCompoundAssignmentLHS(BoundExpression originalL // This is a temporary object that will be rewritten away before the lowering completes. return propertyAccess.Update(TransformPropertyOrEventReceiver(propertyAccess.PropertySymbol, propertyAccess.ReceiverOpt, isRegularCompoundAssignment, stores, temps), - propertyAccess.InitialBindingReceiverIsSubjectToCloning, propertyAccess.PropertySymbol, propertyAccess.ResultKind, propertyAccess.Type); + propertyAccess.InitialBindingReceiverIsSubjectToCloning, propertyAccess.PropertySymbol, propertyAccess.AutoPropertyAccessorKind, propertyAccess.ResultKind, propertyAccess.Type); } } break; diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PropertyAccess.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PropertyAccess.cs index 4d99b1d246af4..a315c14284fe4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PropertyAccess.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_PropertyAccess.cs @@ -55,8 +55,8 @@ private BoundExpression MakePropertyAccess( // This node will be rewritten with MakePropertyAssignment when rewriting the enclosing BoundAssignmentOperator. return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, resultKind, type) : - new BoundPropertyAccess(syntax, rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, resultKind, type); + oldNodeOpt.Update(rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, type) : + new BoundPropertyAccess(syntax, rewrittenReceiverOpt, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, propertySymbol, autoPropertyAccessorKind: AccessorKind.Unknown, resultKind, type); } else { @@ -83,8 +83,8 @@ private BoundExpression MakePropertyGetAccess( { Debug.Assert(argumentRefKindsOpt.IsDefaultOrEmpty); return oldNodeOpt != null ? - oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, LookupResultKind.Viable, property.Type) : - new BoundPropertyAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, LookupResultKind.Viable, property.Type); + oldNodeOpt.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, property.Type) : + new BoundPropertyAccess(syntax, rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, property, autoPropertyAccessorKind: AccessorKind.Unknown, LookupResultKind.Viable, property.Type); } else { diff --git a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs index 69f0e6190720e..1f2fa70219430 100644 --- a/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs @@ -223,7 +223,7 @@ public override BoundNode VisitPropertyAccess(BoundPropertyAccess node) { var rewrittenPropertySymbol = VisitPropertySymbol(node.PropertySymbol); var rewrittenReceiver = (BoundExpression?)Visit(node.ReceiverOpt); - return node.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, rewrittenPropertySymbol, node.ResultKind, VisitType(node.Type)); + return node.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, rewrittenPropertySymbol, node.AutoPropertyAccessorKind, node.ResultKind, VisitType(node.Type)); } public override BoundNode VisitCall(BoundCall node) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index c3d579552b52f..9b789a388dbe2 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -3969,7 +3969,7 @@ private IndexerDeclarationSyntax ParseIndexerDeclaration( } else { - accessorList = this.ParseAccessorList(isEvent: false); + accessorList = this.ParseAccessorList(AccessorDeclaringKind.Indexer); if (this.CurrentToken.Kind == SyntaxKind.SemicolonToken) { semicolon = this.EatTokenWithPrejudice(ErrorCode.ERR_UnexpectedSemicolon); @@ -4023,7 +4023,7 @@ private PropertyDeclarationSyntax ParsePropertyDeclaration( Debug.Assert(IsStartOfPropertyBody(this.CurrentToken.Kind)); var accessorList = this.CurrentToken.Kind == SyntaxKind.OpenBraceToken - ? this.ParseAccessorList(isEvent: false) + ? this.ParseAccessorList(AccessorDeclaringKind.Property) : null; ArrowExpressionClauseSyntax expressionBody = null; @@ -4032,7 +4032,10 @@ private PropertyDeclarationSyntax ParsePropertyDeclaration( // Check for expression body if (this.CurrentToken.Kind == SyntaxKind.EqualsGreaterThanToken) { - expressionBody = this.ParseArrowExpressionClause(); + using (new FieldKeywordContext(this, isInFieldKeywordContext: true)) + { + expressionBody = this.ParseArrowExpressionClause(); + } } // Check if we have an initializer else if (this.CurrentToken.Kind == SyntaxKind.EqualsToken) @@ -4064,7 +4067,32 @@ private PropertyDeclarationSyntax ParsePropertyDeclaration( semicolon); } - private AccessorListSyntax ParseAccessorList(bool isEvent) + private readonly ref struct FieldKeywordContext : IDisposable + { + private readonly LanguageParser _parser; + private readonly bool _previousInFieldKeywordContext; + + public FieldKeywordContext(LanguageParser parser, bool isInFieldKeywordContext) + { + _parser = parser; + _previousInFieldKeywordContext = parser.IsInFieldKeywordContext; + _parser.IsInFieldKeywordContext = isInFieldKeywordContext; + } + + public void Dispose() + { + _parser.IsInFieldKeywordContext = _previousInFieldKeywordContext; + } + } + + private enum AccessorDeclaringKind + { + Property, + Indexer, + Event, + } + + private AccessorListSyntax ParseAccessorList(AccessorDeclaringKind declaringKind) { var openBrace = this.EatToken(SyntaxKind.OpenBraceToken); var accessors = default(SyntaxList); @@ -4082,11 +4110,11 @@ private AccessorListSyntax ParseAccessorList(bool isEvent) } else if (this.IsPossibleAccessor()) { - var acc = this.ParseAccessorDeclaration(isEvent); + var acc = this.ParseAccessorDeclaration(declaringKind); builder.Add(acc); } else if (this.SkipBadAccessorListTokens(ref openBrace, builder, - isEvent ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected) == PostSkipAction.Abort) + declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected) == PostSkipAction.Abort) { break; } @@ -4338,20 +4366,22 @@ private PostSkipAction SkipBadTokensWithErrorCode( return action; } - private AccessorDeclarationSyntax ParseAccessorDeclaration(bool isEvent) + private AccessorDeclarationSyntax ParseAccessorDeclaration(AccessorDeclaringKind declaringKind) { if (this.IsIncrementalAndFactoryContextMatches && SyntaxFacts.IsAccessorDeclaration(this.CurrentNodeKind)) { return (AccessorDeclarationSyntax)this.EatNode(); } + using var __ = new FieldKeywordContext(this, isInFieldKeywordContext: declaringKind is AccessorDeclaringKind.Property); + var accMods = _pool.Allocate(); var accAttrs = this.ParseAttributeDeclarations(inExpressionContext: false); this.ParseModifiers(accMods, forAccessors: true, forTopLevelStatements: false, isPossibleTypeDeclaration: out _); var accessorName = this.EatToken(SyntaxKind.IdentifierToken, - isEvent ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected); + declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected); var accessorKind = GetAccessorKind(accessorName); // Only convert the identifier to a keyword if it's a valid one. Otherwise any @@ -4367,7 +4397,7 @@ private AccessorDeclarationSyntax ParseAccessorDeclaration(bool isEvent) if (!accessorName.IsMissing) { accessorName = this.AddError(accessorName, - isEvent ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected); + declaringKind == AccessorDeclaringKind.Event ? ErrorCode.ERR_AddOrRemoveExpected : ErrorCode.ERR_GetOrSetExpected); } else { @@ -4911,7 +4941,7 @@ private EventDeclarationSyntax ParseEventDeclarationWithAccessors( } else { - accessorList = this.ParseAccessorList(isEvent: true); + accessorList = this.ParseAccessorList(AccessorDeclaringKind.Event); } var decl = _syntaxFactory.EventDeclaration( @@ -5769,6 +5799,13 @@ private bool IsCurrentTokenPartialKeywordOfPartialMethodOrType() return false; } + private bool IsCurrentTokenFieldInKeywordContext() + { + return CurrentToken.ContextualKind == SyntaxKind.FieldKeyword && + IsInFieldKeywordContext && + IsFeatureEnabled(MessageID.IDS_FeatureFieldKeyword); + } + private TypeParameterListSyntax ParseTypeParameterList() { if (this.CurrentToken.Kind != SyntaxKind.LessThanToken) @@ -10759,6 +10796,7 @@ private static Precedence GetPrecedence(SyntaxKind op) case SyntaxKind.DefaultLiteralExpression: case SyntaxKind.ElementAccessExpression: case SyntaxKind.FalseLiteralExpression: + case SyntaxKind.FieldExpression: case SyntaxKind.GenericName: case SyntaxKind.IdentifierName: case SyntaxKind.ImplicitArrayCreationExpression: @@ -11350,6 +11388,10 @@ private ExpressionSyntax ParseTermWithoutPostfix(Precedence precedence) { return ParseDeclarationExpression(ParseTypeMode.Normal, isScoped: false); } + else if (IsCurrentTokenFieldInKeywordContext() && PeekToken(1).Kind != SyntaxKind.ColonColonToken) + { + return _syntaxFactory.FieldExpression(this.EatContextualToken(SyntaxKind.FieldKeyword)); + } else { return this.ParseAliasQualifiedName(NameOptions.InExpression); @@ -13740,7 +13782,8 @@ private bool IsIncrementalAndFactoryContextMatches internal static bool MatchesFactoryContext(GreenNode green, SyntaxFactoryContext context) { return context.IsInAsync == green.ParsedInAsync && - context.IsInQuery == green.ParsedInQuery; + context.IsInQuery == green.ParsedInQuery && + context.IsInFieldKeywordContext == green.ParsedInFieldKeywordContext; } private bool IsInAsync @@ -13761,6 +13804,12 @@ private bool IsInQuery set => _syntaxFactoryContext.IsInQuery = value; } + private bool IsInFieldKeywordContext + { + get => _syntaxFactoryContext.IsInFieldKeywordContext; + set => _syntaxFactoryContext.IsInFieldKeywordContext = value; + } + private delegate PostSkipAction SkipBadTokens( LanguageParser parser, ref SyntaxToken openToken, SeparatedSyntaxListBuilder builder, SyntaxKind expectedKind, SyntaxKind closeTokenKind) where TNode : GreenNode; @@ -13923,7 +13972,8 @@ private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose) base.GetResetPoint(), _termState, IsInAsync, - IsInQuery); + IsInQuery, + IsInFieldKeywordContext); } private void Reset(ref ResetPoint state) @@ -13931,6 +13981,7 @@ private void Reset(ref ResetPoint state) _termState = state.TerminatorState; IsInAsync = state.IsInAsync; IsInQuery = state.IsInQuery; + IsInFieldKeywordContext = state.IsInFieldKeywordContext; base.Reset(ref state.BaseResetPoint); } @@ -13970,17 +14021,20 @@ public void Dispose() internal readonly TerminatorState TerminatorState; internal readonly bool IsInAsync; internal readonly bool IsInQuery; + internal readonly bool IsInFieldKeywordContext; internal ResetPoint( SyntaxParser.ResetPoint resetPoint, TerminatorState terminatorState, bool isInAsync, - bool isInQuery) + bool isInQuery, + bool isInFieldKeywordContext) { this.BaseResetPoint = resetPoint; this.TerminatorState = terminatorState; this.IsInAsync = isInAsync; this.IsInQuery = isInQuery; + this.IsInFieldKeywordContext = isInFieldKeywordContext; } } diff --git a/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs b/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs index 0dc984500c138..62204730e84ce 100644 --- a/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs +++ b/src/Compilers/CSharp/Portable/Parser/SyntaxFactoryContext.cs @@ -34,5 +34,11 @@ internal class SyntaxFactoryContext /// may need to be reinterpreted as query keywords. /// internal bool IsInQuery; + + /// + /// If an accessor kind changes, "field" within the accessor may need to be reinterpreted, + /// to determine whether the token is a keyword or an identifier. + /// + internal bool IsInFieldKeywordContext; } } diff --git a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt index faa2c0b5636f6..6277f221aba0c 100644 --- a/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt +++ b/src/Compilers/CSharp/Portable/PublicAPI.Unshipped.txt @@ -51,3 +51,15 @@ virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitAllowsConstraintC virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefStructConstraint(Microsoft.CodeAnalysis.CSharp.Syntax.RefStructConstraintSyntax! node) -> void virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitAllowsConstraintClause(Microsoft.CodeAnalysis.CSharp.Syntax.AllowsConstraintClauseSyntax! node) -> TResult? virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitRefStructConstraint(Microsoft.CodeAnalysis.CSharp.Syntax.RefStructConstraintSyntax! node) -> TResult? +Microsoft.CodeAnalysis.CSharp.SyntaxKind.FieldExpression = 8757 -> Microsoft.CodeAnalysis.CSharp.SyntaxKind +Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax +virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! node) -> TResult? +override Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! node) -> Microsoft.CodeAnalysis.SyntaxNode? +virtual Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor.VisitFieldExpression(Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! node) -> void +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FieldExpression() -> Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! +static Microsoft.CodeAnalysis.CSharp.SyntaxFactory.FieldExpression(Microsoft.CodeAnalysis.SyntaxToken token) -> Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! +Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax.Token.get -> Microsoft.CodeAnalysis.SyntaxToken +override Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> void +override Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax.Accept(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor! visitor) -> TResult? +Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax.Update(Microsoft.CodeAnalysis.SyntaxToken token) -> Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! +Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax.WithToken(Microsoft.CodeAnalysis.SyntaxToken token) -> Microsoft.CodeAnalysis.CSharp.Syntax.FieldExpressionSyntax! diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 9e7dd960af64c..8e64bfd4ed0ae 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs @@ -1772,6 +1772,8 @@ private Dictionary, ImmutableArray> GetMembersByNam return _lazyMembersDictionary; } + internal bool AreMembersComplete => state.HasComplete(CompletionPart.Members); + internal override IEnumerable GetInstanceFieldsAndEvents() { var membersAndInitializers = this.GetMembersAndInitializers(); @@ -3669,7 +3671,7 @@ void mergePartialMethods(ref Dictionary, ImmutableArray, ImmutableArray< } else { + if (hasInitializer(prevProperty) && hasInitializer(currentProperty)) + { + diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateInitializer, currentProperty.GetFirstLocation()); + } + DuplicateMembersByNameIfCached(ref membersByName); mergeAccessors(ref membersByName, (SourcePropertyAccessorSymbol?)currentProperty.GetMethod, (SourcePropertyAccessorSymbol?)prevProperty.GetMethod); mergeAccessors(ref membersByName, (SourcePropertyAccessorSymbol?)currentProperty.SetMethod, (SourcePropertyAccessorSymbol?)prevProperty.SetMethod); - membersByName[name] = FixPartialMember(membersByName[name], prevProperty, currentProperty); + FixPartialProperty(ref membersByName, name, prevProperty, currentProperty); } void mergeAccessors(ref Dictionary, ImmutableArray> membersByName, SourcePropertyAccessorSymbol? currentAccessor, SourcePropertyAccessorSymbol? prevAccessor) @@ -3712,6 +3719,11 @@ void mergeAccessors(ref Dictionary, ImmutableArray> diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), foundAccessor); } } + + static bool hasInitializer(SourcePropertySymbol property) + { + return property.DeclaredBackingField?.HasInitializer == true; + } } } @@ -3725,7 +3737,7 @@ private void DuplicateMembersByNameIfCached(ref Dictionary, } /// Links together the definition and implementation parts of a partial method. Returns a member list which has the implementation part removed. - private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourceOrdinaryMethodSymbol part1, SourceOrdinaryMethodSymbol part2) + private static ImmutableArray FixPartialMethod(ImmutableArray symbols, SourceOrdinaryMethodSymbol part1, SourceOrdinaryMethodSymbol part2) { SourceOrdinaryMethodSymbol definition; SourceOrdinaryMethodSymbol implementation; @@ -3747,7 +3759,7 @@ private static ImmutableArray FixPartialMember(ImmutableArray sy } /// Links together the definition and implementation parts of a partial property. Returns a member list which has the implementation part removed. - private static ImmutableArray FixPartialMember(ImmutableArray symbols, SourcePropertySymbol part1, SourcePropertySymbol part2) + private static void FixPartialProperty(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertySymbol part1, SourcePropertySymbol part2) { SourcePropertySymbol definition; SourcePropertySymbol implementation; @@ -3762,10 +3774,17 @@ private static ImmutableArray FixPartialMember(ImmutableArray sy implementation = part1; } + if (implementation.DeclaredBackingField is { } implementationField && + definition.DeclaredBackingField is { }) + { + var fieldName = implementationField.Name.AsMemory(); + membersByName[fieldName] = Remove(membersByName[fieldName], implementationField); + } + SourcePropertySymbol.InitializePartialPropertyParts(definition, implementation); // a partial property is represented in the member list by its definition part: - return Remove(symbols, implementation); + membersByName[name] = Remove(membersByName[name], implementation); } private static ImmutableArray Remove(ImmutableArray symbols, Symbol symbol) @@ -4538,7 +4557,9 @@ void addProperty(SynthesizedRecordPropertySymbol property) Debug.Assert(property.SetMethod is object); members.Add(property.GetMethod); members.Add(property.SetMethod); - members.Add(property.BackingField); + var backingField = property.DeclaredBackingField; + Debug.Assert(backingField is object); + members.Add(backingField); builder.AddInstanceInitializerForPositionalMembers(new FieldOrPropertyInitializer(property.BackingField, paramList.Parameters[param.Ordinal])); addedCount++; @@ -4978,13 +4999,13 @@ private void AddNonTypeMembers( AddAccessorIfAvailable(builder.NonTypeMembers, property.GetMethod); AddAccessorIfAvailable(builder.NonTypeMembers, property.SetMethod); - FieldSymbol backingField = property.BackingField; + FieldSymbol? backingField = property.DeclaredBackingField; // TODO: can we leave this out of the member list? // From the 10/12/11 design notes: // In addition, we will change autoproperties to behavior in // a similar manner and make the autoproperty fields private. - if ((object)backingField != null) + if (backingField is { }) { builder.NonTypeMembers.Add(backingField); builder.UpdateIsNullableEnabledForConstructorsAndFields(useStatic: backingField.IsStatic, compilation, propertySyntax); 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 ddfd658541916..a76fc7bd75edf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -40,12 +40,15 @@ private static SourcePropertySymbol Create( GetAccessorDeclarations( syntax, diagnostics, - out bool hasAccessorList, - out bool accessorsHaveImplementation, - out bool isInitOnly, + out bool isExpressionBodied, + out bool hasGetAccessorImplementation, + out bool hasSetAccessorImplementation, + out bool usesFieldKeyword, out var getSyntax, out var setSyntax); + bool accessorsHaveImplementation = hasGetAccessorImplementation || hasSetAccessorImplementation; + var explicitInterfaceSpecifier = GetExplicitInterfaceSpecifier(syntax); SyntaxTokenList modifiersTokenList = GetModifierTokensSyntax(syntax); bool isExplicitInterfaceImplementation = explicitInterfaceSpecifier is object; @@ -59,8 +62,11 @@ private static SourcePropertySymbol Create( diagnostics, out _); - bool isAutoProperty = (modifiers & DeclarationModifiers.Partial) == 0 && !accessorsHaveImplementation; - bool isExpressionBodied = !hasAccessorList && GetArrowExpression(syntax) != null; + bool allowAutoPropertyAccessors = (modifiers & (DeclarationModifiers.Abstract | DeclarationModifiers.Extern | DeclarationModifiers.Indexer)) == 0 && + (!containingType.IsInterface || hasGetAccessorImplementation || hasSetAccessorImplementation || (modifiers & DeclarationModifiers.Static) != 0) && + ((modifiers & DeclarationModifiers.Partial) == 0 || hasGetAccessorImplementation || hasSetAccessorImplementation); + bool hasAutoPropertyGet = allowAutoPropertyAccessors && getSyntax != null && !hasGetAccessorImplementation; + bool hasAutoPropertySet = allowAutoPropertyAccessors && setSyntax != null && !hasSetAccessorImplementation; binder = binder.SetOrClearUnsafeRegionIfNecessary(modifiersTokenList); TypeSymbol? explicitInterfaceType; @@ -77,10 +83,11 @@ private static SourcePropertySymbol Create( aliasQualifierOpt, modifiers, hasExplicitAccessMod: hasExplicitAccessMod, - isAutoProperty: isAutoProperty, + hasAutoPropertyGet: hasAutoPropertyGet, + hasAutoPropertySet: hasAutoPropertySet, isExpressionBodied: isExpressionBodied, - isInitOnly: isInitOnly, accessorsHaveImplementation: accessorsHaveImplementation, + usesFieldKeyword: usesFieldKeyword, memberName, location, diagnostics); @@ -96,28 +103,30 @@ private SourcePropertySymbol( string? aliasQualifierOpt, DeclarationModifiers modifiers, bool hasExplicitAccessMod, - bool isAutoProperty, + bool hasAutoPropertyGet, + bool hasAutoPropertySet, bool isExpressionBodied, - bool isInitOnly, bool accessorsHaveImplementation, + bool usesFieldKeyword, string memberName, Location location, BindingDiagnosticBag diagnostics) : 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, + usesFieldKeyword: usesFieldKeyword, syntax.Type.SkipScoped(out _).GetRefKindInLocalOrReturn(diagnostics), memberName, syntax.AttributeLists, @@ -126,11 +135,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); } @@ -175,8 +186,7 @@ public override OneOrMany> GetAttributeDeclarati // Attributes on partial properties are owned by the definition part. // If this symbol has a non-null PartialDefinitionPart, we should have accessed this method through that definition symbol instead Debug.Assert(PartialDefinitionPart is null - // We might still get here when asking for the attributes on a backing field. - // This is an error scenario (requires using a property initializer and field-targeted attributes on partial property implementation part). + // We might still get here when asking for the attributes on a backing field in error scenarios. || this.BackingField is not null); if (SourcePartialImplementationPart is { } implementationPart) @@ -198,21 +208,23 @@ public override OneOrMany> GetAttributeDeclarati private static void GetAccessorDeclarations( CSharpSyntaxNode syntaxNode, BindingDiagnosticBag diagnostics, - out bool hasAccessorList, - out bool accessorsHaveImplementation, - out bool isInitOnly, - out CSharpSyntaxNode? getSyntax, - out CSharpSyntaxNode? setSyntax) + out bool isExpressionBodied, + out bool hasGetAccessorImplementation, + out bool hasSetAccessorImplementation, + out bool usesFieldKeyword, + 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) { - accessorsHaveImplementation = false; + usesFieldKeyword = false; + hasGetAccessorImplementation = false; + hasSetAccessorImplementation = false; foreach (var accessor in syntax.AccessorList!.Accessors) { switch (accessor.Kind()) @@ -221,6 +233,7 @@ private static void GetAccessorDeclarations( if (getSyntax == null) { getSyntax = accessor; + hasGetAccessorImplementation = hasImplementation(accessor); } else { @@ -232,10 +245,7 @@ private static void GetAccessorDeclarations( if (setSyntax == null) { setSyntax = accessor; - if (accessor.Keyword.IsKind(SyntaxKind.InitKeyword)) - { - isInitOnly = true; - } + hasSetAccessorImplementation = hasImplementation(accessor); } else { @@ -254,16 +264,34 @@ private static void GetAccessorDeclarations( throw ExceptionUtilities.UnexpectedValue(accessor.Kind()); } - if (accessor.Body != null || accessor.ExpressionBody != null) - { - accessorsHaveImplementation = true; - } + usesFieldKeyword = usesFieldKeyword || containsFieldKeyword(accessor); } } else { - accessorsHaveImplementation = GetArrowExpression(syntax) is object; - Debug.Assert(accessorsHaveImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression. + var body = GetArrowExpression(syntax); + hasGetAccessorImplementation = body is object; + hasSetAccessorImplementation = false; + usesFieldKeyword = body is { } && containsFieldKeyword(body); + 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) + { + foreach (var node in syntax.Green.EnumerateNodes()) + { + if (node.RawKind == (int)SyntaxKind.FieldKeyword) + { + return true; + } + } + return false; } } @@ -746,6 +774,11 @@ internal static void InitializePartialPropertyParts(SourcePropertySymbol definit Debug.Assert(definition._otherPartOfPartial == implementation); Debug.Assert(implementation._otherPartOfPartial == definition); + + // Use the same backing field for both parts. + var backingField = definition.DeclaredBackingField ?? implementation.DeclaredBackingField; + definition.SetMergedBackingField(backingField); + implementation.SetMergedBackingField(backingField); } } } diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 15d76661a1cca..aca35156e28ea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -5,15 +5,12 @@ #nullable disable using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Globalization; using System.Linq; -using System.Runtime.CompilerServices; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Emit; -using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -29,14 +26,17 @@ internal abstract class SourcePropertySymbolBase : PropertySymbol, IAttributeTar /// so that we do not have to go back to source to compute this data. /// [Flags] - private enum Flags : byte + private enum Flags : ushort { 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, + RequiresBackingField = 1 << 8, } // TODO (tomat): consider splitting into multiple subclasses/rare data. @@ -72,6 +72,9 @@ private enum Flags : byte public Location Location { get; } #nullable enable + private SynthesizedBackingFieldSymbol? _lazyDeclaredBackingField; + private SynthesizedBackingFieldSymbol? _lazyMergedBackingField; + protected SourcePropertySymbolBase( SourceMemberContainerTypeSymbol containingType, CSharpSyntaxNode syntax, @@ -83,17 +86,18 @@ protected SourcePropertySymbolBase( DeclarationModifiers modifiers, bool hasInitializer, bool hasExplicitAccessMod, - bool isAutoProperty, + bool hasAutoPropertyGet, + bool hasAutoPropertySet, bool isExpressionBodied, - bool isInitOnly, bool accessorsHaveImplementation, + bool usesFieldKeyword, RefKind refKind, string memberName, SyntaxList indexerNameAttributeLists, 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); @@ -114,17 +118,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.HasAutoPropertyGet; + } + + if (hasAutoPropertySet) { - _propertyFlags |= Flags.IsAutoProperty; + _propertyFlags |= Flags.HasAutoPropertySet; + } + + if (usesFieldKeyword) + { + _propertyFlags |= Flags.UsesFieldKeyword; } if (hasInitializer) @@ -142,7 +153,7 @@ protected SourcePropertySymbolBase( _propertyFlags |= Flags.AccessorsHaveImplementation; } - if (isIndexer) + if (IsIndexer) { if (indexerNameAttributeLists.Count == 0 || isExplicitInterfaceImplementation) { @@ -160,26 +171,24 @@ protected SourcePropertySymbolBase( _name = _lazySourceName = memberName; } - if ((isAutoProperty && hasGetAccessor) || hasInitializer) + if (usesFieldKeyword || hasAutoPropertyGet || hasAutoPropertySet || hasInitializer) { Debug.Assert(!IsIndexer); - string fieldName = GeneratedNames.MakeBackingFieldName(_name); - BackingField = new SynthesizedBackingFieldSymbol(this, - fieldName, - isReadOnly: (hasGetAccessor && !hasSetAccessor) || isInitOnly, - this.IsStatic, - hasInitializer); + _propertyFlags |= Flags.RequiresBackingField; } if (hasGetAccessor) { - _getMethod = CreateGetAccessorSymbol(isAutoPropertyAccessor: isAutoProperty, diagnostics); + _getMethod = CreateGetAccessorSymbol(hasAutoPropertyGet, diagnostics); } if (hasSetAccessor) { - _setMethod = CreateSetAccessorSymbol(isAutoPropertyAccessor: isAutoProperty, diagnostics); + _setMethod = CreateSetAccessorSymbol(hasAutoPropertySet, diagnostics); } + + // We shouldn't calculate the backing field before the accessors above are created. + Debug.Assert(_lazyDeclaredBackingField is null); } private void EnsureSignatureGuarded(BindingDiagnosticBag diagnostics) @@ -290,7 +299,7 @@ protected void CheckInitializerIfNeeded(BindingDiagnosticBag diagnostics) { diagnostics.Add(ErrorCode.ERR_InstancePropertyInitializerInInterface, Location); } - else if (!IsAutoProperty) + else if (!IsAutoPropertyOrUsesFieldKeyword) { diagnostics.Add(ErrorCode.ERR_InitializerOnNonAutoProperty, Location); } @@ -638,23 +647,132 @@ public bool HasSkipLocalsInitAttribute } } - internal bool IsAutoPropertyWithGetAccessor - => IsAutoProperty && _getMethod is object; + internal bool IsAutoPropertyOrUsesFieldKeyword + => IsSetOnEitherPart(Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet | Flags.UsesFieldKeyword); + + internal bool UsesFieldKeyword + => IsSetOnEitherPart(Flags.UsesFieldKeyword); protected bool HasExplicitAccessModifier => (_propertyFlags & Flags.HasExplicitAccessModifier) != 0; - protected bool IsAutoProperty - => (_propertyFlags & Flags.IsAutoProperty) != 0; + internal bool IsAutoProperty + => IsSetOnEitherPart(Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet); + + internal bool HasAutoPropertyGet + => IsSetOnEitherPart(Flags.HasAutoPropertyGet); + + internal bool HasAutoPropertySet + => IsSetOnEitherPart(Flags.HasAutoPropertySet); + + /// + /// True if the property has a synthesized backing field, and + /// either no accessor or the accessor is auto-implemented. + /// + internal bool CanUseBackingFieldDirectlyInConstructor(bool useAsLvalue) + { + if (BackingField is null) + { + return false; + } + if (useAsLvalue) + { + return SetMethod is null || HasAutoPropertySet; + } + else + { + return GetMethod is null || HasAutoPropertyGet; + } + } + + private bool IsSetOnEitherPart(Flags flags) + { + return (_propertyFlags & flags) != 0 || + (this is SourcePropertySymbol { OtherPartOfPartial: { } otherPart } && (otherPart._propertyFlags & flags) != 0); + } 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; } + internal SynthesizedBackingFieldSymbol BackingField +#nullable enable + { + get + { + if (_lazyMergedBackingField is null) + { + var backingField = DeclaredBackingField; + // The property should only be used after members in the containing + // type are complete, and partial members have been merged. + if (!_containingType.AreMembersComplete) + { + // When calling through the SemanticModel, partial members are not + // necessarily merged when the containing type includes a primary + // constructor - see https://github.com/dotnet/roslyn/issues/75002. + Debug.Assert(_containingType.PrimaryConstructor is { }); + return backingField; + } + Interlocked.CompareExchange(ref _lazyMergedBackingField, backingField, null); + } + return _lazyMergedBackingField; + } + } + + internal SynthesizedBackingFieldSymbol? DeclaredBackingField + { + get + { + if (_lazyDeclaredBackingField is null && + (_propertyFlags & Flags.RequiresBackingField) != 0) + { + Interlocked.CompareExchange(ref _lazyDeclaredBackingField, CreateBackingField(), null); + } + return _lazyDeclaredBackingField; + } + } + + internal void SetMergedBackingField(SynthesizedBackingFieldSymbol? backingField) + { + Interlocked.CompareExchange(ref _lazyMergedBackingField, backingField, null); + Debug.Assert((object?)_lazyMergedBackingField == backingField); + } + + private SynthesizedBackingFieldSymbol CreateBackingField() + { + string fieldName = GeneratedNames.MakeBackingFieldName(_name); + + // The backing field is readonly if any of the following holds: + // - The containing type is declared readonly and the property is an instance property. + bool isReadOnly; + if (!IsStatic && ContainingType.IsReadOnly) + { + isReadOnly = true; + } + // - The property is declared readonly. + else if (HasReadOnlyModifier) + { + isReadOnly = true; + } + // - The property has no set accessor or is initonly or is declared readonly, and + // the get accessor, if any, is automatically implemented, or declared readonly. + else if ((_setMethod is null || _setMethod.IsInitOnly || _setMethod.IsDeclaredReadOnly) && + (_getMethod is null || (_propertyFlags & Flags.HasAutoPropertyGet) != 0 || _getMethod.IsDeclaredReadOnly)) + { + isReadOnly = true; + } + else + { + isReadOnly = false; + } + + return new SynthesizedBackingFieldSymbol(this, fieldName, isReadOnly: isReadOnly, isStatic: this.IsStatic, hasInitializer: (_propertyFlags & Flags.HasInitializer) != 0); + } +#nullable disable internal override bool MustCallMethodsDirectly { @@ -700,11 +818,9 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, diagnostics.Add(ErrorCode.ERR_RefReturningPropertiesCannotBeRequired, Location); } - if (IsAutoPropertyWithGetAccessor) + if (IsAutoPropertyOrUsesFieldKeyword) { - Debug.Assert(GetMethod is object); - - if (!IsStatic && SetMethod is { IsInitOnly: false }) + if (!IsStatic && ((_propertyFlags & Flags.HasAutoPropertySet) != 0) && SetMethod is { IsInitOnly: false }) { if (ContainingType.IsReadOnly) { @@ -725,13 +841,28 @@ 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); + } } } + if (!IsStatic && + ContainingType.IsInterface && + IsSetOnEitherPart(Flags.RequiresBackingField) && + // Should probably ignore initializer (and report ERR_InterfacesCantContainFields) if the + // property uses 'field' or has an auto-implemented accessor. + !IsSetOnEitherPart(Flags.HasInitializer)) + { + diagnostics.Add(ErrorCode.ERR_InterfacesCantContainFields, Location); + } + if (!IsExpressionBodied) { bool hasGetAccessor = GetMethod is object; @@ -776,8 +907,11 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, diagnostics.Add(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, Location); } } - else if (!hasGetAccessor && IsAutoProperty) + else if (!hasGetAccessor && HasAutoPropertySet) { + // 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()); } @@ -1082,7 +1216,7 @@ private SynthesizedSealedPropertyAccessor MakeSynthesizedSealedAccessor() AttributeLocation IAttributeTargetSymbol.DefaultAttributeLocation => AttributeLocation.Property; AttributeLocation IAttributeTargetSymbol.AllowedAttributeLocations - => IsAutoPropertyWithGetAccessor + => IsAutoPropertyOrUsesFieldKeyword ? AttributeLocation.Property | AttributeLocation.Field : AttributeLocation.Property; @@ -1106,17 +1240,11 @@ private CustomAttributesBag GetAttributesBag() Debug.Assert(!ReferenceEquals(copyFrom, this)); // The property is responsible for completion of the backing field - // NB: when the **field keyword feature** is implemented, it's possible that synthesized field symbols will also be merged or shared between partial property parts. - // If we do that then this check should possibly be moved, and asserts adjusted accordingly. _ = BackingField?.GetAttributes(); bool bagCreatedOnThisThread; if (copyFrom is not null) { - // When partial properties get the ability to have a backing field, - // the implementer will have to decide how the BackingField symbol works in 'copyFrom' scenarios. - Debug.Assert(!IsAutoProperty); - var attributesBag = copyFrom.GetAttributesBag(); bagCreatedOnThisThread = Interlocked.CompareExchange(ref _lazyCustomAttributesBag, attributesBag, null) == null; } @@ -1656,7 +1784,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 1dc7444d03efb..25d605aae2452 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -32,10 +32,11 @@ public SynthesizedRecordEqualityContractProperty(SourceMemberContainerTypeSymbol }, hasInitializer: false, hasExplicitAccessMod: false, - isAutoProperty: false, + hasAutoPropertyGet: false, + hasAutoPropertySet: false, isExpressionBodied: false, - isInitOnly: false, accessorsHaveImplementation: true, + usesFieldKeyword: false, RefKind.None, PropertyName, indexerNameAttributeLists: new SyntaxList(), diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index 2d905f6daf0f8..898b4c160e30a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -30,10 +30,11 @@ 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, + usesFieldKeyword: false, RefKind.None, backingParameter.Name, indexerNameAttributeLists: new SyntaxList(), diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index 323447d1aa226..ba0d9afc70e4b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs @@ -101,7 +101,13 @@ internal override Location ErrorLocation => _property.Location; protected override OneOrMany> GetAttributeDeclarations() - => _property.GetAttributeDeclarations(); + { + // The backing field for a partial property may have been calculated for either + // the definition part or the implementation part. Regardless, we should use + // the attributes from the definition part. + var property = (_property as SourcePropertySymbol)?.SourcePartialDefinitionPart ?? _property; + return property.GetAttributeDeclarations(); + } public override Symbol AssociatedSymbol => _property; @@ -149,7 +155,7 @@ internal override void PostDecodeWellKnownAttributes(ImmutableArrayCreates a LiteralExpressionSyntax node. + + + + + + SyntaxToken representing the field keyword. + + + + Class which represents the syntax node for a field expression. + + + Creates a FieldExpressionSyntax node. + + 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/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index 955520a1f2fb7..cc6acf7d8edd8 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -1737,6 +1737,11 @@ Částečná vlastnost nesmí mít víc implementujících deklarací. + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Přístupový objekt vlastnosti {0} musí být {1}, aby odpovídal definiční části. @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 ad4c4231cdad4..df7c68a5603aa 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -1737,6 +1737,11 @@ Eine partielle Eigenschaft darf nicht über mehrere implementierende Deklarationen verfügen. + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Der Eigenschaftenaccessor "{0}" muss "{1}" sein, damit er mit dem Definitionsteil übereinstimmt. @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 410e9b478870d..cc1f592a978ff 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -1737,6 +1737,11 @@ Una propiedad parcial no puede tener varias declaraciones de implementación + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part El descriptor de acceso de propiedad '{0}' debe ser '{1}' para que coincida con la parte de definición @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 1cefec947c770..ca7197bb3b027 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -1737,6 +1737,11 @@ Une propriété partielle ne peut pas avoir plusieurs déclarations d'implémentation + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part L’accesseur de propriété « {0} » doit être « {1} » pour correspondre à la partie définition @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 85b6fe5686074..62d44f3edad4d 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -1737,6 +1737,11 @@ A partial property may not have multiple implementing declarations + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Property accessor '{0}' must be '{1}' to match the definition part @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 07d43e076319e..f6d8ad651ac05 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -1737,6 +1737,11 @@ 部分プロパティには、複数の実装宣言を含めることができない場合があります + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part プロパティ アクセサー '{0}' は、定義パーツと一致するように '{1}' である必要があります @@ -2632,11 +2637,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 +2927,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. インライン配列変換演算子は、宣言型の式からの変換には使用されません。 @@ -12155,8 +12165,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 d158f9d723cb1..7580ce7779f17 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -1737,6 +1737,11 @@ 부분 속성에는 하나의 구현 선언만 사용할 수 있음 + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part 속성 접근자 '{0}'은(는) 정의 부분과 일치하려면 '{1}'이어야 합니다. @@ -2632,11 +2637,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 +2927,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. 인라인 배열 변환 연산자는 선언 형식의 식에서 변환하는 데 사용되지 않습니다. @@ -12155,8 +12165,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 29935436be650..2a5ae0c67a470 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -1737,6 +1737,11 @@ A partial property may not have multiple implementing declarations + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Property accessor '{0}' must be '{1}' to match the definition part @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 612de9d0185f8..b056de909b5d0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -1737,6 +1737,11 @@ Uma propriedade parcial não pode ter várias declarações de implementação + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part O acessador de propriedade "{0}" deve ser "{1}" para corresponder à parte da definição @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 ea25d88c0f628..b8a6c974a33c5 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -1737,6 +1737,11 @@ A partial property may not have multiple implementing declarations + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Property accessor '{0}' must be '{1}' to match the definition part @@ -2632,11 +2637,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 +2927,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. Оператор преобразования встроенного массива не будет использоваться для преобразования из выражения объявляющего типа. @@ -12156,8 +12166,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 6ccc5670de91c..368d593354b5b 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -1737,6 +1737,11 @@ Bir kısmi özellikte birden fazla uygulama bildirimi olamaz + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part '{0}' özellik erişimcisi, tanım bölümüyle eşleşmek için '{1}' olmalıdır @@ -2632,11 +2637,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 +2927,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. @@ -12155,8 +12165,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 06159052a9bc5..eed597661dae4 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -1737,6 +1737,11 @@ A partial property may not have multiple implementing declarations + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part Property accessor '{0}' must be '{1}' to match the definition part @@ -2632,11 +2637,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 +2927,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. 内联数组转换运算符将不会用于从声明类型的表达式进行转换。 @@ -12155,8 +12165,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 0062115ccf6c0..263bdcb92c0b0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -1737,6 +1737,11 @@ 部分屬性不可具有多個實作的宣告 + + A partial property cannot have an initializer on both the definition and implementation. + A partial property cannot have an initializer on both the definition and implementation. + + Property accessor '{0}' must be '{1}' to match the definition part 屬性存取子 '{0}' 必須是 '{1}' 以符合定義部分 @@ -2632,11 +2637,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 +2927,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. 內嵌陣列轉換運算子不會用於從宣告類型的運算式進行轉換。 @@ -12155,8 +12165,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 322f85516983b..5e6809f136b60 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs @@ -2160,7 +2160,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() { @@ -2188,7 +2188,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 e24e1f369bbec..0802bf3696837 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() { @@ -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/Emit/Emit/EmitMetadataTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EmitMetadataTests.cs index aeca0790f12e8..738a07c592c94 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EmitMetadataTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EmitMetadataTests.cs @@ -1294,7 +1294,7 @@ private static void VerifyAutoProperty(PropertySymbol property, bool isFromSourc { if (property is SourcePropertySymbol sourceProperty) { - Assert.True(sourceProperty.IsAutoPropertyWithGetAccessor); + Assert.True(sourceProperty.IsAutoProperty); } } else 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..26a54410af232 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -0,0 +1,7758 @@ +// 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.Collections.Immutable; +using System.Linq; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class FieldKeywordTests : CSharpTestBase + { + private static TargetFramework GetTargetFramework(bool useInit) => useInit ? TargetFramework.Net80 : TargetFramework.Standard; + + private static string IncludeExpectedOutput(string expectedOutput) => ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null; + + private static string IncludeExpectedOutput(bool useInit, string expectedOutput) => !useInit ? 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 | 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()" + }; + AssertEx.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()" + }; + AssertEx.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: True + """)); + } + + [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)); + } + + [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,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()" + }; + AssertEx.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( + // (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()" + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void FieldInInitializer_02() + { + string source = """ + class C + { + object P { get => field; } = field; + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (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, 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 + // object P { set { } } = field; + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 28)); + } + + [Fact] + public void Lambda_01() + { + string source = """ + using System; + using System.Reflection; + class C + { + public object P1 => F(() => field); + public object P2 { set { F(() => field = value); } } + public static object P3 => F(static () => field); + static object F(Func f) => f(); + } + class Program + { + static void Main() + { + Console.WriteLine((new C().P1, C.P3)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}", field.Name); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: """ + (, ) + k__BackingField + k__BackingField + k__BackingField + """); + verifier.VerifyIL("C.b__2_0()", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld "object C.k__BackingField" + IL_0006: ret + } + """); + verifier.VerifyIL("C.<>c__DisplayClass5_0.b__0()", """ + { + // Code size 21 (0x15) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldfld "C C.<>c__DisplayClass5_0.<>4__this" + IL_0006: ldarg.0 + IL_0007: ldfld "object C.<>c__DisplayClass5_0.value" + IL_000c: dup + IL_000d: stloc.0 + IL_000e: stfld "object C.k__BackingField" + IL_0013: ldloc.0 + IL_0014: ret + } + """); + verifier.VerifyIL("C.<>c.b__8_0()", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldsfld "object C.k__BackingField" + IL_0005: ret + } + """); + } + + [Fact] + public void Lambda_02() + { + string source = """ + using System; + class C + { + public object P => F(static () => field); + static object F(Func f) => f(); + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,39): error CS8821: A static anonymous function cannot contain a reference to 'this' or 'base'. + // public object P => F(static () => field); + Diagnostic(ErrorCode.ERR_StaticAnonymousFunctionCannotCaptureThis, "field").WithLocation(4, 39)); + } + + [Fact] + public void LocalFunction_01() + { + string source = """ + using System; + using System.Reflection; + class C + { + public object P + { + get + { + object F() => field; + return F(); + } + } + public static object Q + { + get + { + object F() => field; + return F(); + } + } + } + class Program + { + static void Main() + { + Console.WriteLine((new C().P, C.Q)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}", field.Name); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: """ + (, ) +

k__BackingField + k__BackingField + """); + verifier.VerifyIL("C.g__F|2_0()", """ + { + // Code size 7 (0x7) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld "object C.

k__BackingField" + IL_0006: ret + } + """); + verifier.VerifyIL("C.g__F|5_0()", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldsfld "object C.k__BackingField" + IL_0005: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ImplicitAccessorBody_01( + [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind, + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) + { + string source = $$""" + {{typeKind}} A + { + public static object P1 { get; set { _ = field; } } + public static object P2 { get { return field; } set; } + public static object P3 { get { return null; } set; } + public object Q1 { get; set { _ = field; } } + public object Q2 { get { return field; } set; } + public object Q3 { get { return field; } init; } + public object Q4 { get; set { } } + public object Q5 { get; init { } } + } + class Program + { + static void Main() + { + _ = new A(); + } + } + """; + + var comp = CreateCompilation( + source, + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + options: TestOptions.ReleaseExe, + targetFramework: TargetFramework.Net80); + + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (3,26): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static object P1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 26), + // (3,46): error CS0103: The name 'field' does not exist in the current context + // public static object P1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 46), + // (4,26): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static object P2 { get { return field; } set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 26), + // (4,44): error CS0103: The name 'field' does not exist in the current context + // public static object P2 { get { return field; } set; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(4, 44), + // (5,26): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public static object P3 { get { return null; } set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P3").WithArguments("field keyword").WithLocation(5, 26), + // (6,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public object Q1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q1").WithArguments("field keyword").WithLocation(6, 19), + // (6,39): error CS0103: The name 'field' does not exist in the current context + // public object Q1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(6, 39), + // (7,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public object Q2 { get { return field; } set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q2").WithArguments("field keyword").WithLocation(7, 19), + // (7,37): error CS0103: The name 'field' does not exist in the current context + // public object Q2 { get { return field; } set; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(7, 37), + // (8,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public object Q3 { get { return field; } init; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q3").WithArguments("field keyword").WithLocation(8, 19), + // (8,37): error CS0103: The name 'field' does not exist in the current context + // public object Q3 { get { return field; } init; } + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(8, 37), + // (9,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public object Q4 { get; set { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q4").WithArguments("field keyword").WithLocation(9, 19), + // (10,19): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public object Q5 { get; init { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q5").WithArguments("field keyword").WithLocation(10, 19)); + } + else + { + var verifier = CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("")); + verifier.VerifyIL("A.P1.get", """ + { + // Code size 6 (0x6) + .maxstack 1 + IL_0000: ldsfld "object A.k__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, LanguageVersionFacts.CSharpNext)] 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, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, bool useInit) + { + string setter = useInit ? "init" : "set "; + string source = $$""" + interface I + { + object Q1 { get; {{setter}} { _ = field; } } + object Q2 { get { return field; } {{setter}}; } + object Q3 { get; {{setter}} { } } + object Q4 { get { return null; } {{setter}}; } + } + """; + + var comp = CreateCompilation( + source, + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + targetFramework: TargetFramework.Net80); + + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // object Q1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q1").WithArguments("field keyword").WithLocation(3, 12), + // (3,12): error CS0525: Interfaces cannot contain instance fields + // object Q1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q1").WithLocation(3, 12), + // (3,33): 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, 33), + // (4,12): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // object Q2 { get { return field; } set ; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q2").WithArguments("field keyword").WithLocation(4, 12), + // (4,12): error CS0525: Interfaces cannot contain instance fields + // object Q2 { get { return field; } set ; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q2").WithLocation(4, 12), + // (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), + // (5,12): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // object Q3 { get; set { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q3").WithArguments("field keyword").WithLocation(5, 12), + // (5,12): error CS0525: Interfaces cannot contain instance fields + // object Q3 { get; set { } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q3").WithLocation(5, 12), + // (6,12): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // object Q4 { get { return null; } set ; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "Q4").WithArguments("field keyword").WithLocation(6, 12), + // (6,12): error CS0525: Interfaces cannot contain instance fields + // object Q4 { get { return null; } set ; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q4").WithLocation(6, 12)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS0525: Interfaces cannot contain instance fields + // object Q1 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q1").WithLocation(3, 12), + // (4,12): error CS0525: Interfaces cannot contain instance fields + // object Q2 { get { return field; } set ; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q2").WithLocation(4, 12), + // (5,12): error CS0525: Interfaces cannot contain instance fields + // object Q3 { get; set { } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q3").WithLocation(5, 12), + // (6,12): error CS0525: Interfaces cannot contain instance fields + // object Q4 { get { return null; } set ; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q4").WithLocation(6, 12)); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(4, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "Q1", IsAutoProperty: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "Q2", IsAutoProperty: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "Q3", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "Q4", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.Equal(languageVersion > LanguageVersion.CSharp13, ((SourcePropertySymbol)actualProperties[0]).UsesFieldKeyword); + Assert.Equal(languageVersion > LanguageVersion.CSharp13, ((SourcePropertySymbol)actualProperties[1]).UsesFieldKeyword); + Assert.False(((SourcePropertySymbol)actualProperties[2]).UsesFieldKeyword); + Assert.False(((SourcePropertySymbol)actualProperties[3]).UsesFieldKeyword); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void ImplicitAccessorBody_04( + [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind, + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] 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, LanguageVersionFacts.CSharpNext)] 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() + { + string source = """ + using System; + class A : Attribute + { + public A(object o) { } + } + 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); + 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); + + argument = attributeArguments[1]; + Assert.IsType(argument); + Assert.Equal("System.Object C.field", model.GetSymbolInfo(argument).Symbol.ToTestDisplayString()); + } + + [Fact] + public void Attribute_02() + { + string source = """ + using System; + 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 + { + const object field = null; + object P3 { [A(field)] get { return null; } set { } } + } + """; + + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (8,20): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // object P1 { [A(field)] get { return null; } set { } } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(8, 20), + // (9,41): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // 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); + var attributeArguments = tree.GetRoot().DescendantNodes().OfType().Select(arg => arg.Expression).ToArray(); + + var argument = attributeArguments[0]; + Assert.IsType(argument); + 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()); + } + + [Fact] + public void Attribute_03() + { + string source = $$""" + 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( + // (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(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(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(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(23, 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), + """)); + } + + [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 + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [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: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(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 + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [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: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(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 + } + """); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + "System.Int32 C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void Initializer_02C(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + interface I + { + 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), + // (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), + // (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), + // (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)); + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [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: GetTargetFramework(useInit)); + 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)); + } + + [Theory] + [CombinatorialData] + public void Interfaces_01(bool includeAccessibility) + { + string accessibility = includeAccessibility ? "public" : " "; + string source = $$""" + using System; + interface I + { + {{accessibility}} static int P1 { get; } + {{accessibility}} static int P2 { get => field; } + {{accessibility}} static int P3 { get => field; set; } + {{accessibility}} static int P4 { get => field; set { } } + {{accessibility}} static int P5 { get => 0; set; } + {{accessibility}} static int P6 { get; set; } + {{accessibility}} static int P7 { get; set { } } + {{accessibility}} static int P8 { set { field = value; } } + {{accessibility}} static int P9 { get { return field; } set { field = value; } } + } + class Program + { + static void Main() + { + I.P3 = 3; + I.P4 = 4; + I.P5 = 5; + I.P6 = 6; + I.P7 = 7; + I.P9 = 9; + Console.WriteLine((I.P1, I.P2, I.P3, I.P4, I.P5, I.P6, I.P7, I.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(0, 0, 3, 0, 0, 6, 0, 9)")); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void Interfaces_02(bool includeAccessibility, bool useInit) + { + string accessibility = includeAccessibility ? "public" : " "; + string setter = useInit ? "init" : "set"; + string source = $$""" + interface I + { + {{accessibility}} int P1 { get; } + {{accessibility}} int P2 { get => field; } + {{accessibility}} int P3 { get => field; {{setter}}; } + {{accessibility}} int P4 { get => field; {{setter}} { } } + {{accessibility}} int P5 { get => 0; {{setter}}; } + {{accessibility}} int P6 { get; {{setter}}; } + {{accessibility}} int P7 { get; {{setter}} { } } + {{accessibility}} int P8 { {{setter}} { field = value; } } + {{accessibility}} int P9 { get { return field; } {{setter}} { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (4,16): error CS0525: Interfaces cannot contain instance fields + // int P2 { get => field; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P2").WithLocation(4, 16), + // (5,16): error CS0525: Interfaces cannot contain instance fields + // int P3 { get => field; set; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P3").WithLocation(5, 16), + // (6,16): error CS0525: Interfaces cannot contain instance fields + // int P4 { get => field; set { } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P4").WithLocation(6, 16), + // (7,16): error CS0525: Interfaces cannot contain instance fields + // int P5 { get => 0; set; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P5").WithLocation(7, 16), + // (9,16): error CS0525: Interfaces cannot contain instance fields + // int P7 { get; set { } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P7").WithLocation(9, 16), + // (10,16): error CS0525: Interfaces cannot contain instance fields + // int P8 { set { field = value; } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P8").WithLocation(10, 16), + // (11,16): error CS0525: Interfaces cannot contain instance fields + // int P9 { get { return field; } set { field = value; } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P9").WithLocation(11, 16)); + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + "System.Int32 I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(9, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P8", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P9", IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [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); + 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: GetTargetFramework(useInit)); + 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.k__BackingField" + IL_000c: ldc.i4.3 + IL_000d: call "void C.P3.set" + IL_0012: ldc.i4.4 + IL_0013: call "void C.P4.set" + IL_0018: ldc.i4.5 + IL_0019: call "void C.P5.set" + IL_001e: ldc.i4.6 + IL_001f: call "void C.P6.set" + IL_0024: ldc.i4.7 + IL_0025: call "void C.P7.set" + IL_002a: ldc.i4.8 + IL_002b: call "void C.P8.set" + IL_0030: ldc.i4.s 9 + IL_0032: call "void C.P9.set" + IL_0037: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_02A([CombinatorialValues("class", "struct", "ref struct")] string typeKind, bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + #pragma warning disable 649 + using System; + {{typeKind}} C1 + { + public int F1; + public int P1 { get; } + public C1(int i) { P1 = i; } + public C1(int x, out int y) { y = P1; F1 = x; } + } + {{typeKind}} C2 + { + public int F2; + public int P2 { get => field; } + public C2(int i) { P2 = i; } + public C2(int x, out int y) { y = P2; F2 = x; } + } + {{typeKind}} C3 + { + public int F3; + public int P3 { get => field; {{setter}}; } + public C3(int i) { P3 = i; } + public C3(int x, out int y) { y = P3; F3 = x; } + } + {{typeKind}} C4 + { + public int F4; + public int P4 { get => field; {{setter}} { } } + public C4(int i) { P4 = i; } + public C4(int x, out int y) { y = P4; F4 = x; } + } + {{typeKind}} C5 + { + public int F5; + public int P5 { get => default; {{setter}}; } + public C5(int i) { P5 = i; } + public C5(int x, out int y) { y = P5; F5 = x; } + } + {{typeKind}} C6 + { + public int F6; + public int P6 { get; {{setter}}; } + public C6(int i) { P6 = i; } + public C6(int x, out int y) { y = P6; F6 = x; } + } + {{typeKind}} C7 + { + public int F7; + public int P7 { get; {{setter}} { } } + public C7(int i) { P7 = i; } + public C7(int x, out int y) { y = P7; F7 = x; } + } + {{typeKind}} C8 + { + public int F8; + public int P8 { {{setter}} { field = value; } } + public C8(int i) { P8 = i; } + } + {{typeKind}} C9 + { + public int F9; + public int P9 { get { return field; } {{setter}} { field = value; } } + public C9(int i) { P9 = i; } + public C9(int x, out int y) { y = P9; F9 = x; } + } + class Program + { + static void Main() + { + var c1 = new C1(1); + var c2 = new C2(2); + var c3 = new C3(3); + var c4 = new C4(4); + var c5 = new C5(5); + var c6 = new C6(6); + var c7 = new C7(7); + var c8 = new C8(8); + var c9 = new C9(9); + Console.WriteLine((c1.P1, c2.P2, c3.P3, c4.P4, c5.P5, c6.P6, c7.P7, c9.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(1, 2, 3, 0, 0, 6, 0, 9)")); + verifier.VerifyDiagnostics(); + if (typeKind == "class") + { + verifier.VerifyIL("C1..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld "int C1.k__BackingField" + IL_000d: ret + } + """); + verifier.VerifyIL("C2..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: stfld "int C2.k__BackingField" + IL_000d: ret + } + """); + verifier.VerifyIL("C3..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C3.P3.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C4..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C4.P4.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C5..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C5.P5.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C6..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C6.P6.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C7..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C7.P7.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C8..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C8.P8.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C9..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: call "void C9.P9.{{setter}}" + IL_000d: ret + } + """); + } + else + { + verifier.VerifyIL("C1..ctor(int)", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C1.F1" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: stfld "int C1.k__BackingField" + IL_000e: ret + } + """); + verifier.VerifyIL("C2..ctor(int)", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C2.F2" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: stfld "int C2.k__BackingField" + IL_000e: ret + } + """); + verifier.VerifyIL("C3..ctor(int)", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C3.F3" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void C3.P3.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("C4..ctor(int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C4.F4" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C4.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void C4.P4.{{setter}}" + IL_0015: ret + } + """); + verifier.VerifyIL("C5..ctor(int)", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C5.F5" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void C5.P5.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("C6..ctor(int)", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C6.F6" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void C6.P6.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("C7..ctor(int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.F7" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C7.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void C7.P7.{{setter}}" + IL_0015: ret + } + """); + verifier.VerifyIL("C8..ctor(int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C8.F8" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C8.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void C8.P8.{{setter}}" + IL_0015: ret + } + """); + verifier.VerifyIL("C9..ctor(int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C9.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void C9.P9.{{setter}}" + IL_0015: ret + } + """); + } + if (typeKind == "class") + { + verifier.VerifyIL("C1..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C1.P1.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C1.F1" + IL_0015: ret + } + """); + verifier.VerifyIL("C2..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C2.P2.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C2.F2" + IL_0015: ret + } + """); + verifier.VerifyIL("C3..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C3.P3.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C3.F3" + IL_0015: ret + } + """); + verifier.VerifyIL("C4..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C4.P4.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C4.F4" + IL_0015: ret + } + """); + verifier.VerifyIL("C5..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C5.P5.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C5.F5" + IL_0015: ret + } + """); + verifier.VerifyIL("C6..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C6.P6.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C6.F6" + IL_0015: ret + } + """); + verifier.VerifyIL("C7..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C7.P7.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C7.F7" + IL_0015: ret + } + """); + verifier.VerifyIL("C9..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: call "int C9.P9.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C9.F9" + IL_0015: ret + } + """); + } + else + { + verifier.VerifyIL("C1..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C1.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C1.P1.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C1.F1" + IL_0016: ret + } + """); + verifier.VerifyIL("C2..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C2.F2" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C2.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C2.P2.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C2.F2" + IL_001d: ret + } + """); + verifier.VerifyIL("C3..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C3.F3" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C3.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C3.P3.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C3.F3" + IL_001d: ret + } + """); + verifier.VerifyIL("C4..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C4.F4" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C4.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C4.P4.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C4.F4" + IL_001d: ret + } + """); + verifier.VerifyIL("C5..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C5.F5" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C5.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C5.P5.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C5.F5" + IL_001d: ret + } + """); + verifier.VerifyIL("C6..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C6.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C6.P6.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C6.F6" + IL_0016: ret + } + """); + verifier.VerifyIL("C7..ctor(int, out int)", $$""" + { + // Code size 23 (0x17) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.k__BackingField" + IL_0007: ldarg.2 + IL_0008: ldarg.0 + IL_0009: call "readonly int C7.P7.get" + IL_000e: stind.i4 + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: stfld "int C7.F7" + IL_0016: ret + } + """); + verifier.VerifyIL("C9..ctor(int, out int)", $$""" + { + // Code size 30 (0x1e) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C9.k__BackingField" + IL_000e: ldarg.2 + IL_000f: ldarg.0 + IL_0010: call "int C9.P9.get" + IL_0015: stind.i4 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: stfld "int C9.F9" + IL_001d: ret + } + """); + } + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_02B(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + #pragma warning disable 649 + using System; + class C3 + { + public int F3; + public virtual int P3 { get => field; {{setter}}; } + public C3(int i) { P3 = i; } + public C3(int x, out int y) { y = P3; F3 = x; } + } + class C6 + { + public int F6; + public virtual int P6 { get; {{setter}}; } + public C6(int i) { P6 = i; } + public C6(int x, out int y) { y = P6; F6 = x; } + } + class C9 + { + public int F9; + public virtual int P9 { get { return field; } {{setter}} { field = value; } } + public C9(int i) { P9 = i; } + public C9(int x, out int y) { y = P9; F9 = x; } + } + class Program + { + static void Main() + { + var c3 = new C3(3); + var c6 = new C6(6); + var c9 = new C9(9); + Console.WriteLine((c3.P3, c6.P6, c9.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(3, 6, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C3..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: callvirt "void C3.P3.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C6..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: callvirt "void C6.P6.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C9..ctor(int)", $$""" + { + // Code size 14 (0xe) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldarg.1 + IL_0008: callvirt "void C9.P9.{{setter}}" + IL_000d: ret + } + """); + verifier.VerifyIL("C3..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: callvirt "int C3.P3.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C3.F3" + IL_0015: ret + } + """); + verifier.VerifyIL("C6..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: callvirt "int C6.P6.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C6.F6" + IL_0015: ret + } + """); + verifier.VerifyIL("C9..ctor(int, out int)", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.2 + IL_0007: ldarg.0 + IL_0008: callvirt "int C9.P9.get" + IL_000d: stind.i4 + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: stfld "int C9.F9" + IL_0015: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_02C(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + #pragma warning disable 649 + using System; + struct C1 + { + public int F1; + public int P1 { get; } + public C1(int i) { P1 += i; F1 = i; } + } + struct C2 + { + public int F2; + public int P2 { get => field; } + public C2(int i) { P2 += i; F2 = i; } + } + struct C3 + { + public int F3; + public int P3 { get => field; {{setter}}; } + public C3(int i) { P3 += i; F3 = i; } + } + struct C6 + { + public int F6; + public int P6 { get; {{setter}}; } + public C6(int i) { P6 += i; F6 = i; } + } + struct C7 + { + public int F7; + public int P7 { get; {{setter}} { field = value; } } + public C7(int i) { P7 += i; F7 = i; } + } + struct C9 + { + public int F9; + public int P9 { get { return field; } {{setter}} { field = value; } } + public C9(int i) { P9 += i; F9 = i; } + } + struct Program + { + static void Main() + { + var c1 = new C1(1); + var c2 = new C2(2); + var c3 = new C3(3); + var c6 = new C6(6); + var c7 = new C7(7); + var c9 = new C9(9); + Console.WriteLine((c1.F1, c1.P1, c2.F2, c2.P2, c3.F3, c3.P3, c6.F6, c6.P6, c7.F7, c7.P7, c9.F9, c9.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(1, 1, 2, 2, 3, 3, 6, 6, 7, 7, 9, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C1..ctor(int)", $$""" + { + // Code size 29 (0x1d) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C1.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: call "readonly int C1.P1.get" + IL_000e: ldarg.1 + IL_000f: add + IL_0010: stfld "int C1.k__BackingField" + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: stfld "int C1.F1" + IL_001c: ret + } + """); + verifier.VerifyIL("C2..ctor(int)", $$""" + { + // Code size 29 (0x1d) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C2.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: call "int C2.P2.get" + IL_000e: ldarg.1 + IL_000f: add + IL_0010: stfld "int C2.k__BackingField" + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: stfld "int C2.F2" + IL_001c: ret + } + """); + verifier.VerifyIL("C3..ctor(int)", $$""" + { + // Code size 29 (0x1d) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C3.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: call "int C3.P3.get" + IL_000e: ldarg.1 + IL_000f: add + IL_0010: call "void C3.P3.{{setter}}" + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: stfld "int C3.F3" + IL_001c: ret + } + """); + verifier.VerifyIL("C6..ctor(int)", $$""" + { + // Code size 29 (0x1d) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C6.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.0 + IL_0009: call "readonly int C6.P6.get" + IL_000e: ldarg.1 + IL_000f: add + IL_0010: call "void C6.P6.{{setter}}" + IL_0015: ldarg.0 + IL_0016: ldarg.1 + IL_0017: stfld "int C6.F6" + IL_001c: ret + } + """); + verifier.VerifyIL("C7..ctor(int)", $$""" + { + // Code size 36 (0x24) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.F7" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C7.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.0 + IL_0010: call "readonly int C7.P7.get" + IL_0015: ldarg.1 + IL_0016: add + IL_0017: call "void C7.P7.{{setter}}" + IL_001c: ldarg.0 + IL_001d: ldarg.1 + IL_001e: stfld "int C7.F7" + IL_0023: ret + } + """); + verifier.VerifyIL("C9..ctor(int)", $$""" + { + // Code size 36 (0x24) + .maxstack 3 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C9.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.0 + IL_0010: call "int C9.P9.get" + IL_0015: ldarg.1 + IL_0016: add + IL_0017: call "void C9.P9.{{setter}}" + IL_001c: ldarg.0 + IL_001d: ldarg.1 + IL_001e: stfld "int C9.F9" + IL_0023: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_02D(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + #pragma warning disable 649 + using System; + struct C1 + { + public int F1; + public int P1 { get; } + public C1(int i) { P1++; F1 = i; } + } + struct C2 + { + public int F2; + public int P2 { get => field; } + public C2(int i) { P2++; F2 = i; } + } + struct C3 + { + public int F3; + public int P3 { get => field; {{setter}}; } + public C3(int i) { P3++; F3 = i; } + } + struct C6 + { + public int F6; + public int P6 { get; {{setter}}; } + public C6(int i) { P6++; F6 = i; } + } + struct C7 + { + public int F7; + public int P7 { get; {{setter}} { field = value; } } + public C7(int i) { P7++; F7 = i; } + } + struct C9 + { + public int F9; + public int P9 { get { return field; } {{setter}} { field = value; } } + public C9(int i) { P9++; F9 = i; } + } + struct Program + { + static void Main() + { + var c1 = new C1(1); + var c2 = new C2(2); + var c3 = new C3(3); + var c6 = new C6(6); + var c7 = new C7(7); + var c9 = new C9(9); + Console.WriteLine((c1.F1, c1.P1, c2.F2, c2.P2, c3.F3, c3.P3, c6.F6, c6.P6, c7.F7, c7.P7, c9.F9, c9.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(1, 1, 2, 1, 3, 1, 6, 1, 7, 1, 9, 1)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C1..ctor(int)", $$""" + { + // Code size 31 (0x1f) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C1.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly int C1.P1.get" + IL_000d: stloc.0 + IL_000e: ldarg.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.1 + IL_0011: add + IL_0012: stfld "int C1.k__BackingField" + IL_0017: ldarg.0 + IL_0018: ldarg.1 + IL_0019: stfld "int C1.F1" + IL_001e: ret + } + """); + verifier.VerifyIL("C2..ctor(int)", $$""" + { + // Code size 31 (0x1f) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C2.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "int C2.P2.get" + IL_000d: stloc.0 + IL_000e: ldarg.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.1 + IL_0011: add + IL_0012: stfld "int C2.k__BackingField" + IL_0017: ldarg.0 + IL_0018: ldarg.1 + IL_0019: stfld "int C2.F2" + IL_001e: ret + } + """); + verifier.VerifyIL("C3..ctor(int)", $$""" + { + // Code size 31 (0x1f) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C3.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "int C3.P3.get" + IL_000d: stloc.0 + IL_000e: ldarg.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.1 + IL_0011: add + IL_0012: call "void C3.P3.{{setter}}" + IL_0017: ldarg.0 + IL_0018: ldarg.1 + IL_0019: stfld "int C3.F3" + IL_001e: ret + } + """); + verifier.VerifyIL("C6..ctor(int)", $$""" + { + // Code size 31 (0x1f) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C6.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly int C6.P6.get" + IL_000d: stloc.0 + IL_000e: ldarg.0 + IL_000f: ldloc.0 + IL_0010: ldc.i4.1 + IL_0011: add + IL_0012: call "void C6.P6.{{setter}}" + IL_0017: ldarg.0 + IL_0018: ldarg.1 + IL_0019: stfld "int C6.F6" + IL_001e: ret + } + """); + verifier.VerifyIL("C7..ctor(int)", $$""" + { + // Code size 38 (0x26) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.F7" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C7.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "readonly int C7.P7.get" + IL_0014: stloc.0 + IL_0015: ldarg.0 + IL_0016: ldloc.0 + IL_0017: ldc.i4.1 + IL_0018: add + IL_0019: call "void C7.P7.{{setter}}" + IL_001e: ldarg.0 + IL_001f: ldarg.1 + IL_0020: stfld "int C7.F7" + IL_0025: ret + } + """); + verifier.VerifyIL("C9..ctor(int)", $$""" + { + // Code size 38 (0x26) + .maxstack 3 + .locals init (int V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C9.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "int C9.P9.get" + IL_0014: stloc.0 + IL_0015: ldarg.0 + IL_0016: ldloc.0 + IL_0017: ldc.i4.1 + IL_0018: add + IL_0019: call "void C9.P9.{{setter}}" + IL_001e: ldarg.0 + IL_001f: ldarg.1 + IL_0020: stfld "int C9.F9" + IL_0025: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_02E(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + #pragma warning disable 649 + using System; + struct C1 + { + public int F1; + public object P1 { get; } + public C1(int value) { P1 ??= value; F1 = value; } + } + struct C2 + { + public int F2; + public object P2 { get => field; } + public C2(int value) { P2 ??= value; F2 = value; } + } + struct C3 + { + public int F3; + public object P3 { get => field; {{setter}}; } + public C3(int value) { P3 ??= value; F3 = value; } + } + struct C6 + { + public int F6; + public object P6 { get; {{setter}}; } + public C6(int value) { P6 ??= value; F6 = value; } + } + struct C7 + { + public int F7; + public object P7 { get; {{setter}} { field = value; } } + public C7(int value) { P7 ??= value; F7 = value; } + } + struct C9 + { + public int F9; + public object P9 { get { return field; } {{setter}} { field = value; } } + public C9(int value) { P9 ??= value; F9 = value; } + } + struct Program + { + static void Main() + { + var c1 = new C1(1); + var c2 = new C2(2); + var c3 = new C3(3); + var c6 = new C6(6); + var c7 = new C7(7); + var c9 = new C9(9); + Console.WriteLine((c1.F1, c1.P1, c2.F2, c2.P2, c3.F3, c3.P3, c6.F6, c6.P6, c7.F7, c7.P7, c9.F9, c9.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(1, 1, 2, 2, 3, 3, 6, 6, 7, 7, 9, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C1..ctor", $$""" + { + // Code size 35 (0x23) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "object C1.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly object C1.P1.get" + IL_000d: brtrue.s IL_001b + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: box "int" + IL_0016: stfld "object C1.k__BackingField" + IL_001b: ldarg.0 + IL_001c: ldarg.1 + IL_001d: stfld "int C1.F1" + IL_0022: ret + } + """); + verifier.VerifyIL("C2..ctor", $$""" + { + // Code size 35 (0x23) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "object C2.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "object C2.P2.get" + IL_000d: brtrue.s IL_001b + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: box "int" + IL_0016: stfld "object C2.k__BackingField" + IL_001b: ldarg.0 + IL_001c: ldarg.1 + IL_001d: stfld "int C2.F2" + IL_0022: ret + } + """); + verifier.VerifyIL("C3..ctor", $$""" + { + // Code size 37 (0x25) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "object C3.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "object C3.P3.get" + IL_000d: brtrue.s IL_001d + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: box "int" + IL_0016: dup + IL_0017: stloc.0 + IL_0018: call "void C3.P3.{{setter}}" + IL_001d: ldarg.0 + IL_001e: ldarg.1 + IL_001f: stfld "int C3.F3" + IL_0024: ret + } + """); + verifier.VerifyIL("C6..ctor", $$""" + { + // Code size 37 (0x25) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldnull + IL_0002: stfld "object C6.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly object C6.P6.get" + IL_000d: brtrue.s IL_001d + IL_000f: ldarg.0 + IL_0010: ldarg.1 + IL_0011: box "int" + IL_0016: dup + IL_0017: stloc.0 + IL_0018: call "void C6.P6.{{setter}}" + IL_001d: ldarg.0 + IL_001e: ldarg.1 + IL_001f: stfld "int C6.F6" + IL_0024: ret + } + """); + verifier.VerifyIL("C7..ctor", $$""" + { + // Code size 44 (0x2c) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C7.F7" + IL_0007: ldarg.0 + IL_0008: ldnull + IL_0009: stfld "object C7.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "readonly object C7.P7.get" + IL_0014: brtrue.s IL_0024 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: box "int" + IL_001d: dup + IL_001e: stloc.0 + IL_001f: call "void C7.P7.{{setter}}" + IL_0024: ldarg.0 + IL_0025: ldarg.1 + IL_0026: stfld "int C7.F7" + IL_002b: ret + } + """); + verifier.VerifyIL("C9..ctor", $$""" + { + // Code size 44 (0x2c) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C9.F9" + IL_0007: ldarg.0 + IL_0008: ldnull + IL_0009: stfld "object C9.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "object C9.P9.get" + IL_0014: brtrue.s IL_0024 + IL_0016: ldarg.0 + IL_0017: ldarg.1 + IL_0018: box "int" + IL_001d: dup + IL_001e: stloc.0 + IL_001f: call "void C9.P9.{{setter}}" + IL_0024: ldarg.0 + IL_0025: ldarg.1 + IL_0026: stfld "int C9.F9" + IL_002b: ret + } + """); + } + + [Fact] + public void ConstructorAssignment_03() + { + string source = """ + using System; + class C + { + static int P1 => field; + int P2 => field; + static C() + { + P1 = 1; + M(() => { P1 = 2; }); + } + C(object o) + { + P2 = 3; + M(() => { P2 = 4; }); + } + static void M(Action a) + { + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (9,19): error CS0200: Property or indexer 'C.P1' cannot be assigned to -- it is read only + // M(() => { P1 = 2; }); + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P1").WithArguments("C.P1").WithLocation(9, 19), + // (14,19): error CS0200: Property or indexer 'C.P2' cannot be assigned to -- it is read only + // M(() => { P2 = 4; }); + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P2").WithArguments("C.P2").WithLocation(14, 19)); + } + + [Fact] + public void ConstructorAssignment_04() + { + string source = """ + using System; + class A + { + public static int P1 { get; private set; } + public static int P3 { get => field; private set; } + public static int P5 { get => field; private set { } } + public static int P7 { get; private set { } } + } + class B : A + { + static B() + { + P1 = 1; + P3 = 3; + P5 = 5; + P7 = 7; + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (13,9): error CS0272: The property or indexer 'A.P1' cannot be used in this context because the set accessor is inaccessible + // P1 = 1; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P1").WithArguments("A.P1").WithLocation(13, 9), + // (14,9): error CS0272: The property or indexer 'A.P3' cannot be used in this context because the set accessor is inaccessible + // P3 = 3; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P3").WithArguments("A.P3").WithLocation(14, 9), + // (15,9): error CS0272: The property or indexer 'A.P5' cannot be used in this context because the set accessor is inaccessible + // P5 = 5; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P5").WithArguments("A.P5").WithLocation(15, 9), + // (16,9): error CS0272: The property or indexer 'A.P7' cannot be used in this context because the set accessor is inaccessible + // P7 = 7; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P7").WithArguments("A.P7").WithLocation(16, 9)); + } + + [Fact] + public void ConstructorAssignment_05() + { + string source = """ + using System; + class A + { + public int P1 { get; private set; } + public int P2 { get; private init; } + public int P3 { get => field; private set; } + public int P4 { get => field; private init; } + public int P5 { get => field; private set { } } + public int P6 { get => field; private init { } } + public int P7 { get; private set { } } + public int P8 { get; private init { } } + } + class B : A + { + public B() + { + P1 = 1; + P2 = 2; + P3 = 3; + P4 = 4; + P5 = 5; + P6 = 6; + P7 = 7; + P8 = 8; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (17,9): error CS0272: The property or indexer 'A.P1' cannot be used in this context because the set accessor is inaccessible + // P1 = 1; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P1").WithArguments("A.P1").WithLocation(17, 9), + // (18,9): error CS0272: The property or indexer 'A.P2' cannot be used in this context because the set accessor is inaccessible + // P2 = 2; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P2").WithArguments("A.P2").WithLocation(18, 9), + // (19,9): error CS0272: The property or indexer 'A.P3' cannot be used in this context because the set accessor is inaccessible + // P3 = 3; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P3").WithArguments("A.P3").WithLocation(19, 9), + // (20,9): error CS0272: The property or indexer 'A.P4' cannot be used in this context because the set accessor is inaccessible + // P4 = 4; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P4").WithArguments("A.P4").WithLocation(20, 9), + // (21,9): error CS0272: The property or indexer 'A.P5' cannot be used in this context because the set accessor is inaccessible + // P5 = 5; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P5").WithArguments("A.P5").WithLocation(21, 9), + // (22,9): error CS0272: The property or indexer 'A.P6' cannot be used in this context because the set accessor is inaccessible + // P6 = 6; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P6").WithArguments("A.P6").WithLocation(22, 9), + // (23,9): error CS0272: The property or indexer 'A.P7' cannot be used in this context because the set accessor is inaccessible + // P7 = 7; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P7").WithArguments("A.P7").WithLocation(23, 9), + // (24,9): error CS0272: The property or indexer 'A.P8' cannot be used in this context because the set accessor is inaccessible + // P8 = 8; + Diagnostic(ErrorCode.ERR_InaccessibleSetter, "P8").WithArguments("A.P8").WithLocation(24, 9)); + } + + [Fact] + public void ConstructorAssignment_06() + { + string source = $$""" + class A + { + public object P1 { get; } + public object P2 { get => field; } + public object P3 { get => field; init; } + public A() + { + this.P1 = 11; + this.P2 = 12; + this.P3 = 13; + } + A(A a) + { + a.P1 = 31; + a.P2 = 32; + a.P3 = 33; + } + } + class B : A + { + B() + { + base.P1 = 21; + base.P2 = 22; + base.P3 = 23; + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (14,9): error CS0200: Property or indexer 'A.P1' cannot be assigned to -- it is read only + // a.P1 = 31; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "a.P1").WithArguments("A.P1").WithLocation(14, 9), + // (15,9): error CS0200: Property or indexer 'A.P2' cannot be assigned to -- it is read only + // a.P2 = 32; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "a.P2").WithArguments("A.P2").WithLocation(15, 9), + // (16,9): error CS8852: Init-only property or indexer 'A.P3' can only be assigned in an object initializer, or on 'this' or 'base' in an instance constructor or an 'init' accessor. + // a.P3 = 33; + Diagnostic(ErrorCode.ERR_AssignmentInitOnly, "a.P3").WithArguments("A.P3").WithLocation(16, 9), + // (23,9): error CS0200: Property or indexer 'A.P1' cannot be assigned to -- it is read only + // base.P1 = 21; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "base.P1").WithArguments("A.P1").WithLocation(23, 9), + // (24,9): error CS0200: Property or indexer 'A.P2' cannot be assigned to -- it is read only + // base.P2 = 22; + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "base.P2").WithArguments("A.P2").WithLocation(24, 9)); + } + + [Fact] + public void ConstructorAssignment_07() + { + string source = $$""" + using System; + class 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 = F( + P1 = 1, + P2 = 2, + P3 = 3, + P4 = 4); + static int F(int x, int y, int z, int w) => x; + } + class Program + { + static void Main() + { + Console.WriteLine((C.P1, C.P2, C.P3, C.P4)); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "(1, 2, 3, 0)"); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..cctor", """ + { + // Code size 39 (0x27) + .maxstack 5 + IL_0000: ldc.i4.1 + IL_0001: dup + IL_0002: stsfld "int C.k__BackingField" + IL_0007: ldc.i4.2 + IL_0008: dup + IL_0009: stsfld "int C.k__BackingField" + IL_000e: ldc.i4.3 + IL_000f: dup + IL_0010: call "void C.P3.set" + IL_0015: ldc.i4.4 + IL_0016: dup + IL_0017: call "void C.P4.set" + IL_001c: call "int C.F(int, int, int, int)" + IL_0021: stsfld "int C.P5" + IL_0026: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void DefaultInitialization_01A(bool useInit, bool includeStructInitializationWarnings) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + struct S1 + { + public int P1 { get; } + public S1(int unused) { _ = P1; } + } + struct S2 + { + public int P2 { get => field; } + public S2(int unused) { _ = P2; } + } + struct S3 + { + public int P3 { get; {{setter}}; } + public S3(int unused) { _ = P3; } + } + struct S4 + { + public int P4 { get => field; {{setter}} { field = value; } } + public S4(int unused) { _ = P4; } + } + struct S5 + { + public int P5 { get; {{setter}} { field = value; } } + public S5(int unused) { _ = P5; } + } + struct S6 + { + public int P6 { get => field; {{setter}}; } + public S6(int unused) { _ = P6; } + } + class Program + { + static void Main() + { + var s1 = new S1(1); + var s2 = new S2(2); + var s3 = new S3(3); + var s4 = new S4(4); + var s5 = new S5(5); + var s6 = new S6(6); + Console.WriteLine((s1.P1, s2.P2, s3.P3, s4.P4, s5.P5, s6.P6)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(0, 0, 0, 0, 0, 0)")); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (5,12): warning CS9021: Control is returned to caller before auto-implemented property 'S1.P1' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S1").WithArguments("S1.P1").WithLocation(5, 12), + // (5,33): warning CS9018: Auto-implemented property 'P1' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P1").WithArguments("P1").WithLocation(5, 33), + // (10,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S2(int unused) { _ = P2; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P2").WithLocation(10, 33), + // (15,12): warning CS9021: Control is returned to caller before auto-implemented property 'S3.P3' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S3(int unused) { _ = P3; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S3").WithArguments("S3.P3").WithLocation(15, 12), + // (15,33): warning CS9018: Auto-implemented property 'P3' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S3(int unused) { _ = P3; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P3").WithArguments("P3").WithLocation(15, 33), + // (20,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S4(int unused) { _ = P4; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P4").WithLocation(20, 33), + // (25,12): warning CS9021: Control is returned to caller before auto-implemented property 'S5.P5' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S5(int unused) { _ = P5; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S5").WithArguments("S5.P5").WithLocation(25, 12), + // (25,33): warning CS9018: Auto-implemented property 'P5' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S5(int unused) { _ = P5; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P5").WithArguments("P5").WithLocation(25, 33), + // (30,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S6(int unused) { _ = P6; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P6").WithLocation(30, 33)); + } + else + { + verifier.VerifyDiagnostics(); + } + verifier.VerifyIL("S1..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S1.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly int S1.P1.get" + IL_000d: pop + IL_000e: ret + } + """); + verifier.VerifyIL("S2..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S2.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "int S2.P2.get" + IL_000d: pop + IL_000e: ret + } + """); + verifier.VerifyIL("S3..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S3.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly int S3.P3.get" + IL_000d: pop + IL_000e: ret + } + """); + verifier.VerifyIL("S4..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S4.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "int S4.P4.get" + IL_000d: pop + IL_000e: ret + } + """); + verifier.VerifyIL("S5..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S5.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "readonly int S5.P5.get" + IL_000d: pop + IL_000e: ret + } + """); + verifier.VerifyIL("S6..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S6.k__BackingField" + IL_0007: ldarg.0 + IL_0008: call "int S6.P6.get" + IL_000d: pop + IL_000e: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void DefaultInitialization_01B(bool useInit, bool includeStructInitializationWarnings) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + struct S1 + { + public int F1; + public int P1 { get; } + public S1(int unused) { _ = P1; } + } + struct S2 + { + public int F2; + public int P2 { get => field; } + public S2(int unused) { _ = P2; } + } + class Program + { + static void Main() + { + var s1 = new S1(1); + var s2 = new S2(2); + Console.WriteLine((s1.F1, s1.P1)); + Console.WriteLine((s2.F2, s2.P2)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, """ + (0, 0) + (0, 0) + """)); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S1.F1' is never assigned to, and will always have its default value 0 + // public int F1; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("S1.F1", "0").WithLocation(4, 16), + // (6,12): warning CS9021: Control is returned to caller before auto-implemented property 'S1.P1' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S1").WithArguments("S1.P1").WithLocation(6, 12), + // (6,12): warning CS9022: Control is returned to caller before field 'S1.F1' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S1").WithArguments("S1.F1").WithLocation(6, 12), + // (6,33): warning CS9018: Auto-implemented property 'P1' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P1").WithArguments("P1").WithLocation(6, 33), + // (10,16): warning CS0649: Field 'S2.F2' is never assigned to, and will always have its default value 0 + // public int F2; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F2").WithArguments("S2.F2", "0").WithLocation(10, 16), + // (12,33): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S2(int unused) { _ = P2; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P2").WithLocation(12, 33)); + } + else + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S1.F1' is never assigned to, and will always have its default value 0 + // public int F1; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F1").WithArguments("S1.F1", "0").WithLocation(4, 16), + // (10,16): warning CS0649: Field 'S2.F2' is never assigned to, and will always have its default value 0 + // public int F2; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F2").WithArguments("S2.F2", "0").WithLocation(10, 16)); + } + verifier.VerifyIL("S1..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S1.F1" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S1.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "readonly int S1.P1.get" + IL_0014: pop + IL_0015: ret + } + """); + verifier.VerifyIL("S2..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S2.F2" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S2.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "int S2.P2.get" + IL_0014: pop + IL_0015: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void DefaultInitialization_02A(bool useInit, bool includeStructInitializationWarnings) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + struct S1 + { + public int P1 { get; } + public S1(int i) { P1 = i; } + } + struct S2 + { + public int P2 { get => field; } + public S2(int i) { P2 = i; } + } + struct S3 + { + public int P3 { get; {{setter}}; } + public S3(int i) { P3 = i; } + } + struct S4 + { + public int P4 { get => field; {{setter}} { field = value; } } + public S4(int i) { P4 = i; } + } + struct S5 + { + public int P5 { get; {{setter}} { field = value; } } + public S5(int i) { P5 = i; } + } + struct S6 + { + public int P6 { get => field; {{setter}}; } + public S6(int i) { P6 = i; } + } + struct S7 + { + public int P7 { {{setter}} { field = value; } } + public S7(int i) { P7 = i; } + } + class Program + { + static void Main() + { + var s1 = new S1(1); + var s2 = new S2(2); + var s3 = new S3(3); + var s4 = new S4(4); + var s5 = new S5(5); + var s6 = new S6(6); + var s7 = new S7(7); + Console.WriteLine((s1.P1, s2.P2, s3.P3, s4.P4, s5.P5, s6.P6)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, "(1, 2, 3, 4, 5, 6)")); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (20,24): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S4(int i) { P4 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P4").WithLocation(20, 24), + // (25,24): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S5(int i) { P5 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P5").WithLocation(25, 24), + // (35,24): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S7(int i) { P7 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P7").WithLocation(35, 24)); + } + else + { + verifier.VerifyDiagnostics(); + } + verifier.VerifyIL("S1..ctor", $$""" + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld "int S1.k__BackingField" + IL_0007: ret + } + """); + verifier.VerifyIL("S2..ctor", $$""" + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: stfld "int S2.k__BackingField" + IL_0007: ret + } + """); + verifier.VerifyIL("S3..ctor", $$""" + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "void S3.P3.{{setter}}" + IL_0007: ret + } + """); + verifier.VerifyIL("S4..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S4.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S4.P4.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("S5..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S5.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S5.P5.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("S6..ctor", $$""" + { + // Code size 8 (0x8) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldarg.1 + IL_0002: call "void S6.P6.{{setter}}" + IL_0007: ret + } + """); + verifier.VerifyIL("S7..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S7.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S7.P7.{{setter}}" + IL_000e: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void DefaultInitialization_02B(bool useInit, bool includeStructInitializationWarnings) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + struct S3 + { + public int F3; + public int P3 { get; {{setter}}; } + public S3(int i) { P3 = i; } + } + struct S4 + { + public int F4; + public int P4 { get => field; {{setter}} { field = value; } } + public S4(int i) { P4 = i; } + } + class Program + { + static void Main() + { + var s3 = new S3(3); + var s4 = new S4(4); + Console.WriteLine((s3.F3, s3.P3)); + Console.WriteLine((s4.F4, s4.P4)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, """ + (0, 3) + (0, 4) + """)); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S3.F3' is never assigned to, and will always have its default value 0 + // public int F3; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F3").WithArguments("S3.F3", "0").WithLocation(4, 16), + // (6,12): warning CS9022: Control is returned to caller before field 'S3.F3' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S3(int i) { P3 = i; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S3").WithArguments("S3.F3").WithLocation(6, 12), + // (10,16): warning CS0649: Field 'S4.F4' is never assigned to, and will always have its default value 0 + // public int F4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F4").WithArguments("S4.F4", "0").WithLocation(10, 16), + // (12,24): warning CS9020: The 'this' object is read before all of its fields have been assigned, causing preceding implicit assignments of 'default' to non-explicitly assigned fields. + // public S4(int i) { P4 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P4").WithLocation(12, 24)); + } + else + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S3.F3' is never assigned to, and will always have its default value 0 + // public int F3; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F3").WithArguments("S3.F3", "0").WithLocation(4, 16), + // (10,16): warning CS0649: Field 'S4.F4' is never assigned to, and will always have its default value 0 + // public int F4; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F4").WithArguments("S4.F4", "0").WithLocation(10, 16)); + } + verifier.VerifyIL("S3..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S3.F3" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S3.P3.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("S4..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S4.F4" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S4.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void S4.P4.{{setter}}" + IL_0015: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_01(bool useReadOnlyType, bool useReadOnlyProperty, bool useInit) + { + static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; + string typeModifier = getReadOnlyModifier(useReadOnlyType); + string propertyModifier = getReadOnlyModifier(useReadOnlyProperty); + string setter = useInit ? "init" : "set"; + string source = $$""" + {{typeModifier}} struct S + { + {{propertyModifier}} object P1 { get; } + {{propertyModifier}} object P2 { get => field; } + {{propertyModifier}} object P3 { get => field; {{setter}}; } + {{propertyModifier}} object P4 { get => field; {{setter}} { } } + {{propertyModifier}} object P5 { get => null; } + {{propertyModifier}} object P6 { get => null; {{setter}}; } + {{propertyModifier}} object P7 { get => null; {{setter}} { } } + {{propertyModifier}} object P8 { get => null; {{setter}} { _ = field; } } + {{propertyModifier}} object P9 { get; {{setter}}; } + {{propertyModifier}} object PA { get; {{setter}} { } } + {{propertyModifier}} object PB { {{setter}} { _ = field; } } + {{propertyModifier}} object PC { get; {{setter}} { field = value; } } + {{propertyModifier}} object PD { {{setter}} { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + if (useInit) + { + comp.VerifyEmitDiagnostics(); + } + else if (useReadOnlyType) + { + comp.VerifyEmitDiagnostics( + // (5,21): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P3 { get => field; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P3").WithLocation(5, 21), + // (8,21): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P6 { get => null; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P6").WithLocation(8, 21), + // (11,21): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P9 { get; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P9").WithLocation(11, 21), + // (14,37): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object PC { get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(14, 37), + // (15,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object PD { set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(15, 32)); + } + else if (useReadOnlyProperty) + { + comp.VerifyEmitDiagnostics( + // (5,21): error CS8659: Auto-implemented property 'S.P3' cannot be marked 'readonly' because it has a 'set' accessor. + // readonly object P3 { get => field; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyWithSetterCantBeReadOnly, "P3").WithArguments("S.P3").WithLocation(5, 21), + // (8,21): error CS8659: Auto-implemented property 'S.P6' cannot be marked 'readonly' because it has a 'set' accessor. + // readonly object P6 { get => null; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyWithSetterCantBeReadOnly, "P6").WithArguments("S.P6").WithLocation(8, 21), + // (11,21): error CS8659: Auto-implemented property 'S.P9' cannot be marked 'readonly' because it has a 'set' accessor. + // readonly object P9 { get; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyWithSetterCantBeReadOnly, "P9").WithArguments("S.P9").WithLocation(11, 21), + // (14,37): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // readonly object PC { get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(14, 37), + // (15,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // readonly object PD { set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(15, 32)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty || useInit}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty || useInit}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty || useInit}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty || useInit}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyProperty || useInit}", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) + { + static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; + string typeModifier = getReadOnlyModifier(useReadOnlyType); + string getModifier = getReadOnlyModifier(useReadOnlyOnGet); + string setModifier = getReadOnlyModifier(!useReadOnlyOnGet); + string source = $$""" + {{typeModifier}} struct S + { + object P3 { {{getModifier}} get => field; {{setModifier}} set; } + object P4 { {{getModifier}} get => field; {{setModifier}} set { } } + object P6 { {{getModifier}} get => null; {{setModifier}} set; } + object P7 { {{getModifier}} get => null; {{setModifier}} set { } } + object P8 { {{getModifier}} get => null; {{setModifier}} set { _ = field; } } + object P9 { {{getModifier}} get; {{setModifier}} set; } + object PA { {{getModifier}} get; {{setModifier}} set { } } + object PC { {{getModifier}} get; {{setModifier}} set { field = value; } } + } + """; + var comp = CreateCompilation(source); + if (useReadOnlyType) + { + if (useReadOnlyOnGet) + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P3 { readonly get => field; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P3").WithLocation(3, 12), + // (5,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P6 { readonly get => null; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P6").WithLocation(5, 12), + // (8,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P9 { readonly get; set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P9").WithLocation(8, 12), + // (10,46): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object PC { readonly get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P3 { get => field; readonly set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P3").WithLocation(3, 12), + // (3,49): error CS8658: Auto-implemented 'set' accessor 'S.P3.set' cannot be marked 'readonly'. + // object P3 { get => field; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P3.set").WithLocation(3, 49), + // (5,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P6 { get => null; readonly set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P6").WithLocation(5, 12), + // (5,48): error CS8658: Auto-implemented 'set' accessor 'S.P6.set' cannot be marked 'readonly'. + // object P6 { get => null; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P6.set").WithLocation(5, 48), + // (8,12): error CS8341: Auto-implemented instance properties in readonly structs must be readonly. + // object P9 { get; readonly set; } + Diagnostic(ErrorCode.ERR_AutoPropsInRoStruct, "P9").WithLocation(8, 12), + // (8,40): error CS8658: Auto-implemented 'set' accessor 'S.P9.set' cannot be marked 'readonly'. + // object P9 { get; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P9.set").WithLocation(8, 40), + // (10,46): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object PC { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); + } + } + else + { + if (useReadOnlyOnGet) + { + comp.VerifyEmitDiagnostics(); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,49): error CS8658: Auto-implemented 'set' accessor 'S.P3.set' cannot be marked 'readonly'. + // object P3 { get => field; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P3.set").WithLocation(3, 49), + // (5,48): error CS8658: Auto-implemented 'set' accessor 'S.P6.set' cannot be marked 'readonly'. + // object P6 { get => null; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P6.set").WithLocation(5, 48), + // (8,40): error CS8658: Auto-implemented 'set' accessor 'S.P9.set' cannot be marked 'readonly'. + // object P9 { get; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P9.set").WithLocation(8, 40), + // (10,46): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object PC { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); + } + } + var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType || !useReadOnlyOnGet}", + $"System.Object S.k__BackingField: {useReadOnlyType || !useReadOnlyOnGet}", + $"System.Object S.k__BackingField: {useReadOnlyType || !useReadOnlyOnGet}", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_03(bool useRefStruct, bool useReadOnlyType, bool useReadOnlyMember) + { + static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; + string typeKind = useRefStruct ? "ref struct" : " struct"; + string typeModifier = getReadOnlyModifier(useReadOnlyType); + string memberModifier = getReadOnlyModifier(useReadOnlyMember); + string sourceA = $$""" + {{typeModifier}} {{typeKind}} S + { + {{memberModifier}} object P1 { get; } + {{memberModifier}} object P5 { get; init; } + object P7 { {{memberModifier}} get; init; } + {{memberModifier}} object Q1 { get => field; } + {{memberModifier}} object Q2 { set { _ = field; } } + {{memberModifier}} object Q3 { init { _ = field; } } + {{memberModifier}} object Q4 { get; set { _ = field; } } + {{memberModifier}} object Q5 { get; init { _ = field; } } + object Q6 { {{memberModifier}} get; set { _ = field; } } + object Q7 { {{memberModifier}} get; init { _ = field; } } + object Q8 { get; {{memberModifier}} set { _ = field; } } + } + """; + string sourceB = """ + using System; + using System.Reflection; + class Program + { + static void Main() + { + foreach (var field in typeof(S).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly); + } + } + """; + var verifier = CompileAndVerify( + [sourceA, sourceB], + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput($$""" + k__BackingField: True + k__BackingField: True + k__BackingField: True + k__BackingField: {{useReadOnlyType || useReadOnlyMember}} + k__BackingField: {{useReadOnlyType || useReadOnlyMember}} + k__BackingField: True + k__BackingField: {{useReadOnlyType || useReadOnlyMember}} + k__BackingField: True + k__BackingField: {{useReadOnlyType}} + k__BackingField: True + k__BackingField: {{useReadOnlyType || useReadOnlyMember}} + """)); + var comp = (CSharpCompilation)verifier.Compilation; + var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyMember}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyMember}", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyMember}", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyMember}", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_04(bool useRefStruct, bool useReadOnlyType, bool useReadOnlyMember) + { + static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; + string typeKind = useRefStruct ? "ref struct" : " struct"; + string typeModifier = getReadOnlyModifier(useReadOnlyType); + string memberModifier = getReadOnlyModifier(useReadOnlyMember); + string sourceA = $$""" + {{typeModifier}} {{typeKind}} S + { + static {{memberModifier}} object P1 { get; } + static {{memberModifier}} object Q1 { get => field; } + static {{memberModifier}} object Q2 { set { _ = field; } } + static {{memberModifier}} object Q4 { get; set { _ = field; } } + static object Q6 { {{memberModifier}} get; set { _ = field; } } + static object Q8 { get; {{memberModifier}} set { _ = field; } } + } + """; + string sourceB = """ + using System; + using System.Reflection; + class Program + { + static void Main() + { + foreach (var field in typeof(S).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly); + } + } + """; + var comp = CreateCompilation([sourceA, sourceB], options: TestOptions.ReleaseExe); + if (useReadOnlyMember) + { + comp.VerifyEmitDiagnostics( + // (3,28): error CS8657: Static member 'S.P1' cannot be marked 'readonly'. + // static readonly object P1 { get; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P1").WithArguments("S.P1").WithLocation(3, 28), + // (4,28): error CS8657: Static member 'S.Q1' cannot be marked 'readonly'. + // static readonly object Q1 { get => field; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "Q1").WithArguments("S.Q1").WithLocation(4, 28), + // (5,28): error CS8657: Static member 'S.Q2' cannot be marked 'readonly'. + // static readonly object Q2 { set { _ = field; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "Q2").WithArguments("S.Q2").WithLocation(5, 28), + // (6,28): error CS8657: Static member 'S.Q4' cannot be marked 'readonly'. + // static readonly object Q4 { get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "Q4").WithArguments("S.Q4").WithLocation(6, 28), + // (7,33): error CS8657: Static member 'S.Q6.get' cannot be marked 'readonly'. + // static object Q6 { readonly get; set { _ = field; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.Q6.get").WithLocation(7, 33), + // (8,38): error CS8657: Static member 'S.Q8.set' cannot be marked 'readonly'. + // static object Q8 { get; readonly set { _ = field; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.Q8.set").WithLocation(8, 38)); + } + else + { + comp.VerifyEmitDiagnostics(); + var verifier = CompileAndVerify(comp, expectedOutput: $$""" + k__BackingField: True + k__BackingField: {{useReadOnlyMember}} + k__BackingField: {{useReadOnlyMember}} + k__BackingField: {{useReadOnlyMember}} + k__BackingField: False + k__BackingField: {{useReadOnlyMember}} + """); + } + var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useReadOnlyMember}", + $"System.Object S.k__BackingField: {useReadOnlyMember}", + $"System.Object S.k__BackingField: {useReadOnlyMember}", + $"System.Object S.k__BackingField: False", + $"System.Object S.k__BackingField: {useReadOnlyMember}", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_05(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + struct S + { + object P1 { readonly get; } + object P2 { readonly {{setter}}; } + object P3 { readonly get; {{setter}}; } + object P4 { get; readonly {{setter}}; } + object P5 { readonly get; readonly {{setter}}; } + object Q1 { readonly get => field; } + object Q2 { readonly {{setter}} { _ = field; } } + object Q3 { readonly get => field; {{setter}}; } + object Q4 { get; readonly {{setter}} { } } + object Q5 { readonly get => field; readonly {{setter}} { } } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + if (useInit) + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS8664: 'S.P1': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P1 { readonly get; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P1").WithArguments("S.P1").WithLocation(3, 12), + // (4,12): error CS8664: 'S.P2': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P2 { readonly init; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P2").WithArguments("S.P2").WithLocation(4, 12), + // (4,26): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.P2' readonly instead. + // object P2 { readonly init; } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.P2").WithLocation(4, 26), + // (4,26): error CS8051: Auto-implemented properties must have get accessors. + // object P2 { readonly init; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "init").WithLocation(4, 26), + // (6,31): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.P4' readonly instead. + // object P4 { get; readonly init; } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.P4").WithLocation(6, 31), + // (7,12): error CS8661: Cannot specify 'readonly' modifiers on both accessors of property or indexer 'S.P5'. Instead, put a 'readonly' modifier on the property itself. + // object P5 { readonly get; readonly init; } + Diagnostic(ErrorCode.ERR_DuplicatePropertyReadOnlyMods, "P5").WithArguments("S.P5").WithLocation(7, 12), + // (7,40): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.P5' readonly instead. + // object P5 { readonly get; readonly init; } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.P5").WithLocation(7, 40), + // (8,12): error CS8664: 'S.Q1': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object Q1 { readonly get => field; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "Q1").WithArguments("S.Q1").WithLocation(8, 12), + // (9,12): error CS8664: 'S.Q2': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object Q2 { readonly init { _ = field; } } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "Q2").WithArguments("S.Q2").WithLocation(9, 12), + // (9,26): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.Q2' readonly instead. + // object Q2 { readonly init { _ = field; } } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.Q2").WithLocation(9, 26), + // (11,31): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.Q4' readonly instead. + // object Q4 { get; readonly init { } } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.Q4").WithLocation(11, 31), + // (12,12): error CS8661: Cannot specify 'readonly' modifiers on both accessors of property or indexer 'S.Q5'. Instead, put a 'readonly' modifier on the property itself. + // object Q5 { readonly get => field; readonly init { } } + Diagnostic(ErrorCode.ERR_DuplicatePropertyReadOnlyMods, "Q5").WithArguments("S.Q5").WithLocation(12, 12), + // (12,49): error CS8903: 'init' accessors cannot be marked 'readonly'. Mark 'S.Q5' readonly instead. + // object Q5 { readonly get => field; readonly init { } } + Diagnostic(ErrorCode.ERR_InitCannotBeReadonly, "init").WithArguments("S.Q5").WithLocation(12, 49)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,12): error CS8664: 'S.P1': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P1 { readonly get; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P1").WithArguments("S.P1").WithLocation(3, 12), + // (4,12): error CS8664: 'S.P2': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P2 { readonly set; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P2").WithArguments("S.P2").WithLocation(4, 12), + // (4,26): error CS8658: Auto-implemented 'set' accessor 'S.P2.set' cannot be marked 'readonly'. + // object P2 { readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P2.set").WithLocation(4, 26), + // (4,26): error CS8051: Auto-implemented properties must have get accessors. + // object P2 { readonly set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, "set").WithLocation(4, 26), + // (6,31): error CS8658: Auto-implemented 'set' accessor 'S.P4.set' cannot be marked 'readonly'. + // object P4 { get; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P4.set").WithLocation(6, 31), + // (7,12): error CS8661: Cannot specify 'readonly' modifiers on both accessors of property or indexer 'S.P5'. Instead, put a 'readonly' modifier on the property itself. + // object P5 { readonly get; readonly set; } + Diagnostic(ErrorCode.ERR_DuplicatePropertyReadOnlyMods, "P5").WithArguments("S.P5").WithLocation(7, 12), + // (7,40): error CS8658: Auto-implemented 'set' accessor 'S.P5.set' cannot be marked 'readonly'. + // object P5 { readonly get; readonly set; } + Diagnostic(ErrorCode.ERR_AutoSetterCantBeReadOnly, "set").WithArguments("S.P5.set").WithLocation(7, 40), + // (8,12): error CS8664: 'S.Q1': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object Q1 { readonly get => field; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "Q1").WithArguments("S.Q1").WithLocation(8, 12), + // (9,12): error CS8664: 'S.Q2': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object Q2 { readonly set { _ = field; } } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "Q2").WithArguments("S.Q2").WithLocation(9, 12), + // (12,12): error CS8661: Cannot specify 'readonly' modifiers on both accessors of property or indexer 'S.Q5'. Instead, put a 'readonly' modifier on the property itself. + // object Q5 { readonly get => field; readonly set { } } + Diagnostic(ErrorCode.ERR_DuplicatePropertyReadOnlyMods, "Q5").WithArguments("S.Q5").WithLocation(12, 12)); + } + var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: True", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void ReadOnly_06(bool useStatic) + { + string propertyModifier = useStatic ? "static" : " "; + string source = $$""" + readonly class C1 + { + {{propertyModifier}} object P1 { get; } + {{propertyModifier}} object P2 { get; set; } + {{propertyModifier}} object P3 { get => field; } + {{propertyModifier}} object P4 { set { field = value; } } + } + class C2 + { + {{propertyModifier}} readonly object P1 { get; } + {{propertyModifier}} readonly object P2 { get; set; } + {{propertyModifier}} readonly object P3 { get => field; } + {{propertyModifier}} readonly object P4 { set { field = value; } } + {{propertyModifier}} object P5 { readonly get; set { field = value; } } + {{propertyModifier}} object P6 { get; readonly set { field = value; } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (1,16): error CS0106: The modifier 'readonly' is not valid for this item + // readonly class C1 + Diagnostic(ErrorCode.ERR_BadMemberFlag, "C1").WithArguments("readonly").WithLocation(1, 16), + // (10,28): error CS0106: The modifier 'readonly' is not valid for this item + // readonly object P1 { get; } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "P1").WithArguments("readonly").WithLocation(10, 28), + // (11,28): error CS0106: The modifier 'readonly' is not valid for this item + // readonly object P2 { get; set; } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "P2").WithArguments("readonly").WithLocation(11, 28), + // (12,28): error CS0106: The modifier 'readonly' is not valid for this item + // readonly object P3 { get => field; } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "P3").WithArguments("readonly").WithLocation(12, 28), + // (13,28): error CS0106: The modifier 'readonly' is not valid for this item + // readonly object P4 { set { field = value; } } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "P4").WithArguments("readonly").WithLocation(13, 28), + // (14,33): error CS0106: The modifier 'readonly' is not valid for this item + // object P5 { readonly get; set { field = value; } } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "get").WithArguments("readonly").WithLocation(14, 33), + // (15,38): error CS0106: The modifier 'readonly' is not valid for this item + // object P6 { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_BadMemberFlag, "set").WithArguments("readonly").WithLocation(15, 38)); + var actualMembers = comp.GetMember("C1").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + var expectedMembers = new[] + { + $"System.Object C1.k__BackingField: True", + $"System.Object C1.k__BackingField: False", + $"System.Object C1.k__BackingField: False", + $"System.Object C1.k__BackingField: False", + }; + AssertEx.Equal(expectedMembers, actualMembers); + actualMembers = comp.GetMember("C2").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); + expectedMembers = new[] + { + $"System.Object C2.k__BackingField: True", + $"System.Object C2.k__BackingField: False", + $"System.Object C2.k__BackingField: False", + $"System.Object C2.k__BackingField: False", + $"System.Object C2.k__BackingField: False", + $"System.Object C2.k__BackingField: False", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void ReadOnly_07() + { + string source = """ + struct S0 + { + object P0 { get { field = null; return null; } } + } + struct S1 + { + object P1 { readonly get { field = null; return null; } } + } + struct S2 + { + readonly object P2 { get { field = null; return null; } } + } + readonly struct S3 + { + object P3 { get { field = null; return null; } } + } + readonly struct S4 + { + object P4 { readonly get { field = null; return null; } } + } + readonly struct S5 + { + readonly object P5 { get { field = null; return null; } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (7,12): error CS8664: 'S1.P1': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P1 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P1").WithArguments("S1.P1").WithLocation(7, 12), + // (7,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object P1 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(7, 32), + // (11,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // readonly object P2 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(11, 32), + // (15,23): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object P3 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(15, 23), + // (19,12): error CS8664: 'S4.P4': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // object P4 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P4").WithArguments("S4.P4").WithLocation(19, 12), + // (19,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // object P4 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(19, 32), + // (23,32): error CS0191: A readonly field cannot be assigned to (except in a constructor or init-only setter of the type in which the field is defined or a variable initializer) + // readonly object P5 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(23, 32)); + } + + [Theory] + [CombinatorialData] + public void RefReturning_01(bool useStruct, bool useRefReadOnly) + { + string type = useStruct ? "struct" : "class"; + string refModifier = useRefReadOnly ? "ref readonly" : "ref "; + string source = $$""" + {{type}} S + { + {{refModifier}} object P1 { get; } + {{refModifier}} object P2 { get => ref field; } + {{refModifier}} object P3 { get => ref field; set; } + {{refModifier}} object P4 { get => ref field; init; } + {{refModifier}} object P5 { get => ref field; set { } } + {{refModifier}} object P6 { get => ref field; init { } } + {{refModifier}} object P7 { get => throw null; } + {{refModifier}} object P8 { get => throw null; set; } + {{refModifier}} object P9 { get => throw null; init; } + {{refModifier}} object PC { get => throw null; set { _ = field; } } + {{refModifier}} object PD { get => throw null; init { _ = field; } } + {{refModifier}} object PE { get; set; } + {{refModifier}} object PF { get; init; } + {{refModifier}} object PG { get; set { } } + {{refModifier}} object PH { get; init { } } + {{refModifier}} object PI { set { _ = field; } } + {{refModifier}} object PJ { init { _ = field; } } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P1 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P1").WithLocation(3, 25), + // (4,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P2 { get => ref field; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P2").WithLocation(4, 25), + // (5,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P3 { get => ref field; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P3").WithLocation(5, 25), + // (5,48): error CS8147: Properties which return by reference cannot have set accessors + // ref object P3 { get => ref field; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(5, 48), + // (6,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P4 { get => ref field; init; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P4").WithLocation(6, 25), + // (6,48): error CS8147: Properties which return by reference cannot have set accessors + // ref object P4 { get => ref field; init; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(6, 48), + // (7,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P5 { get => ref field; set { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P5").WithLocation(7, 25), + // (7,48): error CS8147: Properties which return by reference cannot have set accessors + // ref object P5 { get => ref field; set { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(7, 48), + // (8,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P6 { get => ref field; init { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P6").WithLocation(8, 25), + // (8,48): error CS8147: Properties which return by reference cannot have set accessors + // ref object P6 { get => ref field; init { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(8, 48), + // (10,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P8 { get => throw null; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P8").WithLocation(10, 25), + // (10,49): error CS8147: Properties which return by reference cannot have set accessors + // ref object P8 { get => throw null; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(10, 49), + // (11,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object P9 { get => throw null; init; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P9").WithLocation(11, 25), + // (11,49): error CS8147: Properties which return by reference cannot have set accessors + // ref object P9 { get => throw null; init; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(11, 49), + // (12,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PC { get => throw null; set { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PC").WithLocation(12, 25), + // (12,49): error CS8147: Properties which return by reference cannot have set accessors + // ref object PC { get => throw null; set { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(12, 49), + // (13,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PD { get => throw null; init { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PD").WithLocation(13, 25), + // (13,49): error CS8147: Properties which return by reference cannot have set accessors + // ref object PD { get => throw null; init { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(13, 49), + // (14,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PE { get; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PE").WithLocation(14, 25), + // (14,35): error CS8147: Properties which return by reference cannot have set accessors + // ref object PE { get; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(14, 35), + // (15,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PF { get; init; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PF").WithLocation(15, 25), + // (15,35): error CS8147: Properties which return by reference cannot have set accessors + // ref object PF { get; init; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(15, 35), + // (16,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PG { get; set { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PG").WithLocation(16, 25), + // (16,35): error CS8147: Properties which return by reference cannot have set accessors + // ref object PG { get; set { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(16, 35), + // (17,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PH { get; init { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PH").WithLocation(17, 25), + // (17,35): error CS8147: Properties which return by reference cannot have set accessors + // ref object PH { get; init { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "init").WithLocation(17, 35), + // (18,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PI { set { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PI").WithLocation(18, 25), + // (18,25): error CS8146: Properties which return by reference must have a get accessor + // ref object PI { set { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "PI").WithLocation(18, 25), + // (19,25): error CS8145: Auto-implemented properties cannot return by reference + // ref object PJ { init { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PJ").WithLocation(19, 25), + // (19,25): error CS8146: Properties which return by reference must have a get accessor + // ref object PJ { init { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "PJ").WithLocation(19, 25)); + } + + [Theory] + [CombinatorialData] + public void RefReturning_02(bool useStruct, bool useRefReadOnly) + { + string type = useStruct ? "struct" : "class"; + string refModifier = useRefReadOnly ? "ref readonly" : "ref "; + string source = $$""" + {{type}} S + { + static {{refModifier}} object P1 { get; } + static {{refModifier}} object P2 { get => ref field; } + static {{refModifier}} object P3 { get => ref field; set; } + static {{refModifier}} object P5 { get => ref field; set { } } + static {{refModifier}} object P7 { get => throw null; } + static {{refModifier}} object P8 { get => throw null; set; } + static {{refModifier}} object PC { get => throw null; set { _ = field; } } + static {{refModifier}} object PE { get; set; } + static {{refModifier}} object PG { get; set { } } + static {{refModifier}} object PI { set { _ = field; } } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object P1 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P1").WithLocation(3, 32), + // (4,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object P2 { get => ref field; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P2").WithLocation(4, 32), + // (5,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object P3 { get => ref field; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P3").WithLocation(5, 32), + // (5,55): error CS8147: Properties which return by reference cannot have set accessors + // static ref object P3 { get => ref field; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(5, 55), + // (6,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object P5 { get => ref field; set { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P5").WithLocation(6, 32), + // (6,55): error CS8147: Properties which return by reference cannot have set accessors + // static ref object P5 { get => ref field; set { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(6, 55), + // (8,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object P8 { get => throw null; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "P8").WithLocation(8, 32), + // (8,56): error CS8147: Properties which return by reference cannot have set accessors + // static ref object P8 { get => throw null; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(8, 56), + // (9,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object PC { get => throw null; set { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PC").WithLocation(9, 32), + // (9,56): error CS8147: Properties which return by reference cannot have set accessors + // static ref object PC { get => throw null; set { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(9, 56), + // (10,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object PE { get; set; } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PE").WithLocation(10, 32), + // (10,42): error CS8147: Properties which return by reference cannot have set accessors + // static ref object PE { get; set; } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(10, 42), + // (11,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object PG { get; set { } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PG").WithLocation(11, 32), + // (11,42): error CS8147: Properties which return by reference cannot have set accessors + // static ref object PG { get; set { } } + Diagnostic(ErrorCode.ERR_RefPropertyCannotHaveSetAccessor, "set").WithLocation(11, 42), + // (12,32): error CS8145: Auto-implemented properties cannot return by reference + // static ref object PI { set { _ = field; } } + Diagnostic(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, "PI").WithLocation(12, 32), + // (12,32): error CS8146: Properties which return by reference must have a get accessor + // static ref object PI { set { _ = field; } } + Diagnostic(ErrorCode.ERR_RefPropertyMustHaveGetAccessor, "PI").WithLocation(12, 32)); + } + + [Theory] + [CombinatorialData] + public void Nullability_01(bool useNullableAnnotation) + { + string annotation = useNullableAnnotation ? "?" : " "; + string source = $$""" + #nullable enable + class C + { + object{{annotation}} P1 => field; + object{{annotation}} P2 { get => field; } + object{{annotation}} P3 { set { field = value; } } + object{{annotation}} P4 { get => field; set { field = value; } } + } + """; + var comp = CreateCompilation(source); + if (useNullableAnnotation) + { + comp.VerifyEmitDiagnostics(); + } + else + { + comp.VerifyEmitDiagnostics( + // (4,13): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // object P1 => field; + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P1").WithArguments("property", "P1").WithLocation(4, 13), + // (5,13): 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. + // object P2 { get => field; } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P2").WithArguments("property", "P2").WithLocation(5, 13), + // (6,13): warning CS8618: Non-nullable property 'P3' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // object P3 { set { field = value; } } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P3").WithArguments("property", "P3").WithLocation(6, 13), + // (7,13): warning CS8618: Non-nullable property 'P4' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // object P4 { get => field; set { field = value; } } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P4").WithArguments("property", "P4").WithLocation(7, 13)); + } + } + + [Fact] + public void Nullability_02() + { + string source = """ + #nullable enable + class C + { + string? P1 => field.ToString(); // 1 + string P2 => field.ToString(); + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (4,19): warning CS8602: Dereference of a possibly null reference. + // string? P1 => field.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(4, 19), + // (5,12): 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. + // string P2 => field.ToString(); + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "P2").WithArguments("property", "P2").WithLocation(5, 12)); + } + + [Fact] + public void Nullability_03() + { + string source = """ + #nullable enable + class C + { + string P + { + get + { + if (field.Length == 0) return field; + if (field is null) return field; // 1 + return field; + } + } + C() { P = ""; } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (9,39): warning CS8603: Possible null reference return. + // if (field is null) return field; // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReturn, "field").WithLocation(9, 39)); + } + + [Fact] + public void Nullability_04() + { + string source = """ + #nullable enable + class C + { + string? P + { + set + { + if (value is null) + { + field = value; + field.ToString(); // 1 + return; + } + field = value; + field.ToString(); + } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (11,17): warning CS8602: Dereference of a possibly null reference. + // field.ToString(); // 1 + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "field").WithLocation(11, 17)); + } + + // NullableWalker assumes the backing field is used exactly as in an auto-property, + // (e.g. { get { return field; } set { field = value; } }), and therefore the inferred nullability + // of the initializer value can be used directly for the inferred nullability of the property. + [Theory] + [CombinatorialData] + public void Nullability_05(bool useNullableAnnotation, bool initializeNotNull, bool useInit) + { + string setter = useInit ? "init" : "set "; + string annotation = useNullableAnnotation ? "?" : " "; + string initializerValue = initializeNotNull ? "NotNull()" : "MaybeNull()"; + string source = $$""" + #nullable enable + class C + { + object{{annotation}} P1 { get; } = {{initializerValue}}; + object{{annotation}} P2 { get => field; } = {{initializerValue}}; + object{{annotation}} P3 { get => field; {{setter}}; } = {{initializerValue}}; + object{{annotation}} P4 { get; {{setter}}; } = {{initializerValue}}; + object{{annotation}} P5 { get; {{setter}} { field = value; } } = {{initializerValue}}; + object{{annotation}} P6 { {{setter}} { field = value; } } = {{initializerValue}}; + static object NotNull() => new object(); + static object? MaybeNull() => new object(); + C() + { + P1.ToString(); + P2.ToString(); + P3.ToString(); + P4.ToString(); + P5.ToString(); + } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + if (initializeNotNull) + { + comp.VerifyEmitDiagnostics(); + } + else if (useNullableAnnotation) + { + comp.VerifyEmitDiagnostics( + // (14,9): warning CS8602: Dereference of a possibly null reference. + // P1.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P1").WithLocation(14, 9), + // (15,9): warning CS8602: Dereference of a possibly null reference. + // P2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P2").WithLocation(15, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // P3.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P3").WithLocation(16, 9), + // (17,9): warning CS8602: Dereference of a possibly null reference. + // P4.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P4").WithLocation(17, 9), + // (18,9): warning CS8602: Dereference of a possibly null reference. + // P5.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P5").WithLocation(18, 9)); + } + else + { + comp.VerifyEmitDiagnostics( + // (4,27): warning CS8601: Possible null reference assignment. + // object P1 { get; } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(4, 27), + // (5,36): warning CS8601: Possible null reference assignment. + // object P2 { get => field; } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(5, 36), + // (6,42): warning CS8601: Possible null reference assignment. + // object P3 { get => field; set ; } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(6, 42), + // (7,33): warning CS8601: Possible null reference assignment. + // object P4 { get; set ; } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(7, 33), + // (8,51): warning CS8601: Possible null reference assignment. + // object P5 { get; set { field = value; } } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(8, 51), + // (9,46): warning CS8601: Possible null reference assignment. + // object P6 { set { field = value; } } = MaybeNull(); + Diagnostic(ErrorCode.WRN_NullReferenceAssignment, "MaybeNull()").WithLocation(9, 46), + // (14,9): warning CS8602: Dereference of a possibly null reference. + // P1.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P1").WithLocation(14, 9), + // (15,9): warning CS8602: Dereference of a possibly null reference. + // P2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P2").WithLocation(15, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // P3.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P3").WithLocation(16, 9), + // (17,9): warning CS8602: Dereference of a possibly null reference. + // P4.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P4").WithLocation(17, 9), + // (18,9): warning CS8602: Dereference of a possibly null reference. + // P5.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P5").WithLocation(18, 9)); + } + } + + // Based on RequiredMembersTests.RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_01. + [Theory] + [CombinatorialData] + public void RequiredMemberNullability_01(bool includeRequired) + { + string modifier = includeRequired ? "required" : ""; + string source = $$""" + #nullable enable + class C + { + public {{modifier}} object P1 { get; } + public {{modifier}} object P2 { get => field; } + public {{modifier}} object P3 { get => ""; } + + C(bool unused) { } + + C() : this(true) + { + P1.ToString(); + P2.ToString(); + P3.ToString(); + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + if (includeRequired) + { + comp.VerifyEmitDiagnostics( + // (4,28): error CS9034: Required member 'C.P1' must be settable. + // public required object P1 { get; } + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "P1").WithArguments("C.P1").WithLocation(4, 28), + // (5,28): error CS9034: Required member 'C.P2' must be settable. + // public required object P2 { get => field; } + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "P2").WithArguments("C.P2").WithLocation(5, 28), + // (6,28): error CS9034: Required member 'C.P3' must be settable. + // public required object P3 { get => ""; } + Diagnostic(ErrorCode.ERR_RequiredMemberMustBeSettable, "P3").WithArguments("C.P3").WithLocation(6, 28), + // (12,9): warning CS8602: Dereference of a possibly null reference. + // P1.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P1").WithLocation(12, 9), + // (13,9): warning CS8602: Dereference of a possibly null reference. + // P2.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P2").WithLocation(13, 9), + // (14,9): warning CS8602: Dereference of a possibly null reference. + // P3.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P3").WithLocation(14, 9)); + } + else + { + comp.VerifyEmitDiagnostics( + // (8,5): 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. + // C(bool unused) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P2").WithLocation(8, 5), + // (8,5): warning CS8618: Non-nullable property 'P1' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // C(bool unused) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P1").WithLocation(8, 5)); + } + } + + // Based on RequiredMembersTests.RequiredMemberSuppressesNullabilityWarnings_ChainedConstructor_01. + [Theory] + [CombinatorialData] + public void RequiredMemberNullability_02(bool includeRequired) + { + string modifier = includeRequired ? "required" : ""; + string source = $$""" + #nullable enable + class C + { + public {{modifier}} object P4 { get; set; } + public {{modifier}} object P5 { get => field; set; } + public {{modifier}} object P6 { get => field; set { field = value; } } + public {{modifier}} object P7 { get => ""; set { } } + + C(bool unused) { } + + C() : this(true) + { + P4.ToString(); + P5.ToString(); + P6.ToString(); + P7.ToString(); + } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + if (includeRequired) + { + comp.VerifyEmitDiagnostics( + // (13,9): warning CS8602: Dereference of a possibly null reference. + // P4.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P4").WithLocation(13, 9), + // (14,9): warning CS8602: Dereference of a possibly null reference. + // P5.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P5").WithLocation(14, 9), + // (15,9): warning CS8602: Dereference of a possibly null reference. + // P6.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P6").WithLocation(15, 9), + // (16,9): warning CS8602: Dereference of a possibly null reference. + // P7.ToString(); + Diagnostic(ErrorCode.WRN_NullReferenceReceiver, "P7").WithLocation(16, 9)); + } + else + { + comp.VerifyEmitDiagnostics( + // (9,5): warning CS8618: Non-nullable property 'P5' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // C(bool unused) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P5").WithLocation(9, 5), + // (9,5): warning CS8618: Non-nullable property 'P6' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // C(bool unused) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P6").WithLocation(9, 5), + // (9,5): warning CS8618: Non-nullable property 'P4' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable. + // C(bool unused) { } + Diagnostic(ErrorCode.WRN_UninitializedNonNullableField, "C").WithArguments("property", "P4").WithLocation(9, 5)); + } + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public void AutoPropertyMustHaveGetAccessor(bool useStatic, bool useInit) + { + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string source = $$""" + class C + { + {{modifier}} object P02 { {{setter}}; } + {{modifier}} object P03 { {{setter}} { } } + {{modifier}} object P04 { {{setter}} { field = value; } } + {{modifier}} object P11 { get; } + {{modifier}} object P12 { get; {{setter}}; } + {{modifier}} object P13 { get; {{setter}} { } } + {{modifier}} object P14 { get; {{setter}} { field = value; } } + {{modifier}} object P21 { get => field; } + {{modifier}} object P22 { get => field; {{setter}}; } + {{modifier}} object P23 { get => field; {{setter}} { } } + {{modifier}} object P24 { get => field; {{setter}} { field = value; } } + {{modifier}} object P31 { get => null; } + {{modifier}} object P32 { get => null; {{setter}}; } + {{modifier}} object P33 { get => null; {{setter}} { } } + {{modifier}} object P34 { get => null; {{setter}} { field = value; } } + {{modifier}} object P41 { get { return field; } } + {{modifier}} object P42 { get { return field; } {{setter}}; } + {{modifier}} object P43 { get { return field; } {{setter}} { } } + {{modifier}} object P44 { get { return field; } {{setter}} { field = value; } } + {{modifier}} object P51 { get { return null; } } + {{modifier}} object P52 { get { return null; } {{setter}}; } + {{modifier}} object P53 { get { return null; } {{setter}} { } } + {{modifier}} object P54 { get { return null; } {{setter}} { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,25): error CS8051: Auto-implemented properties must have get accessors. + // object P02 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(3, 25)); + } + + [Theory] + [CombinatorialData] + public void Override_VirtualBase_01(bool useInit) + { + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + class A + { + public virtual object P1 { get; {{setter}}; } + public virtual object P2 { get; } + public virtual object P3 { {{setter}}; } + } + """; + string sourceB0 = $$""" + class B0 : A + { + public override object P1 { get; } + public override object P2 { get; } + public override object P3 { get; } + } + """; + var targetFramework = GetTargetFramework(useInit); + var comp = CreateCompilation([sourceA, sourceB0], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (5,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P3 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P3").WithLocation(5, 28), + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32), + // (5,33): error CS0545: 'B0.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B0.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB1 = $$""" + class B1 : A + { + public override object P1 { get; {{setter}}; } + public override object P2 { get; {{setter}}; } + public override object P3 { get; {{setter}}; } + } + """; + comp = CreateCompilation([sourceA, sourceB1], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (4,38): error CS0546: 'B1.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { get; set; } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B1.P2.{setter}", "A.P2").WithLocation(4, 38), + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32), + // (5,33): error CS0545: 'B1.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get; set; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B1.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB2 = $$""" + class B2 : A + { + public override object P1 { get => field; {{setter}} { } } + public override object P2 { get => field; {{setter}} { } } + public override object P3 { get => field; {{setter}} { } } + } + """; + comp = CreateCompilation([sourceA, sourceB2], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (4,47): error CS0546: 'B2.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { get => field; set { } } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B2.P2.{setter}", "A.P2").WithLocation(4, 47), + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32), + // (5,33): error CS0545: 'B2.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get => field; set { } } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B2.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB3 = $$""" + class B3 : A + { + public override object P1 { get => field; } + public override object P2 { get => field; } + public override object P3 { get => field; } + } + """; + comp = CreateCompilation([sourceA, sourceB3], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { get => field; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (5,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P3 { get => field; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P3").WithLocation(5, 28), + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32), + // (5,33): error CS0545: 'B3.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get => field; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B3.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB4 = $$""" + class B4 : A + { + public override object P1 { {{setter}} { field = value; } } + public override object P2 { {{setter}} { field = value; } } + public override object P3 { {{setter}} { field = value; } } + } + """; + comp = CreateCompilation([sourceA, sourceB4], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { set { field = value; } } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (4,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P2 { set { field = value; } } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P2").WithLocation(4, 28), + // (4,33): error CS0546: 'B4.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { set { field = value; } } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B4.P2.{setter}", "A.P2").WithLocation(4, 33), + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32)); + } + + [Theory] + [CombinatorialData] + public void Override_AbstractBase_01(bool useInit) + { + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + abstract class A + { + public abstract object P1 { get; {{setter}}; } + public abstract object P2 { get; } + public abstract object P3 { {{setter}}; } + } + """; + string sourceB0 = $$""" + class B0 : A + { + public override object P1 { get; } + public override object P2 { get; } + public override object P3 { get; } + } + """; + var targetFramework = GetTargetFramework(useInit); + var comp = CreateCompilation([sourceA, sourceB0], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (1,7): error CS0534: 'B0' does not implement inherited abstract member 'A.P3.set' + // class B0 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B0").WithArguments("B0", $"A.P3.{setter}").WithLocation(1, 7), + // (1,7): error CS0534: 'B0' does not implement inherited abstract member 'A.P1.set' + // class B0 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B0").WithArguments("B0", $"A.P1.{setter}").WithLocation(1, 7), + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (5,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P3 { get; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P3").WithLocation(5, 28), + // (5,33): error CS0545: 'B0.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B0.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB1 = $$""" + class B1 : A + { + public override object P1 { get; {{setter}}; } + public override object P2 { get; {{setter}}; } + public override object P3 { get; {{setter}}; } + } + """; + comp = CreateCompilation([sourceA, sourceB1], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (4,38): error CS0546: 'B1.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { get; set; } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B1.P2.{setter}", "A.P2").WithLocation(4, 38), + // (5,33): error CS0545: 'B1.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get; set; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B1.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB2 = $$""" + class B2 : A + { + public override object P1 { get => field; {{setter}} { } } + public override object P2 { get => field; {{setter}} { } } + public override object P3 { get => field; {{setter}} { } } + } + """; + comp = CreateCompilation([sourceA, sourceB2], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (4,47): error CS0546: 'B2.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { get => field; set { } } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B2.P2.{setter}", "A.P2").WithLocation(4, 47), + // (5,33): error CS0545: 'B2.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get => field; set { } } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B2.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB3 = $$""" + class B3 : A + { + public override object P1 { get => field; } + public override object P2 { get => field; } + public override object P3 { get => field; } + } + """; + comp = CreateCompilation([sourceA, sourceB3], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (1,7): error CS0534: 'B3' does not implement inherited abstract member 'A.P3.set' + // class B3 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B3").WithArguments("B3", $"A.P3.{setter}").WithLocation(1, 7), + // (1,7): error CS0534: 'B3' does not implement inherited abstract member 'A.P1.set' + // class B3 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B3").WithArguments("B3", $"A.P1.{setter}").WithLocation(1, 7), + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { get => field; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (5,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P3 { get => field; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P3").WithLocation(5, 28), + // (5,33): error CS0545: 'B3.P3.get': cannot override because 'A.P3' does not have an overridable get accessor + // public override object P3 { get => field; } + Diagnostic(ErrorCode.ERR_NoGetToOverride, "get").WithArguments("B3.P3.get", "A.P3").WithLocation(5, 33)); + + string sourceB4 = $$""" + class B4 : A + { + public override object P1 { {{setter}} { field = value; } } + public override object P2 { {{setter}} { field = value; } } + public override object P3 { {{setter}} { field = value; } } + } + """; + comp = CreateCompilation([sourceA, sourceB4], targetFramework: targetFramework); + comp.VerifyEmitDiagnostics( + // (1,7): error CS0534: 'B4' does not implement inherited abstract member 'A.P1.get' + // class B4 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B4").WithArguments("B4", "A.P1.get").WithLocation(1, 7), + // (1,7): error CS0534: 'B4' does not implement inherited abstract member 'A.P2.get' + // class B4 : A + Diagnostic(ErrorCode.ERR_UnimplementedAbstractMethod, "B4").WithArguments("B4", "A.P2.get").WithLocation(1, 7), + // (3,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P1 { set { field = value; } } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P1").WithLocation(3, 28), + // (4,28): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override object P2 { set { field = value; } } + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P2").WithLocation(4, 28), + // (4,33): error CS0546: 'B4.P2.set': cannot override because 'A.P2' does not have an overridable set accessor + // public override object P2 { set { field = value; } } + Diagnostic(ErrorCode.ERR_NoSetToOverride, setter).WithArguments($"B4.P2.{setter}", "A.P2").WithLocation(4, 33)); + } + + [Theory] + [CombinatorialData] + public void New_01(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + class A + { + public virtual object P1 { get; {{setter}}; } + public virtual object P2 { get; } + public virtual object P3 { {{setter}}; } + } + class B0 : A + { + public new object P1 { get; } + public new object P2 { get; } + public new object P3 { get; } + } + class B1 : A + { + public new object P1 { get; {{setter}}; } + public new object P2 { get; {{setter}}; } + public new object P3 { get; {{setter}}; } + } + class B2 : A + { + public new object P1 { get => field; {{setter}} { } } + public new object P2 { get => field; {{setter}} { } } + public new object P3 { get => field; {{setter}} { } } + } + class B3 : A + { + public new object P1 { get => field; } + public new object P2 { get => field; } + public new object P3 { get => field; } + } + class B4 : A + { + public new object P1 { {{setter}} { field = value; } } + public new object P2 { {{setter}} { field = value; } } + public new object P3 { {{setter}} { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (5,32): error CS8051: Auto-implemented properties must have get accessors. + // public virtual object P3 { set; } + Diagnostic(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, setter).WithLocation(5, 32)); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public void CompilerGeneratedAttribute(bool missingType, bool missingConstructor) + { + string source = """ + using System; + using System.Reflection; + + class C + { + public int P1 { get; } + public int P2 { get => field; } + public int P3 { set { field = value; } } + public int P4 { init { field = value; } } + } + + class Program + { + static void Main() + { + foreach (var field in typeof(C).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, targetFramework: TargetFramework.Net80, options: TestOptions.ReleaseExe); + if (missingType) + { + comp.MakeTypeMissing(WellKnownType.System_Runtime_CompilerServices_CompilerGeneratedAttribute); + } + if (missingConstructor) + { + comp.MakeMemberMissing(WellKnownMember.System_Runtime_CompilerServices_CompilerGeneratedAttribute__ctor); + } + string expectedAttributes = (missingType || missingConstructor) ? "" : " System.Runtime.CompilerServices.CompilerGeneratedAttribute,"; + CompileAndVerify(comp, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput($$""" + C.k__BackingField:{{expectedAttributes}} + C.k__BackingField:{{expectedAttributes}} + C.k__BackingField:{{expectedAttributes}} + C.k__BackingField:{{expectedAttributes}} + """)); + } + + [Theory] + [CombinatorialData] + public void Conditional(bool useDEBUG) + { + string sourceA = """ + using System.Diagnostics; + class C + { + public static object P1 { get { M(field); return null; } set { } } + public static object P2 { get { return null; } set { M(field); } } + public object P3 { get { M(field); return null; } } + public object P4 { set { M(field); } } + public object P5 { init { M(field); } } + [Conditional("DEBUG")] + static void M( object o) { } + } + """; + string sourceB = """ + using System; + using System.Reflection; + class Program + { + static void Main() + { + foreach (var field in typeof(C).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 parseOptions = TestOptions.RegularNext; + if (useDEBUG) + { + parseOptions = parseOptions.WithPreprocessorSymbols("DEBUG"); + } + var verifier = CompileAndVerify( + [sourceA, sourceB], + parseOptions: parseOptions, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(""" + C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, + C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, + C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, + C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, + C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, + """)); + + if (useDEBUG) + { + verifier.VerifyIL("C.P1.get", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldsfld "object C.k__BackingField" + IL_0005: call "void C.M(object)" + IL_000a: ldnull + IL_000b: ret + } + """); + verifier.VerifyIL("C.P4.set", """ + { + // Code size 12 (0xc) + .maxstack 1 + IL_0000: ldarg.0 + IL_0001: ldfld "object C.k__BackingField" + IL_0006: call "void C.M(object)" + IL_000b: ret + } + """); + } + else + { + verifier.VerifyIL("C.P1.get", """ + { + // Code size 2 (0x2) + .maxstack 1 + IL_0000: ldnull + IL_0001: ret + } + """); + verifier.VerifyIL("C.P4.set", """ + { + // Code size 1 (0x1) + .maxstack 0 + IL_0000: ret + } + """); + } + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + VerifyMergedProperties(actualProperties, actualFields); + } + + [Fact] + public void RestrictedTypes() + { + string source = """ + 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 = $$""" + 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 = $$""" + 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 = """ + 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), + // (10,7): error CS0525: Interfaces cannot contain instance fields + // R Q2 { get => field; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q2").WithLocation(10, 7), + // (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), + // (11,7): error CS0525: Interfaces cannot contain instance fields + // R Q3 { set { _ = field; } } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "Q3").WithLocation(11, 7)); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_01( + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, + bool useInit) + { + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + public partial object P3 { get; {{setter}}; } + public partial object P3 { get; {{setter}} { } } + public partial object P4 { get; {{setter}}; } + public partial object P4 { get => null; {{setter}}; } + } + """; + string sourceB = """ + using System; + using System.Reflection; + class Program + { + static void Main() + { + var c = new C { P3 = 3, P4 = 4 }; + Console.WriteLine((c.P3, c.P4)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}", field.Name); + } + } + """; + var comp = CreateCompilation( + [sourceA, sourceB], + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + options: TestOptions.ReleaseExe, + targetFramework: GetTargetFramework(useInit)); + + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (4,27): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public partial object P3 { get; set { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P3").WithArguments("field keyword").WithLocation(4, 27), + // (6,27): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // public partial object P4 { get => null; set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P4").WithArguments("field keyword").WithLocation(6, 27)); + } + else + { + CompileAndVerify( + comp, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, """ + (, ) + k__BackingField + k__BackingField + """)); + } + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_02(bool reverseOrder, bool useInit) + { + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + public partial object P1 { get; } + public partial object P2 { {{setter}}; } + public partial object P3 { get; {{setter}}; } + public partial object P4 { get; {{setter}}; } + public partial object P5 { get; {{setter}}; } + } + """; + string sourceB = $$""" + partial class C + { + public partial object P1 { get => field; } + public partial object P2 { {{setter}} { field = value; } } + public partial object P3 { get; {{setter}} { field = value; } } + public partial object P4 { get => field; {{setter}}; } + public partial object P5 { get => field; {{setter}} { field = value; } } + } + """; + string sourceC = """ + using System; + using System.Reflection; + class Program + { + static void Main() + { + var c = new C { P2 = 2, P3 = 3, P4 = 4, P5 = 5 }; + Console.WriteLine((c.P3, c.P4, c.P5)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}", field.Name); + } + } + """; + var verifier = CompileAndVerify( + reverseOrder ? [sourceC, sourceB, sourceA] : [sourceA, sourceB, sourceC], + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, """ + (3, 4, 5) + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + """)); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(5, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_01( + [CombinatorialValues(LanguageVersion.CSharp12, LanguageVersion.CSharp13)] LanguageVersion languageVersion, + bool reverseOrder, + bool includeRuntimeSupport) + { + string sourceA = $$""" + partial interface I + { + partial object P1 { get; } + partial object P2 { set; } + } + """; + string sourceB = $$""" + partial interface I + { + partial object P1 { get => null; } + partial object P2 { set { } } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + targetFramework: includeRuntimeSupport ? TargetFramework.Net80 : TargetFramework.Standard); + + Assert.Equal(includeRuntimeSupport, comp.Assembly.RuntimeSupportsDefaultInterfaceImplementation); + + switch (languageVersion, includeRuntimeSupport) + { + case (LanguageVersion.CSharp12, false): + comp.VerifyEmitDiagnostics( + // (3,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P1 { get; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("partial", "12.0", "13.0").WithLocation(3, 20), + // (3,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P1 { get => null; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("partial", "12.0", "13.0").WithLocation(3, 20), + // (3,25): error CS8701: Target runtime doesn't support default interface implementation. + // partial object P1 { get => null; } + Diagnostic(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, "get").WithLocation(3, 25), + // (4,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P2 { set; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P2").WithArguments("partial", "12.0", "13.0").WithLocation(4, 20), + // (4,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P2 { set { } } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P2").WithArguments("partial", "12.0", "13.0").WithLocation(4, 20), + // (4,25): error CS8701: Target runtime doesn't support default interface implementation. + // partial object P2 { set { } } + Diagnostic(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, "set").WithLocation(4, 25)); + break; + case (LanguageVersion.CSharp12, true): + comp.VerifyEmitDiagnostics( + // (3,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P1 { get; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("partial", "12.0", "13.0").WithLocation(3, 20), + // (3,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P1 { get => null; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P1").WithArguments("partial", "12.0", "13.0").WithLocation(3, 20), + // (4,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P2 { set; } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P2").WithArguments("partial", "12.0", "13.0").WithLocation(4, 20), + // (4,20): error CS8703: The modifier 'partial' is not valid for this item in C# 12.0. Please use language version '13.0' or greater. + // partial object P2 { set { } } + Diagnostic(ErrorCode.ERR_InvalidModifierForLanguageVersion, "P2").WithArguments("partial", "12.0", "13.0").WithLocation(4, 20)); + break; + case (LanguageVersion.CSharp13, false): + comp.VerifyEmitDiagnostics( + // (3,25): error CS8701: Target runtime doesn't support default interface implementation. + // partial object P1 { get => null; } + Diagnostic(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, "get").WithLocation(3, 25), + // (4,25): error CS8701: Target runtime doesn't support default interface implementation. + // partial object P2 { set { } } + Diagnostic(ErrorCode.ERR_RuntimeDoesNotSupportDefaultInterfaceImplementation, "set").WithLocation(4, 25)); + break; + case (LanguageVersion.CSharp13, true): + comp.VerifyEmitDiagnostics(); + break; + default: + Assert.True(false); + break; + } + + var containingType = comp.GetMember("I"); + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_02A( + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, + bool reverseOrder) + { + string sourceA = $$""" + partial interface I + { + partial object P1 { get; set; } + partial object P2 { get; init; } + } + """; + string sourceB = $$""" + partial interface I + { + partial object P1 { get; set { } } + partial object P2 { get => null; init; } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + targetFramework: TargetFramework.Net80); + + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (3,20): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // partial object P1 { get; set { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 20), + // (3,20): error CS0525: Interfaces cannot contain instance fields + // partial object P1 { get; set; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(3, 20), + // (4,20): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // partial object P2 { get => null; init; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 20), + // (4,20): error CS0525: Interfaces cannot contain instance fields + // partial object P2 { get; init; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P2").WithLocation(4, 20)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,20): error CS0525: Interfaces cannot contain instance fields + // partial object P1 { get; set; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(3, 20), + // (4,20): error CS0525: Interfaces cannot contain instance fields + // partial object P2 { get; init; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P2").WithLocation(4, 20)); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_02B( + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, + bool reverseOrder) + { + string sourceA = $$""" + partial interface I + { + static partial object P1 { get; set; } + static partial object P2 { get; set; } + } + """; + string sourceB = $$""" + partial interface I + { + static partial object P1 { get; set { } } + static partial object P2 { get => null; set; } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion), + targetFramework: TargetFramework.Net80); + + if (languageVersion == LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (3,27): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static partial object P1 { get; set { } } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(3, 27), + // (4,27): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static partial object P2 { get => null; set; } + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P2").WithArguments("field keyword").WithLocation(4, 27)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_03(bool reverseOrder, bool useStatic) + { + string modifier = useStatic ? "static" : " "; + string sourceA = $$""" + partial interface I + { + {{modifier}} partial object P1 { get; } + {{modifier}} partial object P2 { set; } + } + """; + string sourceB = $$""" + partial interface I + { + {{modifier}} partial object P1 { get => field; } + {{modifier}} partial object P2 { set { field = value; } } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: TargetFramework.Net80); + if (useStatic) + { + comp.VerifyEmitDiagnostics(); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,27): error CS0525: Interfaces cannot contain instance fields + // partial object P1 { get; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(3, 27), + // (4,27): error CS0525: Interfaces cannot contain instance fields + // partial object P2 { set; } + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P2").WithLocation(4, 27)); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_04A(bool reverseOrder, bool useStatic) + { + string modifier = useStatic ? "static" : " "; + string sourceA = $$""" + partial interface I + { + {{modifier}} partial object P1 { get; } = 1; + {{modifier}} partial object P2 { set; } + {{modifier}} partial object P3 { get; set; } = 3; + {{modifier}} partial object P4 { get; set; } + } + """; + string sourceB = $$""" + partial interface I + { + {{modifier}} partial object P1 { get => null; } + {{modifier}} partial object P2 { set { } } = 2; + {{modifier}} partial object P3 { get => null; set { } } + {{modifier}} partial object P4 { get => null; set { } } = 4; + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: TargetFramework.Net80); + if (useStatic) + { + comp.VerifyEmitDiagnostics( + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // static partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), + // (4,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // static partial object P2 { set { } } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(4, 27), + // (5,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // static partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(5, 27), + // (6,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // static partial object P4 { get => null; set { } } = 4; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P4").WithLocation(6, 27)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P1").WithLocation(3, 27), + // (4,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P2 { set { } } = 2; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P2").WithLocation(4, 27), + // (5,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P3").WithLocation(5, 27), + // (6,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P4 { get => null; set { } } = 4; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P4").WithLocation(6, 27)); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().OrderBy(f => f.Name).ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(4, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + // As above, but using field. + [Theory] + [CombinatorialData] + public void PartialProperty_Interface_04B(bool reverseOrder, bool useStatic) + { + string modifier = useStatic ? "static" : " "; + string sourceA = $$""" + partial interface I + { + {{modifier}} partial object P1 { get; } = 1; + {{modifier}} partial object P2 { set; } + {{modifier}} partial object P3 { get; set; } = 3; + {{modifier}} partial object P4 { get; set; } + } + """; + string sourceB = $$""" + partial interface I + { + {{modifier}} partial object P1 { get => field; } + {{modifier}} partial object P2 { set { field = value; } } = 2; + {{modifier}} partial object P3 { get => field; set { } } + {{modifier}} partial object P4 { get => null; set { field = value; } } = 4; + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: TargetFramework.Net80); + if (useStatic) + { + comp.VerifyEmitDiagnostics(); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P1").WithLocation(3, 27), + // (4,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P2 { set { field = value; } } = 2; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P2").WithLocation(4, 27), + // (5,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P3").WithLocation(5, 27), + // (6,27): error CS8053: Instance properties in interfaces cannot have initializers. + // partial object P4 { get => null; set { field = value; } } = 4; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P4").WithLocation(6, 27)); + } + + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().OrderBy(f => f.Name).ToImmutableArray(); + var expectedFields = new[] + { + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + "System.Object I.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(4, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { HasInitializer: true } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + private static void VerifyMergedProperties(ImmutableArray properties, ImmutableArray fields) + { + int fieldIndex = 0; + for (int propertyIndex = 0; propertyIndex < properties.Length; propertyIndex++) + { + var property = (SourcePropertySymbol)properties[propertyIndex]; + var field = (property.BackingField is null) ? null : (SynthesizedBackingFieldSymbol)fields[fieldIndex++]; + Assert.Equal(property.IsPartial, property.IsPartialDefinition); + VerifyMergedProperty(property, field); + } + Assert.Equal(fields.Length, fieldIndex); + } + + private static void VerifyMergedProperty(SourcePropertySymbol property, SynthesizedBackingFieldSymbol fieldOpt) + { + Assert.Same(property.BackingField, fieldOpt); + if (property.OtherPartOfPartial is { } otherPart) + { + Assert.True(otherPart.IsPartial); + Assert.Equal(property.IsPartialDefinition, !otherPart.IsPartialDefinition); + Assert.Equal(property.IsPartialImplementation, !otherPart.IsPartialImplementation); + Assert.Same(property.BackingField, otherPart.BackingField); + } + } + + [Theory] + [CombinatorialData] + public void PartialProperty_ConstructorAssignment( + [CombinatorialValues("partial class", "partial struct", "ref partial struct", "partial record", "partial record struct")] string typeKind, + bool reverseOrder, + bool useStatic) + { + string modifier = useStatic ? "static" : " "; + string constructorModifier = useStatic ? "static" : "public"; + string sourceA = $$""" + {{typeKind}} C + { + internal {{modifier}} partial object P1 { get; } + internal {{modifier}} partial object P2 { get => field; } + } + """; + string sourceB = $$""" + {{typeKind}} C + { + internal {{modifier}} partial object P1 { get => field; } + internal {{modifier}} partial object P2 { get; } + {{constructorModifier}} C() + { + P1 = 1; + P2 = 2; + } + } + """; + string sourceC = useStatic ? + """ + using System; + class Program + { + static void Main() + { + Console.WriteLine((C.P1, C.P2)); + } + } + """ : + """ + using System; + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine((c.P1, c.P2)); + } + } + """; + var verifier = CompileAndVerify( + reverseOrder ? [sourceC, sourceB, sourceA] : [sourceA, sourceB, sourceC], + expectedOutput: "(1, 2)"); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().OrderBy(f => f.Name).ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().Where(p => p.Name != "EqualityContract").OrderBy(p => p.Name).ToImmutableArray(); + Assert.Equal(2, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public void PartialProperty_Initializer_01(bool useStatic, bool useInit) + { + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string source = $$""" + partial class C + { + {{modifier}} partial object P1 { get; } = 1; + {{modifier}} partial object P1 { get; } + {{modifier}} partial object P2 { {{setter}}; } = 2; + {{modifier}} partial object P2 { {{setter}}; } + {{modifier}} partial object P3 { get; {{setter}}; } = 3; + {{modifier}} partial object P3 { get; {{setter}}; } + } + """; + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,27): error CS9248: Partial property 'C.P1' must have an implementation part. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P1").WithArguments("C.P1").WithLocation(3, 27), + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), + // (4,27): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P1 { get; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P1").WithLocation(4, 27), + // (4,27): error CS0102: The type 'C' already contains a definition for 'P1' + // partial object P1 { get; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P1").WithArguments("C", "P1").WithLocation(4, 27), + // (5,27): error CS9248: Partial property 'C.P2' must have an implementation part. + // partial object P2 { set; } = 2; + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P2").WithArguments("C.P2").WithLocation(5, 27), + // (5,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P2 { set; } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(5, 27), + // (6,27): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P2 { set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P2").WithLocation(6, 27), + // (6,27): error CS0102: The type 'C' already contains a definition for 'P2' + // partial object P2 { set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P2").WithArguments("C", "P2").WithLocation(6, 27), + // (7,27): error CS9248: Partial property 'C.P3' must have an implementation part. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_PartialPropertyMissingImplementation, "P3").WithArguments("C.P3").WithLocation(7, 27), + // (7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(7, 27), + // (8,27): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P3 { get; set; } + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P3").WithLocation(8, 27), + // (8,27): error CS0102: The type 'C' already contains a definition for 'P3' + // partial object P3 { get; set; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P3").WithArguments("C", "P3").WithLocation(8, 27)); + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(6, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Initializer_02(bool reverseOrder, bool useStatic, bool useInit) + { + if (useStatic && useInit) return; + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + {{modifier}} partial object P1 { get; } = 1; + {{modifier}} partial object P2 { {{setter}}; } = 2; + {{modifier}} partial object P3 { get; {{setter}}; } = 3; + {{modifier}} partial object Q1 { get => null; } = 1; + {{modifier}} partial object Q2 { {{setter}} { } } = 2; + {{modifier}} partial object Q3 { get => null; {{setter}} { } } = 3; + } + """; + string sourceB = $$""" + partial class C + { + {{modifier}} partial object P1 { get => null; } + {{modifier}} partial object P2 { {{setter}} { } } + {{modifier}} partial object P3 { get => null; {{setter}} { } } + {{modifier}} partial object Q1 { get; } + {{modifier}} partial object Q2 { {{setter}}; } + {{modifier}} partial object Q3 { get; {{setter}}; } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), + // (4,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P2 { set; } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(4, 27), + // (5,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(5, 27), + // (6,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object Q1 { get => null; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "Q1").WithLocation(6, 27), + // (7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object Q2 { set { } } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "Q2").WithLocation(7, 27), + // (8,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object Q3 { get => null; set { } } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "Q3").WithLocation(8, 27)); + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().OrderBy(p => p.Name).ToImmutableArray(); + Assert.Equal(6, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "Q1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "Q2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "Q3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Initializer_03(bool reverseOrder, bool useStatic, bool useInit) + { + if (useStatic && useInit) return; + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + public {{modifier}} partial object P1 { get; } = 1; + public {{modifier}} partial object P2 { {{setter}}; } = 2; + public {{modifier}} partial object P3 { get; {{setter}}; } = 3; + public {{modifier}} partial object P4 { get; {{setter}}; } = 4; + public {{modifier}} partial object P5 { get; {{setter}}; } = 5; + public {{modifier}} partial object P6 { get; {{setter}}; } = 6; + public {{modifier}} partial object P7 { get; {{setter}}; } = 7; + public {{modifier}} partial object Q1 { get => field; } = 1; + public {{modifier}} partial object Q2 { {{setter}} { field = value; } } = 2; + public {{modifier}} partial object Q3 { get; {{setter}} { field = value; } } = 3; + public {{modifier}} partial object Q4 { get => field; {{setter}}; } = 4; + public {{modifier}} partial object Q5 { get => field; {{setter}} { field = value; } } = 5; + public {{modifier}} partial object Q6 { get; {{setter}} { } } = 6; + public {{modifier}} partial object Q7 { get => null; {{setter}}; } = 7; + } + """; + string sourceB = $$""" + partial class C + { + public {{modifier}} partial object P1 { get => field; } + public {{modifier}} partial object P2 { {{setter}} { field = value; } } + public {{modifier}} partial object P3 { get; {{setter}} { field = value; } } + public {{modifier}} partial object P4 { get => field; {{setter}}; } + public {{modifier}} partial object P5 { get => field; {{setter}} { field = value; } } + public {{modifier}} partial object P6 { get; {{setter}} { } } + public {{modifier}} partial object P7 { get => null; {{setter}}; } + public {{modifier}} partial object Q1 { get; } + public {{modifier}} partial object Q2 { {{setter}}; } + public {{modifier}} partial object Q3 { get; {{setter}}; } + public {{modifier}} partial object Q4 { get; {{setter}}; } + public {{modifier}} partial object Q5 { get; {{setter}}; } + public {{modifier}} partial object Q6 { get; {{setter}}; } + public {{modifier}} partial object Q7 { get; {{setter}}; } + } + """; + string receiver = useStatic ? "C" : "c"; + string sourceC = $$""" + using System; + using System.Reflection; + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine(({{receiver}}.P1, {{receiver}}.P3, {{receiver}}.P4, {{receiver}}.P5, {{receiver}}.P6, {{receiver}}.P7, {{receiver}}.Q1, {{receiver}}.Q3, {{receiver}}.Q4, {{receiver}}.Q5, {{receiver}}.Q6, {{receiver}}.Q7)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) + Console.WriteLine("{0}", field.Name); + } + } + """; + var verifier = CompileAndVerify( + reverseOrder ? [sourceC, sourceB, sourceA] : [sourceA, sourceB, sourceC], + targetFramework: GetTargetFramework(useInit), + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(useInit, """ + (1, 3, 4, 5, 6, , 1, 3, 4, 5, 6, ) + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + k__BackingField + """)); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().OrderBy(p => p.Name).ToImmutableArray(); + Assert.Equal(14, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P6", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P7", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "Q1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "Q2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[9] is SourcePropertySymbol { Name: "Q3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[10] is SourcePropertySymbol { Name: "Q4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[11] is SourcePropertySymbol { Name: "Q5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[12] is SourcePropertySymbol { Name: "Q6", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[13] is SourcePropertySymbol { Name: "Q7", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Initializer_04(bool reverseOrder, bool useStatic, bool useInit) + { + if (useStatic && useInit) return; + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + {{modifier}} partial object P1 { get; } = 1; + {{modifier}} partial object P2 { {{setter}}; } = 2; + {{modifier}} partial object P3 { get; {{setter}}; } = 3; + } + """; + string sourceB = $$""" + partial class C + { + {{modifier}} partial object P1 { get => null; } = 1; + {{modifier}} partial object P2 { {{setter}} { } } = 2; + {{modifier}} partial object P3 { get => null; {{setter}} { } } = 3; + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // partial object P1 { get => null; } = 1; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P1").WithLocation(3, 27), + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P1 { get => null; } = 1; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), + // (4,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // partial object P2 { set { } } = 2; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P2").WithLocation(4, 27), + // (4,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P2 { set; } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(4, 27), + // (4,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P2 { set { } } = 2; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(4, 27), + // (5,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // partial object P3 { get => null; set { } } = 3; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P3").WithLocation(5, 27), + // (5,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P3 { get; set; } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(5, 27), + // (5,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // partial object P3 { get => null; set { } } = 3; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(5, 27)); + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(3, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Initializer_05(bool reverseOrder, bool useStatic, bool useInit) + { + if (useStatic && useInit) return; + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + partial class C + { + public {{modifier}} partial object P1 { get; } = 1; + public {{modifier}} partial object P2 { {{setter}}; } = 2; + public {{modifier}} partial object P3 { get; {{setter}}; } = 3; + public {{modifier}} partial object P4 { get; {{setter}}; } = 4; + public {{modifier}} partial object P5 { get; {{setter}}; } = 5; + } + """; + string sourceB = $$""" + partial class C + { + public {{modifier}} partial object P1 { get => field; } = -1; + public {{modifier}} partial object P2 { {{setter}} { field = value; } } = -2; + public {{modifier}} partial object P3 { get; {{setter}} { field = value; } } = -3; + public {{modifier}} partial object P4 { get => field; {{setter}}; } = -4; + public {{modifier}} partial object P5 { get => field; {{setter}} { field = value; } } = -5; + } + """; + + var comp = CreateCompilation( + reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB], + targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,34): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial object P1 { get => field; } = -1; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P1").WithLocation(3, 34), + // (4,34): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial object P2 { set { field = value; } } = -2; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P2").WithLocation(4, 34), + // (5,34): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial object P3 { get; set { field = value; } } = -3; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P3").WithLocation(5, 34), + // (6,34): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial object P4 { get => field; set; } = -4; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P4").WithLocation(6, 34), + // (7,34): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial object P5 { get => field; set { field = value; } } = -5; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P5").WithLocation(7, 34)); + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(5, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + + var actualValues = getInitializerValues(comp, comp.SyntaxTrees[reverseOrder ? 1 : 0]); + var expectedValues = new[] + { + ((object)1, "System.Int32", "System.Object"), + ((object)2, "System.Int32", "System.Object"), + ((object)3, "System.Int32", "System.Object"), + ((object)4, "System.Int32", "System.Object"), + ((object)5, "System.Int32", "System.Object"), + }; + AssertEx.Equal(expectedValues, actualValues); + + actualValues = getInitializerValues(comp, comp.SyntaxTrees[reverseOrder ? 0 : 1]); + expectedValues = new[] + { + ((object)-1, "System.Int32", "System.Object"), + ((object)-2, "System.Int32", "System.Object"), + ((object)-3, "System.Int32", "System.Object"), + ((object)-4, "System.Int32", "System.Object"), + ((object)-5, "System.Int32", "System.Object"), + }; + AssertEx.Equal(expectedValues, actualValues); + + static (object, string, string)[] getInitializerValues(CSharpCompilation comp, SyntaxTree tree) + { + var model = comp.GetSemanticModel(tree); + return tree.GetRoot().DescendantNodes().OfType(). + Select(p => + { + var value = p.Initializer.Value; + var typeInfo = model.GetTypeInfo(value); + return (model.GetConstantValue(value).Value, typeInfo.Type.ToTestDisplayString(), typeInfo.ConvertedType.ToTestDisplayString()); + + }).ToArray(); + } + } + + [Fact] + public void PartialProperty_Initializer_06() + { + string source = $$""" + partial class C + { + partial object P1 { get; set; } = 1; // A1 + partial object P2 { get; set; } // A2 + partial object P3 { get; set; } = 3; // A3 + partial object P4 { get; set; } // A4 + partial object P5 { get { return field; } set { field = value; } } = 5; // A5 + partial object P6 { get { return field; } set { field = value; } } // A6 + + partial object P1 { get; set; } // B1 + partial object P2 { get; set; } // B2 + partial object P3 { get { return field; } set { field = value; } } // B3 + partial object P4 { get { return field; } set { field = value; } } // B4 + partial object P5 { get; set; } // B5 + partial object P6 { get { return field; } set { field = value; } } // B6 + + partial object P1 { get { return field; } set { field = value; } } // C1 + partial object P2 { get { return field; } set { field = value; } } = 2; // C2 + partial object P3 { get { return field; } set { field = value; } } // C3 + partial object P4 { get { return field; } set { field = value; } } = 4; // C4 + partial object P5 { get; set; } // C5 + partial object P6 { get; set; } = 6; // C6 + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (10,20): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P1 { get; set; } // B1 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P1").WithLocation(10, 20), + // (10,20): error CS0102: The type 'C' already contains a definition for 'P1' + // partial object P1 { get; set; } // B1 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P1").WithArguments("C", "P1").WithLocation(10, 20), + // (11,20): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P2 { get; set; } // B2 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P2").WithLocation(11, 20), + // (11,20): error CS0102: The type 'C' already contains a definition for 'P2' + // partial object P2 { get; set; } // B2 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P2").WithArguments("C", "P2").WithLocation(11, 20), + // (15,20): error CS9251: A partial property may not have multiple implementing declarations + // partial object P6 { get { return field; } set { field = value; } } // B6 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P6").WithLocation(15, 20), + // (19,20): error CS9251: A partial property may not have multiple implementing declarations + // partial object P3 { get { return field; } set { field = value; } } // C3 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P3").WithLocation(19, 20), + // (19,20): error CS0102: The type 'C' already contains a definition for 'P3' + // partial object P3 { get { return field; } set { field = value; } } // C3 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P3").WithArguments("C", "P3").WithLocation(19, 20), + // (20,20): error CS9251: A partial property may not have multiple implementing declarations + // partial object P4 { get { return field; } set { field = value; } } = 4; // C4 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateImplementation, "P4").WithLocation(20, 20), + // (20,20): error CS0102: The type 'C' already contains a definition for 'P4' + // partial object P4 { get { return field; } set { field = value; } } = 4; // C4 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P4").WithArguments("C", "P4").WithLocation(20, 20), + // (21,20): error CS9250: A partial property may not have multiple defining declarations, and cannot be an auto-property. + // partial object P5 { get; set; } // C5 + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateDefinition, "P5").WithLocation(21, 20), + // (21,20): error CS0102: The type 'C' already contains a definition for 'P5' + // partial object P5 { get; set; } // C5 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P5").WithArguments("C", "P5").WithLocation(21, 20), + // (22,20): error CS0102: The type 'C' already contains a definition for 'P6' + // partial object P6 { get; set; } = 6; // C6 + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "P6").WithArguments("C", "P6").WithLocation(22, 20)); + + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + "System.Object C.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(12, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[6] is SourcePropertySymbol { Name: "P5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[7] is SourcePropertySymbol { Name: "P6", IsPartialDefinition: false, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[8] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: false, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[9] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: false, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[10] is SourcePropertySymbol { Name: "P5", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[11] is SourcePropertySymbol { Name: "P6", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperty((SourcePropertySymbol)actualProperties[0], (SynthesizedBackingFieldSymbol)actualFields[0]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[1], (SynthesizedBackingFieldSymbol)actualFields[5]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[2], (SynthesizedBackingFieldSymbol)actualFields[1]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[3], (SynthesizedBackingFieldSymbol)actualFields[3]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[4], null); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[5], null); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[6], (SynthesizedBackingFieldSymbol)actualFields[2]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[7], (SynthesizedBackingFieldSymbol)actualFields[4]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[8], (SynthesizedBackingFieldSymbol)actualFields[6]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[9], (SynthesizedBackingFieldSymbol)actualFields[7]); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[10], null); + VerifyMergedProperty((SourcePropertySymbol)actualProperties[11], (SynthesizedBackingFieldSymbol)actualFields[8]); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_ReadOnly(bool reverseOrder, bool useReadOnlyDefinition, bool useReadOnlyImplementation) + { + string modifierDefinition = useReadOnlyDefinition ? "readonly" : " "; + string modifierImplementation = useReadOnlyImplementation ? "readonly" : " "; + string sourceA = $$""" + partial struct S + { + {{modifierDefinition}} partial object P1 { get; } + {{modifierDefinition}} partial object P2 { set; } + partial object P3 { {{modifierDefinition}} get; } + partial object P4 { {{modifierDefinition}} set; } + {{modifierDefinition}} partial object P5 { get; set; } + partial object P6 { {{modifierDefinition}} get; set; } + partial object P7 { get; {{modifierDefinition}} set; } + partial object P8 { {{modifierDefinition}} get; set; } + partial object P9 { get; {{modifierDefinition}} set; } + } + """; + string sourceB = $$""" + partial struct S + { + {{modifierImplementation}} partial object P1 { get => field; } + {{modifierImplementation}} partial object P2 { set { _ = field; } } + partial object P3 { {{modifierImplementation}} get => field; } + partial object P4 { {{modifierImplementation}} set { _ = field; } } + {{modifierImplementation}} partial object P5 { get; set { } } + partial object P6 { {{modifierImplementation}} get; set { } } + partial object P7 { get; {{modifierImplementation}} set { } } + partial object P8 { {{modifierImplementation}} get => field; set { } } + partial object P9 { get => field; {{modifierImplementation}} set { } } + } + """; + var comp = CreateCompilation(reverseOrder ? [sourceB, sourceA] : [sourceA, sourceB]); + switch (useReadOnlyDefinition, useReadOnlyImplementation) + { + case (false, false): + comp.VerifyEmitDiagnostics(); + break; + case (false, true): + comp.VerifyEmitDiagnostics( + // (3,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // readonly partial object P1 { get => field; } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P1").WithLocation(3, 29), + // (4,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // readonly partial object P2 { set { _ = field; } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P2").WithLocation(4, 29), + // (5,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P3 { readonly get => field; } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(5, 34), + // (6,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P4 { readonly set { _ = field; } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(6, 34), + // (7,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // readonly partial object P5 { get; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P5").WithLocation(7, 29), + // (8,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P6 { readonly get; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(8, 34), + // (9,39): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P7 { get; readonly set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(9, 39), + // (10,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P8 { readonly get => field; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(10, 34), + // (11,48): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P9 { get => field; readonly set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(11, 48)); + break; + case (true, false): + comp.VerifyEmitDiagnostics( + // (3,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P1 { get => field; } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P1").WithLocation(3, 29), + // (4,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P2 { set { _ = field; } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P2").WithLocation(4, 29), + // (5,20): error CS8664: 'S.P3': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // partial object P3 { readonly get; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P3").WithArguments("S.P3").WithLocation(5, 20), + // (5,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P3 { get => field; } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(5, 34), + // (6,20): error CS8664: 'S.P4': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // partial object P4 { readonly set; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P4").WithArguments("S.P4").WithLocation(6, 20), + // (6,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P4 { set { _ = field; } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(6, 34), + // (7,29): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P5 { get; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "P5").WithLocation(7, 29), + // (8,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P6 { get; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(8, 34), + // (9,39): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P7 { get; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(9, 39), + // (10,34): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P8 { get => field; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "get").WithLocation(10, 34), + // (11,48): error CS8663: Both partial member declarations must be readonly or neither may be readonly + // partial object P9 { get => field; set { } } + Diagnostic(ErrorCode.ERR_PartialMemberReadOnlyDifference, "set").WithLocation(11, 48)); + break; + case (true, true): + comp.VerifyEmitDiagnostics( + // (5,20): error CS8664: 'S.P3': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // partial object P3 { readonly get; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P3").WithArguments("S.P3").WithLocation(5, 20), + // (6,20): error CS8664: 'S.P4': 'readonly' can only be used on accessors if the property or indexer has both a get and a set accessor + // partial object P4 { readonly set; } + Diagnostic(ErrorCode.ERR_ReadOnlyModMissingAccessor, "P4").WithArguments("S.P4").WithLocation(6, 20)); + break; + } + + var containingType = comp.GetMember("S"); + var actualMembers = comp.GetMember("S"). + GetMembers(). + OfType(). + Select(p => + { + var property = (SourcePropertySymbol)p; + var field = property.BackingField; + return $"{field.ToTestDisplayString()}: IsAutoProperty: {property.IsAutoProperty}, UsesFieldKeyword: {property.UsesFieldKeyword}, BackingField.IsReadOnly: {field.IsReadOnly}"; + }). + ToArray(); + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: True, UsesFieldKeyword: False, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: True, UsesFieldKeyword: False, BackingField.IsReadOnly: False", + $"System.Object S.k__BackingField: IsAutoProperty: True, UsesFieldKeyword: False, BackingField.IsReadOnly: {useReadOnlyImplementation}", + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: False", + $"System.Object S.k__BackingField: IsAutoProperty: False, UsesFieldKeyword: True, BackingField.IsReadOnly: False", + }; + AssertEx.Equal(expectedMembers, actualMembers); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Attribute_01(bool reverseOrder, bool useStatic) + { + string modifier = useStatic ? "static" : " "; + string sourceA = $$""" + using System; + class A : Attribute + { + public A(object o) { } + } + """; + string sourceB1 = $$""" + partial class B + { + {{modifier}} partial object P1 { get; } + {{modifier}} partial object P2 { get; set; } + {{modifier}} partial object P3 { [A(field)] get; } + {{modifier}} partial object P4 { get; [A(field)] set; } + } + """; + string sourceB2 = $$""" + partial class B + { + {{modifier}} partial object P1 { [A(field)] get { return null; } } + {{modifier}} partial object P2 { get { return null; } [A(field)] set { } } + {{modifier}} partial object P3 { get { return null; } } + {{modifier}} partial object P4 { get { return null; } set { } } + } + """; + var comp = CreateCompilation(reverseOrder ? [sourceB2, sourceB1, sourceA] : [sourceA, sourceB1, sourceB2]); + comp.VerifyEmitDiagnostics( + // (3,35): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P1 { [A(field)] get { return null; } } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(3, 35), + // (4,56): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P2 { get { return null; } [A(field)] set { } } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(4, 56), + // (5,35): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P3 { [A(field)] get; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(5, 35), + // (6,40): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P4 { get; [A(field)] set; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(6, 40)); + + var containingType = comp.GetMember("B"); + var actualFields = containingType.GetMembers().OfType().OrderBy(f => f.Name).ToImmutableArray(); + var expectedFields = new[] + { + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(4, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + // Similar to previous test, but using backing field within accessors as well as in attributes. + [Theory] + [CombinatorialData] + public void PartialProperty_Attribute_02(bool reverseOrder, bool useStatic, bool useInit) + { + if (useStatic && useInit) return; + string modifier = useStatic ? "static" : " "; + string setter = useInit ? "init" : "set"; + string sourceA = $$""" + using System; + class A : Attribute + { + public A(object o) { } + } + """; + string sourceB1 = $$""" + partial class B + { + {{modifier}} partial object P1 { get; } + {{modifier}} partial object P2 { get; {{setter}}; } + {{modifier}} partial object P3 { [A(field)] get; } + {{modifier}} partial object P4 { get; [A(field)] {{setter}}; } + } + """; + string sourceB2 = $$""" + partial class B + { + {{modifier}} partial object P1 { [A(field)] get { return field; } } + {{modifier}} partial object P2 { get { return null; } [A(field)] {{setter}}; } + {{modifier}} partial object P3 { get { return field; } } + {{modifier}} partial object P4 { get { return null; } {{setter}}; } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB2, sourceB1, sourceA] : [sourceA, sourceB1, sourceB2], + targetFramework: GetTargetFramework(useInit)); + comp.VerifyEmitDiagnostics( + // (3,35): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P1 { [A(field)] get { return field; } } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(3, 35), + // (4,56): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P2 { get { return null; } [A(field)] set; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(4, 56), + // (5,35): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P3 { [A(field)] get; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(5, 35), + // (6,40): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P4 { get; [A(field)] set; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(6, 40)); + + var containingType = comp.GetMember("B"); + var actualFields = containingType.GetMembers().OfType().OrderBy(f => f.Name).ToImmutableArray(); + var expectedFields = new[] + { + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(4, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "P4", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [CombinatorialData] + public void PartialProperty_Attribute_03(bool reverseOrder) + { + string sourceA = """ + using System; + class A : Attribute + { + } + """; + string sourceB1 = """ + partial class B + { + [field: A] partial object P1 { get; set; } + [field: A] partial object P2 { get; set; } + [field: A] partial object P3 { get; set; } + partial object Q1 { get; set; } + partial object Q2 { get; set; } + partial object Q3 { get; set; } + } + """; + string sourceB2 = """ + partial class B + { + partial object P1 { get => null; set { } } + partial object P2 { get => field; set { } } + partial object P3 { get => null; set; } + [field: A] partial object Q1 { get => null; set { } } + [field: A] partial object Q2 { get => field; set { } } + [field: A] partial object Q3 { get => null; set; } + } + """; + var comp = CreateCompilation(reverseOrder ? [sourceB2, sourceB1, sourceA] : [sourceA, sourceB1, sourceB2]); + comp.VerifyEmitDiagnostics( + // (3,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. + // [field: A] partial object P1 { get; set; } + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(3, 6), + // (6,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. + // [field: A] partial object Q1 { get => null; set { } } + Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(6, 6)); + + var containingType = comp.GetMember("B"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(6, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + Assert.True(actualProperties[3] is SourcePropertySymbol { Name: "Q1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: false, BackingField: null }); + Assert.True(actualProperties[4] is SourcePropertySymbol { Name: "Q2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[5] is SourcePropertySymbol { Name: "Q3", IsPartialDefinition: true, IsAutoProperty: true, UsesFieldKeyword: false, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + // Backing field required for implementation part only (no initializer), + // or required for both parts (with initializer). + [Theory] + [CombinatorialData] + public void PartialProperty_Attribute_04(bool reverseOrder, bool includeInitializer) + { + string getInitializer(int value) => includeInitializer ? $"= {value};" : ""; + string sourceA = """ + using System; + [AttributeUsage(AttributeTargets.All, AllowMultiple=true)] + class A : Attribute + { + private readonly object _obj; + public A(object obj) { _obj = obj; } + public override string ToString() => $"A({_obj})"; + } + """; + string sourceB1 = $$""" + partial class B + { + partial object P1 { get; } {{getInitializer(1)}} + [field: A(3)] partial object P2 { get; } {{getInitializer(2)}} + [field: A(5)] partial object P3 { get; } {{getInitializer(3)}} + } + """; + string sourceB2 = """ + partial class B + { + [field: A(2)] partial object P1 { get => field; } + partial object P2 { get => field; } + [field: A(6)] partial object P3 { get => field; } + } + """; + string sourceC = """ + using System; + using System.Reflection; + 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}:", field.Name); + foreach (var obj in field.GetCustomAttributes()) + Console.Write(" {0},", obj.ToString()); + Console.WriteLine(); + } + } + """; + var verifier = CompileAndVerify( + reverseOrder ? [sourceC, sourceB2, sourceB1, sourceA] : [sourceA, sourceB1, sourceB2, sourceC], + expectedOutput: """ + k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(2), + k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(3), + k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, A(5), A(6), + """); + verifier.VerifyDiagnostics(); + + var comp = (CSharpCompilation)verifier.Compilation; + var containingType = comp.GetMember("B"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + AssertEx.Equal(["A(2)"], actualFields[0].GetAttributes().ToStrings()); + AssertEx.Equal(["A(3)"], actualFields[1].GetAttributes().ToStrings()); + AssertEx.Equal(["A(5)", "A(6)"], actualFields[2].GetAttributes().ToStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(3, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + // Backing field required for definition part only. + [Theory] + [CombinatorialData] + public void PartialProperty_Attribute_05(bool reverseOrder) + { + string sourceA = """ + using System; + [AttributeUsage(AttributeTargets.All, AllowMultiple=true)] + class A : Attribute + { + private readonly object _obj; + public A(object obj) { _obj = obj; } + public override string ToString() => $"A({_obj})"; + } + """; + string sourceB1 = """ + partial class B + { + partial object P1 { [A(field)] get; } + [field: A(3)] partial object P2 { [A(field)] get; } + [field: A(5)] partial object P3 { [A(field)] get; } + } + """; + string sourceB2 = """ + partial class B + { + [field: A(2)] partial object P1 { get => null; } + partial object P2 { get => null; } + [field: A(6)] partial object P3 { get => null; } + } + """; + var comp = CreateCompilation( + reverseOrder ? [sourceB2, sourceB1, sourceA] : [sourceA, sourceB1, sourceB2]); + comp.VerifyEmitDiagnostics( + // (3,42): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // partial object P1 { [A(field)] get; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(3, 42), + // (4,42): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [field: A(3)] partial object P2 { [A(field)] get; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(4, 42), + // (5,42): error CS0182: An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type + // [field: A(5)] partial object P3 { [A(field)] get; } + Diagnostic(ErrorCode.ERR_BadAttributeArgument, "field").WithLocation(5, 42)); + + var containingType = comp.GetMember("B"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] + { + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + "System.Object B.k__BackingField", + }; + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + AssertEx.Equal(["A(2)"], actualFields[0].GetAttributes().ToStrings()); + AssertEx.Equal(["A(3)"], actualFields[1].GetAttributes().ToStrings()); + AssertEx.Equal(["A(5)", "A(6)"], actualFields[2].GetAttributes().ToStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + Assert.Equal(3, actualProperties.Length); + Assert.True(actualProperties[0] is SourcePropertySymbol { Name: "P1", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[1] is SourcePropertySymbol { Name: "P2", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + Assert.True(actualProperties[2] is SourcePropertySymbol { Name: "P3", IsPartialDefinition: true, IsAutoProperty: false, UsesFieldKeyword: true, BackingField: { } }); + + VerifyMergedProperties(actualProperties, actualFields); + } + + [Theory] + [InlineData("{ get; }")] + [InlineData("{ get; set; }")] + [InlineData("{ get => field; }")] + [InlineData("{ set { field = value; } }")] + [InlineData("{ get => field; set; }")] + [InlineData("{ get; set { field = value; } }")] + [InlineData("{ get => field; set { field = value; } }")] + public void Nameof_01(string accessors) + { + string source = $$""" + #nullable enable + using static System.Console; + struct S1 + { + static object? P1 {{accessors}} + static S1() + { + WriteLine(nameof(P1)); + WriteLine(nameof(S1.P1)); + } + public static void M() + { + WriteLine(nameof(P1)); + WriteLine(nameof(S1.P1)); + } + } + struct S2 + { + object? P2 {{accessors}} + public S2(S2 s) + { + WriteLine(nameof(P2)); + WriteLine(nameof(S2.P2)); + WriteLine(nameof(this.P2)); + } + public void M(S2 s) + { + WriteLine(nameof(P2)); + WriteLine(nameof(S2.P2)); + WriteLine(nameof(this.P2)); + } + } + class Program + { + static void Main() + { + S1.M(); + new S2(default).M(default); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: """ + P1 + P1 + P1 + P1 + P2 + P2 + P2 + P2 + P2 + P2 + """); + verifier.VerifyDiagnostics(); + } + + [Theory] + [InlineData("{ get; }")] + [InlineData("{ get; set; }")] + [InlineData("{ get => field; }")] + [InlineData("{ set { field = value; } }")] + [InlineData("{ get => field; set; }")] + [InlineData("{ get; set { field = value; } }")] + [InlineData("{ get => field; set { field = value; } }")] + public void Nameof_02(string accessors) + { + string source = $$""" + #nullable enable + struct S + { + object? P {{accessors}} + public S(bool unused) + { + _ = nameof(new S().P); + } + public void M() + { + _ = nameof(new S().P); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (7,20): error CS8082: Sub-expression cannot be used in an argument to nameof. + // _ = nameof(new S().P); + Diagnostic(ErrorCode.ERR_SubexpressionNotInNameof, "new S()").WithLocation(7, 20), + // (11,20): error CS8082: Sub-expression cannot be used in an argument to nameof. + // _ = nameof(new S().P); + Diagnostic(ErrorCode.ERR_SubexpressionNotInNameof, "new S()").WithLocation(11, 20)); + } + + [Theory] + [InlineData("{ get; }")] + [InlineData("{ get => field; }")] + public void Nameof_03(string accessors) + { + string source = $$""" + #nullable enable + class C + { + public object? F = null; + } + struct S1 + { + static C? P1 {{accessors}} + static S1() + { + _ = nameof((P1 = new()).F); + } + static void M() + { + _ = nameof((P1 = new()).F); + } + } + struct S2 + { + C? P2 {{accessors}} + S2(bool unused) + { + _ = nameof((P2 = new()).F); + } + void M() + { + _ = nameof((P2 = new()).F); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (11,20): error CS8082: Sub-expression cannot be used in an argument to nameof. + // _ = nameof((P1 = new()).F); + Diagnostic(ErrorCode.ERR_SubexpressionNotInNameof, "(P1 = new())").WithLocation(11, 20), + // (15,21): error CS0200: Property or indexer 'S1.P1' cannot be assigned to -- it is read only + // _ = nameof((P1 = new()).F); + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P1").WithArguments("S1.P1").WithLocation(15, 21), + // (23,20): error CS8082: Sub-expression cannot be used in an argument to nameof. + // _ = nameof((P2 = new()).F); + Diagnostic(ErrorCode.ERR_SubexpressionNotInNameof, "(P2 = new())").WithLocation(23, 20), + // (27,21): error CS0200: Property or indexer 'S2.P2' cannot be assigned to -- it is read only + // _ = nameof((P2 = new()).F); + Diagnostic(ErrorCode.ERR_AssgReadonlyProp, "P2").WithArguments("S2.P2").WithLocation(27, 21)); + } + + [Theory] + [InlineData("{ get; }")] + [InlineData("{ get => field; }")] + public void RangeVariableValue_01(string accessors) + { + string source = $$""" + #nullable enable + using System.Linq; + struct S + { + object? P {{accessors}} + S(object value) + { + _ = from x in new [] { value } + let y = (P = x) + select (P = y); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (9,22): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead. + // let y = (P = x) + Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "P").WithLocation(9, 22), + // (10,21): error CS1673: Anonymous methods, lambda expressions, query expressions, and local functions inside structs cannot access instance members of 'this'. Consider copying 'this' to a local variable outside the anonymous method, lambda expression, query expression, or local function and using the local instead. + // select (P = y); + Diagnostic(ErrorCode.ERR_ThisStructNotInAnonMeth, "P").WithLocation(10, 21)); + } + + [Theory] + [InlineData("{ get; set; }")] + [InlineData("{ get => field; set; }")] + public void RangeVariableValue_02(string accessors) + { + string source = $$""" + #nullable enable + using System.Linq; + struct S + { + object? P {{accessors}} + S(S s, object value) + { + _ = from x in new [] { value } + let y = (s.P = x) + select (s.P = y); + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics(); + } + } +} diff --git a/src/Compilers/CSharp/Test/Emit3/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Emit3/Semantics/PrimaryConstructorTests.cs index 9f994c27677c1..b35dc0ce5acde 100644 --- a/src/Compilers/CSharp/Test/Emit3/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/Semantics/PrimaryConstructorTests.cs @@ -19777,6 +19777,142 @@ class C(int p) AssertEx.Equal("System.Int32 C.this[System.Int32 i] { get; }", info.Symbol.ToTestDisplayString()); } + [WorkItem("https://github.com/dotnet/roslyn/issues/75002")] + [Fact] + public void PartialMembers_01() + { + var source1 = """ + C c = null; + c.M(); + _ = c.P; + """; + var source2 = """ + #pragma warning disable 9113 // parameter is unread + partial class C(int p) + { + public partial void M() { } + public partial void M(); + public partial object P { get; } + public partial object P { get => null; } + } + """; + var comp = CreateCompilation([source1, source2]); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + // https://github.com/dotnet/roslyn/issues/75002: SemanticModel.GetDiagnostics() does not merge partial members. + model.GetDiagnostics().Verify( + // (2,3): error CS0121: The call is ambiguous between the following methods or properties: 'C.M()' and 'C.M()' + // c.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M()", "C.M()").WithLocation(2, 3), + // (3,7): error CS0229: Ambiguity between 'C.P' and 'C.P' + // _ = c.P; + Diagnostic(ErrorCode.ERR_AmbigMember, "P").WithArguments("C.P", "C.P").WithLocation(3, 7)); + } + + [Fact] + public void NullableAttributes_01() + { + var source1 = """ + #nullable enable + C c = null!; + object o; + o = c.M(); + o = c.P; + """; + var source2 = """ + #pragma warning disable 9113 // parameter is unread + #nullable enable + using System.Diagnostics.CodeAnalysis; + class C(int p) + { + [return: MaybeNull] public object M() => new(); + [MaybeNull] public object P { get => new(); } + } + """; + var comp = CreateCompilation([source1, source2], targetFramework: TargetFramework.Net80); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + model.GetDiagnostics().Verify( + // (4,5): warning CS8600: Converting null literal or possible null value to non-nullable type. + // o = c.M(); + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c.M()").WithLocation(4, 5), + // (5,5): warning CS8600: Converting null literal or possible null value to non-nullable type. + // o = c.P; + Diagnostic(ErrorCode.WRN_ConvertingNullableToNonNullable, "c.P").WithLocation(5, 5)); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/75002")] + [Fact] + public void NullableAttributes_PartialMembers_01() + { + var source1 = """ + #nullable enable + C c = null!; + object o; + o = c.M(); + o = c.P; + """; + var source2 = """ + #pragma warning disable 9113 // parameter is unread + #nullable enable + using System.Diagnostics.CodeAnalysis; + partial class C(int p) + { + public partial object M() => new(); + [return: MaybeNull] public partial object M(); + [MaybeNull] public partial object P { get; } + public partial object P { get => new(); } + } + """; + var comp = CreateCompilation([source1, source2], targetFramework: TargetFramework.Net80); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + // https://github.com/dotnet/roslyn/issues/75002: SemanticModel.GetDiagnostics() does not merge partial members. + model.GetDiagnostics().Verify( + // (4,7): error CS0121: The call is ambiguous between the following methods or properties: 'C.M()' and 'C.M()' + // o = c.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M()", "C.M()").WithLocation(4, 7), + // (5,7): error CS0229: Ambiguity between 'C.P' and 'C.P' + // o = c.P; + Diagnostic(ErrorCode.ERR_AmbigMember, "P").WithArguments("C.P", "C.P").WithLocation(5, 7)); + } + + [WorkItem("https://github.com/dotnet/roslyn/issues/75002")] + [Fact] + public void NullableAttributes_PartialMembers_02() + { + var source1 = """ + #nullable enable + C c = null!; + object o; + o = c.M(); + o = c.P; + """; + var source2 = """ + #pragma warning disable 9113 // parameter is unread + #nullable enable + using System.Diagnostics.CodeAnalysis; + partial class C(int p) + { + [return: MaybeNull] public partial object M() => new(); + public partial object M(); + public partial object P { get; } + [MaybeNull] public partial object P { get => new(); } + } + """; + var comp = CreateCompilation([source1, source2], targetFramework: TargetFramework.Net80); + var tree = comp.SyntaxTrees[0]; + var model = comp.GetSemanticModel(tree); + // https://github.com/dotnet/roslyn/issues/75002: SemanticModel.GetDiagnostics() does not merge partial members. + model.GetDiagnostics().Verify( + // (4,7): error CS0121: The call is ambiguous between the following methods or properties: 'C.M()' and 'C.M()' + // o = c.M(); + Diagnostic(ErrorCode.ERR_AmbigCall, "M").WithArguments("C.M()", "C.M()").WithLocation(4, 7), + // (5,7): error CS0229: Ambiguity between 'C.P' and 'C.P' + // o = c.P; + Diagnostic(ErrorCode.ERR_AmbigMember, "P").WithArguments("C.P", "C.P").WithLocation(5, 7)); + } + [Fact] public void IllegalCapturingInStruct_01() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InheritanceBindingTests.cs index 9c9f1813a7a75..2d7ff0cb065f0 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 "; CreateCompilationWithMscorlib461(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/Semantic/Semantics/InitOnlyMemberTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/InitOnlyMemberTests.cs index e0d74de2e5e92..37542bede467a 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 05e9e10891d0a..acffbb4ef862a 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -15266,6 +15266,12 @@ 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): error CS8080: Auto-implemented properties must override all accessors of the overridden property. + // public override Func P2 { set; } // warn + Diagnostic(ErrorCode.ERR_AutoPropertyMustOverrideSet, "P2").WithLocation(16, 34), + // (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/RecordStructTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs index ddb864685943f..3b21e8954a2a3 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RecordStructTests.cs @@ -3997,14 +3997,14 @@ record struct Pos2(int X) // (2,15): error CS0171: Field 'Pos.x' must be fully assigned before control is returned to the caller. Consider updating to language version '11.0' to auto-default the field. // record struct Pos(int X) Diagnostic(ErrorCode.ERR_UnassignedThisUnsupportedVersion, "Pos").WithArguments("Pos.x", "11.0").WithLocation(2, 15), - // (5,16): error CS8050: Only auto-implemented properties can have initializers. + // (5,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public int X { get { return x; } set { x = value; } } = X; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "X").WithLocation(5, 16) ); comp = CreateCompilation(source, parseOptions: TestOptions.Regular11); comp.VerifyEmitDiagnostics( - // (5,16): error CS8050: Only auto-implemented properties can have initializers. + // (5,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public int X { get { return x; } set { x = value; } } = X; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "X").WithLocation(5, 16) ); diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs index 3487d96bc6b2e..3a48f854b3685 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs @@ -674,7 +674,7 @@ public void StructNonAutoPropertyInitializer() // (3,16): error CS8773: Feature 'struct field initializers' is not available in C# 9.0. Please use language version 10.0 or greater. // public int I { get { throw null; } set {} } = 9; Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion9, "I").WithArguments("struct field initializers", "10.0").WithLocation(3, 16), - // (3,16): error CS8050: Only auto-implemented properties can have initializers. + // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public int I { get { throw null; } set {} } = 9; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithLocation(3, 16)); @@ -683,7 +683,7 @@ public void StructNonAutoPropertyInitializer() // (1,8): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. // struct S Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S").WithLocation(1, 8), - // (3,16): error CS8050: Only auto-implemented properties can have initializers. + // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public int I { get { throw null; } set {} } = 9; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithLocation(3, 16)); } diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs index cdc7c38eca163..22c58d94fdcf9 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, or properties that use the 'field' keyword, 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 5e13b97569141..252bf355fe09b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -3217,7 +3217,7 @@ public interface I1 // (4,34): error CS1014: A get or set accessor expected // static abstract int P1 {add; remove;} = 0; Diagnostic(ErrorCode.ERR_GetOrSetExpected, "remove").WithLocation(4, 34), - // (4,25): error CS8050: Only auto-implemented properties can have initializers. + // (4,25): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static abstract int P1 {add; remove;} = 0; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(4, 25), // (4,25): error CS0548: 'I1.P1': property or indexer must have at least one accessor @@ -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, or properties that use the 'field' keyword, 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) ); @@ -3306,7 +3309,7 @@ public interface I1 targetFramework: TargetFramework.Net60); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); compilation1.VerifyEmitDiagnostics( - // (4,25): error CS8050: Only auto-implemented properties can have initializers. + // (4,25): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static abstract int P1 {get; set;} = 0; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(4, 25) ); @@ -3343,7 +3346,7 @@ public interface I1 [Theory] [CombinatorialData] - public void PropertyImplementation_109(bool isStatic) + public void PropertyImplementation_109A(bool isStatic, bool useCSharp13) { string declModifiers = isStatic ? "static virtual " : ""; @@ -3366,17 +3369,39 @@ 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); - // 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) - ); + switch (isStatic, useCSharp13) + { + case (true, true): + 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)); + break; + case (true, false): + compilation1.VerifyDiagnostics(); + break; + case (false, true): + compilation1.VerifyDiagnostics( + // (4,9): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // int P1 + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 9), + // (4,9): error CS0525: Interfaces cannot contain instance fields + // int P1 + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(4, 9)); + break; + case (false, false): + // See also earlier 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( + // (4,9): error CS0525: Interfaces cannot contain instance fields + // int P1 + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(4, 9)); + break; + } var p1 = compilation1.GetMember("I1.P1"); var getP1 = p1.GetMethod; @@ -3384,6 +3409,9 @@ class Test1 : I1 Assert.False(p1.IsReadOnly); Assert.False(p1.IsWriteOnly); + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); + Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual); Assert.False(getP1.IsAbstract); @@ -3403,7 +3431,72 @@ class Test1 : I1 [Theory] [CombinatorialData] - public void PropertyImplementation_110(bool isStatic) + public void PropertyImplementation_109B(bool useCSharp13) + { + var source1 = +@" +public interface I1 +{ + static int P1 + { + get + { + System.Console.WriteLine(""get P1""); + return 0; + } + set; + } +} + +class Test1 : I1 +{} +"; + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + parseOptions: useCSharp13 ? TestOptions.Regular13 : TestOptions.RegularPreview, + targetFramework: TargetFramework.Net60); + Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); + + if (useCSharp13) + { + compilation1.VerifyDiagnostics( + // (4,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static int P1 + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 16)); + } + else + { + compilation1.VerifyDiagnostics(); + } + + var p1 = compilation1.GetMember("I1.P1"); + var getP1 = p1.GetMethod; + var setP1 = p1.SetMethod; + Assert.False(p1.IsReadOnly); + Assert.False(p1.IsWriteOnly); + + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); + + Assert.False(p1.IsAbstract); + Assert.False(p1.IsVirtual); + Assert.False(getP1.IsAbstract); + Assert.False(getP1.IsVirtual); + Assert.False(setP1.IsAbstract); + Assert.False(setP1.IsVirtual); + + var test1 = compilation1.GetTypeByMetadataName("Test1"); + + Assert.Null(test1.FindImplementationForInterfaceMember(p1)); + Assert.Null(test1.FindImplementationForInterfaceMember(getP1)); + Assert.Null(test1.FindImplementationForInterfaceMember(setP1)); + + Assert.False(getP1.IsMetadataVirtual()); + Assert.False(setP1.IsMetadataVirtual()); + } + + [Theory] + [CombinatorialData] + public void PropertyImplementation_110A(bool isStatic, bool useCSharp13) { string declModifiers = isStatic ? "static virtual " : ""; @@ -3422,17 +3515,39 @@ 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); - // 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) - ); + switch (isStatic, useCSharp13) + { + case (true, true): + 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)); + break; + case (true, false): + compilation1.VerifyDiagnostics(); + break; + case (false, true): + compilation1.VerifyDiagnostics( + // (4,9): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // int P1 + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 9), + // (4,9): error CS0525: Interfaces cannot contain instance fields + // int P1 + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(4, 9)); + break; + case (false, false): + // See also earlier 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( + // (4,9): error CS0525: Interfaces cannot contain instance fields + // int P1 + Diagnostic(ErrorCode.ERR_InterfacesCantContainFields, "P1").WithLocation(4, 9)); + break; + } var p1 = compilation1.GetMember("I1.P1"); var getP1 = p1.GetMethod; @@ -3440,6 +3555,9 @@ class Test1 : I1 Assert.False(p1.IsReadOnly); Assert.False(p1.IsWriteOnly); + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); + Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual); Assert.False(getP1.IsAbstract); @@ -3457,6 +3575,67 @@ class Test1 : I1 Assert.True(setP1.IsMetadataVirtual()); } + [Theory] + [CombinatorialData] + public void PropertyImplementation_110B(bool useCSharp13) + { + var source1 = +@" +public interface I1 +{ + static int P1 + { + get; + set => System.Console.WriteLine(""set P1""); + } +} + +class Test1 : I1 +{} +"; + var compilation1 = CreateCompilation(source1, options: TestOptions.DebugDll, + parseOptions: useCSharp13 ? TestOptions.Regular13 : TestOptions.RegularPreview, + targetFramework: TargetFramework.Net60); + Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); + + if (useCSharp13) + { + compilation1.VerifyDiagnostics( + // (4,16): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. + // static int P1 + Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 16)); + } + else + { + compilation1.VerifyDiagnostics(); + } + + var p1 = compilation1.GetMember("I1.P1"); + var getP1 = p1.GetMethod; + var setP1 = p1.SetMethod; + Assert.False(p1.IsReadOnly); + Assert.False(p1.IsWriteOnly); + + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); + + Assert.False(p1.IsAbstract); + Assert.False(p1.IsVirtual); + Assert.False(getP1.IsAbstract); + Assert.False(getP1.IsVirtual); + Assert.False(setP1.IsAbstract); + Assert.False(setP1.IsVirtual); + + var test1 = compilation1.GetTypeByMetadataName("Test1"); + + Assert.Null(test1.FindImplementationForInterfaceMember(p1)); + Assert.Null(test1.FindImplementationForInterfaceMember(getP1)); + Assert.Null(test1.FindImplementationForInterfaceMember(setP1)); + + Assert.False(getP1.IsMetadataVirtual()); + Assert.False(setP1.IsMetadataVirtual()); + } + [Theory] [CombinatorialData] public void PropertyImplementation_201(bool isStatic) @@ -56681,7 +56860,7 @@ class Test1 : I2 } "; ValidatePropertyReAbstraction_014(source1, isStatic: true, - // (9,28): error CS8050: Only auto-implemented properties can have initializers. + // (9,28): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static abstract int I1.P1 { get; set; } = 0; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(9, 28), // (12,15): error CS0535: 'Test1' does not implement interface member 'I1.P1' @@ -56739,7 +56918,7 @@ class Test1 : I2 } "; ValidatePropertyReAbstraction_014(source1, isStatic: true, - // (9,28): error CS8050: Only auto-implemented properties can have initializers. + // (9,28): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static abstract int I1.P1 { get; } = 0; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(9, 28), // (12,15): error CS0535: 'Test1' does not implement interface member 'I1.P1' @@ -56797,7 +56976,7 @@ class Test1 : I2 } "; ValidatePropertyReAbstraction_014(source1, isStatic: true, - // (9,28): error CS8050: Only auto-implemented properties can have initializers. + // (9,28): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static abstract int I1.P1 { set; } = 0; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(9, 28), // (12,15): error CS0535: 'Test1' does not implement interface member 'I1.P1' @@ -67575,6 +67754,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/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index 36758dd62d35b..b20d76e5f052d 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -1524,9 +1524,6 @@ partial class C // (11,30): error CS8799: Both partial member declarations must have identical accessibility modifiers. // partial int P1 { private get => 1; private set; } Diagnostic(ErrorCode.ERR_PartialMemberAccessibilityDifference, "get").WithLocation(11, 30), - // (11,48): error CS0501: 'C.P1.set' must declare a body because it is not marked abstract, extern, or partial - // partial int P1 { private get => 1; private set; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("C.P1.set").WithLocation(11, 48), // (11,48): error CS8799: Both partial member declarations must have identical accessibility modifiers. // partial int P1 { private get => 1; private set; } Diagnostic(ErrorCode.ERR_PartialMemberAccessibilityDifference, "set").WithLocation(11, 48)); @@ -3771,18 +3768,41 @@ partial class C var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (3,27): error CS8050: Only auto-implemented properties can have initializers. + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P1 { get; set; } = "a"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), - // (7,27): error CS8050: Only auto-implemented properties can have initializers. + // (7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P2 { get => ""; set { } } = "b"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(7, 27), - // (9,27): error CS8050: Only auto-implemented properties can have initializers. + // (9,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get; set; } = "c"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(9, 27), - // (10,27): error CS8050: Only auto-implemented properties can have initializers. + // (10,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial string P3 { get => ""; set { } } = "d"; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P3").WithLocation(10, 27), + // (10,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get => ""; set { } } = "d"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(10, 27)); + + AssertEx.Equal([ + "System.String C.k__BackingField", + "System.String C.P1 { get; set; }", + "System.String C.P1.get", + "void C.P1.set", + "System.String C.P2 { get; set; }", + "System.String C.P2.get", + "void C.P2.set", + "System.String C.k__BackingField", + "System.String C.k__BackingField", + "System.String C.P3 { get; set; }", + "System.String C.P3.get", + "void C.P3.set", + "C..ctor()"], + comp.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString())); + + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); } [Fact] @@ -3804,30 +3824,53 @@ partial class C var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (3,27): error CS8050: Only auto-implemented properties can have initializers. + // (3,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P1 { get; set; } = ERROR; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(3, 27), // (3,46): error CS0103: The name 'ERROR' does not exist in the current context // public partial string P1 { get; set; } = ERROR; Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(3, 46), - // (7,27): error CS8050: Only auto-implemented properties can have initializers. + // (7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P2 { get => ""; set { } } = ERROR; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(7, 27), // (7,55): error CS0103: The name 'ERROR' does not exist in the current context // public partial string P2 { get => ""; set { } } = ERROR; Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(7, 55), - // (9,27): error CS8050: Only auto-implemented properties can have initializers. + // (9,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get; set; } = ERROR; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(9, 27), // (9,46): error CS0103: The name 'ERROR' does not exist in the current context // public partial string P3 { get; set; } = ERROR; Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(9, 46), - // (10,27): error CS8050: Only auto-implemented properties can have initializers. + // (10,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial string P3 { get => ""; set { } } = ERROR; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P3").WithLocation(10, 27), + // (10,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get => ""; set { } } = ERROR; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(10, 27), // (10,55): error CS0103: The name 'ERROR' does not exist in the current context // public partial string P3 { get => ""; set { } } = ERROR; Diagnostic(ErrorCode.ERR_NameNotInContext, "ERROR").WithArguments("ERROR").WithLocation(10, 55)); + + AssertEx.Equal([ + "System.String C.k__BackingField", + "System.String C.P1 { get; set; }", + "System.String C.P1.get", + "void C.P1.set", + "System.String C.P2 { get; set; }", + "System.String C.P2.get", + "void C.P2.set", + "System.String C.k__BackingField", + "System.String C.k__BackingField", + "System.String C.P3 { get; set; }", + "System.String C.P3.get", + "void C.P3.set", + "C..ctor()"], + comp.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString())); + + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); } [Fact] @@ -3862,7 +3905,7 @@ partial class C // (6,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. // [field: Attr1] Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(6, 6), - // (7,27): error CS8050: Only auto-implemented properties can have initializers. + // (7,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P1 { get; set; } = "a"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P1").WithLocation(7, 27), // (8,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. @@ -3874,19 +3917,22 @@ partial class C // (13,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. // [field: Attr2] Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(13, 6), - // (14,27): error CS8050: Only auto-implemented properties can have initializers. + // (14,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P2 { get => ""; set { } } = "b"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P2").WithLocation(14, 27), // (16,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. // [field: Attr1] Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(16, 6), - // (17,27): error CS8050: Only auto-implemented properties can have initializers. + // (17,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get; set; } = "c"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(17, 27), // (18,6): warning CS0657: 'field' is not a valid attribute location for this declaration. Valid attribute locations for this declaration are 'property'. All attributes in this block will be ignored. // [field: Attr2] Diagnostic(ErrorCode.WRN_AttributeLocationOnBadDeclaration, "field").WithArguments("field", "property").WithLocation(18, 6), - // (19,27): error CS8050: Only auto-implemented properties can have initializers. + // (19,27): error CS9263: A partial property cannot have an initializer on both the definition and implementation. + // public partial string P3 { get => ""; set { } } = "d"; + Diagnostic(ErrorCode.ERR_PartialPropertyDuplicateInitializer, "P3").WithLocation(19, 27), + // (19,27): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // public partial string P3 { get => ""; set { } } = "d"; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P3").WithLocation(19, 27)); @@ -3903,17 +3949,12 @@ partial class C "System.String C.P3 { get; set; }", "System.String C.P3.get", "void C.P3.set", - "System.String C.k__BackingField", "C..ctor()"], comp.GetMember("C").GetMembers().SelectAsArray(m => m.ToTestDisplayString())); Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); - - var p3Fields = comp.GetMembers("C.k__BackingField"); - Assert.Equal(2, p3Fields.Length); - Assert.Empty(p3Fields[0].GetAttributes()); - Assert.Empty(p3Fields[1].GetAttributes()); + Assert.Empty(comp.GetMember("C.k__BackingField").GetAttributes()); } [Theory] @@ -4992,13 +5033,7 @@ partial class C """; var comp = CreateCompilation(source); - comp.VerifyEmitDiagnostics( - // (4,42): error CS0501: 'C.Prop1.set' must declare a body because it is not marked abstract, extern, or partial - // public partial int Prop1 { get => 1; set; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("C.Prop1.set").WithLocation(4, 42), - // (7,32): error CS0501: 'C.Prop2.get' must declare a body because it is not marked abstract, extern, or partial - // public partial int Prop2 { get; set { } } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("C.Prop2.get").WithLocation(7, 32)); + comp.VerifyEmitDiagnostics(); } [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/74679")] diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/ExpressionBodiedPropertyTests.cs index d672812e99439..f4efc173efae9 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/Symbol/Symbols/Source/PropertyTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/Source/PropertyTests.cs index 428c0b9693ba4..fc17e45995e8a 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 58650bbfadcdb..9c3566875c603 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -1761,13 +1761,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 @@ -1797,6 +1797,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), @@ -8126,13 +8129,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] @@ -16849,13 +16860,13 @@ public void CS8050ERR_InitializerOnNonAutoProperty() protected int P { get { throw null; } set { } } = 1; }"; CreateCompilation(source).VerifyDiagnostics( - // (5,9): error CS8050: Only auto-implemented properties can have initializers. + // (5,9): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // int I { get { throw null; } set { } } = 1; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "I").WithLocation(5, 9), - // (6,16): error CS8050: Only auto-implemented properties can have initializers. + // (6,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // static int S { get { throw null; } set { } } = 1; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "S").WithLocation(6, 16), - // (7,19): error CS8050: Only auto-implemented properties can have initializers. + // (7,19): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // protected int P { get { throw null; } set { } } = 1; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(7, 19) ); 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/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..9492d589e19da --- /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.CSharp13; + } + + 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.CSharp13, 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.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13; + 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.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13; + 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.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + { + bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13; + 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.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion, + bool useInit) + { + bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13; + 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion, + bool useInit) + { + bool expectedParsedAsToken = languageVersion > LanguageVersion.CSharp13; + 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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.CSharp13, 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..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 @@ -241,21 +246,56 @@ 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)); + 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.CSharp13, LanguageVersionFacts.CSharpNext)] 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)); + 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 @@ -272,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; @@ -283,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; @@ -303,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; @@ -323,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; @@ -343,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; @@ -363,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 @@ -389,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 @@ -406,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 @@ -423,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 @@ -440,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 @@ -456,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 @@ -478,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 @@ -496,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 @@ -513,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 @@ -524,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 @@ -558,9 +640,10 @@ 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 class C { void Deconstruct(out object x, out object y) => throw null; @@ -576,19 +659,29 @@ 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 - // 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. - // (field, @value) = new C(); - Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(10, 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 @@ -610,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 @@ -639,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] @@ -786,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 = $$""" @@ -813,13 +920,32 @@ event EventHandler E } """; var comp = CreateCompilation(source, parseOptions: TestOptions.Regular.WithLanguageVersion(languageVersion)); - comp.VerifyEmitDiagnostics(); + if (!escapeIdentifier && languageVersion > LanguageVersion.CSharp13) + { + comp.VerifyEmitDiagnostics( + // (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.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): 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.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)); + } + else + { + comp.VerifyEmitDiagnostics(); + } } [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 = $$""" @@ -846,12 +972,177 @@ object P1 { comp.VerifyEmitDiagnostics(); } - else + 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.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.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(13, 23)); + Diagnostic(ErrorCode.ERR_ExpressionHasNoName, "field").WithLocation(13, 23)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + } + + [Fact] + public void NameOf_01() + { + string source = """ + class C + { + object P => nameof(field); + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (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): 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); } } } 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/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 89385d382c921..e762383d458fc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -7,222 +7,509 @@ using Microsoft.CodeAnalysis.Test.Utilities; using Xunit; -namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Recommendations; + +[Trait(Traits.Feature, Traits.Features.KeywordRecommending)] +public sealed class FieldKeywordRecommenderTests : KeywordRecommenderTests { - [Trait(Traits.Feature, Traits.Features.KeywordRecommending)] - public class FieldKeywordRecommenderTests : KeywordRecommenderTests - { - [Fact] - public async Task TestNotAtRoot_Interactive() - { - await VerifyAbsenceAsync(SourceCodeKind.Script, -@"$$"); - } - - [Fact] - public async Task TestNotAfterClass_Interactive() - { - await VerifyAbsenceAsync(SourceCodeKind.Script, - """ - class C { } - $$ - """); - } - - [Fact] - public async Task TestNotAfterGlobalStatement_Interactive() - { - await VerifyAbsenceAsync(SourceCodeKind.Script, - """ - System.Console.WriteLine(); - $$ - """); - } - - [Fact] - public async Task TestNotAfterGlobalVariableDeclaration_Interactive() - { - await VerifyAbsenceAsync(SourceCodeKind.Script, - """ - int i = 0; - $$ - """); - } - - [Fact] - public async Task TestNotInUsingAlias() - { - await VerifyAbsenceAsync( -@"using Goo = $$"); - } - - [Fact] - public async Task TestNotInGlobalUsingAlias() - { - await VerifyAbsenceAsync( -@"global using Goo = $$"); - } - - [Fact] - public async Task TestNotInEmptyStatement() - { - await VerifyAbsenceAsync(AddInsideMethod( -@"$$")); - } - - [Fact] - public async Task TestInAttributeInsideClass() - { - await VerifyKeywordAsync( - """ - class C { - [$$ - """); - } - - [Theory] - [InlineData("record")] - [InlineData("record class")] - [InlineData("record struct")] - public async Task TestInAttributeInsideRecord(string record) - { - // The recommender doesn't work in record in script - // Tracked by https://github.com/dotnet/roslyn/issues/44865 - await VerifyWorkerAsync( -$@"{record} C {{ - [$$", absent: false, TestOptions.RegularPreview); - } - - [Fact] - public async Task TestInAttributeAfterAttributeInsideClass() - { - await VerifyKeywordAsync( - """ - class C { - [Goo] - [$$ - """); - } - - [Fact] - public async Task TestInAttributeAfterMethod() - { - await VerifyKeywordAsync( - """ - class C { - void Goo() { + [Fact] + public async Task TestNotAtRoot_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, + """$$"""); + } + + [Fact] + public async Task TestNotAfterClass_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, + """ + class C { } + $$ + """); + } + + [Fact] + public async Task TestNotAfterGlobalStatement_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, + """ + System.Console.WriteLine(); + $$ + """); + } + + [Fact] + public async Task TestNotAfterGlobalVariableDeclaration_Interactive() + { + await VerifyAbsenceAsync(SourceCodeKind.Script, + """ + int i = 0; + $$ + """); + } + + [Fact] + public async Task TestNotInUsingAlias() + { + await VerifyAbsenceAsync( + """using Goo = $$"""); + } + + [Fact] + public async Task TestNotInGlobalUsingAlias() + { + await VerifyAbsenceAsync( + """global using Goo = $$"""); + } + + [Fact] + public async Task TestNotInEmptyStatement() + { + await VerifyAbsenceAsync(AddInsideMethod( + """$$""")); + } + + [Fact] + public async Task TestInAttributeInsideClass() + { + await VerifyKeywordAsync( + """ + class C { + [$$ + """); + } + + [Fact] + public async Task TestNotInAttributeArgumentInsideClass1() + { + await VerifyAbsenceAsync( + """ + class C { + [field: $$ + """); + } + + [Fact] + public async Task TestNotInAttributeArgumentInsideClass2() + { + await VerifyAbsenceAsync( + """ + class C { + [field: Goo($$)] + """); + } + + [Fact] + public async Task TestNotInAttributeArgumentInsideClass3() + { + await VerifyAbsenceAsync( + """ + class C { + [field: Goo($$)] int Prop { get; } + """); + } + + [Theory] + [InlineData("record")] + [InlineData("record class")] + [InlineData("record struct")] + public async Task TestInAttributeInsideRecord(string record) + { + // The recommender doesn't work in record in script + // Tracked by https://github.com/dotnet/roslyn/issues/44865 + await VerifyWorkerAsync( + $$""" + {{record}} C { + [$$ + """, absent: false, TestOptions.RegularPreview); + } + + [Fact] + public async Task TestInAttributeAfterAttributeInsideClass() + { + await VerifyKeywordAsync( + """ + class C { + [Goo] + [$$ + """); + } + + [Fact] + public async Task TestInAttributeAfterMethod() + { + await VerifyKeywordAsync( + """ + class C { + void Goo() { + } + [$$ + """); + } + + [Fact] + public async Task TestInAttributeAfterProperty() + { + await VerifyKeywordAsync( + """ + class C { + int Goo { + get; + } + [$$ + """); + } + + [Fact] + public async Task TestInAttributeAfterField() + { + await VerifyKeywordAsync( + """ + class C { + int Goo; + [$$ + """); + } + + [Fact] + public async Task TestInAttributeAfterEvent() + { + await VerifyKeywordAsync( + """ + class C { + event Action Goo; + [$$ + """); + } + + [Fact] + public async Task TestNotInOuterAttribute() + { + await VerifyAbsenceAsync( + """[$$"""); + } + + [Fact] + public async Task TestNotInParameterAttribute() + { + await VerifyAbsenceAsync( + """ + class C { + void Goo([$$ + """); + } + + [Fact] + public async Task TestNotInPropertyAttribute() + { + await VerifyAbsenceAsync( + """ + class C { + int Goo { [$$ + """); + } + + [Fact] + public async Task TestNotInEventAttribute() + { + await VerifyAbsenceAsync( + """ + class C { + event Action Goo { [$$ + """); + } + + [Fact] + public async Task TestNotInTypeParameters() + { + await VerifyAbsenceAsync( + """class C<[$$"""); + } + + [Fact] + public async Task TestNotInInterface() + { + await VerifyAbsenceAsync( + """ + interface I { + [$$ + """); + } + + [Fact] + public async Task TestInStruct() + { + await VerifyKeywordAsync( + """ + struct S { + [$$ + """); + } + + [Fact] + public async Task TestInEnum() + { + await VerifyKeywordAsync( + """ + enum E { + [$$ + """); + } + + [Fact] + public async Task TestNotInPropertyInitializer() + { + await VerifyAbsenceAsync( + """ + class C + { + int Goo { get; } = $$ + } + """); + } + + [Fact] + public async Task TestInPropertyExpressionBody() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo => $$ + } + """); + } + + [Fact] + public async Task TestNotInPropertyExpressionBody_NotPrimary() + { + await VerifyAbsenceAsync( + """ + class C + { + int Goo => this.$$ + } + """); + } + + [Fact] + public async Task TestInPropertyAccessor1() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get => $$ } + } + """); + } + + [Fact] + public async Task TestInPropertyAccessor2() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get { return $$ } } + } + """); + } + + [Fact] + public async Task TestInPropertyStatement() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get { $$ } } + } + """); + } + + [Fact] + public async Task TestInPropertyExpressionContext() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get { var v = 1 + $$ } } + } + """); + } + + [Fact] + public async Task TestInPropertyArgument1() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get { Bar($$) } } + } + """); + } + + [Fact] + public async Task TestInPropertyArgument2() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo { get { Bar(ref $$) } } + } + """); + } + + [Fact] + public async Task TestNotInPropertyNameof() + { + await VerifyAbsenceAsync( + """ + class C + { + int Goo { get { Bar(nameof($$)) } } + } + """); + } + + [Fact] + public async Task TestInLocalFunctionInProperty() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo + { + get + { + void Bar() { return $$ } } - [$$ - """); - } - - [Fact] - public async Task TestInAttributeAfterProperty() - { - await VerifyKeywordAsync( - """ - class C { - int Goo { - get; + } + } + """); + } + + [Fact] + public async Task TestInLambdaInProperty() + { + await VerifyKeywordAsync( + """ + class C + { + int Goo + { + get + { + var v = customers.Where(c => c.Age > $$); } - [$$ - """); - } - - [Fact] - public async Task TestInAttributeAfterField() - { - await VerifyKeywordAsync( - """ - class C { - int Goo; - [$$ - """); - } - - [Fact] - public async Task TestInAttributeAfterEvent() - { - await VerifyKeywordAsync( - """ - class C { - event Action Goo; - [$$ - """); - } - - [Fact] - public async Task TestNotInOuterAttribute() - { - await VerifyAbsenceAsync( -@"[$$"); - } - - [Fact] - public async Task TestNotInParameterAttribute() - { - await VerifyAbsenceAsync( - """ - class C { - void Goo([$$ - """); - } - - [Fact] - public async Task TestNotInPropertyAttribute() - { - await VerifyAbsenceAsync( - """ - class C { - int Goo { [$$ - """); - } - - [Fact] - public async Task TestNotInEventAttribute() - { - await VerifyAbsenceAsync( - """ - class C { - event Action Goo { [$$ - """); - } - - [Fact] - public async Task TestNotInTypeParameters() - { - await VerifyAbsenceAsync( -@"class C<[$$"); - } - - [Fact] - public async Task TestNotInInterface() - { - await VerifyAbsenceAsync( - """ - interface I { - [$$ - """); - } - - [Fact] - public async Task TestInStruct() - { - await VerifyKeywordAsync( - """ - struct S { - [$$ - """); - } - - [Fact] - public async Task TestInEnum() - { - await VerifyKeywordAsync( - """ - enum E { - [$$ - """); - } + } + } + """); + } + + [Fact] + public async Task TestNotInAccessorAttribute() + { + await VerifyAbsenceAsync( + """ + class C + { + [Bar($$)] + int Goo { get; } + } + """); + } + + [Fact] + public async Task TestNotInIndexer1() + { + await VerifyAbsenceAsync( + """ + class C + { + int this[int index] => $$ + } + """); + } + + [Fact] + public async Task TestNotInIndexer2() + { + await VerifyAbsenceAsync( + """ + class C + { + int this[int index] { get => $$ } + } + """); + } + + [Fact] + public async Task TestNotInIndexer3() + { + await VerifyAbsenceAsync( + """ + class C + { + int this[int index] { get { return $$ } } + } + """); + } + + [Fact] + public async Task TestNotInEvent1() + { + await VerifyAbsenceAsync( + """ + class C + { + event Action E { add { $$ } } + } + """); + } + + [Fact] + public async Task TestNotInMethodContext() + { + await VerifyAbsenceAsync( + """ + class C + { + void M() + { + $$ + } + } + """); + } + + [Fact] + public async Task TestNotInMethodExpressionContext() + { + await VerifyAbsenceAsync( + """ + class C + { + void M() + { + Goo($$); + } + } + """); + } + + [Fact] + public async Task TestNotInGlobalStatement() + { + await VerifyAbsenceAsync( + """ + $$ + """); } } diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs index 85e256443e1d5..c27ef93bb9f76 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using System.Threading; @@ -158,7 +159,10 @@ private Task VerifyAtEndOfFileAsync(string text, int position, bool absent, CSha private Task VerifyAtEndOfFile_KeywordPartiallyWrittenAsync(string text, int position, bool absent, CSharpParseOptions? options, int? matchPriority) => VerifyAtEndOfFileAsync(text, position, absent, KeywordText[..1], options: options, matchPriority: matchPriority); - internal async Task VerifyKeywordAsync(string text, CSharpParseOptions? options = null, CSharpParseOptions? scriptOptions = null) + internal async Task VerifyKeywordAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string text, + CSharpParseOptions? options = null, + CSharpParseOptions? scriptOptions = null) { // run the verification in both context(normal and script) await VerifyWorkerAsync(text, absent: false, options: options); @@ -179,14 +183,19 @@ protected async Task VerifyKeywordAsync(SourceCodeKind kind, string text) } } - protected async Task VerifyAbsenceAsync(string text, CSharpParseOptions? options = null, CSharpParseOptions? scriptOptions = null) + protected async Task VerifyAbsenceAsync( + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string text, + CSharpParseOptions? options = null, + CSharpParseOptions? scriptOptions = null) { // run the verification in both context(normal and script) await VerifyWorkerAsync(text, absent: true, options: options); await VerifyWorkerAsync(text, absent: true, options: scriptOptions ?? Options.Script); } - protected async Task VerifyAbsenceAsync(SourceCodeKind kind, string text) + protected async Task VerifyAbsenceAsync( + SourceCodeKind kind, + [StringSyntax(PredefinedEmbeddedLanguageNames.CSharpTest)] string text) { switch (kind) { 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))] diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 40e75cb897832..d0a726ffe7ebc 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -5,10 +5,12 @@ using System.Collections.Generic; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders; -internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommender +internal sealed class FieldKeywordRecommender() + : AbstractSyntacticSingleKeywordRecommender(SyntaxKind.FieldKeyword) { // interfaces don't have members that you can put a [field:] attribute on private static readonly ISet s_validTypeDeclarations = new HashSet(SyntaxFacts.EqualityComparer) @@ -20,11 +22,36 @@ internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommend SyntaxKind.EnumDeclaration, }; - public FieldKeywordRecommender() - : base(SyntaxKind.FieldKeyword) + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { + // `[field:` is legal in an attribute within a type. + if (context.IsMemberAttributeContext(s_validTypeDeclarations, cancellationToken)) + return true; + + // Check if we're within a property accessor where the `field` keyword is legal. Note: we do not do a lang + // version check here. We do not want to interfere with users trying to use/learn this feature. The user will + // get a clear message if they're not on the right lang version telling them about the issue, and offering to + // upgrade their project if they way. + if (context.IsAnyExpressionContext || context.IsStatementContext) + { + if (!context.IsNameOfContext && IsInPropertyAccessor(context.TargetToken)) + return true; + } + + return false; } - protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) - => context.IsMemberAttributeContext(s_validTypeDeclarations, cancellationToken); + private static bool IsInPropertyAccessor(SyntaxToken targetToken) + { + for (var node = targetToken.Parent; node != null; node = node.Parent) + { + if (node is ArrowExpressionClauseSyntax { Parent: PropertyDeclarationSyntax }) + return true; + + if (node is AccessorDeclarationSyntax { Parent: AccessorListSyntax { Parent: PropertyDeclarationSyntax } }) + return true; + } + + return false; + } } 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]; }