Improve error recovery for => in constructor initializers#80764
Improve error recovery for => in constructor initializers#80764CyrusNajmabadi merged 10 commits intomainfrom
Conversation
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
| [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/40879")] | ||
| public void ConstructorInitializerWithArrowInsteadOfColon_Base() | ||
| { | ||
| UsingTree(@" |
There was a problem hiding this comment.
@copilot use raw string literals for these tests. Also, add tests that do things like => this[0]; and this.Goo() and the same for the base case. We want to ensure that we only detect this problem if you have this( and base( and not the start of some legal expression that starts with this/base. If there are other reasonable constructs to test, add those as well.
There was a problem hiding this comment.
Updated tests to use raw string literals and added edge case tests for => this[0], => this.Goo(), => base.Goo(), and => base[0] to ensure these legal expression-bodied constructors are not treated as errors. All tests pass. Changes in commit 0c7c8e6.
…alizers Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
|
@dotnet/roslyn-compiler ptal. |
| } | ||
| private ConstructorInitializerSyntax ParseConstructorInitializer() | ||
| { | ||
| var colon = this.EatTokenAsKind(SyntaxKind.ColonToken); |
There was a problem hiding this comment.
There are several other callers of ParseConstructorInitializer. Should all of them be consuming the current token as a : for error recovery?
There was a problem hiding this comment.
i'm fine not touching the other caller. I think this is an appropriate fix to address the case of someone wanting ot write the constructor initializer, because they know they're in a constructor, but misuse the wrong leading cahr.
Note: the reason for detecting the this/base initialier on a method is to have better error recovery around when constructors are accidentally not written as constructors (like accidetnally writing 'void'). The check for the initializer means we don't go off the rails there.
I don't think we to both be resilient to having an errant return type and a this/base written with =>.
There was a problem hiding this comment.
I'm more concerned about the behavior change here. EatToken(kind) does not consume the current token if it doesn't match, and EatTokenAsKind does. We should at least investigate potential behavioral changes there.
There was a problem hiding this comment.
Added assert and comment in 930cbd4 to help explain what's going on.
| UsingTree(""" | ||
| class C | ||
| { | ||
| C() => base() { } |
There was a problem hiding this comment.
Let's add some more variations on this. For example, C() => base(1);. Or C() => base(1) => Console.Write(1);.
There was a problem hiding this comment.
And same for the this tests below. Could we factor these two into a theory?
There was a problem hiding this comment.
my preference with theories is they not then contain conditional logic within them to specify the expected outcomes (as i find it hard to mentally then template instantiate each theory out in my head). I'm good with individual tests that overlap by a lot.
…kinfo-for-this-base
|
@jjonescz can you ptal? |
|
Will try to get a review in this afternoon |
| UsingTree(""" | ||
| class C | ||
| { | ||
| C() => base(1) => Console.WriteLine(); |
There was a problem hiding this comment.
Would also be good to see behavior of cases like
C() => base(1)(no body at all)C() => base(1);(semicolon body. This would be an error case for partial constructor definition part, I guess.)
There was a problem hiding this comment.
@copilot can you please add tests for the above two cases?
There was a problem hiding this comment.
Added tests for both cases:
C() => base(1)(no body at all)C() => base(1);(semicolon body)
Also added corresponding tests for this constructor initializers for consistency. All tests pass. Changes in commit 9c8f2a3.
Co-authored-by: CyrusNajmabadi <4564579+CyrusNajmabadi@users.noreply.github.com>
Problem
When users mistakenly write
public MyClass() => base()instead of the correct syntaxpublic MyClass() : base(), the C# compiler produced a cascade of 7+ confusing error messages that didn't clearly indicate the actual problem:Previous error messages:
These errors were confusing because they suggested problems with top-level statements, modifiers, and type declarations, when the actual issue was simply using the wrong token before the constructor initializer.
Solution
Following the pattern established in PR #80695 for similar error recovery scenarios, the parser now:
=>followed bybase(orthis((with open parenthesis)EatTokenAsKindto consume the=>token as if it were a:, attaching it as skipped syntax=> this.Goo(),=> this[0],=> base.Goo(), and=> base[0]are parsed correctly as expression bodiesChanges
Modified
ParseConstructorDeclarationinLanguageParser.cs:=> base(or=> this((specifically with open parenthesis)EatTokenAsKind(SyntaxKind.ColonToken)to consume the errant=>with proper error reportingAdded comprehensive tests in
DeclarationParsingTests.cs:base()andthis():C() => base()andC() => this()C() => base(1) { }andC() => this(1) { }C() => base(1)andC() => this(1)C() => base(1);andC() => this(1);C() => base(1) => Console.WriteLine();andC() => this(1) => Console.WriteLine();C() => this[0] = 1;(indexer access)C() => this.Goo();(member access)C() => base.Goo();(base member access)C() => base[0] = 1;(base indexer access)Result
New error message:
This single, clear error message immediately tells the developer exactly what's wrong and where.
Visual Demonstration
Testing
base(andthis(trigger error recovery, notbase.,this.,base[, orthis[Fixes #40879
Original prompt
Fixes #40879
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.