From a55ed9c3c42a4191c0e1dd10dd01279ec1c8cb90 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Thu, 1 Aug 2024 13:17:09 -0700 Subject: [PATCH 01/18] Parse `field` as a contextual keyword (#73947) --- .../Portable/Binder/Binder_Expressions.cs | 50 +- .../Portable/Generated/CSharp.Generated.g4 | 5 + .../Syntax.xml.Internal.Generated.cs | 110 + .../Syntax.xml.Main.Generated.cs | 20 + .../Syntax.xml.Syntax.Generated.cs | 40 + .../CSharp/Portable/Parser/LanguageParser.cs | 80 +- .../Portable/Parser/SyntaxFactoryContext.cs | 6 + .../CSharp/Portable/PublicAPI.Unshipped.txt | 12 + .../Symbols/Source/SourcePropertySymbol.cs | 27 +- .../Source/SourcePropertySymbolBase.cs | 12 +- ...nthesizedRecordEqualityContractProperty.cs | 1 + .../SynthesizedRecordPropertySymbol.cs | 1 + .../Syntax/InternalSyntax/CSharpSyntaxNode.cs | 14 +- .../InternalSyntax/CSharpSyntaxNodeCache.cs | 5 + .../CSharp/Portable/Syntax/Syntax.xml | 15 + .../CSharp/Portable/Syntax/SyntaxKind.cs | 1 + .../Emit/CodeGen/CodeGenRefReturnTests.cs | 4 +- .../Test/Emit2/PDB/PDBDynamicLocalsTests.cs | 4 +- .../CSharp/Test/Emit3/FieldKeywordTests.cs | 403 ++++ .../Semantics/InheritanceBindingTests.cs | 2 +- .../Source/ExpressionBodiedPropertyTests.cs | 4 +- .../Generated/Syntax.Test.xml.Generated.cs | 78 + .../Parsing/FieldKeywordParsingTests.cs | 2008 +++++++++++++++++ .../Test/Syntax/Parsing/ParsingTests.cs | 6 +- .../Syntax/FieldAndValueKeywordTests.cs | 109 +- .../Core/Portable/Syntax/GreenNode.cs | 21 +- .../ClassificationTypeNamesTests.cs | 2 +- 27 files changed, 2981 insertions(+), 59 deletions(-) create mode 100644 src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs create mode 100644 src/Compilers/CSharp/Test/Syntax/Parsing/FieldKeywordParsingTests.cs diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 5133d93430e02..b51735f28319b 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,42 @@ 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; + + ReportFieldContextualKeywordConflict(node, node.Token, diagnostics); + + 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); + } + /// 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) { @@ -1744,10 +1782,18 @@ internal void ReportFieldContextualKeywordConflictIfAny(SyntaxNode syntax, Synta 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()); + ReportFieldContextualKeywordConflict(syntax, identifier, diagnostics); } } + + private static void ReportFieldContextualKeywordConflict(SyntaxNode syntax, SyntaxToken identifier, BindingDiagnosticBag diagnostics) + { + // PROTOTYPE: Should this diagnostic be dropped when compiling with the latest language version + // when 'field' would not otherwise bind to a different symbol? + string name = identifier.Text; + var requiredVersion = MessageID.IDS_FeatureFieldKeyword.RequiredVersion(); + diagnostics.Add(ErrorCode.INF_IdentifierConflictWithContextualKeyword, syntax, name, requiredVersion.ToDisplayString()); + } #nullable disable private void LookupIdentifier(LookupResult lookupResult, SimpleNameSyntax node, bool invoked, ref CompoundUseSiteInfo useSiteInfo) diff --git a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 index 602ad728c80df..6968dc30e0fb5 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 +++ b/src/Compilers/CSharp/Portable/Generated/CSharp.Generated.g4 @@ -747,6 +747,7 @@ expression | default_expression | element_access_expression | element_binding_expression + | field_expression | implicit_array_creation_expression | implicit_element_access | implicit_stack_alloc_array_creation_expression @@ -891,6 +892,10 @@ element_binding_expression : bracketed_argument_list ; +field_expression + : 'field' + ; + implicit_array_creation_expression : 'new' '[' ','* ']' initializer_expression ; diff --git a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs index ed09f4ccd3931..78d8aedb8b8f2 100644 --- a/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/CSharpSyntaxGenerator/CSharpSyntaxGenerator.SourceGenerator/Syntax.xml.Internal.Generated.cs @@ -3201,6 +3201,71 @@ internal override GreenNode SetAnnotations(SyntaxAnnotation[]? annotations) => 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/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index d68834aefdfe3..7f0c494254135 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) @@ -10752,6 +10789,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: @@ -11343,6 +11381,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); @@ -13733,7 +13775,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 @@ -13754,6 +13797,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; @@ -13916,7 +13965,8 @@ private DisposableResetPoint GetDisposableResetPoint(bool resetOnDispose) base.GetResetPoint(), _termState, IsInAsync, - IsInQuery); + IsInQuery, + IsInFieldKeywordContext); } private void Reset(ref ResetPoint state) @@ -13924,6 +13974,7 @@ private void Reset(ref ResetPoint state) _termState = state.TerminatorState; IsInAsync = state.IsInAsync; IsInQuery = state.IsInQuery; + IsInFieldKeywordContext = state.IsInFieldKeywordContext; base.Reset(ref state.BaseResetPoint); } @@ -13963,17 +14014,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/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index ddfd658541916..e575d7840340a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -42,6 +42,7 @@ private static SourcePropertySymbol Create( diagnostics, out bool hasAccessorList, out bool accessorsHaveImplementation, + out bool usesFieldKeyword, out bool isInitOnly, out var getSyntax, out var setSyntax); @@ -81,6 +82,7 @@ private static SourcePropertySymbol Create( isExpressionBodied: isExpressionBodied, isInitOnly: isInitOnly, accessorsHaveImplementation: accessorsHaveImplementation, + usesFieldKeyword: usesFieldKeyword, memberName, location, diagnostics); @@ -100,6 +102,7 @@ private SourcePropertySymbol( bool isExpressionBodied, bool isInitOnly, bool accessorsHaveImplementation, + bool usesFieldKeyword, string memberName, Location location, BindingDiagnosticBag diagnostics) @@ -118,6 +121,7 @@ private SourcePropertySymbol( isExpressionBodied: isExpressionBodied, isInitOnly: isInitOnly, accessorsHaveImplementation: accessorsHaveImplementation, + usesFieldKeyword: usesFieldKeyword, syntax.Type.SkipScoped(out _).GetRefKindInLocalOrReturn(diagnostics), memberName, syntax.AttributeLists, @@ -200,6 +204,7 @@ private static void GetAccessorDeclarations( BindingDiagnosticBag diagnostics, out bool hasAccessorList, out bool accessorsHaveImplementation, + out bool usesFieldKeyword, out bool isInitOnly, out CSharpSyntaxNode? getSyntax, out CSharpSyntaxNode? setSyntax) @@ -212,6 +217,7 @@ private static void GetAccessorDeclarations( if (hasAccessorList) { + usesFieldKeyword = false; accessorsHaveImplementation = false; foreach (var accessor in syntax.AccessorList!.Accessors) { @@ -254,17 +260,34 @@ private static void GetAccessorDeclarations( throw ExceptionUtilities.UnexpectedValue(accessor.Kind()); } - if (accessor.Body != null || accessor.ExpressionBody != null) + var body = (SyntaxNode?)accessor.Body ?? accessor.ExpressionBody; + if (body != null) { accessorsHaveImplementation = true; } + + usesFieldKeyword = usesFieldKeyword || containsFieldKeyword(accessor); } } else { - accessorsHaveImplementation = GetArrowExpression(syntax) is object; + var body = GetArrowExpression(syntax); + accessorsHaveImplementation = body is object; + usesFieldKeyword = body is { } && containsFieldKeyword(body); Debug.Assert(accessorsHaveImplementation); // it's not clear how this even parsed as a property if it has no accessor list and no arrow expression. } + + static bool containsFieldKeyword(SyntaxNode syntax) + { + foreach (var node in syntax.Green.EnumerateNodes()) + { + if (node.RawKind == (int)SyntaxKind.FieldKeyword) + { + return true; + } + } + return false; + } } private static AccessorDeclarationSyntax GetGetAccessorDeclaration(BasePropertyDeclarationSyntax syntax) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index e6a081ac788c1..2ba54ae1a0b8e 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; @@ -87,6 +84,7 @@ protected SourcePropertySymbolBase( bool isExpressionBodied, bool isInitOnly, bool accessorsHaveImplementation, + bool usesFieldKeyword, RefKind refKind, string memberName, SyntaxList indexerNameAttributeLists, @@ -160,13 +158,17 @@ protected SourcePropertySymbolBase( _name = _lazySourceName = memberName; } - if ((isAutoProperty && hasGetAccessor) || hasInitializer) + if (usesFieldKeyword || (isAutoProperty && hasGetAccessor) || hasInitializer) { Debug.Assert(!IsIndexer); string fieldName = GeneratedNames.MakeBackingFieldName(_name); BackingField = new SynthesizedBackingFieldSymbol(this, fieldName, - isReadOnly: (hasGetAccessor && !hasSetAccessor) || isInitOnly, + // Synthesized backing field for 'field' should not be marked 'initonly' + // since the field might be modified in the get accessor. + // PROTOTYPE: Should the backing field be 'initonly' when the containing + // type, property, or accessor is declared 'readonly'? + isReadOnly: !usesFieldKeyword && ((hasGetAccessor && !hasSetAccessor) || isInitOnly), this.IsStatic, hasInitializer); } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs index 1dc7444d03efb..6fab1931f2763 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -36,6 +36,7 @@ public SynthesizedRecordEqualityContractProperty(SourceMemberContainerTypeSymbol 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..798028c8b916a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -34,6 +34,7 @@ public SynthesizedRecordPropertySymbol( isExpressionBodied: false, isInitOnly: ShouldUseInit(containingType), accessorsHaveImplementation: true, + usesFieldKeyword: false, RefKind.None, backingParameter.Name, indexerNameAttributeLists: new SyntaxList(), diff --git a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs index 19869a60ef521..39479fcba12c6 100644 --- a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs +++ b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNode.cs @@ -230,21 +230,11 @@ protected void SetFactoryContext(SyntaxFactoryContext context) { SetFlags(NodeFlags.FactoryContextIsInQuery); } - } - - internal static NodeFlags SetFactoryContext(NodeFlags flags, SyntaxFactoryContext context) - { - if (context.IsInAsync) - { - flags |= NodeFlags.FactoryContextIsInAsync; - } - if (context.IsInQuery) + if (context.IsInFieldKeywordContext) { - flags |= NodeFlags.FactoryContextIsInQuery; + SetFlags(NodeFlags.FactoryContextIsInFieldKeywordContext); } - - return flags; } public sealed override CodeAnalysis.SyntaxToken CreateSeparator(SyntaxNode element) diff --git a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNodeCache.cs b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNodeCache.cs index a7787fec446e0..fc4d86ef66ac6 100644 --- a/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNodeCache.cs +++ b/src/Compilers/CSharp/Portable/Syntax/InternalSyntax/CSharpSyntaxNodeCache.cs @@ -39,6 +39,11 @@ private static GreenNode.NodeFlags GetNodeFlags(SyntaxFactoryContext context) flags |= GreenNode.NodeFlags.FactoryContextIsInQuery; } + if (context.IsInFieldKeywordContext) + { + flags |= GreenNode.NodeFlags.FactoryContextIsInFieldKeywordContext; + } + return flags; } } diff --git a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml index 6dc00ef5262e5..8e05ae9fee8b7 100644 --- a/src/Compilers/CSharp/Portable/Syntax/Syntax.xml +++ b/src/Compilers/CSharp/Portable/Syntax/Syntax.xml @@ -910,6 +910,21 @@ Creates 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/Test/Emit/CodeGen/CodeGenRefReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs index 728fafdf945a1..c9ead91a833c5 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs @@ -174,7 +174,7 @@ public void RefReturnStaticProperty() class Program { static int field = 0; - static ref int P { get { return ref field; } } + static ref int P { get { return ref @field; } } static ref int M() { @@ -229,7 +229,7 @@ public void RefReturnClassInstanceProperty() class Program { int field = 0; - ref int P { get { return ref field; } } + ref int P { get { return ref @field; } } ref int M() { diff --git a/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs b/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs index a1f47885298b6..609973fb80b61 100644 --- a/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/PDB/PDBDynamicLocalsTests.cs @@ -660,7 +660,7 @@ public dynamic Field { get { - dynamic d = field + field; + dynamic d = @field + @field; return d; } set @@ -695,7 +695,7 @@ public static void Main(string[] args) - + diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs new file mode 100644 index 0000000000000..92aa16f749918 --- /dev/null +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -0,0 +1,403 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable disable + +using Microsoft.CodeAnalysis.CSharp.Symbols; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp.Test.Utilities; +using Microsoft.CodeAnalysis.Test.Utilities; +using Roslyn.Test.Utilities; +using System.Linq; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.UnitTests +{ + public class FieldKeywordTests : CSharpTestBase + { + private static string IncludeExpectedOutput(string expectedOutput) => ExecutionConditionUtil.IsMonoOrCoreClr ? expectedOutput : null; + + [Fact] + public void Field_01() + { + string source = """ + using System; + using System.Reflection; + class C + { + public object P => field = 1; + public static object Q { get => field = 2; } + } + class Program + { + static void Main() + { + Console.WriteLine((new C().P, C.Q)); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) + Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly); + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Static)) + Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: """ + (1, 2) +

k__BackingField: False + k__BackingField: False + """); + verifier.VerifyIL("C.P.get", """ + { + // Code size 16 (0x10) + .maxstack 3 + .locals init (object V_0) + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: box "int" + IL_0007: dup + IL_0008: stloc.0 + IL_0009: stfld "object C.

k__BackingField" + IL_000e: ldloc.0 + IL_000f: ret + } + """); + verifier.VerifyIL("C.Q.get", """ + { + // Code size 13 (0xd) + .maxstack 2 + IL_0000: ldc.i4.2 + IL_0001: box "int" + IL_0006: dup + IL_0007: stsfld "object C.k__BackingField" + IL_000c: ret + } + """); + var comp = (CSharpCompilation)verifier.Compilation; + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "System.Object C.

k__BackingField", + "System.Object C.P { get; }", + "System.Object C.P.get", + "System.Object C.k__BackingField", + "System.Object C.Q { get; }", + "System.Object C.Q.get", + "C..ctor()" + }; + Assert.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void Field_02() + { + string source = """ + using System; + class C + { + public object P => Initialize(out field, 1); + public object Q { get => Initialize(out field, 2); } + static object Initialize(out object field, object value) + { + field = value; + return field; + } + } + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine((c.P, c.Q)); + } + } + """; + var verifier = CompileAndVerify(source, expectedOutput: "(1, 2)"); + verifier.VerifyIL("C.P.get", """ + { + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldflda "object C.

k__BackingField" + IL_0006: ldc.i4.1 + IL_0007: box "int" + IL_000c: call "object C.Initialize(out object, object)" + IL_0011: ret + } + """); + verifier.VerifyIL("C.Q.get", """ + { + // Code size 18 (0x12) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldflda "object C.k__BackingField" + IL_0006: ldc.i4.2 + IL_0007: box "int" + IL_000c: call "object C.Initialize(out object, object)" + IL_0011: ret + } + """); + var comp = (CSharpCompilation)verifier.Compilation; + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "System.Object C.

k__BackingField", + "System.Object C.P { get; }", + "System.Object C.P.get", + "System.Object C.k__BackingField", + "System.Object C.Q { get; }", + "System.Object C.Q.get", + "System.Object C.Initialize(out System.Object field, System.Object value)", + "C..ctor()" + }; + Assert.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void Field_03() + { + string source = """ + using System; + using System.Reflection; + class C + { + public object P { get { return field; } init { field = 1; } } + public object Q { init { field = 2; } } + } + class Program + { + static void Main() + { + foreach (var field in typeof(C).GetFields(BindingFlags.NonPublic | BindingFlags.Instance)) + Console.WriteLine("{0}: {1}", field.Name, field.IsInitOnly); + } + } + """; + CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(""" +

k__BackingField: False + k__BackingField: False + """)); + } + + [Fact] + public void FieldReference_01() + { + string source = """ + class C + { + static C _other = new(); + object P + { + get { return _other.field; } + set { _ = field; } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // get { return _other.field; } + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29), + // (7,19): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead. + // set { _ = field; } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(7, 19)); + } + + [Fact] + public void FieldReference_02() + { + string source = """ + class C + { + C P + { + get { return null; } + set { field = value.field; } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (6,15): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead. + // set { field = value.field; } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(6, 15), + // (6,29): error CS1061: 'C' does not contain a definition for 'field' and no accessible extension method 'field' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // set { field = value.field; } + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "field").WithArguments("C", "field").WithLocation(6, 29)); + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "C C.

k__BackingField", + "C C.P { get; set; }", + "C C.P.get", + "void C.P.set", + "C..ctor()" + }; + Assert.Equal(expectedMembers, actualMembers); + } + + [Fact] + public void FieldReference_03() + { + string source = """ + class C + { + int P + { + get { return field; } + set { _ = this is { field: 0 }; } + } + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (5,22): info CS9258: 'field' is a contextual keyword in property accessors starting in language version preview. Use '@field' instead. + // get { return field; } + Diagnostic(ErrorCode.INF_IdentifierConflictWithContextualKeyword, "field").WithArguments("field", "preview").WithLocation(5, 22), + // (6,29): error CS0117: 'C' does not contain a definition for 'field' + // set { _ = this is { field: 0 }; } + Diagnostic(ErrorCode.ERR_NoSuchMember, "field").WithArguments("C", "field").WithLocation(6, 29)); + } + + [Fact] + public void FieldInInitializer_01() + { + string source = """ + class C + { + object P { get; } = F(field); + static object F(object value) => value; + } + """; + var comp = CreateCompilation(source); + comp.VerifyEmitDiagnostics( + // (3,27): error CS0103: The name 'field' does not exist in the current context + // object P { get; } = F(field); + Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 27)); + var actualMembers = comp.GetMember("C").GetMembers().ToTestDisplayStrings(); + var expectedMembers = new[] + { + "System.Object C.

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

k__BackingField" : "System.String Base.field"; + Assert.Equal(expectedSymbol, symbolInfo.Symbol.ToTestDisplayString()); + + var actualFields = comp.GetMember("Derived").GetMembers().Where(m => m.Kind == SymbolKind.Field).ToTestDisplayStrings(); + string[] expectedFields = synthesizeField ? ["System.String Derived.

k__BackingField"] : []; + AssertEx.Equal(expectedFields, actualFields); + } + } } } From a878dd235e5da507b5ec3234e4237a2d7f98123a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Aug 2024 16:11:25 -0700 Subject: [PATCH 05/18] Move feature to only working when in 'Preview' --- .../Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs index 334581270c3ab..ac65a9e833852 100644 --- a/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseAutoProperty/CSharpUseAutoPropertyAnalyzer.cs @@ -46,7 +46,7 @@ protected override bool SupportsPropertyInitializer(Compilation compilation) => compilation.LanguageVersion() >= LanguageVersion.CSharp6; protected override bool SupportsFieldExpression(Compilation compilation) - => compilation.LanguageVersion() >= LanguageVersion.CSharp13; + => compilation.LanguageVersion() >= LanguageVersion.Preview; protected override ExpressionSyntax? GetFieldInitializer(VariableDeclaratorSyntax variable, CancellationToken cancellationToken) => variable.Initializer?.Value; From c01a6bc316d29437a80670f9015a185bf0e093ff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 23 Aug 2024 16:27:33 -0700 Subject: [PATCH 06/18] Update tests --- .../UseAutoPropertyTests_Field.cs | 138 ++++++++++-------- 1 file changed, 79 insertions(+), 59 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs index 4e12a182143d7..41b95d6f61dd5 100644 --- a/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs +++ b/src/Analyzers/CSharp/Tests/UseAutoProperty/UseAutoPropertyTests_Field.cs @@ -15,6 +15,26 @@ public sealed partial class UseAutoPropertyTests private readonly ParseOptions CSharp13 = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.CSharp13); private readonly ParseOptions Preview = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview); + [Fact] + public async Task TestNotInCSharp13() + { + await TestMissingInRegularAndScriptAsync( + """ + class Class + { + [|string s|]; + + string P + { + get + { + return s.Trim(); + } + } + } + """, new(parseOptions: CSharp13)); + } + [Fact] public async Task TestFieldSimplestCase() { @@ -44,7 +64,7 @@ string P } } } - """, parseOptions: CSharp13); + """, parseOptions: Preview); } [Fact] @@ -76,7 +96,7 @@ string P } } } - """, parseOptions: CSharp13); + """, parseOptions: Preview); } [Fact] @@ -108,7 +128,7 @@ static string P } } } - """, parseOptions: CSharp13); + """, parseOptions: Preview); } [Fact] @@ -142,13 +162,13 @@ int P } } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -183,13 +203,13 @@ int P } } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSetterWithMultipleStatementsAndGetterWithSingleStatement_Field2() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -221,13 +241,13 @@ int P } } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSimpleFieldInExpressionBody() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -241,7 +261,7 @@ class Class { string P => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -255,7 +275,7 @@ class Class int Total => x + y; } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -277,7 +297,7 @@ int Total } } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -310,7 +330,7 @@ int Total set; } } - """); + """, parseOptions: Preview); } [Fact] @@ -332,7 +352,7 @@ class Class int X => field + y; } - """); + """, parseOptions: Preview); } [Fact] @@ -354,7 +374,7 @@ class Class int X => field + y; } - """); + """, parseOptions: Preview); } [Fact] @@ -375,7 +395,7 @@ string P } } } - """, new TestParameters(parseOptions: Preview)); + """, new(parseOptions: Preview)); } [Fact] @@ -397,7 +417,7 @@ string P } } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -419,7 +439,7 @@ string P } } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -445,7 +465,7 @@ void M() throw new ArgumentNullException(nameof(s)); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -471,7 +491,7 @@ void M() throw new ArgumentNullException(nameof(this.s)); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -485,7 +505,7 @@ class Class string P => s; } - """); + """, new(parseOptions: CSharp13)); } [Fact] @@ -513,7 +533,7 @@ void Init(ref string s) { } } - """); + """, parseOptions: Preview); } [Fact] @@ -536,7 +556,7 @@ void Init(ref string s) { } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -570,7 +590,7 @@ string P } } } - """); + """, parseOptions: Preview); } [Fact] @@ -595,7 +615,7 @@ void M() ref string s1 = ref s; } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -635,7 +655,7 @@ int P } } } - """); + """, parseOptions: Preview); } [Fact] @@ -663,7 +683,7 @@ unsafe void M() int* p = &s; } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -694,7 +714,7 @@ public bool StrictMode set; } } - """); + """, parseOptions: Preview); } [Fact] @@ -718,7 +738,7 @@ class Builder { public List List => field ??= new(); } - """); + """, parseOptions: Preview); } [Fact] @@ -741,7 +761,7 @@ class Builder void Set(ref int a, int b) { } } - """); + """, parseOptions: Preview); } [Fact] @@ -783,7 +803,7 @@ public int Prop void Set(ref int a, int b) { } void OnPropChanged() { } } - """); + """, parseOptions: Preview); } [Fact] @@ -804,7 +824,7 @@ class C [field: Something] public int Prop { get; set; } } - """); + """, parseOptions: Preview); } [Fact] @@ -825,7 +845,7 @@ class C [field: Something] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -849,7 +869,7 @@ class C [PropAttribute] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -875,7 +895,7 @@ class C [PropAttribute] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -901,7 +921,7 @@ class C [PropAttribute][PropAttribute2] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -925,7 +945,7 @@ class C [field: Something] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -950,7 +970,7 @@ class C [field: Something] public string Prop => field.Trim(); } - """); + """, parseOptions: Preview); } [Fact] @@ -984,13 +1004,13 @@ public string Prop } } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere1() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -1014,13 +1034,13 @@ void M() P = ""; } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere2() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -1044,13 +1064,13 @@ void M() P = ""; } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere3() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -1080,13 +1100,13 @@ void M() P = ""; } } - """); + """, parseOptions: Preview); } [Fact] public async Task TestSimpleFieldInExpressionBody_FieldWrittenElsewhere4() { - await TestInRegularAndScript1Async( + await TestInRegularAndScriptAsync( """ class Class { @@ -1124,7 +1144,7 @@ void M() P = ""; } } - """); + """, parseOptions: Preview); } [Fact] @@ -1143,7 +1163,7 @@ void M() Console.WriteLine(i); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1162,7 +1182,7 @@ void M() Console.WriteLine(this.i); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1181,7 +1201,7 @@ void M() i = 1; } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1200,7 +1220,7 @@ void M() this.i = 1; } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1220,7 +1240,7 @@ class Class { public int I { get; set => field = value / 2; } } - """); + """, parseOptions: Preview); } [Fact] @@ -1239,7 +1259,7 @@ void M() Console.WriteLine(this.i++); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1258,7 +1278,7 @@ void M() Console.WriteLine(this.i++); } } - """); + """, new(parseOptions: Preview)); } [Fact] @@ -1288,7 +1308,7 @@ void M() Console.WriteLine(I); } } - """); + """, parseOptions: Preview); } [Fact] @@ -1318,7 +1338,7 @@ void M() I = 1; } } - """); + """, parseOptions: Preview); } [Fact] @@ -1337,7 +1357,7 @@ class C { public string Prop => $"{field:prop}"; } - """); + """, parseOptions: Preview); } [Fact] @@ -1360,7 +1380,7 @@ class C void M() { Prop = "..."; } } - """); + """, parseOptions: Preview); } [Fact] @@ -1374,6 +1394,6 @@ class C [ThisIsMyBackingField(nameof(prop))] public string Prop { get => prop; set => prop = value; } } - """); + """, new(parseOptions: Preview)); } } From b8c1ea0b6d6d888be3bd832dfc64b375b295d0c2 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:58:44 -0700 Subject: [PATCH 07/18] Field-backed properties: update uses of IsAutoProperty (#74798) Co-authored-by: Fred Silberberg --- .../Portable/Binder/Binder.ValueChecks.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 5 +- .../CSharp/Portable/CSharpResources.resx | 2 +- .../LocalRewriter_AssignmentOperator.cs | 3 +- .../Source/SourcePropertySymbolBase.cs | 27 +- .../Portable/xlf/CSharpResources.cs.xlf | 4 +- .../Portable/xlf/CSharpResources.de.xlf | 4 +- .../Portable/xlf/CSharpResources.es.xlf | 4 +- .../Portable/xlf/CSharpResources.fr.xlf | 4 +- .../Portable/xlf/CSharpResources.it.xlf | 4 +- .../Portable/xlf/CSharpResources.ja.xlf | 4 +- .../Portable/xlf/CSharpResources.ko.xlf | 4 +- .../Portable/xlf/CSharpResources.pl.xlf | 4 +- .../Portable/xlf/CSharpResources.pt-BR.xlf | 4 +- .../Portable/xlf/CSharpResources.ru.xlf | 4 +- .../Portable/xlf/CSharpResources.tr.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hans.xlf | 4 +- .../Portable/xlf/CSharpResources.zh-Hant.xlf | 4 +- .../Test/Emit/CodeGen/CodeGenRefLocalTests.cs | 4 +- .../Emit/CodeGen/CodeGenRefReturnTests.cs | 4 +- .../CSharp/Test/Emit3/FieldKeywordTests.cs | 2212 ++++++++++++++++- .../Semantics/NullableReferenceTypesTests.cs | 3 + .../Semantic/Semantics/RecordStructTests.cs | 4 +- .../Test/Semantic/Semantics/StructsTests.cs | 4 +- .../UninitializedNonNullableFieldTests.cs | 2 +- .../DefaultInterfaceImplementationTests.cs | 158 +- .../Symbol/Symbols/PartialPropertiesTests.cs | 24 +- .../Test/Symbol/Symbols/SymbolErrorTests.cs | 6 +- 28 files changed, 2430 insertions(+), 82 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index a23da4551f2f3..f550f1c8f3f2f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs @@ -1694,7 +1694,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV if (setMethod is null) { var containing = this.ContainingMemberOrLambda; - if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing) + if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing, allowFieldKeyword: true) && !isAllowedDespiteReadonly(receiver)) { Error(diagnostics, ErrorCode.ERR_AssgReadonlyProp, node, propertySymbol); diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index c2c03dddb53f8..65ec316dad808 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1754,7 +1754,8 @@ internal static bool AccessingAutoPropertyFromConstructor(BoundPropertyAccess pr return AccessingAutoPropertyFromConstructor(propertyAccess.ReceiverOpt, propertyAccess.PropertySymbol, fromMember); } - private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember) + // PROTOTYPE: Review all callers for allowFieldKeyword. + private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember, bool allowFieldKeyword = false) { if (!propertySymbol.IsDefinition && propertySymbol.ContainingType.Equals(propertySymbol.ContainingType.OriginalDefinition, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)) { @@ -1765,7 +1766,7 @@ private static bool AccessingAutoPropertyFromConstructor(BoundExpression receive var propertyIsStatic = propertySymbol.IsStatic; return (object)sourceProperty != null && - sourceProperty.IsAutoProperty && + (allowFieldKeyword ? sourceProperty.IsAutoPropertyOrUsesFieldKeyword : sourceProperty.IsAutoProperty) && TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.AllIgnoreOptions) && IsConstructorOrField(fromMember, isStatic: propertyIsStatic) && (propertyIsStatic || receiver.Kind == BoundKind.ThisReference); diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index e5af9ff4a7b96..d85e31bbcd749 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4891,7 +4891,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Expected identifier or numeric literal - Only auto-implemented properties can have initializers. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. Instance properties in interfaces cannot have initializers. diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs index 5ff4636c6c674..9a2d1ec5b9caa 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_AssignmentOperator.cs @@ -281,8 +281,9 @@ private BoundExpression MakePropertyAssignment( if (setMethod is null) { var autoProp = (SourcePropertySymbolBase)property.OriginalDefinition; - Debug.Assert(autoProp.IsAutoProperty, + Debug.Assert(autoProp.IsAutoPropertyOrUsesFieldKeyword, "only autoproperties can be assignable without having setters"); + Debug.Assert(_factory.CurrentFunction.IsConstructor()); Debug.Assert(property.Equals(autoProp, TypeCompareKind.IgnoreNullableModifiersForReferenceTypes)); var backingField = autoProp.BackingField; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 95fa2f85e9da4..85b0273488847 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -302,7 +302,7 @@ protected void CheckInitializerIfNeeded(BindingDiagnosticBag diagnostics) { diagnostics.Add(ErrorCode.ERR_InstancePropertyInitializerInInterface, Location); } - else if (!IsAutoProperty) + else if (!IsAutoPropertyOrUsesFieldKeyword) { diagnostics.Add(ErrorCode.ERR_InitializerOnNonAutoProperty, Location); } @@ -653,7 +653,7 @@ public bool HasSkipLocalsInitAttribute internal bool IsAutoPropertyOrUsesFieldKeyword => IsAutoProperty || UsesFieldKeyword; - protected bool UsesFieldKeyword + private bool UsesFieldKeyword => (_propertyFlags & Flags.UsesFieldKeyword) != 0; protected bool HasExplicitAccessModifier @@ -666,8 +666,9 @@ protected bool AccessorsHaveImplementation => (_propertyFlags & Flags.AccessorsHaveImplementation) != 0; ///

- /// Backing field for automatically implemented property, or - /// for a property with an initializer. + /// Backing field for an automatically implemented property, or + /// a property with an accessor using the 'field' keyword, or + /// a property with an initializer. /// internal SynthesizedBackingFieldSymbol BackingField { get; } @@ -715,9 +716,9 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, diagnostics.Add(ErrorCode.ERR_RefReturningPropertiesCannotBeRequired, Location); } - if (IsAutoProperty) + if (IsAutoPropertyOrUsesFieldKeyword) { - if (!IsStatic && SetMethod is { IsInitOnly: false }) + if (!IsStatic && ((_propertyFlags & Flags.HasAutoPropertySet) != 0) && SetMethod is { IsInitOnly: false }) { if (ContainingType.IsReadOnly) { @@ -738,10 +739,15 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, diagnostics.Add(ErrorCode.ERR_AutoPropertyCannotBeRefReturning, Location); } - // get-only auto property should not override settable properties - if (this.IsOverride && SetMethod is null && !this.IsReadOnly) + // Auto property should override both accessors. + if (this.IsOverride) { - diagnostics.Add(ErrorCode.ERR_AutoPropertyMustOverrideSet, Location); + var overriddenProperty = (PropertySymbol)this.GetLeastOverriddenMember(this.ContainingType); + if ((overriddenProperty.GetMethod is { } && GetMethod is null) || + (overriddenProperty.SetMethod is { } && SetMethod is null)) + { + diagnostics.Add(ErrorCode.ERR_AutoPropertyMustOverrideSet, Location); + } } } @@ -791,6 +797,9 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } else if (!hasGetAccessor && IsAutoProperty) { + // The only forms of auto-property that are disallowed are { set; } and { init; }. + // Other forms of auto- or manually-implemented accessors are allowed + // including equivalent field cases such as { set { field = value; } }. diagnostics.Add(ErrorCode.ERR_AutoPropertyMustHaveGetAccessor, _setMethod!.GetFirstLocation()); } diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index c070c270fb61b..3e3c96569ce32 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf @@ -12160,8 +12160,8 @@ Pokud chcete odstranit toto varování, můžete místo toho použít /reference - Only auto-implemented properties can have initializers. - Jenom automaticky implementované vlastnosti můžou mít inicializátory. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Jenom automaticky implementované vlastnosti můžou mít inicializátory. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 0078527e65793..e0c3e7f6cdf2f 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf @@ -12160,8 +12160,8 @@ Um die Warnung zu beheben, können Sie stattdessen /reference verwenden (Einbett - Only auto-implemented properties can have initializers. - Nur automatisch implementierte Eigenschaften können Initialisierer aufweisen. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Nur automatisch implementierte Eigenschaften können Initialisierer aufweisen. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index fe8b9998c951b..d14b5b75cece9 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf @@ -12160,8 +12160,8 @@ Para eliminar la advertencia puede usar /reference (establezca la propiedad Embe - Only auto-implemented properties can have initializers. - Solo las propiedades implementadas automáticamente pueden tener inicializadores. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Solo las propiedades implementadas automáticamente pueden tener inicializadores. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index 0d55d57e5c894..2979a2f6128f6 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf @@ -12160,8 +12160,8 @@ Pour supprimer l'avertissement, vous pouvez utiliser la commande /reference (dé - Only auto-implemented properties can have initializers. - Seules les propriétés implémentées automatiquement peuvent avoir des initialiseurs. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Seules les propriétés implémentées automatiquement peuvent avoir des initialiseurs. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index e0d85c231f8e7..81fe93c7f4b76 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf @@ -12160,8 +12160,8 @@ Per rimuovere l'avviso, è invece possibile usare /reference (impostare la propr - Only auto-implemented properties can have initializers. - Solo le proprietà implementate automaticamente possono avere inizializzatori. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Solo le proprietà implementate automaticamente possono avere inizializzatori. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 8ecfc63478d6c..056d3ac1f3559 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf @@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - Only auto-implemented properties can have initializers. - 自動実装プロパティのみが初期化子を持つことができます。 + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + 自動実装プロパティのみが初期化子を持つことができます。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index ae92ac759f259..709344ded6891 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf @@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - Only auto-implemented properties can have initializers. - 자동 구현 속성만 이니셜라이저를 사용할 수 있습니다. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + 자동 구현 속성만 이니셜라이저를 사용할 수 있습니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 0f18b1d6b3ad5..0daffc73f4fd7 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf @@ -12160,8 +12160,8 @@ Aby usunąć ostrzeżenie, możesz zamiast tego użyć opcji /reference (ustaw w - Only auto-implemented properties can have initializers. - Tylko właściwości zaimplementowane automatycznie mogą mieć inicjatory. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Tylko właściwości zaimplementowane automatycznie mogą mieć inicjatory. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 5b182e2af6227..41e99905d18d0 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf @@ -12160,8 +12160,8 @@ Para incorporar informações de tipo de interoperabilidade para os dois assembl - Only auto-implemented properties can have initializers. - Somente propriedades implementadas automaticamente podem ter inicializadores. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Somente propriedades implementadas automaticamente podem ter inicializadores. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index f660d123cf6ef..03bcaab1d8307 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf @@ -12161,8 +12161,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - Only auto-implemented properties can have initializers. - Инициализаторы могут иметь только автоматически реализованные свойства. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Инициализаторы могут иметь только автоматически реализованные свойства. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index b7ccaf7545791..baadb43153104 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf @@ -12160,8 +12160,8 @@ Uyarıyı kaldırmak için, /reference kullanabilirsiniz (Birlikte Çalışma T - Only auto-implemented properties can have initializers. - Yalnızca otomatik uygulanan özelliklerin başlatıcıları olabilir. + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + Yalnızca otomatik uygulanan özelliklerin başlatıcıları olabilir. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index ac1f52a36f53b..2423d50dbab33 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf @@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - Only auto-implemented properties can have initializers. - 只有自动实现的属性才能具有初始值设定项。 + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + 只有自动实现的属性才能具有初始值设定项。 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index b3f556c0b0476..b8e18bf92daa3 100644 --- a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf +++ b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf @@ -12160,8 +12160,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ - Only auto-implemented properties can have initializers. - 只有自動實作的屬性可以有初始設定式。 + Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + 只有自動實作的屬性可以有初始設定式。 diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs index 23cf311d431af..70450c3879552 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefLocalTests.cs @@ -1761,7 +1761,7 @@ public void RefAssignStaticProperty() class Program { static int field = 0; - static ref int P { get { return ref field; } } + static ref int P { get { return ref @field; } } static void M() { @@ -1789,7 +1789,7 @@ public void RefAssignClassInstanceProperty() class Program { int field = 0; - ref int P { get { return ref field; } } + ref int P { get { return ref @field; } } void M() { diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs index c9ead91a833c5..82cd6e6b97d32 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenRefReturnTests.cs @@ -1258,7 +1258,7 @@ class Program { int field = 0; - ref int P { get { return ref field; } } + ref int P { get { return ref @field; } } ref int this[int i] { get { return ref field; } } @@ -1455,7 +1455,7 @@ class Program { int field = 0; - ref int P { get { return ref field; } } + ref int P { get { return ref @field; } } ref int this[int i] { get { return ref field; } } diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 0fc3ee90cf8ac..92745d17f1cd2 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -285,9 +285,6 @@ class C """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (3,12): error CS8050: Only auto-implemented properties can have initializers. - // object P { get => field; } = field; - Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12), // (3,34): error CS0103: The name 'field' does not exist in the current context // object P { get => field; } = field; Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 34)); @@ -304,7 +301,7 @@ class C """; var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( - // (3,12): error CS8050: Only auto-implemented properties can have initializers. + // (3,12): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. // object P { set { } } = field; Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "P").WithLocation(3, 12), // (3,28): error CS0103: The name 'field' does not exist in the current context @@ -1031,6 +1028,2213 @@ static void ReportField(FieldInfo field) """)); } + [Theory] + [CombinatorialData] + public void Initializer_01A([CombinatorialValues("class", "struct", "ref struct", "interface")] string typeKind) + { + string source = $$""" + using System; + {{typeKind}} C + { + public static int P1 { get; } = 1; + public static int P2 { get => field; } = 2; + public static int P3 { get => field; set; } = 3; + public static int P4 { get => field; set { } } = 4; + public static int P5 { get => 0; set; } = 5; + public static int P6 { get; set; } = 6; + public static int P7 { get; set { } } = 7; + public static int P8 { set { field = value; } } = 8; + public static int P9 { get { return field; } set { field = value; } } = 9; + } + class Program + { + static void Main() + { + Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..cctor", """ + { + // Code size 56 (0x38) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: stsfld "int C.k__BackingField" + IL_0006: ldc.i4.2 + IL_0007: stsfld "int C.k__BackingField" + IL_000c: ldc.i4.3 + IL_000d: stsfld "int C.k__BackingField" + IL_0012: ldc.i4.4 + IL_0013: stsfld "int C.k__BackingField" + IL_0018: ldc.i4.5 + IL_0019: stsfld "int C.k__BackingField" + IL_001e: ldc.i4.6 + IL_001f: stsfld "int C.k__BackingField" + IL_0024: ldc.i4.7 + IL_0025: stsfld "int C.k__BackingField" + IL_002a: ldc.i4.8 + IL_002b: stsfld "int C.k__BackingField" + IL_0030: ldc.i4.s 9 + IL_0032: stsfld "int C.k__BackingField" + IL_0037: ret + } + """); + } + + [Fact] + public void Initializer_01B() + { + string source = """ + using System; + interface C + { + public static int P1 { get; } = 1; + public static int P2 { get => field; } = 2; + public static int P3 { get => field; set; } = 3; + public static int P4 { get => field; set { } } = 4; + public static int P5 { get => 0; set; } = 5; + public static int P6 { get; set; } = 6; + public static int P7 { get; set { } } = 7; + public static int P8 { set { field = value; } } = 8; + public static int P9 { get { return field; } set { field = value; } } = 9; + } + class Program + { + static void Main() + { + Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..cctor", """ + { + // Code size 56 (0x38) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: stsfld "int C.k__BackingField" + IL_0006: ldc.i4.2 + IL_0007: stsfld "int C.k__BackingField" + IL_000c: ldc.i4.3 + IL_000d: stsfld "int C.k__BackingField" + IL_0012: ldc.i4.4 + IL_0013: stsfld "int C.k__BackingField" + IL_0018: ldc.i4.5 + IL_0019: stsfld "int C.k__BackingField" + IL_001e: ldc.i4.6 + IL_001f: stsfld "int C.k__BackingField" + IL_0024: ldc.i4.7 + IL_0025: stsfld "int C.k__BackingField" + IL_002a: ldc.i4.8 + IL_002b: stsfld "int C.k__BackingField" + IL_0030: ldc.i4.s 9 + IL_0032: stsfld "int C.k__BackingField" + IL_0037: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void Initializer_02A(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + class C + { + public int P1 { get; } = 1; + public int P2 { get => field; } = 2; + public int P3 { get => field; {{setter}}; } = 3; + public int P4 { get => field; {{setter}} { } } = 4; + public int P5 { get => 0; {{setter}}; } = 5; + public int P6 { get; {{setter}}; } = 6; + public int P7 { get; {{setter}} { } } = 7; + public int P8 { {{setter}} { field = value; } } = 8; + public int P9 { get { return field; } {{setter}} { field = value; } } = 9; + } + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine((c.P1, c.P2, c.P3, c.P4, c.P5, c.P6, c.P7, c.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..ctor", """ + { + // Code size 71 (0x47) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: stfld "int C.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldc.i4.2 + IL_0009: stfld "int C.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldc.i4.3 + IL_0010: stfld "int C.k__BackingField" + IL_0015: ldarg.0 + IL_0016: ldc.i4.4 + IL_0017: stfld "int C.k__BackingField" + IL_001c: ldarg.0 + IL_001d: ldc.i4.5 + IL_001e: stfld "int C.k__BackingField" + IL_0023: ldarg.0 + IL_0024: ldc.i4.6 + IL_0025: stfld "int C.k__BackingField" + IL_002a: ldarg.0 + IL_002b: ldc.i4.7 + IL_002c: stfld "int C.k__BackingField" + IL_0031: ldarg.0 + IL_0032: ldc.i4.8 + IL_0033: stfld "int C.k__BackingField" + IL_0038: ldarg.0 + IL_0039: ldc.i4.s 9 + IL_003b: stfld "int C.k__BackingField" + IL_0040: ldarg.0 + IL_0041: call "object..ctor()" + IL_0046: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void Initializer_02B(bool useRefStruct, bool useInit) + { + string setter = useInit ? "init" : "set"; + string typeKind = useRefStruct ? "ref struct" : "struct"; + string source = $$""" + using System; + {{typeKind}} C + { + public int P1 { get; } = 1; + public int P2 { get => field; } = 2; + public int P3 { get => field; {{setter}}; } = 3; + public int P4 { get => field; {{setter}} { } } = 4; + public int P5 { get => 0; {{setter}}; } = 5; + public int P6 { get; {{setter}}; } = 6; + public int P7 { get; {{setter}} { } } = 7; + public int P8 { {{setter}} { field = value; } } = 8; + public int P9 { get { return field; } {{setter}} { field = value; } } = 9; + public C() { } + } + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine((c.P1, c.P2, c.P3, c.P4, c.P5, c.P6, c.P7, c.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..ctor", """ + { + // Code size 65 (0x41) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.1 + IL_0002: stfld "int C.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldc.i4.2 + IL_0009: stfld "int C.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldc.i4.3 + IL_0010: stfld "int C.k__BackingField" + IL_0015: ldarg.0 + IL_0016: ldc.i4.4 + IL_0017: stfld "int C.k__BackingField" + IL_001c: ldarg.0 + IL_001d: ldc.i4.5 + IL_001e: stfld "int C.k__BackingField" + IL_0023: ldarg.0 + IL_0024: ldc.i4.6 + IL_0025: stfld "int C.k__BackingField" + IL_002a: ldarg.0 + IL_002b: ldc.i4.7 + IL_002c: stfld "int C.k__BackingField" + IL_0031: ldarg.0 + IL_0032: ldc.i4.8 + IL_0033: stfld "int C.k__BackingField" + IL_0038: ldarg.0 + IL_0039: ldc.i4.s 9 + IL_003b: stfld "int C.k__BackingField" + IL_0040: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void Initializer_02C(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + interface C + { + public int P1 { get; } = 1; + public int P2 { get => field; } = 2; + public int P3 { get => field; {{setter}}; } = 3; + public int P4 { get => field; {{setter}} { } } = 4; + public int P5 { get => 0; {{setter}}; } = 5; + public int P6 { get; {{setter}}; } = 6; + public int P7 { get; {{setter}} { } } = 7; + public int P8 { {{setter}} { field = value; } } = 8; + public int P9 { get { return field; } {{setter}} { field = value; } } = 9; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (4,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P1 { get; } = 1; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P1").WithLocation(4, 16), + // (5,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P2 { get => field; } = 2; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P2").WithLocation(5, 16), + // (6,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P3 { get => field; set; } = 3; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P3").WithLocation(6, 16), + // (6,35): error CS0501: 'C.P3.set' must declare a body because it is not marked abstract, extern, or partial + // public int P3 { get => field; set; } = 3; + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P3.{setter}").WithLocation(6, 35), + // (7,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P4 { get => field; set { } } = 4; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P4").WithLocation(7, 16), + // (8,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P5 { get => 0; set; } = 5; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P5").WithLocation(8, 16), + // (8,31): error CS0501: 'C.P5.set' must declare a body because it is not marked abstract, extern, or partial + // public int P5 { get => 0; set; } = 5; + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P5.{setter}").WithLocation(8, 31), + // (9,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P6 { get; set; } = 6; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P6").WithLocation(9, 16), + // (10,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P7 { get; set { } } = 7; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P7").WithLocation(10, 16), + // (10,21): error CS0501: 'C.P7.get' must declare a body because it is not marked abstract, extern, or partial + // public int P7 { get; set { } } = 7; + Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("C.P7.get").WithLocation(10, 21), + // (11,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P8 { set { field = value; } } = 8; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P8").WithLocation(11, 16), + // (12,16): error CS8053: Instance properties in interfaces cannot have initializers. + // public int P9 { get { return field; } set { field = value; } } = 9; + Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P9").WithLocation(12, 16)); + } + + [Theory] + [CombinatorialData] + public void Initializer_02D(bool useRefStruct, bool useInit) + { + string setter = useInit ? "init" : "set"; + string typeKind = useRefStruct ? "ref struct" : " struct"; + string source = $$""" + {{typeKind}} S1 + { + public int P1 { get; } = 1; + } + {{typeKind}} S2 + { + public int P2 { get => field; } = 2; + } + {{typeKind}} S3 + { + public int P3 { get => field; {{setter}}; } = 3; + } + {{typeKind}} S6 + { + public int P6 { get; {{setter}}; } = 6; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (1,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. + // struct S1 + Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S1").WithLocation(1, 12), + // (5,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. + // struct S2 + Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S2").WithLocation(5, 12), + // (9,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. + // struct S3 + Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S3").WithLocation(9, 12), + // (13,12): error CS8983: A 'struct' with field initializers must include an explicitly declared constructor. + // struct S6 + Diagnostic(ErrorCode.ERR_StructHasInitializersAndNoDeclaredConstructor, "S6").WithLocation(13, 12)); + } + + [Fact] + public void Initializer_03() + { + string source = """ + class C + { + public static int PA { get => 0; } = 10; + public static int PB { get => 0; set { } } = 11; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,23): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // public static int PA { get => 0; } = 10; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PA").WithLocation(3, 23), + // (4,23): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // public static int PB { get => 0; set { } } = 11; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PB").WithLocation(4, 23)); + } + + [Theory] + [CombinatorialData] + public void Initializer_04(bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + class C + { + public int PA { get => 0; } = 10; + public int PB { get => 0; {{setter}} { } } = 11; + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + comp.VerifyEmitDiagnostics( + // (3,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // public int PA { get => 0; } = 10; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PA").WithLocation(3, 16), + // (4,16): error CS8050: Only auto-implemented properties, or properties that use the 'field' keyword, can have initializers. + // public int PB { get => 0; set { } } = 11; + Diagnostic(ErrorCode.ERR_InitializerOnNonAutoProperty, "PB").WithLocation(4, 16)); + } + + [Theory] + [CombinatorialData] + public void ConstructorAssignment_01([CombinatorialValues("class", "struct", "ref struct", "interface")] string typeKind) + { + string source = $$""" + using System; + {{typeKind}} C + { + public static int P1 { get; } + public static int P2 { get => field; } + public static int P3 { get => field; set; } + public static int P4 { get => field; set { } } + public static int P5 { get => 0; set; } + public static int P6 { get; set; } + public static int P7 { get; set { } } + public static int P8 { set { field = value; } } + public static int P9 { get { return field; } set { field = value; } } + static C() + { + P1 = 1; + P2 = 2; + P3 = 3; + P4 = 4; + P5 = 5; + P6 = 6; + P7 = 7; + P8 = 8; + P9 = 9; + } + } + class Program + { + static void Main() + { + Console.WriteLine((C.P1, C.P2, C.P3, C.P4, C.P5, C.P6, C.P7, C.P9)); + } + } + """; + var verifier = CompileAndVerify( + source, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput("(1, 2, 3, 0, 0, 6, 0, 9)")); + verifier.VerifyDiagnostics(); + verifier.VerifyIL("C..cctor", """ + { + // Code size 56 (0x38) + .maxstack 1 + IL_0000: ldc.i4.1 + IL_0001: stsfld "int C.k__BackingField" + IL_0006: ldc.i4.2 + IL_0007: stsfld "int C.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_02([CombinatorialValues("class", "struct", "ref struct")] string typeKind, bool useInit) + { + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + {{typeKind}} C + { + public int P1 { get; } + public int P2 { get => field; } + public int P3 { get => field; {{setter}}; } + public int P4 { get => field; {{setter}} { } } + public int P5 { get => 0; {{setter}}; } + public int P6 { get; {{setter}}; } + public int P7 { get; {{setter}} { } } + public int P8 { {{setter}} { field = value; } } + public int P9 { get { return field; } {{setter}} { field = value; } } + public C() + { + P1 = 1; + P2 = 2; + P3 = 3; + P4 = 4; + P5 = 5; + P6 = 6; + P7 = 7; + P8 = 8; + P9 = 9; + } + } + class Program + { + static void Main() + { + var c = new C(); + Console.WriteLine((c.P1, c.P2, c.P3, c.P4, c.P5, c.P6, c.P7, c.P9)); + } + } + """; + var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("(1, 2, 3, 0, 0, 6, 0, 9)")); + verifier.VerifyDiagnostics(); + if (typeKind == "class") + { + verifier.VerifyIL("C..ctor", $$""" + { + // Code size 71 (0x47) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: call "object..ctor()" + IL_0006: ldarg.0 + IL_0007: ldc.i4.1 + IL_0008: stfld "int C.k__BackingField" + IL_000d: ldarg.0 + IL_000e: ldc.i4.2 + IL_000f: stfld "int C.k__BackingField" + IL_0014: ldarg.0 + IL_0015: ldc.i4.3 + IL_0016: call "void C.P3.{{setter}}" + IL_001b: ldarg.0 + IL_001c: ldc.i4.4 + IL_001d: call "void C.P4.{{setter}}" + IL_0022: ldarg.0 + IL_0023: ldc.i4.5 + IL_0024: call "void C.P5.{{setter}}" + IL_0029: ldarg.0 + IL_002a: ldc.i4.6 + IL_002b: call "void C.P6.{{setter}}" + IL_0030: ldarg.0 + IL_0031: ldc.i4.7 + IL_0032: call "void C.P7.{{setter}}" + IL_0037: ldarg.0 + IL_0038: ldc.i4.8 + IL_0039: call "void C.P8.{{setter}}" + IL_003e: ldarg.0 + IL_003f: ldc.i4.s 9 + IL_0041: call "void C.P9.{{setter}}" + IL_0046: ret + } + """); + } + else + { + verifier.VerifyIL("C..ctor", $$""" + { + // Code size 121 (0x79) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int C.k__BackingField" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int C.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldc.i4.0 + IL_0010: stfld "int C.k__BackingField" + IL_0015: ldarg.0 + IL_0016: ldc.i4.0 + IL_0017: stfld "int C.k__BackingField" + IL_001c: ldarg.0 + IL_001d: ldc.i4.0 + IL_001e: stfld "int C.k__BackingField" + IL_0023: ldarg.0 + IL_0024: ldc.i4.0 + IL_0025: stfld "int C.k__BackingField" + IL_002a: ldarg.0 + IL_002b: ldc.i4.0 + IL_002c: stfld "int C.k__BackingField" + IL_0031: ldarg.0 + IL_0032: ldc.i4.0 + IL_0033: stfld "int C.k__BackingField" + IL_0038: ldarg.0 + IL_0039: ldc.i4.1 + IL_003a: stfld "int C.k__BackingField" + IL_003f: ldarg.0 + IL_0040: ldc.i4.2 + IL_0041: stfld "int C.k__BackingField" + IL_0046: ldarg.0 + IL_0047: ldc.i4.3 + IL_0048: call "void C.P3.{{setter}}" + IL_004d: ldarg.0 + IL_004e: ldc.i4.4 + IL_004f: call "void C.P4.{{setter}}" + IL_0054: ldarg.0 + IL_0055: ldc.i4.5 + IL_0056: call "void C.P5.{{setter}}" + IL_005b: ldarg.0 + IL_005c: ldc.i4.6 + IL_005d: call "void C.P6.{{setter}}" + IL_0062: ldarg.0 + IL_0063: ldc.i4.7 + IL_0064: call "void C.P7.{{setter}}" + IL_0069: ldarg.0 + IL_006a: ldc.i4.8 + IL_006b: call "void C.P8.{{setter}}" + IL_0070: ldarg.0 + IL_0071: ldc.i4.s 9 + IL_0073: call "void C.P9.{{setter}}" + IL_0078: 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, targetFramework: TargetFramework.Net80); + 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_01(bool useRefStruct, bool useInit, bool includeStructInitializationWarnings) + { + string typeKind = useRefStruct ? "ref struct" : " struct"; + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + {{typeKind}} S0 + { + public int F0; + public int P0 { get; {{setter}}; } + public S0(int unused) { _ = P0; } + } + {{typeKind}} S1 + { + public int F1; + public int P1 { get => field; } + public S1(int unused) { _ = P1; } + } + {{typeKind}} S2 + { + public int F2; + public int P2 { get => field; {{setter}}; } + public S2(int unused) { _ = P2; } + } + {{typeKind}} S3 + { + public int F3; + public int P3 { get => field; {{setter}} { field = value; } } + public S3(int unused) { _ = P3; } + } + class Program + { + static void Main() + { + var s0 = new S0(-1); + var s1 = new S1(1); + var s2 = new S2(2); + var s3 = new S3(3); + Console.WriteLine((s0.F0, s0.P0)); + Console.WriteLine((s1.F1, s1.P1)); + Console.WriteLine((s2.F2, s2.P2)); + Console.WriteLine((s3.F3, s3.P3)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(""" + (0, 0) + (0, 0) + (0, 0) + (0, 0) + """)); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 + // public int F0; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), + // (6,12): warning CS9021: Control is returned to caller before auto-implemented property 'S0.P0' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S0(int unused) { _ = P0; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S0").WithArguments("S0.P0").WithLocation(6, 12), + // (6,12): warning CS9022: Control is returned to caller before field 'S0.F0' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S0(int unused) { _ = P0; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S0").WithArguments("S0.F0").WithLocation(6, 12), + // (6,33): warning CS9018: Auto-implemented property 'P0' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S0(int unused) { _ = P0; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P0").WithArguments("P0").WithLocation(6, 33), + // (10,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(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 S1(int unused) { _ = P1; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P1").WithLocation(12, 33), + // (16,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(16, 16), + // (18,12): warning CS9021: Control is returned to caller before auto-implemented property 'S2.P2' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S2(int unused) { _ = P2; } + Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S2").WithArguments("S2.P2").WithLocation(18, 12), + // (18,12): warning CS9022: Control is returned to caller before field 'S2.F2' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S2(int unused) { _ = P2; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S2").WithArguments("S2.F2").WithLocation(18, 12), + // (18,33): warning CS9018: Auto-implemented property 'P2' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S2(int unused) { _ = P2; } + Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P2").WithArguments("P2").WithLocation(18, 33), + // (22,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(22, 16), + // (24,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 S3(int unused) { _ = P3; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P3").WithLocation(24, 33)); + } + else + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 + // public int F0; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), + // (10,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(10, 16), + // (16,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(16, 16), + // (22,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(22, 16)); + } + verifier.VerifyIL("S0..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S0.F0" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S0.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "readonly int S0.P0.get" + IL_0014: pop + IL_0015: ret + } + """); + 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 "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 + } + """); + verifier.VerifyIL("S3..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S3.F3" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S3.k__BackingField" + IL_000e: ldarg.0 + IL_000f: call "int S3.P3.get" + IL_0014: pop + IL_0015: ret + } + """); + } + + [Theory] + [CombinatorialData] + public void DefaultInitialization_02(bool useRefStruct, bool useInit, bool includeStructInitializationWarnings) + { + string typeKind = useRefStruct ? "ref struct" : " struct"; + string setter = useInit ? "init" : "set"; + string source = $$""" + using System; + {{typeKind}} S0 + { + public int F0; + public int P0 { get; {{setter}}; } + public S0(int i) { P0 = i; } + } + {{typeKind}} S1 + { + public int F1; + public int P1 { get => field; } + public S1(int i) { P1 = i; } + } + {{typeKind}} S2 + { + public int F2; + public int P2 { get => field; {{setter}}; } + public S2(int i) { P2 = i; } + } + {{typeKind}} S3 + { + public int F3; + public int P3 { get => field; {{setter}} { field = value; } } + public S3(int i) { P3 = i; } + } + class Program + { + static void Main() + { + var s0 = new S0(-1); + var s1 = new S1(1); + var s2 = new S2(2); + var s3 = new S3(3); + Console.WriteLine((s0.F0, s0.P0)); + Console.WriteLine((s1.F1, s1.P1)); + Console.WriteLine((s2.F2, s2.P2)); + Console.WriteLine((s3.F3, s3.P3)); + } + } + """; + var verifier = CompileAndVerify( + source, + options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, + targetFramework: TargetFramework.Net80, + verify: Verification.Skipped, + expectedOutput: IncludeExpectedOutput(""" + (0, -1) + (0, 1) + (0, 2) + (0, 3) + """)); + if (includeStructInitializationWarnings) + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 + // public int F0; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), + // (6,12): warning CS9022: Control is returned to caller before field 'S0.F0' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S0(int i) { P0 = i; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S0").WithArguments("S0.F0").WithLocation(6, 12), + // (10,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(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 S1(int i) { P1 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P1").WithLocation(12, 24), + // (16,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(16, 16), + // (18,12): warning CS9022: Control is returned to caller before field 'S2.F2' is explicitly assigned, causing a preceding implicit assignment of 'default'. + // public S2(int i) { P2 = i; } + Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S2").WithArguments("S2.F2").WithLocation(18, 12), + // (22,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(22, 16), + // (24,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 S3(int i) { P3 = i; } + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P3").WithLocation(24, 24)); + } + else + { + verifier.VerifyDiagnostics( + // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 + // public int F0; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), + // (10,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(10, 16), + // (16,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(16, 16), + // (22,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(22, 16)); + } + verifier.VerifyIL("S0..ctor", $$""" + { + // Code size 15 (0xf) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S0.F0" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S0.P0.{{setter}}" + IL_000e: ret + } + """); + 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: ldarg.1 + IL_0010: stfld "int S1.k__BackingField" + IL_0015: 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.F2" + IL_0007: ldarg.0 + IL_0008: ldarg.1 + IL_0009: call "void S2.P2.{{setter}}" + IL_000e: ret + } + """); + verifier.VerifyIL("S3..ctor", $$""" + { + // Code size 22 (0x16) + .maxstack 2 + IL_0000: ldarg.0 + IL_0001: ldc.i4.0 + IL_0002: stfld "int S3.F3" + IL_0007: ldarg.0 + IL_0008: ldc.i4.0 + IL_0009: stfld "int S3.k__BackingField" + IL_000e: ldarg.0 + IL_000f: ldarg.1 + IL_0010: call "void S3.P3.{{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: TargetFramework.Net80); + 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 CS1604: Cannot assign to 'field' because it is read-only + // object PC { get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(14, 37), + // (15,32): error CS1604: Cannot assign to 'field' because it is read-only + // object PD { set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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 CS1604: Cannot assign to 'field' because it is read-only + // readonly object PC { get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(14, 37), + // (15,32): error CS1604: Cannot assign to 'field' because it is read-only + // readonly object PD { set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(15, 32)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + } + + [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, targetFramework: TargetFramework.Net80); + 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 CS1604: Cannot assign to 'field' because it is read-only + // object PC { readonly get; set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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 CS1604: Cannot assign to 'field' because it is read-only + // object PC { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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 CS1604: Cannot assign to 'field' because it is read-only + // object PC { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(10, 46)); + } + } + } + + [Theory] + [CombinatorialData] + public void ReadOnly_03(bool useReadOnlyType, bool useReadOnlyProperty) + { + static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; + string typeModifier = getReadOnlyModifier(useReadOnlyType); + string propertyModifier = getReadOnlyModifier(useReadOnlyProperty); + string source = $$""" + {{typeModifier}} struct S + { + static {{propertyModifier}} object P1 { get; } + static {{propertyModifier}} object P2 { get => field; } + static {{propertyModifier}} object P3 { get => field; set; } + static {{propertyModifier}} object P9 { get; set; } + static {{propertyModifier}} object PA { get; set { } } + static {{propertyModifier}} object PC { get; set { field = value; } } + static {{propertyModifier}} object PD { set { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + if (useReadOnlyProperty) + { + 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.P2' cannot be marked 'readonly'. + // static readonly object P2 { get => field; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P2").WithArguments("S.P2").WithLocation(4, 28), + // (5,28): error CS8657: Static member 'S.P3' cannot be marked 'readonly'. + // static readonly object P3 { get => field; set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P3").WithArguments("S.P3").WithLocation(5, 28), + // (6,28): error CS8657: Static member 'S.P9' cannot be marked 'readonly'. + // static readonly object P9 { get; set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P9").WithArguments("S.P9").WithLocation(6, 28), + // (7,28): error CS8657: Static member 'S.PA' cannot be marked 'readonly'. + // static readonly object PA { get; set { } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PA").WithArguments("S.PA").WithLocation(7, 28), + // (8,28): error CS8657: Static member 'S.PC' cannot be marked 'readonly'. + // static readonly object PC { get; set { field = value; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PC").WithArguments("S.PC").WithLocation(8, 28), + // (9,28): error CS8657: Static member 'S.PD' cannot be marked 'readonly'. + // static readonly object PD { set { field = value; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PD").WithArguments("S.PD").WithLocation(9, 28)); + } + else + { + comp.VerifyEmitDiagnostics(); + } + } + + [Theory] + [CombinatorialData] + public void ReadOnly_04(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 + { + static object P3 { {{getModifier}} get => field; {{setModifier}} set; } + static object P9 { {{getModifier}} get; {{setModifier}} set; } + static object PD { {{getModifier}} get; {{setModifier}} set { field = value; } } + } + """; + var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + if (useReadOnlyOnGet) + { + comp.VerifyEmitDiagnostics( + // (3,33): error CS8657: Static member 'S.P3.get' cannot be marked 'readonly'. + // static object P3 { readonly get => field; set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.P3.get").WithLocation(3, 33), + // (4,33): error CS8657: Static member 'S.P9.get' cannot be marked 'readonly'. + // static object P9 { readonly get; set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.P9.get").WithLocation(4, 33), + // (5,33): error CS8657: Static member 'S.PD.get' cannot be marked 'readonly'. + // static object PD { readonly get; set { field = value; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.PD.get").WithLocation(5, 33)); + } + else + { + comp.VerifyEmitDiagnostics( + // (3,56): error CS8657: Static member 'S.P3.set' cannot be marked 'readonly'. + // static object P3 { get => field; readonly set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.P3.set").WithLocation(3, 56), + // (4,47): error CS8657: Static member 'S.P9.set' cannot be marked 'readonly'. + // static object P9 { get; readonly set; } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.P9.set").WithLocation(4, 47), + // (5,47): error CS8657: Static member 'S.PD.set' cannot be marked 'readonly'. + // static object PD { get; readonly set { field = value; } } + Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.PD.set").WithLocation(5, 47)); + } + } + + [Fact] + public void ReadOnly_05() + { + 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 CS1604: Cannot assign to 'field' because it is read-only + // object P1 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(7, 32), + // (11,32): error CS1604: Cannot assign to 'field' because it is read-only + // readonly object P2 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(11, 32), + // (15,23): error CS1604: Cannot assign to 'field' because it is read-only + // object P3 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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 CS1604: Cannot assign to 'field' because it is read-only + // object P4 { readonly get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(19, 32), + // (23,32): error CS1604: Cannot assign to 'field' because it is read-only + // readonly object P5 { get { field = null; return null; } } + Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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, targetFramework: TargetFramework.Net80); + 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] + [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: TargetFramework.Net80); + 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 comp = CreateCompilation([sourceA, sourceB0], targetFramework: TargetFramework.Net80); + 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.Net80); + 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.Net80); + 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.Net80); + 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.Net80); + 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 comp = CreateCompilation([sourceA, sourceB0], targetFramework: TargetFramework.Net80); + 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.Net80); + 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.Net80); + 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.Net80); + 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.Net80); + 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: TargetFramework.Net80); + 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 actualMembers = comp.GetMember("C").GetMembers().OfType().ToTestDisplayStrings(); + var expectedMembers = 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(expectedMembers, actualMembers); + } + [Fact] public void RestrictedTypes() { diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs index 1f13aa457cc29..3569fdcb0d1b4 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/NullableReferenceTypesTests.cs @@ -15264,6 +15264,9 @@ public override Func P1 { set {} } // warn // (15,39): warning CS8767: Nullability of reference types in type of parameter 'value' of 'void C.P1.set' doesn't match implicitly implemented member 'void A.P1.set' (possibly because of nullability attributes). // public override Func P1 { set {} } // warn Diagnostic(ErrorCode.WRN_TopLevelNullabilityMismatchInParameterTypeOnImplicitImplementation, "set").WithArguments("value", "void C.P1.set", "void A.P1.set").WithLocation(15, 39), + // (16,34): 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), 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 853cac424e3ae..03316ddba7223 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/StructsTests.cs @@ -673,7 +673,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)); @@ -682,7 +682,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 44ff7a3e97c93..22c58d94fdcf9 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/UninitializedNonNullableFieldTests.cs @@ -2631,7 +2631,7 @@ public C() // (5,19): error CS0548: 'C.P': property or indexer must have at least one accessor // public string P { } Diagnostic(ErrorCode.ERR_PropertyWithNoAccessors, "P").WithArguments("C.P").WithLocation(5, 19), - // (7,19): error CS8050: Only auto-implemented properties can have initializers. + // (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 diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index 9e8c95afbbc13..bbfac8d1ce82d 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 @@ -3251,7 +3251,7 @@ public interface I1 // (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,24): error CS8050: Only auto-implemented properties can have initializers. + // (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 @@ -3309,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) ); @@ -3346,7 +3346,7 @@ public interface I1 [Theory] [CombinatorialData] - public void PropertyImplementation_109(bool isStatic, bool useCSharp13) + public void PropertyImplementation_109A(bool isStatic, bool useCSharp13) { string declModifiers = isStatic ? "static virtual " : ""; @@ -3373,9 +3373,6 @@ class Test1 : I1 targetFramework: TargetFramework.Net60); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); - // PROTOTYPE: Confirm that we now allow one accessor to have an implementation. - // According to LDM decision captured at https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-04-18.md, - // we don't want to allow only one accessor to have an implementation. if (isStatic && useCSharp13) { compilation1.VerifyDiagnostics( @@ -3389,6 +3386,8 @@ class Test1 : I1 } else { + // 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; @@ -3402,6 +3401,9 @@ class Test1 : I1 Assert.False(p1.IsReadOnly); Assert.False(p1.IsWriteOnly); + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal(isStatic ? "System.Int32 I1.k__BackingField" : null, field1?.ToTestDisplayString()); + Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual); Assert.False(getP1.IsAbstract); @@ -3421,7 +3423,72 @@ class Test1 : I1 [Theory] [CombinatorialData] - public void PropertyImplementation_110(bool isStatic, bool useCSharp13) + 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 " : ""; @@ -3444,9 +3511,6 @@ class Test1 : I1 targetFramework: TargetFramework.Net60); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); - // PROTOTYPE: Confirm that we now allow one accessor to have an implementation. - // According to LDM decision captured at https://github.com/dotnet/csharplang/blob/main/meetings/2017/LDM-2017-04-18.md, - // we don't want to allow only one accessor to have an implementation. if (isStatic && useCSharp13) { compilation1.VerifyDiagnostics( @@ -3460,6 +3524,8 @@ class Test1 : I1 } else { + // 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; @@ -3473,6 +3539,9 @@ class Test1 : I1 Assert.False(p1.IsReadOnly); Assert.False(p1.IsWriteOnly); + var field1 = ((SourcePropertySymbolBase)p1).BackingField; + Assert.Equal(isStatic ? "System.Int32 I1.k__BackingField" : null, field1?.ToTestDisplayString()); + Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual); Assert.False(getP1.IsAbstract); @@ -3490,6 +3559,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) @@ -56714,7 +56844,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' @@ -56772,7 +56902,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' @@ -56830,7 +56960,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' diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index d1a8b34d21ea1..9788d119e5e99 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs @@ -3771,16 +3771,16 @@ 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 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)); } @@ -3804,25 +3804,25 @@ 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 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 @@ -3862,7 +3862,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 +3874,19 @@ 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 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)); diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs index 31be06d2e2ace..d0b0be43ca294 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/SymbolErrorTests.cs @@ -16859,13 +16859,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) ); From 10f9b1ee6792caea3c2ec2eccafbaa098c054102 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:23:48 -0700 Subject: [PATCH 08/18] Field-backed properties: readonly backing field (#74922) --- .../Source/SourcePropertySymbolBase.cs | 37 +- .../CSharp/Test/Emit3/FieldKeywordTests.cs | 432 ++++++++++++++---- 2 files changed, 378 insertions(+), 91 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 85b0273488847..586248c633afd 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -172,15 +172,34 @@ protected SourcePropertySymbolBase( { Debug.Assert(!IsIndexer); string fieldName = GeneratedNames.MakeBackingFieldName(_name); - BackingField = new SynthesizedBackingFieldSymbol(this, - fieldName, - // Synthesized backing field for 'field' should not be marked 'initonly' - // since the field might be modified in the get accessor. - // PROTOTYPE: Should the backing field be 'initonly' when the containing - // type, property, or accessor is declared 'readonly'? - isReadOnly: !usesFieldKeyword && ((hasGetAccessor && !hasSetAccessor) || isInitOnly), - this.IsStatic, - hasInitializer); + + // 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 (but may have an init accessor) and + // the get accessor, if any, is automatically implemented. + else if ((!hasSetAccessor || isInitOnly) && (!hasGetAccessor || hasAutoPropertyGet)) + { + isReadOnly = true; + } + else + { + // PROTOTYPE: We could treat the field as readonly if all manually implemented get and set + // accessors are declared readonly. Although, to do so, we might need to bind the accessor + // declarations before creating the backing field. See FieldKeywordTests.ReadOnly_05(). + isReadOnly = false; + } + + BackingField = new SynthesizedBackingFieldSymbol(this, fieldName, isReadOnly: isReadOnly, this.IsStatic, hasInitializer); } if (hasGetAccessor) diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 92745d17f1cd2..42c35b548aabe 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -172,7 +172,7 @@ static void Main() """; CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("""

k__BackingField: False - k__BackingField: False + k__BackingField: True """)); } @@ -2265,12 +2265,12 @@ public void ReadOnly_01(bool useReadOnlyType, bool useReadOnlyProperty, bool use // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(14, 37), - // (15,32): error CS1604: Cannot assign to 'field' because it is read-only + 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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(15, 32)); + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(15, 32)); } else if (useReadOnlyProperty) { @@ -2284,17 +2284,33 @@ public void ReadOnly_01(bool useReadOnlyType, bool useReadOnlyProperty, bool use // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(14, 37), - // (15,32): error CS1604: Cannot assign to 'field' because it is read-only + 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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(15, 32)); + 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] @@ -2333,9 +2349,9 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(10, 46)); + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); } else { @@ -2358,9 +2374,9 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(10, 46)); + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); } } else @@ -2386,106 +2402,358 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("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}", + $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType}", + }; + AssertEx.Equal(expectedMembers, actualMembers); } [Theory] [CombinatorialData] - public void ReadOnly_03(bool useReadOnlyType, bool useReadOnlyProperty) + 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 propertyModifier = getReadOnlyModifier(useReadOnlyProperty); - string source = $$""" - {{typeModifier}} struct S + 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 {{propertyModifier}} object P1 { get; } - static {{propertyModifier}} object P2 { get => field; } - static {{propertyModifier}} object P3 { get => field; set; } - static {{propertyModifier}} object P9 { get; set; } - static {{propertyModifier}} object PA { get; set { } } - static {{propertyModifier}} object PC { get; set { field = value; } } - static {{propertyModifier}} object PD { set { field = value; } } + 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(source, targetFramework: TargetFramework.Net80); - if (useReadOnlyProperty) + 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}} + """)); + 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}", + }; + 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.P2' cannot be marked 'readonly'. - // static readonly object P2 { get => field; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P2").WithArguments("S.P2").WithLocation(4, 28), - // (5,28): error CS8657: Static member 'S.P3' cannot be marked 'readonly'. - // static readonly object P3 { get => field; set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P3").WithArguments("S.P3").WithLocation(5, 28), - // (6,28): error CS8657: Static member 'S.P9' cannot be marked 'readonly'. - // static readonly object P9 { get; set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "P9").WithArguments("S.P9").WithLocation(6, 28), - // (7,28): error CS8657: Static member 'S.PA' cannot be marked 'readonly'. - // static readonly object PA { get; set { } } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PA").WithArguments("S.PA").WithLocation(7, 28), - // (8,28): error CS8657: Static member 'S.PC' cannot be marked 'readonly'. - // static readonly object PC { get; set { field = value; } } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PC").WithArguments("S.PC").WithLocation(8, 28), - // (9,28): error CS8657: Static member 'S.PD' cannot be marked 'readonly'. - // static readonly object PD { set { field = value; } } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "PD").WithArguments("S.PD").WithLocation(9, 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: False + """); } + 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: False", + }; + AssertEx.Equal(expectedMembers, actualMembers); } [Theory] [CombinatorialData] - public void ReadOnly_04(bool useReadOnlyType, bool useReadOnlyOnGet) + public void ReadOnly_05(bool useInit) { - static string getReadOnlyModifier(bool useReadOnly) => useReadOnly ? "readonly" : " "; - string typeModifier = getReadOnlyModifier(useReadOnlyType); - string getModifier = getReadOnlyModifier(useReadOnlyOnGet); - string setModifier = getReadOnlyModifier(!useReadOnlyOnGet); + string setter = useInit ? "init" : "set"; string source = $$""" - {{typeModifier}} struct S - { - static object P3 { {{getModifier}} get => field; {{setModifier}} set; } - static object P9 { {{getModifier}} get; {{setModifier}} set; } - static object PD { {{getModifier}} get; {{setModifier}} set { field = value; } } + 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: TargetFramework.Net80); - if (useReadOnlyOnGet) + if (useInit) { comp.VerifyEmitDiagnostics( - // (3,33): error CS8657: Static member 'S.P3.get' cannot be marked 'readonly'. - // static object P3 { readonly get => field; set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.P3.get").WithLocation(3, 33), - // (4,33): error CS8657: Static member 'S.P9.get' cannot be marked 'readonly'. - // static object P9 { readonly get; set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.P9.get").WithLocation(4, 33), - // (5,33): error CS8657: Static member 'S.PD.get' cannot be marked 'readonly'. - // static object PD { readonly get; set { field = value; } } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "get").WithArguments("S.PD.get").WithLocation(5, 33)); + // (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,56): error CS8657: Static member 'S.P3.set' cannot be marked 'readonly'. - // static object P3 { get => field; readonly set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.P3.set").WithLocation(3, 56), - // (4,47): error CS8657: Static member 'S.P9.set' cannot be marked 'readonly'. - // static object P9 { get; readonly set; } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.P9.set").WithLocation(4, 47), - // (5,47): error CS8657: Static member 'S.PD.set' cannot be marked 'readonly'. - // static object PD { get; readonly set { field = value; } } - Diagnostic(ErrorCode.ERR_StaticMemberCantBeReadOnly, "set").WithArguments("S.PD.set").WithLocation(5, 47)); + // (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}"); + // PROTOTYPE: When determining whether the backing field should be readonly in + // SourcePropertySymbolBase..ctor(), we're ignoring the readonly modifier on accessors. + var expectedMembers = new[] + { + $"System.Object S.k__BackingField: True", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: False", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: False", + $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: False", + }; + 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_05() + public void ReadOnly_07() { string source = """ struct S0 @@ -2521,21 +2789,21 @@ readonly struct S5 // (7,32): error CS1604: Cannot assign to 'field' because it is read-only // object P1 { readonly get { field = null; return null; } } Diagnostic(ErrorCode.ERR_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(7, 32), - // (11,32): error CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(11, 32), - // (15,23): error CS1604: Cannot assign to 'field' because it is read-only + 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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(15, 23), + 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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(19, 32), - // (23,32): error CS1604: Cannot assign to 'field' because it is read-only + 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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(23, 32)); + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(23, 32)); } [Theory] From a61e789588fa5c25a494efcdc8a867a66b74348a Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 10:14:17 -0700 Subject: [PATCH 09/18] Add 'field' keyword recommender Add test Add test --- .../FieldKeywordRecommenderTests.cs | 625 ++++++++++++------ .../FieldKeywordRecommender.cs | 37 +- 2 files changed, 443 insertions(+), 219 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 89385d382c921..69fb7a4a5b1ae 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -7,222 +7,419 @@ 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 { + [$$ + """); + } + + [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 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/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 40e75cb897832..6da6edd53e67f 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -2,13 +2,17 @@ // 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.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; 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 +24,34 @@ internal class FieldKeywordRecommender : AbstractSyntacticSingleKeywordRecommend SyntaxKind.EnumDeclaration, }; - public FieldKeywordRecommender() - : base(SyntaxKind.FieldKeyword) + protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) { + if (context.IsMemberAttributeContext(s_validTypeDeclarations, cancellationToken)) + return true; + + if (context.IsAnyExpressionContext || context.IsStatementContext) + { + var token = context.TargetToken; + if (IsInPropertyAccessor(token.Parent)) + return true; + } + + return false; } - protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken) - => context.IsMemberAttributeContext(s_validTypeDeclarations, cancellationToken); + private static bool IsInPropertyAccessor(SyntaxNode? node) + { + while (node != null) + { + if (node is ArrowExpressionClauseSyntax { Parent: PropertyDeclarationSyntax }) + return true; + + if (node is AccessorDeclarationSyntax { Parent: AccessorListSyntax { Parent: PropertyDeclarationSyntax } }) + return true; + + node = node.Parent; + } + + return false; + } } From 17c6298a4f345b10d6f5188f42c1218320ff7342 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 10:21:39 -0700 Subject: [PATCH 10/18] Docs --- .../KeywordRecommenders/FieldKeywordRecommender.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 6da6edd53e67f..ad7c188c6c420 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -26,9 +26,14 @@ internal sealed class FieldKeywordRecommender() 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) { var token = context.TargetToken; From fbb9634935b17bf55e7390601fb3ba2b5807aaff Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 10:25:09 -0700 Subject: [PATCH 11/18] Simplify --- .../Completion/KeywordRecommenders/FieldKeywordRecommender.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index ad7c188c6c420..3d103f2873320 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -2,12 +2,10 @@ // 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.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery; using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders; From e9152c081229e9f2ba3590ad6dcae184af1d3333 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 10:37:23 -0700 Subject: [PATCH 12/18] Fixes --- .../FieldKeywordRecommenderTests.cs | 60 +++++++++++++++++++ .../FieldKeywordRecommender.cs | 3 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 69fb7a4a5b1ae..515365d9f4e21 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -287,6 +287,66 @@ class C """); } + [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() { diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 3d103f2873320..0958de924d504 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -34,8 +34,7 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context // upgrade their project if they way. if (context.IsAnyExpressionContext || context.IsStatementContext) { - var token = context.TargetToken; - if (IsInPropertyAccessor(token.Parent)) + if (!context.IsNameOfContext && IsInPropertyAccessor(context.TargetToken.Parent)) return true; } From 62270aed09192c7af811e51c013d8c8f5e850c33 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 11:01:00 -0700 Subject: [PATCH 13/18] Simplify --- .../KeywordRecommenders/FieldKeywordRecommender.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs index 0958de924d504..d0a726ffe7ebc 100644 --- a/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs +++ b/src/Features/CSharp/Portable/Completion/KeywordRecommenders/FieldKeywordRecommender.cs @@ -34,24 +34,22 @@ protected override bool IsValidContext(int position, CSharpSyntaxContext context // upgrade their project if they way. if (context.IsAnyExpressionContext || context.IsStatementContext) { - if (!context.IsNameOfContext && IsInPropertyAccessor(context.TargetToken.Parent)) + if (!context.IsNameOfContext && IsInPropertyAccessor(context.TargetToken)) return true; } return false; } - private static bool IsInPropertyAccessor(SyntaxNode? node) + private static bool IsInPropertyAccessor(SyntaxToken targetToken) { - while (node != null) + 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; - - node = node.Parent; } return false; From d4a3602de3996c7a40d10ec1e9137573e17f9956 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 11:07:01 -0700 Subject: [PATCH 14/18] Add tests --- .../FieldKeywordRecommenderTests.cs | 30 +++++++++++++++++++ .../Recommendations/RecommenderTests.cs | 11 +++++-- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs index 515365d9f4e21..e762383d458fc 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/FieldKeywordRecommenderTests.cs @@ -80,6 +80,36 @@ 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")] diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs index 89ca1dc86f303..a62628c41512a 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,7 +183,10 @@ 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); From ea1609773773e8992887ef6736e0d9269ee97cf6 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 30 Aug 2024 11:08:25 -0700 Subject: [PATCH 15/18] classification --- .../CSharpTest2/Recommendations/RecommenderTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs b/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs index a62628c41512a..fbcd55536d31c 100644 --- a/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs +++ b/src/EditorFeatures/CSharpTest2/Recommendations/RecommenderTests.cs @@ -193,7 +193,9 @@ protected async Task VerifyAbsenceAsync( 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) { From 3bfa34797eb9a5c6120b8b1ad6f9740910252cfb Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:20:42 -0700 Subject: [PATCH 16/18] Field-backed properties: support partial properties with auto-implemented accessors and with initializers (#74959) --- .../CSharp/Portable/CSharpResources.resx | 3 + .../CSharp/Portable/Errors/ErrorCode.cs | 1 + .../CSharp/Portable/Errors/ErrorFacts.cs | 1 + .../Source/SourceMemberContainerSymbol.cs | 37 +- .../Symbols/Source/SourcePropertySymbol.cs | 23 +- .../Source/SourcePropertySymbolBase.cs | 133 +- ...nthesizedRecordEqualityContractProperty.cs | 1 - .../SynthesizedRecordPropertySymbol.cs | 1 - .../SynthesizedBackingFieldSymbol.cs | 8 +- .../Portable/xlf/CSharpResources.cs.xlf | 5 + .../Portable/xlf/CSharpResources.de.xlf | 5 + .../Portable/xlf/CSharpResources.es.xlf | 5 + .../Portable/xlf/CSharpResources.fr.xlf | 5 + .../Portable/xlf/CSharpResources.it.xlf | 5 + .../Portable/xlf/CSharpResources.ja.xlf | 5 + .../Portable/xlf/CSharpResources.ko.xlf | 5 + .../Portable/xlf/CSharpResources.pl.xlf | 5 + .../Portable/xlf/CSharpResources.pt-BR.xlf | 5 + .../Portable/xlf/CSharpResources.ru.xlf | 5 + .../Portable/xlf/CSharpResources.tr.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hans.xlf | 5 + .../Portable/xlf/CSharpResources.zh-Hant.xlf | 5 + .../Semantics/PrimaryConstructorTests.cs | 136 ++ .../CSharp/Test/Emit3/FieldKeywordTests.cs | 1580 ++++++++++++++++- .../Symbol/Symbols/PartialPropertiesTests.cs | 67 +- 25 files changed, 1919 insertions(+), 137 deletions(-) diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index b5e007df36047..0035474bb186a 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -7977,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/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index 22f200a0dac99..2d727666d7ac5 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -2343,6 +2343,7 @@ internal enum ErrorCode 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 66d6ee6d41efc..614402c6bb543 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs @@ -2454,6 +2454,7 @@ 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/Symbols/Source/SourceMemberContainerSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol.cs index 3d7f8bb62991e..0298a33d0800b 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(); @@ -3674,7 +3676,7 @@ void mergePartialMethods(ref Dictionary, ImmutableArray, ImmutableArray>(membersByName, ReadOnlyMemoryOfCharComparer.Instance); } - membersByName[name] = FixPartialMember(membersByName[name], prevMethod, currentMethod); + membersByName[name] = FixPartialMethod(membersByName[name], prevMethod, currentMethod); } } @@ -3692,6 +3694,11 @@ void mergePartialProperties(ref Dictionary, ImmutableArray< } else { + if (hasInitializer(prevProperty) && hasInitializer(currentProperty)) + { + diagnostics.Add(ErrorCode.ERR_PartialPropertyDuplicateInitializer, currentProperty.GetFirstLocation()); + } + var (currentGet, prevGet) = ((SourcePropertyAccessorSymbol?)currentProperty.GetMethod, (SourcePropertyAccessorSymbol?)prevProperty.GetMethod); if (currentGet != null || prevGet != null) { @@ -3712,7 +3719,7 @@ void mergePartialProperties(ref Dictionary, ImmutableArray< membersByName = new Dictionary, ImmutableArray>(membersByName, ReadOnlyMemoryOfCharComparer.Instance); } - membersByName[name] = FixPartialMember(membersByName[name], prevProperty, currentProperty); + FixPartialProperty(ref membersByName, name, prevProperty, currentProperty); } void mergeAccessors(ref Dictionary, ImmutableArray> membersByName, ReadOnlyMemory name, SourcePropertyAccessorSymbol? currentAccessor, SourcePropertyAccessorSymbol? prevAccessor) @@ -3734,11 +3741,16 @@ void mergeAccessors(ref Dictionary, ImmutableArray> diagnostics.Add(errorCode, propertyToBlame.GetFirstLocation(), foundAccessor); } } + + static bool hasInitializer(SourcePropertySymbol property) + { + return property.DeclaredBackingField?.HasInitializer == true; + } } } ///

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; @@ -3760,7 +3772,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; @@ -3775,10 +3787,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) @@ -4551,7 +4570,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++; @@ -4991,13 +5012,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/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index 64eb737684431..a69fd0205790b 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -44,7 +44,6 @@ private static SourcePropertySymbol Create( out bool hasGetAccessorImplementation, out bool hasSetAccessorImplementation, out bool usesFieldKeyword, - out bool isInitOnly, out var getSyntax, out var setSyntax); @@ -63,8 +62,9 @@ private static SourcePropertySymbol Create( diagnostics, out _); - bool allowAutoPropertyAccessors = (modifiers & (DeclarationModifiers.Partial | DeclarationModifiers.Abstract | DeclarationModifiers.Extern | DeclarationModifiers.Indexer)) == 0 && - (!containingType.IsInterface || (modifiers & DeclarationModifiers.Static) != 0); + bool allowAutoPropertyAccessors = (modifiers & (DeclarationModifiers.Abstract | DeclarationModifiers.Extern | DeclarationModifiers.Indexer)) == 0 && + (!containingType.IsInterface || (modifiers & DeclarationModifiers.Static) != 0) && + ((modifiers & DeclarationModifiers.Partial) == 0 || hasGetAccessorImplementation || hasSetAccessorImplementation); bool hasAutoPropertyGet = allowAutoPropertyAccessors && getSyntax != null && !hasGetAccessorImplementation; bool hasAutoPropertySet = allowAutoPropertyAccessors && setSyntax != null && !hasSetAccessorImplementation; @@ -86,7 +86,6 @@ private static SourcePropertySymbol Create( hasAutoPropertyGet: hasAutoPropertyGet, hasAutoPropertySet: hasAutoPropertySet, isExpressionBodied: isExpressionBodied, - isInitOnly: isInitOnly, accessorsHaveImplementation: accessorsHaveImplementation, usesFieldKeyword: usesFieldKeyword, memberName, @@ -107,7 +106,6 @@ private SourcePropertySymbol( bool hasAutoPropertyGet, bool hasAutoPropertySet, bool isExpressionBodied, - bool isInitOnly, bool accessorsHaveImplementation, bool usesFieldKeyword, string memberName, @@ -127,7 +125,6 @@ private SourcePropertySymbol( hasAutoPropertyGet: hasAutoPropertyGet, hasAutoPropertySet: hasAutoPropertySet, isExpressionBodied: isExpressionBodied, - isInitOnly: isInitOnly, accessorsHaveImplementation: accessorsHaveImplementation, usesFieldKeyword: usesFieldKeyword, syntax.Type.SkipScoped(out _).GetRefKindInLocalOrReturn(diagnostics), @@ -189,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) @@ -216,7 +212,6 @@ private static void GetAccessorDeclarations( out bool hasGetAccessorImplementation, out bool hasSetAccessorImplementation, out bool usesFieldKeyword, - out bool isInitOnly, out AccessorDeclarationSyntax? getSyntax, out AccessorDeclarationSyntax? setSyntax) { @@ -224,7 +219,6 @@ private static void GetAccessorDeclarations( isExpressionBodied = syntax.AccessorList is null; getSyntax = null; setSyntax = null; - isInitOnly = false; if (!isExpressionBodied) { @@ -252,10 +246,6 @@ private static void GetAccessorDeclarations( { setSyntax = accessor; hasSetAccessorImplementation = hasImplementation(accessor); - if (accessor.Keyword.IsKind(SyntaxKind.InitKeyword)) - { - isInitOnly = true; - } } else { @@ -784,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 586248c633afd..1e8f2e460eee4 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -26,7 +26,7 @@ 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, HasAutoPropertyGet = 1 << 1, @@ -36,6 +36,7 @@ private enum Flags : byte HasInitializer = 1 << 5, AccessorsHaveImplementation = 1 << 6, HasExplicitAccessModifier = 1 << 7, + RequiresBackingField = 1 << 8, } // TODO (tomat): consider splitting into multiple subclasses/rare data. @@ -71,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, @@ -85,7 +89,6 @@ protected SourcePropertySymbolBase( bool hasAutoPropertyGet, bool hasAutoPropertySet, bool isExpressionBodied, - bool isInitOnly, bool accessorsHaveImplementation, bool usesFieldKeyword, RefKind refKind, @@ -171,35 +174,7 @@ protected SourcePropertySymbolBase( if (usesFieldKeyword || hasAutoPropertyGet || hasAutoPropertySet || hasInitializer) { Debug.Assert(!IsIndexer); - 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 (but may have an init accessor) and - // the get accessor, if any, is automatically implemented. - else if ((!hasSetAccessor || isInitOnly) && (!hasGetAccessor || hasAutoPropertyGet)) - { - isReadOnly = true; - } - else - { - // PROTOTYPE: We could treat the field as readonly if all manually implemented get and set - // accessors are declared readonly. Although, to do so, we might need to bind the accessor - // declarations before creating the backing field. See FieldKeywordTests.ReadOnly_05(). - isReadOnly = false; - } - - BackingField = new SynthesizedBackingFieldSymbol(this, fieldName, isReadOnly: isReadOnly, this.IsStatic, hasInitializer); + _propertyFlags |= Flags.RequiresBackingField; } if (hasGetAccessor) @@ -211,6 +186,9 @@ protected SourcePropertySymbolBase( { _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) @@ -672,14 +650,20 @@ public bool HasSkipLocalsInitAttribute internal bool IsAutoPropertyOrUsesFieldKeyword => IsAutoProperty || UsesFieldKeyword; - private bool UsesFieldKeyword - => (_propertyFlags & Flags.UsesFieldKeyword) != 0; + internal bool UsesFieldKeyword + => IsSetOnEitherPart(Flags.UsesFieldKeyword); protected bool HasExplicitAccessModifier => (_propertyFlags & Flags.HasExplicitAccessModifier) != 0; internal bool IsAutoProperty - => (_propertyFlags & (Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet)) != 0; + => IsSetOnEitherPart(Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet); + + 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; @@ -689,7 +673,80 @@ protected bool AccessorsHaveImplementation /// 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 { @@ -1147,17 +1204,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; } diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs index af8a830d393fc..25d605aae2452 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordEqualityContractProperty.cs @@ -35,7 +35,6 @@ public SynthesizedRecordEqualityContractProperty(SourceMemberContainerTypeSymbol hasAutoPropertyGet: false, hasAutoPropertySet: false, isExpressionBodied: false, - isInitOnly: false, accessorsHaveImplementation: true, usesFieldKeyword: false, RefKind.None, diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs index 14ef25bb0fa59..898b4c160e30a 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Synthesized/Records/SynthesizedRecordPropertySymbol.cs @@ -33,7 +33,6 @@ public SynthesizedRecordPropertySymbol( hasAutoPropertyGet: true, hasAutoPropertySet: true, isExpressionBodied: false, - isInitOnly: ShouldUseInit(containingType), accessorsHaveImplementation: true, usesFieldKeyword: false, RefKind.None, diff --git a/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedBackingFieldSymbol.cs index a56bf57992fb0..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; diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf index aadecf37d975c..1f58788172e22 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. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf index 8a2ae5ac16c8c..45ad041f6ae6c 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. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf index 882867ef83437..2ae5d4b600605 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf index b3a577a145482..579931a97065d 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.it.xlf index 8f3577b4e4fac..4addab2376160 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ja.xlf index 96371dbe3da88..3b10a72ebdb0f 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}' である必要があります diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ko.xlf index 82af9ee6061e4..d2b45fd318a98 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}'이어야 합니다. diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pl.xlf index 9b35c1269306f..87cb633b28c27 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.pt-BR.xlf index 7c6b53d01ed1f..83b8342446513 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.ru.xlf index 6ecf2470a600c..d14e5a5057c4b 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.tr.xlf index 7ed3be4dba16c..921efc9fb3278 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hans.xlf index ab6c71e7285c9..63fbf35a42936 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 diff --git a/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf b/src/Compilers/CSharp/Portable/xlf/CSharpResources.zh-Hant.xlf index 0a99264756fdd..e42a769c90026 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}' 以符合定義部分 diff --git a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs b/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs index 9f994c27677c1..b35dc0ce5acde 100644 --- a/src/Compilers/CSharp/Test/Emit2/Semantics/PrimaryConstructorTests.cs +++ b/src/Compilers/CSharp/Test/Emit2/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/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 42c35b548aabe..77be174c384bd 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; using Roslyn.Test.Utilities; +using System.Collections.Immutable; using System.Linq; using Xunit; @@ -16,8 +17,12 @@ 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() { @@ -309,11 +314,152 @@ class C 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, LanguageVersion.Preview)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) { string source = $$""" {{typeKind}} A @@ -483,7 +629,7 @@ .maxstack 2 [Theory] [CombinatorialData] public void ImplicitAccessorBody_02( - [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) { string source = """ interface I @@ -554,7 +700,7 @@ .maxstack 1 [Theory] [CombinatorialData] public void ImplicitAccessorBody_03( - [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) { string source = """ interface I @@ -648,7 +794,7 @@ interface I [CombinatorialData] public void ImplicitAccessorBody_04( [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind, - [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) { string source = $$""" {{typeKind}} A @@ -745,7 +891,7 @@ static void Main() [CombinatorialData] public void ImplicitAccessorBody_05( [CombinatorialValues("class", "struct", "ref struct", "record", "record struct")] string typeKind, - [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersion.Preview)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) { string source = $$""" {{typeKind}} A @@ -1175,9 +1321,9 @@ static void Main() """; var verifier = CompileAndVerify( source, - targetFramework: TargetFramework.Net80, + targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, - expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + expectedOutput: IncludeExpectedOutput(useInit, "(1, 2, 3, 4, 0, 6, 7, 9)")); verifier.VerifyDiagnostics(); verifier.VerifyIL("C..ctor", """ { @@ -1249,9 +1395,9 @@ static void Main() """; var verifier = CompileAndVerify( source, - targetFramework: TargetFramework.Net80, + targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, - expectedOutput: IncludeExpectedOutput("(1, 2, 3, 4, 0, 6, 7, 9)")); + expectedOutput: IncludeExpectedOutput(useInit, "(1, 2, 3, 4, 0, 6, 7, 9)")); verifier.VerifyDiagnostics(); verifier.VerifyIL("C..ctor", """ { @@ -1373,7 +1519,7 @@ public void Initializer_02D(bool useRefStruct, bool useInit) public int P6 { get; {{setter}}; } = 6; } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + 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 @@ -1399,7 +1545,7 @@ class C public static int PB { get => 0; set { } } = 11; } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + 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; @@ -1421,7 +1567,7 @@ class C public int PB { get => 0; {{setter}} { } } = 11; } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + 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; @@ -1542,7 +1688,11 @@ static void Main() } } """; - var verifier = CompileAndVerify(source, targetFramework: TargetFramework.Net80, verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput("(1, 2, 3, 0, 0, 6, 0, 9)")); + 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") { @@ -1670,7 +1820,7 @@ static void M(Action a) } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + 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; }); @@ -1930,9 +2080,9 @@ static void Main() var verifier = CompileAndVerify( source, options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, - targetFramework: TargetFramework.Net80, + targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, - expectedOutput: IncludeExpectedOutput(""" + expectedOutput: IncludeExpectedOutput(useInit, """ (0, 0) (0, 0) (0, 0) @@ -2110,9 +2260,9 @@ static void Main() var verifier = CompileAndVerify( source, options: includeStructInitializationWarnings ? TestOptions.ReleaseExe.WithSpecificDiagnosticOptions(ReportStructInitializationWarnings) : TestOptions.ReleaseExe, - targetFramework: TargetFramework.Net80, + targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, - expectedOutput: IncludeExpectedOutput(""" + expectedOutput: IncludeExpectedOutput(useInit, """ (0, -1) (0, 1) (0, 2) @@ -2248,7 +2398,7 @@ public void ReadOnly_01(bool useReadOnlyType, bool useReadOnlyProperty, bool use {{propertyModifier}} object PD { {{setter}} { field = value; } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); if (useInit) { comp.VerifyEmitDiagnostics(); @@ -2334,7 +2484,7 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) object PC { {{getModifier}} get; {{setModifier}} set { field = value; } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + var comp = CreateCompilation(source); if (useReadOnlyType) { if (useReadOnlyOnGet) @@ -2397,9 +2547,9 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(10, 46)); + Diagnostic(ErrorCode.ERR_AssgReadonly, "field").WithLocation(10, 46)); } } var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); @@ -2409,9 +2559,9 @@ public void ReadOnly_02(bool useReadOnlyType, bool useReadOnlyOnGet) $"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}", - $"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); } @@ -2467,7 +2617,7 @@ static void Main() k__BackingField: True k__BackingField: {{useReadOnlyType}} k__BackingField: True - k__BackingField: {{useReadOnlyType}} + k__BackingField: {{useReadOnlyType || useReadOnlyMember}} """)); var comp = (CSharpCompilation)verifier.Compilation; var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); @@ -2483,7 +2633,7 @@ static void Main() $"System.Object S.k__BackingField: True", $"System.Object S.k__BackingField: {useReadOnlyType}", $"System.Object S.k__BackingField: True", - $"System.Object S.k__BackingField: {useReadOnlyType}", + $"System.Object S.k__BackingField: {useReadOnlyType || useReadOnlyMember}", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -2551,7 +2701,7 @@ static void Main() k__BackingField: {{useReadOnlyMember}} k__BackingField: {{useReadOnlyMember}} k__BackingField: False - k__BackingField: False + k__BackingField: {{useReadOnlyMember}} """); } var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); @@ -2562,7 +2712,7 @@ static void Main() $"System.Object S.k__BackingField: {useReadOnlyMember}", $"System.Object S.k__BackingField: {useReadOnlyMember}", $"System.Object S.k__BackingField: False", - $"System.Object S.k__BackingField: False", + $"System.Object S.k__BackingField: {useReadOnlyMember}", }; AssertEx.Equal(expectedMembers, actualMembers); } @@ -2587,7 +2737,7 @@ struct S object Q5 { readonly get => field; readonly {{setter}} { } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); if (useInit) { comp.VerifyEmitDiagnostics( @@ -2666,20 +2816,18 @@ struct S Diagnostic(ErrorCode.ERR_DuplicatePropertyReadOnlyMods, "Q5").WithArguments("S.Q5").WithLocation(12, 12)); } var actualMembers = comp.GetMember("S").GetMembers().OfType().Select(f => $"{f.ToTestDisplayString()}: {f.IsReadOnly}"); - // PROTOTYPE: When determining whether the backing field should be readonly in - // SourcePropertySymbolBase..ctor(), we're ignoring the readonly modifier on accessors. var expectedMembers = new[] { $"System.Object S.k__BackingField: True", - $"System.Object S.k__BackingField: {useInit}", + $"System.Object S.k__BackingField: True", $"System.Object S.k__BackingField: {useInit}", - $"System.Object S.k__BackingField: {useInit}", - $"System.Object S.k__BackingField: {useInit}", - $"System.Object S.k__BackingField: False", - $"System.Object S.k__BackingField: {useInit}", - $"System.Object S.k__BackingField: False", - $"System.Object S.k__BackingField: {useInit}", - $"System.Object S.k__BackingField: False", + $"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); } @@ -2786,9 +2934,9 @@ readonly struct S5 // (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 CS1604: Cannot assign to 'field' because it is read-only + // (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_AssgReadonlyLocal, "field").WithArguments("field").WithLocation(7, 32), + 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), @@ -2949,7 +3097,7 @@ public void RefReturning_02(bool useStruct, bool useRefReadOnly) static {{refModifier}} object PI { set { _ = field; } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + var comp = CreateCompilation(source); comp.VerifyEmitDiagnostics( // (3,32): error CS8145: Auto-implemented properties cannot return by reference // static ref object P1 { get; } @@ -3037,7 +3185,7 @@ class C {{modifier}} object P54 { get { return null; } {{setter}} { field = value; } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + var comp = CreateCompilation(source, targetFramework: GetTargetFramework(useInit)); comp.VerifyEmitDiagnostics( // (3,25): error CS8051: Auto-implemented properties must have get accessors. // object P02 { set; } @@ -3065,7 +3213,8 @@ class B0 : A public override object P3 { get; } } """; - var comp = CreateCompilation([sourceA, sourceB0], targetFramework: TargetFramework.Net80); + 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; } @@ -3088,7 +3237,7 @@ class B1 : A public override object P3 { get; {{setter}}; } } """; - comp = CreateCompilation([sourceA, sourceB1], targetFramework: TargetFramework.Net80); + 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; } @@ -3108,7 +3257,7 @@ class B2 : A public override object P3 { get => field; {{setter}} { } } } """; - comp = CreateCompilation([sourceA, sourceB2], targetFramework: TargetFramework.Net80); + 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 { } } @@ -3128,7 +3277,7 @@ class B3 : A public override object P3 { get => field; } } """; - comp = CreateCompilation([sourceA, sourceB3], targetFramework: TargetFramework.Net80); + 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; } @@ -3151,7 +3300,7 @@ class B4 : A public override object P3 { {{setter}} { field = value; } } } """; - comp = CreateCompilation([sourceA, sourceB4], targetFramework: TargetFramework.Net80); + 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; } } @@ -3188,7 +3337,8 @@ class B0 : A public override object P3 { get; } } """; - var comp = CreateCompilation([sourceA, sourceB0], targetFramework: TargetFramework.Net80); + 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 @@ -3214,7 +3364,7 @@ class B1 : A public override object P3 { get; {{setter}}; } } """; - comp = CreateCompilation([sourceA, sourceB1], targetFramework: TargetFramework.Net80); + 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; } @@ -3231,7 +3381,7 @@ class B2 : A public override object P3 { get => field; {{setter}} { } } } """; - comp = CreateCompilation([sourceA, sourceB2], targetFramework: TargetFramework.Net80); + 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 { } } @@ -3248,7 +3398,7 @@ class B3 : A public override object P3 { get => field; } } """; - comp = CreateCompilation([sourceA, sourceB3], targetFramework: TargetFramework.Net80); + 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 @@ -3274,7 +3424,7 @@ class B4 : A public override object P3 { {{setter}} { field = value; } } } """; - comp = CreateCompilation([sourceA, sourceB4], targetFramework: TargetFramework.Net80); + 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 @@ -3336,7 +3486,7 @@ class B4 : A public new object P3 { {{setter}} { field = value; } } } """; - var comp = CreateCompilation(source, targetFramework: TargetFramework.Net80); + 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; } @@ -3664,5 +3814,1325 @@ interface I // R Q3 { set { _ = field; } } Diagnostic(ErrorCode.ERR_FieldAutoPropCantBeByRefLike, "R").WithArguments("R").WithLocation(11, 5)); } + + [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); + } + + 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); + } } } diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/PartialPropertiesTests.cs index 5e33a86726a19..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)); @@ -3780,9 +3777,32 @@ partial class C // (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 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] @@ -3822,12 +3842,35 @@ partial class C // (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 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] @@ -3886,6 +3929,9 @@ partial class C // (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 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")] From 5aceebceafff24565b56b19b1ab78bc63c434999 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:22:34 -0700 Subject: [PATCH 17/18] Field-backed properties: definite assignment (#75116) --- .../Portable/Binder/Binder.ValueChecks.cs | 29 +- .../Portable/Binder/Binder_Attributes.cs | 2 +- .../Portable/Binder/Binder_Expressions.cs | 2 +- .../CSharp/Portable/Binder/Binder_Patterns.cs | 2 +- .../Portable/Binder/Binder_Statements.cs | 38 +- .../CSharp/Portable/BoundTree/BoundNode.cs | 21 + .../CSharp/Portable/BoundTree/BoundNodes.xml | 2 + .../Portable/Compiler/MethodCompiler.cs | 89 + .../Portable/FlowAnalysis/NullableWalker.cs | 4 +- .../Generated/BoundNodes.xml.Generated.cs | 17 +- ...ocalRewriter_CompoundAssignmentOperator.cs | 2 +- .../LocalRewriter_PropertyAccess.cs | 8 +- .../Lowering/MethodToClassRewriter.cs | 2 +- .../Source/SourcePropertySymbolBase.cs | 30 +- .../CSharp/Test/Emit3/FieldKeywordTests.cs | 2718 ++++++++++++++--- 15 files changed, 2542 insertions(+), 424 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs b/src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs index f550f1c8f3f2f..c12c6d9256706 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; @@ -1694,7 +1721,7 @@ private bool CheckPropertyValueKind(SyntaxNode node, BoundExpression expr, BindV if (setMethod is null) { var containing = this.ContainingMemberOrLambda; - if (!AccessingAutoPropertyFromConstructor(receiver, propertySymbol, containing, allowFieldKeyword: true) + 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 5ce1a7173d21d..024136a1148f9 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 8e14810c77d5e..aca7c5f07bea2 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -8617,7 +8617,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 471ca997b5f30..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,27 +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); } - // PROTOTYPE: Review all callers for allowFieldKeyword. - private static bool AccessingAutoPropertyFromConstructor(BoundExpression receiver, PropertySymbol propertySymbol, Symbol fromMember, bool allowFieldKeyword = false) + 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 && - (allowFieldKeyword ? sourceProperty.IsAutoPropertyOrUsesFieldKeyword : sourceProperty.IsAutoProperty) && - 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) @@ -1779,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 8f92867f0dd10..21a2248341a44 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/Compiler/MethodCompiler.cs b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs index ab895e6cac45b..24bbbb7a32868 100644 --- a/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs +++ b/src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs @@ -1823,6 +1823,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); @@ -2205,6 +2206,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/FlowAnalysis/NullableWalker.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs index 3eca1824c79c2..5d9509d18b4f7 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs @@ -9754,8 +9754,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 IsAutoProperty || UsesFieldKeyword; + => IsSetOnEitherPart(Flags.HasAutoPropertyGet | Flags.HasAutoPropertySet | Flags.UsesFieldKeyword); internal bool UsesFieldKeyword => IsSetOnEitherPart(Flags.UsesFieldKeyword); @@ -659,6 +659,32 @@ protected bool HasExplicitAccessModifier 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 || @@ -871,7 +897,7 @@ 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 diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index 77be174c384bd..e7a5814a09e19 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -1650,41 +1650,1000 @@ .maxstack 1 [Theory] [CombinatorialData] - public void ConstructorAssignment_02([CombinatorialValues("class", "struct", "ref struct")] string typeKind, bool useInit) + 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}} C + {{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 int P4 { get => field; {{setter}} { } } - public int P5 { get => 0; {{setter}}; } + public C3(int i) { P3++; F3 = i; } + } + struct C6 + { + public int F6; public int P6 { get; {{setter}}; } - public int P7 { get; {{setter}} { } } - public int P8 { {{setter}} { field = value; } } + 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 C() - { - P1 = 1; - P2 = 2; - P3 = 3; - P4 = 4; - P5 = 5; - P6 = 6; - P7 = 7; - P8 = 8; - P9 = 9; - } + public C9(int i) { P9++; F9 = i; } } - class Program + struct 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 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)); } } """; @@ -1692,108 +2651,350 @@ static void Main() source, targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, - expectedOutput: IncludeExpectedOutput(useInit, "(1, 2, 3, 0, 0, 6, 0, 9)")); + expectedOutput: IncludeExpectedOutput(useInit, "(1, 1, 2, 1, 3, 1, 6, 1, 7, 1, 9, 1)")); verifier.VerifyDiagnostics(); - if (typeKind == "class") - { - verifier.VerifyIL("C..ctor", $$""" - { - // Code size 71 (0x47) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: call "object..ctor()" - IL_0006: ldarg.0 - IL_0007: ldc.i4.1 - IL_0008: stfld "int C.k__BackingField" - IL_000d: ldarg.0 - IL_000e: ldc.i4.2 - IL_000f: stfld "int C.k__BackingField" - IL_0014: ldarg.0 - IL_0015: ldc.i4.3 - IL_0016: call "void C.P3.{{setter}}" - IL_001b: ldarg.0 - IL_001c: ldc.i4.4 - IL_001d: call "void C.P4.{{setter}}" - IL_0022: ldarg.0 - IL_0023: ldc.i4.5 - IL_0024: call "void C.P5.{{setter}}" - IL_0029: ldarg.0 - IL_002a: ldc.i4.6 - IL_002b: call "void C.P6.{{setter}}" - IL_0030: ldarg.0 - IL_0031: ldc.i4.7 - IL_0032: call "void C.P7.{{setter}}" - IL_0037: ldarg.0 - IL_0038: ldc.i4.8 - IL_0039: call "void C.P8.{{setter}}" - IL_003e: ldarg.0 - IL_003f: ldc.i4.s 9 - IL_0041: call "void C.P9.{{setter}}" - IL_0046: ret - } - """); - } - else - { - verifier.VerifyIL("C..ctor", $$""" + 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() { - // Code size 121 (0x79) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stfld "int C.k__BackingField" - IL_0007: ldarg.0 - IL_0008: ldc.i4.0 - IL_0009: stfld "int C.k__BackingField" - IL_000e: ldarg.0 - IL_000f: ldc.i4.0 - IL_0010: stfld "int C.k__BackingField" - IL_0015: ldarg.0 - IL_0016: ldc.i4.0 - IL_0017: stfld "int C.k__BackingField" - IL_001c: ldarg.0 - IL_001d: ldc.i4.0 - IL_001e: stfld "int C.k__BackingField" - IL_0023: ldarg.0 - IL_0024: ldc.i4.0 - IL_0025: stfld "int C.k__BackingField" - IL_002a: ldarg.0 - IL_002b: ldc.i4.0 - IL_002c: stfld "int C.k__BackingField" - IL_0031: ldarg.0 - IL_0032: ldc.i4.0 - IL_0033: stfld "int C.k__BackingField" - IL_0038: ldarg.0 - IL_0039: ldc.i4.1 - IL_003a: stfld "int C.k__BackingField" - IL_003f: ldarg.0 - IL_0040: ldc.i4.2 - IL_0041: stfld "int C.k__BackingField" - IL_0046: ldarg.0 - IL_0047: ldc.i4.3 - IL_0048: call "void C.P3.{{setter}}" - IL_004d: ldarg.0 - IL_004e: ldc.i4.4 - IL_004f: call "void C.P4.{{setter}}" - IL_0054: ldarg.0 - IL_0055: ldc.i4.5 - IL_0056: call "void C.P5.{{setter}}" - IL_005b: ldarg.0 - IL_005c: ldc.i4.6 - IL_005d: call "void C.P6.{{setter}}" - IL_0062: ldarg.0 - IL_0063: ldc.i4.7 - IL_0064: call "void C.P7.{{setter}}" - IL_0069: ldarg.0 - IL_006a: ldc.i4.8 - IL_006b: call "void C.P8.{{setter}}" - IL_0070: ldarg.0 - IL_0071: ldc.i4.s 9 - IL_0073: call "void C.P9.{{setter}}" - IL_0078: ret + 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] @@ -1997,83 +3198,238 @@ class C P4 = 4); static int F(int x, int y, int z, int w) => x; } - class Program + 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", $$""" { - static void Main() - { - Console.WriteLine((C.P1, C.P2, C.P3, C.P4)); - } + // 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 } - """; - var verifier = CompileAndVerify(source, expectedOutput: "(1, 2, 3, 0)"); - verifier.VerifyDiagnostics(); - verifier.VerifyIL("C..cctor", """ + """); + verifier.VerifyIL("S3..ctor", $$""" { - // 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 + // 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_01(bool useRefStruct, bool useInit, bool includeStructInitializationWarnings) + public void DefaultInitialization_01B(bool useInit, bool includeStructInitializationWarnings) { - string typeKind = useRefStruct ? "ref struct" : " struct"; string setter = useInit ? "init" : "set"; string source = $$""" using System; - {{typeKind}} S0 - { - public int F0; - public int P0 { get; {{setter}}; } - public S0(int unused) { _ = P0; } - } - {{typeKind}} S1 + struct S1 { public int F1; - public int P1 { get => field; } + public int P1 { get; } public S1(int unused) { _ = P1; } } - {{typeKind}} S2 + struct S2 { public int F2; - public int P2 { get => field; {{setter}}; } + public int P2 { get => field; } public S2(int unused) { _ = P2; } } - {{typeKind}} S3 - { - public int F3; - public int P3 { get => field; {{setter}} { field = value; } } - public S3(int unused) { _ = P3; } - } class Program { static void Main() { - var s0 = new S0(-1); var s1 = new S1(1); var s2 = new S2(2); - var s3 = new S3(3); - Console.WriteLine((s0.F0, s0.P0)); Console.WriteLine((s1.F1, s1.P1)); Console.WriteLine((s2.F2, s2.P2)); - Console.WriteLine((s3.F3, s3.P3)); } } """; @@ -2083,177 +3439,263 @@ static void Main() targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(useInit, """ - (0, 0) - (0, 0) (0, 0) (0, 0) """)); if (includeStructInitializationWarnings) { verifier.VerifyDiagnostics( - // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 - // public int F0; - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), - // (6,12): warning CS9021: Control is returned to caller before auto-implemented property 'S0.P0' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S0(int unused) { _ = P0; } - Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S0").WithArguments("S0.P0").WithLocation(6, 12), - // (6,12): warning CS9022: Control is returned to caller before field 'S0.F0' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S0(int unused) { _ = P0; } - Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S0").WithArguments("S0.F0").WithLocation(6, 12), - // (6,33): warning CS9018: Auto-implemented property 'P0' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S0(int unused) { _ = P0; } - Diagnostic(ErrorCode.WRN_UseDefViolationPropertySupportedVersion, "P0").WithArguments("P0").WithLocation(6, 33), - // (10,16): warning CS0649: Field 'S1.F1' is never assigned to, and will always have its default value 0 + // (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(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. + 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_UseDefViolationThisSupportedVersion, "P1").WithLocation(12, 33), - // (16,16): warning CS0649: Field 'S2.F2' is never assigned to, and will always have its default value 0 + 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(16, 16), - // (18,12): warning CS9021: Control is returned to caller before auto-implemented property 'S2.P2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UnassignedThisAutoPropertySupportedVersion, "S2").WithArguments("S2.P2").WithLocation(18, 12), - // (18,12): warning CS9022: Control is returned to caller before field 'S2.F2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int unused) { _ = P2; } - Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S2").WithArguments("S2.F2").WithLocation(18, 12), - // (18,33): warning CS9018: Auto-implemented property 'P2' is read before being explicitly assigned, causing a preceding implicit assignment of 'default'. + 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_UseDefViolationPropertySupportedVersion, "P2").WithArguments("P2").WithLocation(18, 33), - // (22,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(22, 16), - // (24,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 S3(int unused) { _ = P3; } - Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P3").WithLocation(24, 33)); + Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P2").WithLocation(12, 33)); } else { verifier.VerifyDiagnostics( - // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 - // public int F0; - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), - // (10,16): warning CS0649: Field 'S1.F1' is never assigned to, and will always have its default value 0 + // (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(10, 16), - // (16,16): warning CS0649: Field 'S2.F2' is never assigned to, and will always have its default value 0 + 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(16, 16), - // (22,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(22, 16)); + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F2").WithArguments("S2.F2", "0").WithLocation(10, 16)); } - verifier.VerifyIL("S0..ctor", $$""" - { - // Code size 22 (0x16) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stfld "int S0.F0" - IL_0007: ldarg.0 - IL_0008: ldc.i4.0 - IL_0009: stfld "int S0.k__BackingField" - IL_000e: ldarg.0 - IL_000f: call "readonly int S0.P0.get" - IL_0014: pop - IL_0015: ret - } - """); 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 "int S1.P1.get" - IL_0014: pop - IL_0015: ret - } - """); + { + // 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 - } - """); - verifier.VerifyIL("S3..ctor", $$""" - { - // Code size 22 (0x16) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stfld "int S3.F3" - IL_0007: ldarg.0 - IL_0008: ldc.i4.0 - IL_0009: stfld "int S3.k__BackingField" - IL_000e: ldarg.0 - IL_000f: call "int S3.P3.get" - IL_0014: pop - IL_0015: ret - } - """); + { + // 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_02(bool useRefStruct, bool useInit, bool includeStructInitializationWarnings) + public void DefaultInitialization_02A(bool useInit, bool includeStructInitializationWarnings) { - string typeKind = useRefStruct ? "ref struct" : " struct"; string setter = useInit ? "init" : "set"; string source = $$""" using System; - {{typeKind}} S0 - { - public int F0; - public int P0 { get; {{setter}}; } - public S0(int i) { P0 = i; } - } - {{typeKind}} S1 + struct S1 { - public int F1; - public int P1 { get => field; } + public int P1 { get; } public S1(int i) { P1 = i; } } - {{typeKind}} S2 + struct S2 { - public int F2; - public int P2 { get => field; {{setter}}; } + public int P2 { get => field; } public S2(int i) { P2 = i; } } - {{typeKind}} S3 + struct S3 { - public int F3; - public int P3 { get => field; {{setter}} { field = value; } } + 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 s0 = new S0(-1); var s1 = new S1(1); var s2 = new S2(2); var s3 = new S3(3); - Console.WriteLine((s0.F0, s0.P0)); - Console.WriteLine((s1.F1, s1.P1)); - Console.WriteLine((s2.F2, s2.P2)); + 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)); } } """; @@ -2263,113 +3705,64 @@ static void Main() targetFramework: GetTargetFramework(useInit), verify: Verification.Skipped, expectedOutput: IncludeExpectedOutput(useInit, """ - (0, -1) - (0, 1) - (0, 2) (0, 3) + (0, 4) """)); if (includeStructInitializationWarnings) { verifier.VerifyDiagnostics( - // (4,16): warning CS0649: Field 'S0.F0' is never assigned to, and will always have its default value 0 - // public int F0; - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), - // (6,12): warning CS9022: Control is returned to caller before field 'S0.F0' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S0(int i) { P0 = i; } - Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S0").WithArguments("S0.F0").WithLocation(6, 12), - // (10,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(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 S1(int i) { P1 = i; } - Diagnostic(ErrorCode.WRN_UseDefViolationThisSupportedVersion, "P1").WithLocation(12, 24), - // (16,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(16, 16), - // (18,12): warning CS9022: Control is returned to caller before field 'S2.F2' is explicitly assigned, causing a preceding implicit assignment of 'default'. - // public S2(int i) { P2 = i; } - Diagnostic(ErrorCode.WRN_UnassignedThisSupportedVersion, "S2").WithArguments("S2.F2").WithLocation(18, 12), - // (22,16): warning CS0649: Field 'S3.F3' is never assigned to, and will always have its default value 0 + // (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(22, 16), - // (24,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. + 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_UseDefViolationThisSupportedVersion, "P3").WithLocation(24, 24)); + 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 'S0.F0' is never assigned to, and will always have its default value 0 - // public int F0; - Diagnostic(ErrorCode.WRN_UnassignedInternalField, "F0").WithArguments("S0.F0", "0").WithLocation(4, 16), - // (10,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(10, 16), - // (16,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(16, 16), - // (22,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(22, 16)); - } - verifier.VerifyIL("S0..ctor", $$""" - { - // Code size 15 (0xf) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stfld "int S0.F0" - IL_0007: ldarg.0 - IL_0008: ldarg.1 - IL_0009: call "void S0.P0.{{setter}}" - IL_000e: ret - } - """); - 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: ldarg.1 - IL_0010: stfld "int S1.k__BackingField" - IL_0015: 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.F2" - IL_0007: ldarg.0 - IL_0008: ldarg.1 - IL_0009: call "void S2.P2.{{setter}}" - IL_000e: ret - } - """); + // (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 22 (0x16) - .maxstack 2 - IL_0000: ldarg.0 - IL_0001: ldc.i4.0 - IL_0002: stfld "int S3.F3" - IL_0007: ldarg.0 - IL_0008: ldc.i4.0 - IL_0009: stfld "int S3.k__BackingField" - IL_000e: ldarg.0 - IL_000f: ldarg.1 - IL_0010: call "void S3.P3.{{setter}}" - IL_0015: ret - } - """); + { + // 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] @@ -3149,6 +4542,333 @@ public void RefReturning_02(bool useStruct, bool useRefReadOnly) 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)] @@ -3599,6 +5319,7 @@ static void ReportField(FieldInfo field) C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, C.k__BackingField: System.Runtime.CompilerServices.CompilerGeneratedAttribute, """)); + if (useDEBUG) { verifier.VerifyIL("C.P1.get", """ @@ -3640,9 +5361,11 @@ .maxstack 0 } """); } + var comp = (CSharpCompilation)verifier.Compilation; - var actualMembers = comp.GetMember("C").GetMembers().OfType().ToTestDisplayStrings(); - var expectedMembers = new[] + var containingType = comp.GetMember("C"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] { "System.Object C.k__BackingField", "System.Object C.k__BackingField", @@ -3650,7 +5373,10 @@ .maxstack 0 "System.Object C.k__BackingField", "System.Object C.k__BackingField", }; - AssertEx.Equal(expectedMembers, actualMembers); + AssertEx.Equal(expectedFields, actualFields.ToTestDisplayStrings()); + + var actualProperties = containingType.GetMembers().OfType().ToImmutableArray(); + VerifyMergedProperties(actualProperties, actualFields); } [Fact] @@ -5134,5 +6860,211 @@ partial class B 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(); + } } } From f1b28238731c2d1c681e32d8f0a778068460b949 Mon Sep 17 00:00:00 2001 From: Charles Stoner <10732005+cston@users.noreply.github.com> Date: Tue, 24 Sep 2024 19:37:12 -0700 Subject: [PATCH 18/18] Field-backed properties: interface properties (#75219) --- .../Symbols/Source/SourcePropertySymbol.cs | 2 +- .../Source/SourcePropertySymbolBase.cs | 10 + .../CSharp/Test/Emit3/FieldKeywordTests.cs | 840 ++++++++++++++++-- .../DefaultInterfaceImplementationTests.cs | 96 +- 4 files changed, 831 insertions(+), 117 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs index a69fd0205790b..a76fc7bd75edf 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbol.cs @@ -63,7 +63,7 @@ private static SourcePropertySymbol Create( out _); bool allowAutoPropertyAccessors = (modifiers & (DeclarationModifiers.Abstract | DeclarationModifiers.Extern | DeclarationModifiers.Indexer)) == 0 && - (!containingType.IsInterface || (modifiers & DeclarationModifiers.Static) != 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; diff --git a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs index 7f297bfea248c..aca35156e28ea 100644 --- a/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs +++ b/src/Compilers/CSharp/Portable/Symbols/Source/SourcePropertySymbolBase.cs @@ -853,6 +853,16 @@ internal override void AfterAddingTypeMembersChecks(ConversionsBase conversions, } } + 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; diff --git a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs index e7a5814a09e19..26a54410af232 100644 --- a/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs +++ b/src/Compilers/CSharp/Test/Emit3/FieldKeywordTests.cs @@ -700,14 +700,16 @@ .maxstack 1 [Theory] [CombinatorialData] public void ImplicitAccessorBody_03( - [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion) + [CombinatorialValues(LanguageVersion.CSharp13, LanguageVersionFacts.CSharpNext)] LanguageVersion languageVersion, bool useInit) { - string source = """ + string setter = useInit ? "init" : "set "; + string source = $$""" interface I { - object Q1 { get; set { _ = field; } } - object Q2 { get { return field; } set; } - object Q3 { get { return field; } init; } + object Q1 { get; {{setter}} { _ = field; } } + object Q2 { get { return field; } {{setter}}; } + object Q3 { get; {{setter}} { } } + object Q4 { get { return null; } {{setter}}; } } """; @@ -719,75 +721,77 @@ interface I if (languageVersion == LanguageVersion.CSharp13) { comp.VerifyEmitDiagnostics( - // (3,17): error CS0501: 'I.Q1.get' must declare a body because it is not marked abstract, extern, or partial - // object Q1 { get; set { _ = field; } } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I.Q1.get").WithLocation(3, 17), - // (3,32): error CS0103: The name 'field' does not exist in the current context - // object Q1 { get; set { _ = field; } } - Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(3, 32), + // (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; } + // object Q2 { get { return field; } set ; } Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(4, 30), - // (4,39): error CS0501: 'I.Q2.set' must declare a body because it is not marked abstract, extern, or partial - // object Q2 { get { return field; } set; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I.Q2.set").WithLocation(4, 39), - // (5,30): error CS0103: The name 'field' does not exist in the current context - // object Q3 { get { return field; } init; } - Diagnostic(ErrorCode.ERR_NameNotInContext, "field").WithArguments("field").WithLocation(5, 30), - // (5,39): error CS0501: 'I.Q3.init' must declare a body because it is not marked abstract, extern, or partial - // object Q3 { get { return field; } init; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "init").WithArguments("I.Q3.init").WithLocation(5, 39)); + // (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,17): error CS0501: 'I.Q1.get' must declare a body because it is not marked abstract, extern, or partial - // object Q1 { get; set { _ = field; } } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("I.Q1.get").WithLocation(3, 17), - // (4,39): error CS0501: 'I.Q2.set' must declare a body because it is not marked abstract, extern, or partial - // object Q2 { get { return field; } set; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "set").WithArguments("I.Q2.set").WithLocation(4, 39), - // (5,39): error CS0501: 'I.Q3.init' must declare a body because it is not marked abstract, extern, or partial - // object Q3 { get { return field; } init; } - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "init").WithArguments("I.Q3.init").WithLocation(5, 39)); + // (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 actualMembers = comp.GetMember("I").GetMembers().ToTestDisplayStrings(); - string[] expectedMembers; - if (languageVersion == LanguageVersion.CSharp13) - { - expectedMembers = new[] - { - "System.Object I.Q1 { get; set; }", - "System.Object I.Q1.get", - "void I.Q1.set", - "System.Object I.Q2 { get; set; }", - "System.Object I.Q2.get", - "void I.Q2.set", - "System.Object I.Q3 { get; init; }", - "System.Object I.Q3.get", - "void modreq(System.Runtime.CompilerServices.IsExternalInit) I.Q3.init", - }; - } - else + var containingType = comp.GetMember("I"); + var actualFields = containingType.GetMembers().OfType().ToImmutableArray(); + var expectedFields = new[] { - expectedMembers = new[] - { - "System.Object I.k__BackingField", - "System.Object I.Q1 { get; set; }", - "System.Object I.Q1.get", - "void I.Q1.set", - "System.Object I.k__BackingField", - "System.Object I.Q2 { get; set; }", - "System.Object I.Q2.get", - "void I.Q2.set", - "System.Object I.k__BackingField", - "System.Object I.Q3 { get; init; }", - "System.Object I.Q3.get", - "void modreq(System.Runtime.CompilerServices.IsExternalInit) I.Q3.init", - }; - } - AssertEx.Equal(expectedMembers, actualMembers); + "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] @@ -1231,6 +1235,37 @@ .maxstack 1 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] @@ -1361,6 +1396,37 @@ .maxstack 2 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] @@ -1433,6 +1499,37 @@ .maxstack 2 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] @@ -1442,7 +1539,7 @@ public void Initializer_02C(bool useInit) string setter = useInit ? "init" : "set"; string source = $$""" using System; - interface C + interface I { public int P1 { get; } = 1; public int P2 { get => field; } = 2; @@ -1466,33 +1563,54 @@ interface C // (6,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P3 { get => field; set; } = 3; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P3").WithLocation(6, 16), - // (6,35): error CS0501: 'C.P3.set' must declare a body because it is not marked abstract, extern, or partial - // public int P3 { get => field; set; } = 3; - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P3.{setter}").WithLocation(6, 35), // (7,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P4 { get => field; set { } } = 4; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P4").WithLocation(7, 16), // (8,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P5 { get => 0; set; } = 5; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P5").WithLocation(8, 16), - // (8,31): error CS0501: 'C.P5.set' must declare a body because it is not marked abstract, extern, or partial - // public int P5 { get => 0; set; } = 5; - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, setter).WithArguments($"C.P5.{setter}").WithLocation(8, 31), // (9,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P6 { get; set; } = 6; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P6").WithLocation(9, 16), // (10,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P7 { get; set { } } = 7; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P7").WithLocation(10, 16), - // (10,21): error CS0501: 'C.P7.get' must declare a body because it is not marked abstract, extern, or partial - // public int P7 { get; set { } } = 7; - Diagnostic(ErrorCode.ERR_ConcreteMissingBody, "get").WithArguments("C.P7.get").WithLocation(10, 21), // (11,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P8 { set { field = value; } } = 8; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P8").WithLocation(11, 16), // (12,16): error CS8053: Instance properties in interfaces cannot have initializers. // public int P9 { get { return field; } set { field = value; } } = 9; Diagnostic(ErrorCode.ERR_InstancePropertyInitializerInInterface, "P9").WithLocation(12, 16)); + + 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] @@ -1535,6 +1653,151 @@ public void Initializer_02D(bool useRefStruct, bool useInit) 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() { @@ -5536,9 +5799,15 @@ interface I // (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)); + 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] @@ -5693,6 +5962,425 @@ static void Main() 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; diff --git a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs index 8e9340ba7993a..252bf355fe09b 100644 --- a/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs +++ b/src/Compilers/CSharp/Test/Symbol/Symbols/DefaultInterfaceImplementationTests.cs @@ -3373,26 +3373,34 @@ class Test1 : I1 targetFramework: TargetFramework.Net60); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); - if (isStatic && useCSharp13) + switch (isStatic, useCSharp13) { - compilation1.VerifyDiagnostics( - // (4,24): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // static virtual int P1 - Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 24)); - } - else if (isStatic) - { - compilation1.VerifyDiagnostics(); - } - else - { - // 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) - ); + 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"); @@ -3402,7 +3410,7 @@ class Test1 : I1 Assert.False(p1.IsWriteOnly); var field1 = ((SourcePropertySymbolBase)p1).BackingField; - Assert.Equal(isStatic ? "System.Int32 I1.k__BackingField" : null, field1?.ToTestDisplayString()); + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual); @@ -3511,26 +3519,34 @@ class Test1 : I1 targetFramework: TargetFramework.Net60); Assert.True(compilation1.Assembly.RuntimeSupportsDefaultInterfaceImplementation); - if (isStatic && useCSharp13) + switch (isStatic, useCSharp13) { - compilation1.VerifyDiagnostics( - // (4,24): error CS8652: The feature 'field keyword' is currently in Preview and *unsupported*. To use Preview features, use the 'preview' language version. - // static virtual int P1 - Diagnostic(ErrorCode.ERR_FeatureInPreview, "P1").WithArguments("field keyword").WithLocation(4, 24)); - } - else if (isStatic) - { - compilation1.VerifyDiagnostics(); - } - else - { - // 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) - ); + 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"); @@ -3540,7 +3556,7 @@ class Test1 : I1 Assert.False(p1.IsWriteOnly); var field1 = ((SourcePropertySymbolBase)p1).BackingField; - Assert.Equal(isStatic ? "System.Int32 I1.k__BackingField" : null, field1?.ToTestDisplayString()); + Assert.Equal("System.Int32 I1.k__BackingField", field1?.ToTestDisplayString()); Assert.False(p1.IsAbstract); Assert.True(p1.IsVirtual);