From de052c826da486b1b3d8ba99a09167aee7c5997c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:17:34 +0000 Subject: [PATCH 01/11] Initial plan From a498773eff3fb5e640fb72577867dcf0f3ea0744 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:47:37 +0000 Subject: [PATCH 02/11] Implement error recovery for property declarations with missing identifiers Add error recovery in TryParseIndexerOrPropertyDeclaration to handle cases where a property has a missing identifier but the accessor list or expression body is present. When we detect a property body start token ('{' or '=>') but no identifier, we synthesize a missing identifier and continue parsing as a property rather than creating an incomplete member. This improves parser recovery when users forget to add a property name, allowing the rest of the file to parse correctly instead of cascading errors. Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../CSharp/Portable/Parser/LanguageParser.cs | 25 ++- .../Parsing/MemberDeclarationParsingTests.cs | 207 ++++++++++++++++++ 2 files changed, 229 insertions(+), 3 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 735d94e43cf1f..ee0dfe059d07c 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -2811,7 +2811,9 @@ private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind goto parse_member_name; } - Debug.Assert(identifierOrThisOpt != null); + // identifierOrThisOpt may be null if we're doing error recovery for a property with missing identifier + Debug.Assert(identifierOrThisOpt != null || IsStartOfPropertyBody(this.CurrentToken.Kind) || + (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))); if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result)) { @@ -2988,6 +2990,15 @@ private bool IsNoneOrIncompleteMember(SyntaxKind parentKind, SyntaxList`), this is likely a property + // with a missing identifier, not an incomplete member. Let the property parsing logic handle it. + if (IsStartOfPropertyBody(this.CurrentToken.Kind) || + (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))) + { + result = null; + return false; + } + if (attributes.Count == 0 && modifiers.Count == 0 && type.IsMissing && type.Kind != SyntaxKind.RefType) { // we haven't advanced, the caller needs to consume the tokens ahead @@ -3054,7 +3065,7 @@ private bool TryParseIndexerOrPropertyDeclaration(SyntaxList 42; + public Value => null; + } + """; + + UsingTree(source, + // (4,18): error CS1001: Identifier expected + // public Value => null; + Diagnostic(ErrorCode.ERR_IdentifierExpected, "=>").WithLocation(4, 18) + ); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "C"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.IdentifierToken, "Prop"); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "42"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Value"); + } + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NullLiteralExpression); + { + N(SyntaxKind.NullKeyword); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + #endregion } } From 39495f9dc3b90e54332e00819c22121d3bdae7e8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:53:16 +0000 Subject: [PATCH 03/11] Update tests for improved error recovery behavior Update RequiredModifierProperty tests to reflect the improved error recovery. With the new parser changes, properties with missing identifiers now produce clearer ERR_IdentifierExpected errors instead of generic ERR_InvalidMemberDecl errors, which is better for users. Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../Parsing/MemberDeclarationParsingTests.cs | 61 +++++++++++++------ 1 file changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index f3e98d44c2156..4faa4886f386c 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -1224,20 +1224,28 @@ public void RequiredModifierProperty_02(CSharpParseOptions parseOptions) public void RequiredModifierProperty_03() { UsingDeclaration("required Prop { get; }", options: RequiredMembersOptions, - // (1,1): error CS1073: Unexpected token '{' + // (1,15): error CS1001: Identifier expected // required Prop { get; } - Diagnostic(ErrorCode.ERR_UnexpectedToken, "required Prop").WithArguments("{").WithLocation(1, 1), - // (1,15): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration - // required Prop { get; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(1, 15) + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 15) ); - N(SyntaxKind.IncompleteMember); + N(SyntaxKind.PropertyDeclaration); { N(SyntaxKind.RequiredKeyword); N(SyntaxKind.IdentifierName); { N(SyntaxKind.IdentifierToken, "Prop"); } + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } } EOF(); } @@ -1272,17 +1280,28 @@ public void RequiredModifierProperty_04(CSharpParseOptions parseOptions) public void RequiredModifierProperty_05() { UsingDeclaration("required required { get; }", options: RequiredMembersOptions, - // (1,1): error CS1073: Unexpected token '{' + // (1,19): error CS1031: Type expected // required required { get; } - Diagnostic(ErrorCode.ERR_UnexpectedToken, "required required").WithArguments("{").WithLocation(1, 1), - // (1,19): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_TypeExpected, "{").WithLocation(1, 19), + // (1,19): error CS1001: Identifier expected // required required { get; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(1, 19) + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 19) ); - N(SyntaxKind.IncompleteMember); + N(SyntaxKind.PropertyDeclaration); { N(SyntaxKind.RequiredKeyword); N(SyntaxKind.RequiredKeyword); + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } } EOF(); } @@ -1291,14 +1310,11 @@ public void RequiredModifierProperty_05() public void RequiredModifierProperty_06() { UsingDeclaration("required required Prop { get; }", options: RequiredMembersOptions, - // (1,1): error CS1073: Unexpected token '{' - // required required Prop { get; } - Diagnostic(ErrorCode.ERR_UnexpectedToken, "required required Prop").WithArguments("{").WithLocation(1, 1), - // (1,24): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration + // (1,24): error CS1001: Identifier expected // required required Prop { get; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(1, 24) + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 24) ); - N(SyntaxKind.IncompleteMember); + N(SyntaxKind.PropertyDeclaration); { N(SyntaxKind.RequiredKeyword); N(SyntaxKind.RequiredKeyword); @@ -1306,6 +1322,17 @@ public void RequiredModifierProperty_06() { N(SyntaxKind.IdentifierToken, "Prop"); } + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.AccessorList); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } } EOF(); } From b15760685be93e8baaf780e388c8c9ce0fe1bd93 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:59:05 +0000 Subject: [PATCH 04/11] Add comment to main test validating all members parse as properties Add clarifying comment to PropertyWithMissingIdentifier_WithAccessors test to highlight that the key validation is that all members were parsed as properties (not incomplete members), which confirms the error recovery is working correctly. Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index 4faa4886f386c..1383ba21f046b 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -20782,6 +20782,7 @@ public class Stuff Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(6, 27) ); + // Validate all members were parsed as properties (not incomplete members) N(SyntaxKind.CompilationUnit); { N(SyntaxKind.ClassDeclaration); From 648c53901d34bda06be4b904d86b4d3859a2a69f Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 14:57:37 -0800 Subject: [PATCH 05/11] Refine --- .../CSharp/Portable/Parser/LanguageParser.cs | 112 +++++++++++++----- .../Syntax/LexicalAndXml/LexicalErrorTests.cs | 30 +---- .../Syntax/Parsing/DeclarationParsingTests.cs | 63 +++++++++- .../Parsing/LambdaParameterParsingTests.cs | 30 ++++- .../Parsing/MemberDeclarationParsingTests.cs | 8 +- .../Test/Syntax/Parsing/RefReadonlyTests.cs | 30 ++--- 6 files changed, 192 insertions(+), 81 deletions(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index ee0dfe059d07c..637d1026dfa79 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -15,6 +15,7 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax { + using System.Security.Cryptography.X509Certificates; using Microsoft.CodeAnalysis.Syntax.InternalSyntax; internal sealed partial class LanguageParser : SyntaxParser @@ -2812,9 +2813,7 @@ private MemberDeclarationSyntax ParseMemberDeclarationOrStatementCore(SyntaxKind } // identifierOrThisOpt may be null if we're doing error recovery for a property with missing identifier - Debug.Assert(identifierOrThisOpt != null || IsStartOfPropertyBody(this.CurrentToken.Kind) || - (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))); - + Debug.Assert(identifierOrThisOpt != null || IsStartOfPropertyBody(index: 0)); if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result)) { return result; @@ -2984,16 +2983,19 @@ this.CurrentToken.ContextualKind is not (SyntaxKind.PartialKeyword or SyntaxKind return false; } - private bool IsNoneOrIncompleteMember(SyntaxKind parentKind, SyntaxList attributes, SyntaxListBuilder modifiers, TypeSyntax type, - ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, SyntaxToken identifierOrThisOpt, TypeParameterListSyntax typeParameterListOpt, - out MemberDeclarationSyntax result) + private bool IsNoneOrIncompleteMember( + SyntaxKind parentKind, + SyntaxList attributes, + SyntaxListBuilder modifiers, + TypeSyntax type, + ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, + SyntaxToken identifierOrThisOpt, + TypeParameterListSyntax typeParameterListOpt, + out MemberDeclarationSyntax result) { if (explicitInterfaceOpt == null && identifierOrThisOpt == null && typeParameterListOpt == null) { - // Error recovery: if we see a property body (`{` or `=>`), this is likely a property - // with a missing identifier, not an incomplete member. Let the property parsing logic handle it. - if (IsStartOfPropertyBody(this.CurrentToken.Kind) || - (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))) + if (looksLikeStartOfPropertyBody()) { result = null; return false; @@ -3036,6 +3038,43 @@ private bool IsNoneOrIncompleteMember(SyntaxKind parentKind, SyntaxList` + // tokens would lead us to think we're a property in inappropriate places. + if (modifiers.Count == 0 && !looksLikePropertyType()) + return false; + + if (!IsStartOfPropertyBody(index: 0)) + return false; + + return true; + } + + bool looksLikePropertyType() + { + var propertyType = type; + + // We really want to see at least `Type {` or `Type =>` to think of this as a property. And we want the + // parser to be confident it got a good return type for the property, not something it was confused + // about and already reported errors on. Otherwise, we can too easily end up in cases where an error is + // next to a `{` and we don't want that to be a property. + if (propertyType.IsMissing || ContainsErrorDiagnostic(propertyType)) + return false; + + if (propertyType is RefTypeSyntax refType) + propertyType = refType.Type; + + // Unlikely cases to actually be a property. Likely some runaway error recovery case. + if (propertyType is IdentifierNameSyntax { Identifier.ValueText: "extension" or "with" }) + return false; + + // Can add more cases here if we find ourselves being too eager. + + return true; + } } private bool ReconsideredTypeAsAsyncModifier(ref SyntaxListBuilder modifiers, ref TypeSyntax type, ref ResetPoint afterTypeResetPoint, @@ -3061,11 +3100,16 @@ private bool ReconsideredTypeAsAsyncModifier(ref SyntaxListBuilder modifiers, re return false; } - private bool TryParseIndexerOrPropertyDeclaration(SyntaxList attributes, SyntaxListBuilder modifiers, TypeSyntax type, - ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, SyntaxToken identifierOrThisOpt, - TypeParameterListSyntax typeParameterListOpt, out MemberDeclarationSyntax result) + private bool TryParseIndexerOrPropertyDeclaration( + SyntaxList attributes, + SyntaxListBuilder modifiers, + TypeSyntax type, + ExplicitInterfaceSpecifierSyntax explicitInterfaceOpt, + SyntaxToken identifierOrThisOpt, + TypeParameterListSyntax typeParameterListOpt, + out MemberDeclarationSyntax result) { - if (identifierOrThisOpt != null && identifierOrThisOpt.Kind == SyntaxKind.ThisKeyword) + if (identifierOrThisOpt?.Kind == SyntaxKind.ThisKeyword) { result = this.ParseIndexerDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); return true; @@ -3073,14 +3117,10 @@ private bool TryParseIndexerOrPropertyDeclaration(SyntaxList` definitely start a property. Also allow // `; {` and `; =>` as error recovery for a misplaced semicolon. - if (IsStartOfPropertyBody(this.CurrentToken.Kind) || - (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))) + if (IsStartOfPropertyBody(index: 0)) { // Error recovery: if we see a property body but no identifier, synthesize a missing identifier - if (identifierOrThisOpt == null) - { - identifierOrThisOpt = this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected); - } + identifierOrThisOpt ??= this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected); result = this.ParsePropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); return true; @@ -3090,8 +3130,24 @@ private bool TryParseIndexerOrPropertyDeclaration(SyntaxList kind is SyntaxKind.OpenBraceToken or SyntaxKind.EqualsGreaterThanToken; + private bool IsStartOfPropertyBody(int index) + { + var kind = this.PeekToken(index).Kind; + + // If we have `{` or `=>`, definitely a property body. + if (kind is SyntaxKind.OpenBraceToken or SyntaxKind.EqualsGreaterThanToken) + return true; + + // For error recovery, also consider `; {` and `; =>` as starting a property body. + if (kind is SyntaxKind.SemicolonToken) + { + kind = this.PeekToken(index + 1).Kind; + if (kind is SyntaxKind.OpenBraceToken or SyntaxKind.EqualsGreaterThanToken) + return true; + } + + return false; + } // Returns null if we can't parse anything (even partially). internal MemberDeclarationSyntax ParseMemberDeclaration(SyntaxKind parentKind) @@ -3245,9 +3301,7 @@ private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind } // identifierOrThisOpt may be null if we're doing error recovery for a property with missing identifier - Debug.Assert(identifierOrThisOpt != null || IsStartOfPropertyBody(this.CurrentToken.Kind) || - (this.CurrentToken.Kind is SyntaxKind.SemicolonToken && IsStartOfPropertyBody(this.PeekToken(1).Kind))); - + Debug.Assert(identifierOrThisOpt != null || IsStartOfPropertyBody(index: 0)); if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result)) { return result; @@ -3319,19 +3373,17 @@ private bool IsFieldDeclaration(bool isEvent, bool isGlobalScriptLevel) // b) generic // c) a property // d) a method (unless we already know we're parsing an event) - var kind = this.PeekToken(1).Kind; // Error recovery, don't allow a misplaced semicolon after the name in a property to throw off the entire parse. // // e.g. `public int MyProperty; { get; set; }` should still be parsed as a property with a skipped token. if (!isGlobalScriptLevel && - kind == SyntaxKind.SemicolonToken && - IsStartOfPropertyBody(this.PeekToken(2).Kind)) + IsStartOfPropertyBody(index: 1)) { return false; } - switch (kind) + switch (this.PeekToken(1).Kind) { case SyntaxKind.DotToken: // Goo. explicit case SyntaxKind.ColonColonToken: // Goo:: explicit @@ -4150,7 +4202,7 @@ private PropertyDeclarationSyntax ParsePropertyDeclaration( } // We know we are parsing a property because we have seen either an open brace or an arrow token - Debug.Assert(IsStartOfPropertyBody(this.CurrentToken.Kind)); + Debug.Assert(IsStartOfPropertyBody(index: 0)); var accessorList = this.CurrentToken.Kind == SyntaxKind.OpenBraceToken ? this.ParseAccessorList(AccessorDeclaringKind.Property) diff --git a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs index 497b53202caa3..4d33580e69ef0 100644 --- a/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/LexicalAndXml/LexicalErrorTests.cs @@ -172,15 +172,9 @@ public class \e // (3,18): error CS1056: Unexpected character '\' // public class \e Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "").WithArguments("\\").WithLocation(3, 18), - // (3,19): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // (3,20): error CS1001: Identifier expected // public class \e - Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "e").WithLocation(3, 19), - // (4,5): error CS1022: Type or namespace definition, or end-of-file expected - // { - Diagnostic(ErrorCode.ERR_EOFExpected, "{").WithLocation(4, 5), - // (6,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(6, 1)); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(3, 20)); } [Fact] @@ -205,15 +199,9 @@ public class X\e // (3,19): error CS1056: Unexpected character '\' // public class X\e Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "").WithArguments("\\").WithLocation(3, 19), - // (3,20): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // (3,21): error CS1001: Identifier expected // public class X\e - Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "e").WithLocation(3, 20), - // (4,5): error CS1022: Type or namespace definition, or end-of-file expected - // { - Diagnostic(ErrorCode.ERR_EOFExpected, "{").WithLocation(4, 5), - // (6,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(6, 1)); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(3, 21)); } [Fact] @@ -241,15 +229,9 @@ public class \eX // (3,18): error CS1056: Unexpected character '\' // public class \eX Diagnostic(ErrorCode.ERR_UnexpectedCharacter, "").WithArguments("\\").WithLocation(3, 18), - // (3,19): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // (3,21): error CS1001: Identifier expected // public class \eX - Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "eX").WithLocation(3, 19), - // (4,5): error CS1022: Type or namespace definition, or end-of-file expected - // { - Diagnostic(ErrorCode.ERR_EOFExpected, "{").WithLocation(4, 5), - // (6,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(6, 1)); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(3, 21)); } [Fact] diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs index 34ae48481fda2..9798c20d17623 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/DeclarationParsingTests.cs @@ -2974,8 +2974,67 @@ public void TestStructBadExpressionProperty() Assert.Equal(3, file.Errors().Length); Assert.Equal(ErrorCode.ERR_SemicolonExpected, (ErrorCode)file.Errors()[0].Code); - Assert.Equal(ErrorCode.ERR_InvalidMemberDecl, (ErrorCode)file.Errors()[1].Code); - Assert.Equal(ErrorCode.ERR_InvalidMemberDecl, (ErrorCode)file.Errors()[2].Code); + Assert.Equal(ErrorCode.ERR_TypeExpected, (ErrorCode)file.Errors()[1].Code); + Assert.Equal(ErrorCode.ERR_IdentifierExpected, (ErrorCode)file.Errors()[2].Code); + + UsingTree(text, + // (3,18): error CS1002: ; expected + // public int P readonly => 0; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "readonly").WithLocation(3, 18), + // (3,27): error CS1031: Type expected + // public int P readonly => 0; + Diagnostic(ErrorCode.ERR_TypeExpected, "=>").WithLocation(3, 27), + // (3,27): error CS1001: Identifier expected + // public int P readonly => 0; + Diagnostic(ErrorCode.ERR_IdentifierExpected, "=>").WithLocation(3, 27)); + + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.StructDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.StructKeyword); + N(SyntaxKind.IdentifierToken, "S"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.FieldDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.VariableDeclaration); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.IntKeyword); + } + N(SyntaxKind.VariableDeclarator); + { + N(SyntaxKind.IdentifierToken, "P"); + } + } + M(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.ReadOnlyKeyword); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + N(SyntaxKind.NumericLiteralExpression); + { + N(SyntaxKind.NumericLiteralToken, "0"); + } + } + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); } [Fact] diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs index 4416e74463d73..3d4af50a8dab1 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/LambdaParameterParsingTests.cs @@ -5469,12 +5469,18 @@ public void KeywordParameterName_12() // (1,20): error CS1002: ; expected // Action a = public => { }; Diagnostic(ErrorCode.ERR_SemicolonExpected, "public").WithLocation(1, 20), - // (1,20): error CS0116: A namespace cannot directly contain members such as fields, methods or statements + // (1,27): error CS1031: Type expected // Action a = public => { }; - Diagnostic(ErrorCode.ERR_NamespaceUnexpected, "public").WithLocation(1, 20), - // (1,27): error CS1022: Type or namespace definition, or end-of-file expected + Diagnostic(ErrorCode.ERR_TypeExpected, "=>").WithLocation(1, 27), + // (1,27): error CS1001: Identifier expected // Action a = public => { }; - Diagnostic(ErrorCode.ERR_EOFExpected, "=>").WithLocation(1, 27)); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "=>").WithLocation(1, 27), + // (1,30): error CS1525: Invalid expression term '{' + // Action a = public => { }; + Diagnostic(ErrorCode.ERR_InvalidExprTerm, "{").WithArguments("{").WithLocation(1, 30), + // (1,30): error CS1002: ; expected + // Action a = public => { }; + Diagnostic(ErrorCode.ERR_SemicolonExpected, "{").WithLocation(1, 30)); N(SyntaxKind.CompilationUnit); { @@ -5513,9 +5519,23 @@ public void KeywordParameterName_12() M(SyntaxKind.SemicolonToken); } } - N(SyntaxKind.IncompleteMember); + N(SyntaxKind.PropertyDeclaration); { N(SyntaxKind.PublicKeyword); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + M(SyntaxKind.IdentifierToken); + N(SyntaxKind.ArrowExpressionClause); + { + N(SyntaxKind.EqualsGreaterThanToken); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } + } + M(SyntaxKind.SemicolonToken); } N(SyntaxKind.GlobalStatement); { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index 4faa4886f386c..0d346abfe73a2 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -1285,12 +1285,16 @@ public void RequiredModifierProperty_05() Diagnostic(ErrorCode.ERR_TypeExpected, "{").WithLocation(1, 19), // (1,19): error CS1001: Identifier expected // required required { get; } - Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 19) - ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(1, 19)); + N(SyntaxKind.PropertyDeclaration); { N(SyntaxKind.RequiredKeyword); N(SyntaxKind.RequiredKeyword); + M(SyntaxKind.IdentifierName); + { + M(SyntaxKind.IdentifierToken); + } M(SyntaxKind.IdentifierToken); N(SyntaxKind.AccessorList); { diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyTests.cs index d5c9558277130..6979c47d06efe 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/RefReadonlyTests.cs @@ -134,30 +134,24 @@ static async ref readonly Task M() "; ParseAndValidate(text, TestOptions.Regular9, - // (11,41): error CS1519: Invalid token 'operator' in class, record, struct, or interface member declaration + // (11,41): error CS1519: Invalid token 'operator' in a member declaration // public static ref readonly Program operator +(Program x, Program y) Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(11, 41), - // (11,41): error CS1519: Invalid token 'operator' in class, record, struct, or interface member declaration + // (11,41): error CS1519: Invalid token 'operator' in a member declaration // public static ref readonly Program operator +(Program x, Program y) Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "operator").WithArguments("operator").WithLocation(11, 41), - // (12,5): error CS1519: Invalid token '{' ref readonly class, struct, or interface member declaration - // { - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(12, 5), - // (12,5): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration - // { - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(12, 5), - // (17,5): error CS8803: Top-level statements must precede namespace and type declarations. - // static async ref readonly Task M() - Diagnostic(ErrorCode.ERR_TopLevelStatementAfterNamespaceOrType, @"static async ref readonly Task M() - { - throw null; - }").WithLocation(17, 5), + // (11,74): error CS1001: Identifier expected + // public static ref readonly Program operator +(Program x, Program y) + Diagnostic(ErrorCode.ERR_IdentifierExpected, "").WithLocation(11, 74), + // (13,9): error CS1014: A get or set accessor expected + // throw null; + Diagnostic(ErrorCode.ERR_GetOrSetExpected, "throw").WithLocation(13, 9), + // (13,19): error CS1014: A get or set accessor expected + // throw null; + Diagnostic(ErrorCode.ERR_GetOrSetExpected, ";").WithLocation(13, 19), // (22,25): error CS1031: Type expected // public ref readonly virtual int* P1 => throw null; - Diagnostic(ErrorCode.ERR_TypeExpected, "virtual").WithLocation(22, 25), - // (24,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(24, 1)); + Diagnostic(ErrorCode.ERR_TypeExpected, "virtual").WithLocation(22, 25)); } [Fact] From adff6153daeffcc6ad3508833c107116ad9c3545 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 16:28:05 -0800 Subject: [PATCH 06/11] Update test --- .../Test/Semantic/Semantics/RefFieldTests.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs index 4c999cf04176d..2f5a20c580f7c 100644 --- a/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs +++ b/src/Compilers/CSharp/Test/Semantic/Semantics/RefFieldTests.cs @@ -21994,22 +21994,12 @@ public void RefScoped() // (3,18): error CS1002: ; expected // ref scoped R Property { get => throw null; } Diagnostic(ErrorCode.ERR_SemicolonExpected, "Property").WithLocation(3, 18), - // (3,27): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration + // (3,18): error CS0246: The type or namespace name 'Property' could not be found (are you missing a using directive or an assembly reference?) // ref scoped R Property { get => throw null; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(3, 27), - // (3,27): error CS1519: Invalid token '{' in class, record, struct, or interface member declaration + Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "Property").WithArguments("Property").WithLocation(3, 18), + // (3,27): error CS1001: Identifier expected // ref scoped R Property { get => throw null; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "{").WithArguments("{").WithLocation(3, 27), - // (3,33): error CS1519: Invalid token '=>' in class, record, struct, or interface member declaration - // ref scoped R Property { get => throw null; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "=>").WithArguments("=>").WithLocation(3, 33), - // (3,33): error CS1519: Invalid token '=>' in class, record, struct, or interface member declaration - // ref scoped R Property { get => throw null; } - Diagnostic(ErrorCode.ERR_InvalidMemberDecl, "=>").WithArguments("=>").WithLocation(3, 33), - // (4,1): error CS1022: Type or namespace definition, or end-of-file expected - // } - Diagnostic(ErrorCode.ERR_EOFExpected, "}").WithLocation(4, 1) - ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(3, 27)); source = @"ref struct R From 4eb88818185e569ec6d34d28843f5c8299602e85 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 16:36:57 -0800 Subject: [PATCH 07/11] Refine --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 637d1026dfa79..d701dd3079760 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -3068,7 +3068,7 @@ bool looksLikePropertyType() propertyType = refType.Type; // Unlikely cases to actually be a property. Likely some runaway error recovery case. - if (propertyType is IdentifierNameSyntax { Identifier.ValueText: "extension" or "with" }) + if (propertyType is IdentifierNameSyntax { Identifier.ValueText: "extension" or "with" or "get" or "set" or "add" or "remove" or "init" }) return false; // Can add more cases here if we find ourselves being too eager. From 31418f69ea6a007501c34c2186cfa0d89b14c711 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Mon, 3 Nov 2025 16:37:55 -0800 Subject: [PATCH 08/11] Docs --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index d701dd3079760..d4ad1a9d467f5 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -3067,7 +3067,10 @@ bool looksLikePropertyType() if (propertyType is RefTypeSyntax refType) propertyType = refType.Type; - // Unlikely cases to actually be a property. Likely some runaway error recovery case. + // Unlikely cases to actually be a property. Likely some runaway error recovery case. For example: + // with { } (likely a with expression) + // extension { } (likely an extension block being written) + // get { } (likely an accessor, not a property called 'get'). Same for set/add/remove/init. if (propertyType is IdentifierNameSyntax { Identifier.ValueText: "extension" or "with" or "get" or "set" or "add" or "remove" or "init" }) return false; From d9c69abf7f840c6752dc10ffe4957a5681384fb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 00:46:16 +0000 Subject: [PATCH 09/11] Add WorkItem attributes and fix formatting for new tests - Add [Fact, WorkItem(...)] attributes to PropertyWithMissingIdentifier tests - Move closing parenthesis to end of last Diagnostic line for consistency Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com> --- .../Syntax/Parsing/MemberDeclarationParsingTests.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs index ea62d03538deb..30a625adc65e3 100644 --- a/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs +++ b/src/Compilers/CSharp/Test/Syntax/Parsing/MemberDeclarationParsingTests.cs @@ -20764,7 +20764,7 @@ public void MissingClosingAngleBracket_ConversionOperator02() } } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72354")] public void PropertyWithMissingIdentifier_WithAccessors() { const string source = """ @@ -20783,8 +20783,7 @@ public class Stuff Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(4, 27), // (6,27): error CS1001: Identifier expected // public SecondaryValue { get; set; } - Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(6, 27) - ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(6, 27)); // Validate all members were parsed as properties (not incomplete members) N(SyntaxKind.CompilationUnit); @@ -20904,7 +20903,7 @@ public class Stuff EOF(); } - [Fact] + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72354")] public void PropertyWithMissingIdentifier_ExpressionBody() { const string source = """ @@ -20918,8 +20917,7 @@ public class C UsingTree(source, // (4,18): error CS1001: Identifier expected // public Value => null; - Diagnostic(ErrorCode.ERR_IdentifierExpected, "=>").WithLocation(4, 18) - ); + Diagnostic(ErrorCode.ERR_IdentifierExpected, "=>").WithLocation(4, 18)); N(SyntaxKind.CompilationUnit); { From c38401a31be951d8bad1e978f085af157cef6b6c Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 4 Nov 2025 12:07:43 -0800 Subject: [PATCH 10/11] Restore --- .../CSharpAsAndNullCheckTests_FixAllTests.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Analyzers/CSharp/Tests/UsePatternMatching/CSharpAsAndNullCheckTests_FixAllTests.cs b/src/Analyzers/CSharp/Tests/UsePatternMatching/CSharpAsAndNullCheckTests_FixAllTests.cs index c6e7760b37457..b17b847167ff3 100644 --- a/src/Analyzers/CSharp/Tests/UsePatternMatching/CSharpAsAndNullCheckTests_FixAllTests.cs +++ b/src/Analyzers/CSharp/Tests/UsePatternMatching/CSharpAsAndNullCheckTests_FixAllTests.cs @@ -160,22 +160,22 @@ class Symbol void M(object o, bool b0, bool b1) { - if (o is Symbol symbol) - { - while ((object)symbol != null && b1) + if (o is Symbol symbol) { - symbol = symbol.ContainingSymbol as Symbol; - } + while ((object)symbol != null && b1) + { + symbol = symbol.ContainingSymbol as Symbol; + } - if ((object)symbol == null || b2) - { - throw null; - } + if ((object)symbol == null || b2) + { + throw null; + } - var use = symbol; + var use = symbol; + } } } - } """); [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/26679")] From 3c8097ccb9e2b45c8235bd2769bc727a19a83b91 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Tue, 4 Nov 2025 18:05:48 -0800 Subject: [PATCH 11/11] Update src/Compilers/CSharp/Portable/Parser/LanguageParser.cs Co-authored-by: Rikki Gibson --- src/Compilers/CSharp/Portable/Parser/LanguageParser.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index d4ad1a9d467f5..3ef71c786ddcf 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -15,7 +15,6 @@ namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax { - using System.Security.Cryptography.X509Certificates; using Microsoft.CodeAnalysis.Syntax.InternalSyntax; internal sealed partial class LanguageParser : SyntaxParser