Skip to content

Improve error messages for keywords used as identifiers in LINQ let clauses#80695

Merged
CyrusNajmabadi merged 14 commits intomainfrom
copilot/improve-error-messages-keyword
Oct 31, 2025
Merged

Improve error messages for keywords used as identifiers in LINQ let clauses#80695
CyrusNajmabadi merged 14 commits intomainfrom
copilot/improve-error-messages-keyword

Conversation

Copy link
Contributor

Copilot AI commented Oct 14, 2025

Fix error messages for using a keyword instead of an identifier in LINQ let clauses

Issue Summary

When using a reserved keyword (like params) as an identifier in a LINQ let clause, the compiler previously produced many confusing error messages (8-10 errors). The goal was to:

  1. Change "CS1001: Identifier expected" to "CS1041: Identifier expected; 'params' is a keyword"
  2. Reduce the number of noisy error messages by better error recovery

Implementation

Modified LanguageParser.ParseLetClause() to:

  • Detect when a reserved keyword is followed by = (the expected pattern for let clause) using a conditional expression
  • Use EatTokenAsKind to create a missing identifier token with the keyword attached as skipped syntax
  • This produces the proper ERR_IdentifierExpectedKW error and allows better error recovery

This results in just 1 clear error message instead of 8-10 confusing ones.

Changes Made

  1. src/Compilers/CSharp/Portable/Parser/LanguageParser.cs:
    • Updated ParseLetClause method using conditional (ternary) expression with ? and : on new lines
    • Uses EatTokenAsKind to create missing identifier token with keyword as skipped syntax
    • Fixed blank lines to not contain any whitespace (Roslyn linting requirement)
  2. src/Compilers/CSharp/Test/Syntax/Parsing/ExpressionParsingTests.cs:
    • Added test case LetClauseWithKeywordAsIdentifier
    • Test verifies missing identifier token with keyword in skipped syntax
  3. src/Compilers/CSharp/Test/Semantic/Semantics/QueryTests.cs:
    • Updated QueryWithMultipleParseErrorsAndScriptParseOption test to expect improved parsing
    • The query now parses completely instead of stopping after "let "
  4. .github/copilot-instructions.md:
    • Added note about blank lines not containing whitespace to avoid linting errors
    • Removed redundant formatting instruction

Test Results

  • All 596 expression parsing tests pass
  • All syntax parsing tests pass
  • QueryWithMultipleParseErrorsAndScriptParseOption test updated and passing
  • No regressions in existing tests

Before/After

Before: Using params in a let clause produced 8+ errors including CS1001, CS1003, CS1525, CS0742, etc.

After: Using params in a let clause produces 1 clear CS1041 error: "Identifier expected; 'params' is a keyword"

Original prompt

This section details on the original issue you should resolve

<issue_title>Error messages for using a keyword instead of an identifier could be improved</issue_title>
<issue_description>Consider the following statement with a LINQ query:

var x =
    from m in typeof(Foo).GetMethods()
    let params = m.GetParameters()
    select new { m, params };

This is invalid, because params is a C# keyword. But while this is a seemingly simple error, it causes the compiler to output all of this:

For line 3:

error CS1001: Identifier expected
error CS1003: Syntax error, '=' expected
error CS1525: Invalid expression term 'params'
error CS0742: A query body must end with a select clause or a group clause
error CS1003: Syntax error, ',' expected
error CS1002: ; expected
error CS1525: Invalid expression term '='
error CS1002: ; expected

For line 4:

error CS1002: ; expected
error CS1041: Identifier expected; 'params' is a keyword

Only the last message clearly identifies the actual error.

My requests:

  1. Could the "CS1001: Identifier expected" for line 3 be changed into the more descriptive "CS1041: Identifier expected; 'params' is a keyword"?
  2. It would be nice if using a keyword this way wouldn't cause so many noisy error messages. I realize parsing this requires the compiler to guess how the code should be parsed, but I think the guess is currently not great in this case.
    </issue_description>

<agent_instructions>Copilot see if this is easy to fix. This would be in LanguageParser.ParseLetClause. if we see a keyword, but still parsed followed by an equals sign, then recover well. You can use EatTokenEvenWithIncorrectKind if you detect a keyword as that should produce a good error message.

Test can be added in src\Compilers\CSharp\Test\Syntax\Parsing\ExpressionParsingTests.cs. It should use UsingExpression and should validate the diagnostic, and the parse tree shape.
</agent_instructions>

Comments on the Issue (you are @copilot in this section)

Fixes #11418


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 2 commits October 14, 2025 10:51
Modified ParseLetClause to detect keywords followed by '=' and use
EatTokenEvenWithIncorrectKind to produce ERR_IdentifierExpectedKW error
instead of ERR_IdentifierExpected, reducing cascading errors.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Improved code readability by extracting the condition into a well-named
helper method as suggested in code review.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI changed the title [WIP] Improve error messages for using keywords as identifiers Improve error messages for keywords used as identifiers in LINQ let clauses Oct 14, 2025
Copilot AI requested a review from CyrusNajmabadi October 14, 2025 11:01
this.ParseExpressionCore());
}

private bool IsReservedKeywordFollowedByEquals()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot inline this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit fe41692 (inlined the method directly into the if condition).

Comment on lines +13911 to +13920
SyntaxToken identifier;
if (IsReservedKeywordFollowedByEquals())
{
var keyword = this.EatTokenEvenWithIncorrectKind(SyntaxKind.IdentifierToken);
identifier = ConvertToIdentifier(keyword);
}
else
{
identifier = this.ParseIdentifierToken();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot conver this to a simple conditional expression. placce the ? and : on new lines with the following expresison on the same line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 570ed57 - converted to ternary operator with ? and : on new lines.

Address code review feedback by inlining the helper method directly
into the if condition in ParseLetClause.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
if (IsReservedKeywordFollowedByEquals())
{
var keyword = this.EatTokenEvenWithIncorrectKind(SyntaxKind.IdentifierToken);
identifier = ConvertToIdentifier(keyword);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot instead of this pair, use EatTokenAsKind instead. We want a missing token here with the keyword attached as a skipped token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 570ed57 - now using EatTokenAsKind which creates a missing identifier token with the keyword attached as skipped syntax.

- Converted if/else to ternary operator with ? and : on new lines
- Changed from EatTokenEvenWithIncorrectKind + ConvertToIdentifier
  to EatTokenAsKind for better error recovery
- Updated test to expect missing identifier token with keyword as
  skipped syntax

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review October 14, 2025 11:45
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner October 14, 2025 11:45
{
Debug.Assert(this.CurrentToken.ContextualKind == SyntaxKind.LetKeyword);
var letKeyword = this.EatContextualToken(SyntaxKind.LetKeyword);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot Roslyn disallows blank lines with spaces on them. Please update the copilot contributing document to mention that this should be avoided as it will cause linting errors that will have to be fixed up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in commit 2ce564d. Fixed the blank lines with spaces in ParseLetClause and updated .github/copilot-instructions.md to include a note about this requirement under the Formatting section.

- Removed spaces from blank lines in ParseLetClause (lines 13908, 13914)
- Updated .github/copilot-instructions.md to warn about blank lines
  with whitespace causing linting errors

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copilot AI requested a review from a team as a code owner October 14, 2025 12:31
Copilot AI requested a review from CyrusNajmabadi October 14, 2025 12:32
Co-authored-by: Joey Robichaud <jorobich@microsoft.com>
@CyrusNajmabadi
Copy link
Contributor

@copilot QueryWithMultipleParseErrorsAndScriptParseOption is failing now (since parsing improved in that test case). Can you determine why the test is failing and update the expected results?

Updated the test to expect the complete query expression
'from i in expr1 let namespace = expr1 select i' instead of the
truncated 'from i in expr1 let ' since the parser now recovers
better and continues parsing after encountering the keyword.

Also removed redundant line from copilot-instructions.md formatting section.

Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Oct 14, 2025

@copilot QueryWithMultipleParseErrorsAndScriptParseOption is failing now (since parsing improved in that test case). Can you determine why the test is failing and update the expected results?

Done in commit 9406664. The test was failing because the parser now recovers better - instead of stopping after "let ", it continues parsing to "from i in expr1 let namespace = expr1 select i". Updated the test to expect the complete query expression.

@CyrusNajmabadi
Copy link
Contributor

@dotnet/roslyn-compiler this is ready fro review.

@CyrusNajmabadi
Copy link
Contributor

@dotnet/roslyn-compiler ptal

- `dotnet format whitespace --folder . --include <relative path to file>` - Applies formatting preferences to a particular .cs or .vb file
- When running `dotnet format whitespace` use the `--folder .` option followed by `--include <path to file>` to avoid a design-time build
- Apply formatting preferences to any modified .cs or .vb file
- **Important**: Blank lines must not contain any whitespace characters (spaces or tabs). This will cause linting errors that must be fixed.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is 100% going to come back to bite someone when they're trying to test a line of whitespaces, but I don't see any other options.

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/11418")]
public void LetClauseWithKeywordAsIdentifier()
{
UsingExpression("from m in methods let params = 1 select m",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add one with a contextual keyword, to document behavior?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (squash) October 31, 2025 05:24
@CyrusNajmabadi CyrusNajmabadi merged commit d945942 into main Oct 31, 2025
23 of 24 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Oct 31, 2025
@CyrusNajmabadi CyrusNajmabadi deleted the copilot/improve-error-messages-keyword branch November 1, 2025 00:42
@davidwengier davidwengier modified the milestones: Next, 18.3 Jan 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error messages for using a keyword instead of an identifier could be improved

7 participants