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")] diff --git a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs index 59cee09dddb6c..d66daff2b89c2 100644 --- a/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs +++ b/src/Compilers/CSharp/Portable/Parser/LanguageParser.cs @@ -2811,8 +2811,8 @@ 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(index: 0)); if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result)) { return result; @@ -2982,12 +2982,24 @@ 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) { + if (looksLikeStartOfPropertyBody()) + { + 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 @@ -3025,6 +3037,46 @@ 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. 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; + + // 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, @@ -3050,11 +3102,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.Kind == SyntaxKind.ThisKeyword) + if (identifierOrThisOpt?.Kind == SyntaxKind.ThisKeyword) { result = this.ParseIndexerDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); return true; @@ -3062,9 +3119,11 @@ 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 + identifierOrThisOpt ??= this.AddError(CreateMissingIdentifierToken(), ErrorCode.ERR_IdentifierExpected); + result = this.ParsePropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt); return true; } @@ -3073,8 +3132,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) @@ -3227,8 +3302,8 @@ private MemberDeclarationSyntax ParseMemberDeclarationCore(SyntaxKind parentKind 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(index: 0)); if (TryParseIndexerOrPropertyDeclaration(attributes, modifiers, type, explicitInterfaceOpt, identifierOrThisOpt, typeParameterListOpt, out result)) { return result; @@ -3300,19 +3375,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 @@ -4141,7 +4214,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/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 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 a6bd6583bc722..c1207e35743f8 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 534e1b59cb656..30a625adc65e3 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,32 @@ 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) - ); - N(SyntaxKind.IncompleteMember); + 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); + { + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.GetAccessorDeclaration); + { + N(SyntaxKind.GetKeyword); + N(SyntaxKind.SemicolonToken); + } + N(SyntaxKind.CloseBraceToken); + } } EOF(); } @@ -1291,14 +1314,11 @@ public void RequiredModifierProperty_05() public void RequiredModifierProperty_06() { UsingDeclaration("required required Prop { get; }", options: RequiredMembersOptions, - // (1,1): error CS1073: Unexpected token '{' + // (1,24): error CS1001: Identifier expected // 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 - // 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 +1326,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(); } @@ -20733,6 +20764,212 @@ public void MissingClosingAngleBracket_ConversionOperator02() } } + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72354")] + public void PropertyWithMissingIdentifier_WithAccessors() + { + const string source = """ + public class Stuff + { + public required string Name { get; set; } + public required Value { get; set; } + public string? SecondaryName { get; set; } + public SecondaryValue { get; set; } + } + """; + + UsingTree(source, + // (4,27): error CS1001: Identifier expected + // public required Value { get; set; } + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(4, 27), + // (6,27): error CS1001: Identifier expected + // public SecondaryValue { get; set; } + Diagnostic(ErrorCode.ERR_IdentifierExpected, "{").WithLocation(6, 27)); + + // Validate all members were parsed as properties (not incomplete members) + N(SyntaxKind.CompilationUnit); + { + N(SyntaxKind.ClassDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.ClassKeyword); + N(SyntaxKind.IdentifierToken, "Stuff"); + N(SyntaxKind.OpenBraceToken); + N(SyntaxKind.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.RequiredKeyword); + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.IdentifierToken, "Name"); + 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.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.RequiredKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "Value"); + } + M(SyntaxKind.IdentifierToken); + 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.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.NullableType); + { + N(SyntaxKind.PredefinedType); + { + N(SyntaxKind.StringKeyword); + } + N(SyntaxKind.QuestionToken); + } + N(SyntaxKind.IdentifierToken, "SecondaryName"); + 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.PropertyDeclaration); + { + N(SyntaxKind.PublicKeyword); + N(SyntaxKind.IdentifierName); + { + N(SyntaxKind.IdentifierToken, "SecondaryValue"); + } + M(SyntaxKind.IdentifierToken); + 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.CloseBraceToken); + } + N(SyntaxKind.EndOfFileToken); + } + EOF(); + } + + [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/72354")] + public void PropertyWithMissingIdentifier_ExpressionBody() + { + const string source = """ + public class C + { + public int Prop => 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 } } 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]