-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix 'else without if' parsing - better error msg #28115
Fix 'else without if' parsing - better error msg #28115
Conversation
// Semantic model has a special case here that we match: if the underlying syntax is missing, don't create a conversion expression, | ||
// and instead directly return the operand, which will be a BoundBadExpression. When we generate a node for the BoundBadExpression, | ||
// the resulting IOperation will also have a null Type. | ||
Debug.Assert(boundOperand.Kind == BoundKind.BadExpression || |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need your help, please.
Trying to copy the idea from 'try-catch-finally' parsing I created missing if (true) {}
for existing else
clause. Then, I got the runtime error of failing an assert.
Could you suggest what I should do with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which test failed with an assert?
From the CI log, I see only one test that failed (IIfstatementWithStatementMissing
). I think that test expected some parsing diagnostics which just need to be updated.
In reply to: 198106984 [](ancestors = 198106984)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was live debugging session in VS. Repro code as in the image attached to the description of this PR.
Anyway, I'll try what @CyrusNajmabadi suggests first.
SyntaxToken.CreateMissing(SyntaxKind.OpenBraceToken, null, null), | ||
default, | ||
SyntaxToken.CreateMissing(SyntaxKind.CloseBraceToken, null, null)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add some parsing tests to demonstrate the shape of the parse tree after the change. Look at StatementParsingTests.ParsePrivate
as an example.
Note that the M
method will print out the correct code, but you first have to uncomment #define PARSING_TESTS_DUMP
in src\Compilers\CSharp\Test\Syntax\Parsing\ParsingTests.cs
. #Closed
@jcouv Can you help shephard this in? #Closed |
@CyrusNajmabadi Yes, that's what I'm doing. #Closed |
elseClause = this.AddErrorToFirstToken(elseClause, ErrorCode.ERR_SyntaxError, SyntaxFacts.GetText(SyntaxKind.IfKeyword)); | ||
|
||
// synthesize missing tokens for "if (true) { }": | ||
@if = SyntaxToken.CreateMissing(SyntaxKind.IfKeyword, null, null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems redundant. You could just always call @if = this.EatToken(SyntaxKind.IfKeyword);
above. It will do the right thing if you are/aren't on an 'if' keyword. This also means you won't need to manually add the 'ERR_SyntaxError' to the 'elseClause'.
You can do the same for all the rest of the required bits of the else. #Closed
statement = _syntaxFactory.Block( | ||
SyntaxToken.CreateMissing(SyntaxKind.OpenBraceToken, null, null), | ||
default, | ||
SyntaxToken.CreateMissing(SyntaxKind.CloseBraceToken, null, null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would not make a block here. Remember that if-statements don't require blocks for the if/else sections. Instead, just call ParseExpressionStatement. Also, add .ElseKeyword to IsInvalidSubExpression. #Closed
openParen = SyntaxToken.CreateMissing(SyntaxKind.OpenParenToken, null, null); | ||
condition = this._syntaxFactory.LiteralExpression( | ||
SyntaxKind.TrueLiteralExpression, | ||
SyntaxToken.CreateMissing(SyntaxKind.TrueKeyword, null, null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
don't make a true-expression. just call ParseExpression(). #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, just keep the parsing logic the same as it was before except for the point that you'll call statement = this.ParseEmbeddedStatement();
. There, you want to just check if you're on an 'else' keyword. In that case, you don't want to call ParseEmbeddedStatement (since you now support 'else' starting a statement). so i would expect:
var @if = this.EatToken(SyntaxKind.IfKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var condition = this.ParseExpressionCore();
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
// comment this line appropriately.
var statement = this.CurrentToken.Kind == SyntaxKind.ElseKeyword ? this.ParseExpressionStatement() : this.ParseEmbeddedStatement();
var elseClause = ParseElseClauseOpt();
``` #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks guys! That helped a lot. Although I see it generates many more syntax error entries (like missing (
, )
etc.) but I also see this is a common approach here. At least syntax error, missing if
comes first in such series and therefore should put people on good track of finding the root cause. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yup yup! #Closed
948fe8d
to
e693854
Compare
@dotnet-bot retest windows_debug_vs-integration_prtest please #Closed |
var statement = this.ParseEmbeddedStatement(); | ||
var elseClause = ParseElseClauseOpt(); | ||
|
||
// when 'if' is missing, then we parse an expression statement in a stadard way (which adds missing syntax properly) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
stadard [](start = 80, length = 7)
stadard [](start = 80, length = 7)
Typo: stadard -> standard #Closed
@wachulski could you please add some IOperation tests for similar scenarios to your syntax tests? You'll want to add them in
Feel free to ping me if you run into any issues. I'd guess you're very likely to run into binding issues, and when those are resolved I'd guess you're likely to run into IOperation factory issues :). My gut feeling is that, for this case, IOperation should probably produce an |
N(SyntaxKind.OpenBraceToken); | ||
N(SyntaxKind.CloseBraceToken); | ||
} | ||
N(SyntaxKind.ElseClause); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ElseClause [](start = 29, length = 10)
The second else { }
isn't captured, since this code parses as two statement. You may need to wrap the whole thing in a block { ... }
or use UsingTree
instead of UsingStatement
.
@wachulski Aside from Fred's suggestion, I have a small test nit. Otherwise things look good. Thanks I'm going to retarget this PR to the features/compiler branch, because master is currently meant for 15.8 which is close to shipping (so the bug bar is high). #Closed |
e693854
to
f605c4d
Compare
[Fact] | ||
public void ParseElseKeywordPlacedAsIfEmbeddedStatement() | ||
{ | ||
UsingStatement("if (a) else {}", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we have an "else { } else { }" as well? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is ParseSubsequentElseWithoutPrecedingIfStatement
already with its UsingStatement("{ if (a) { } else { } else { } }"
. Do you mean UsingStatement("{ if (a) else { } else { } }"
? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope. just else { } else { }
:) #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you go: b17e8b7 #Closed
f605c4d
to
1ef941a
Compare
I've been wondering whether IOperation tests that output sth like |
@wachulski if I understand your question correctly, yes it's expected that some of the nodes in the new |
/*</bind>*/ } | ||
} | ||
"; | ||
string expectedOperationTree = @" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
expectedOperationTree [](start = 19, length = 21)
Why aren't the second else
and its block appearing in the output? #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to bind 2 operation statements. In 3016d83 I wrapped them in a block statement. #Closed
} | ||
else | ||
{ | ||
Op(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Op(); [](start = 12, length = 5)
I would have expected the invocation of Op()
to appear in the output. Do you know why it's missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (iteration 2).
@wachulski I didn't hear back from you on a couple of remaining questions. Thanks #Closed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 3)
I'll let @AlekseyTs and @333fred comment on whether this warrants some control-flow graph (CFG) tests too.
@wachulski sorry for the delay here. I meant to take a look a month ago, and it slipped off my radar. I'm looking now. #Closed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 3). @CyrusNajmabadi, did you have any other comments?
(Reopened to make sure that status checks were up to date) #Closed |
@wachulski, can you please retarget this to master? #Closed |
@@ -6613,6 +6613,7 @@ private StatementSyntax ParseStatementNoDeclaration(bool allowAnyExpression) | |||
case SyntaxKind.GotoKeyword: | |||
return this.ParseGotoStatement(); | |||
case SyntaxKind.IfKeyword: | |||
case SyntaxKind.ElseKeyword: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should probably be a comment here explaining that this is to handle error cases. #Closed
|
||
// when 'if' is missing, then we parse an expression statement in a standard way (which adds missing syntax properly) | ||
// parsing embedded one expects non-null statement to be present | ||
var statement = this.CurrentToken.Kind == SyntaxKind.ElseKeyword ? this.ParseExpressionStatement() : this.ParseEmbeddedStatement(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this seems to be strange (and indicates to me a change in behavior. specifically if you had if (a) else
. Was it intentional? I think what you were trying to do was something more like:
var firstTokenIsElse = this.currentToken == .ElseKeyword;
var @if = this.EatToken(SyntaxKind.IfKeyword);
var openParen = this.EatToken(SyntaxKind.OpenParenToken);
var condition = this.ParseExpressionCore();
var closeParen = this.EatToken(SyntaxKind.CloseParenToken);
var statement = firstTokenIsElse ? ...
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: if you do want the current form, then the comment needs to be fixed up Because it applies to both when an 'if' is missing, and when tehre is no statement following the close-paren. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I followed your #28115 (comment) but most likely missed the point in the comment. I'll change the code to the what you proposed last. #Closed
I think this needs to be retargeted to |
…out-if-error-message * origin/master: (1712 commits) User-defined operator checks should ignore differences in tuple member names (dotnet#30774) Attempted fix for correctly reporting error CS1069 when using implicit namespaces (dotnet#30244) Invert the binding order of InitializerExpressions and CreationExpressions (dotnet#30805) Use Arcade bootstrapping scripts (dotnet#30498) Ensure that the compilers produce double.NaN values in IEEE canonical form. (dotnet#30587) Remove properties set in BeforeCommonTargets.targets Fix publishing of dependent projects Contributing page: reference Unix build instructions Delete 0 Propagate values from AbstractProject to VisualStudioProjectCreationInfo Fix publishing nuget info of dev16.0.x-vs-deps branch Revert "Add a SetProperty API for CPS to passing msbuild properties" Validate generic arguments in `using static` directives (dotnet#30737) Correct 15.9 publish data Enable test. Do not inject attribute types into .Net modules. Add a SetProperty API for CPS to passing msbuild properties Revert "add beta2 suffix to dev16 branch" Fix references Remove commit sha from package versions ...
@gafter Retargeted #Closed |
It appears there are a couple of failing unit tests. #Closed |
Yeah, Im on it
From: Neal Gafter
Sent: 31 October 2018 22:44
To: dotnet/roslyn
Cc: Marcin Wachulski; Mention
Subject: Re: [dotnet/roslyn] Fix 'else without if' parsing - better error msg(#28115)
It appears there are a couple of failing unit tests.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.
#Closed
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Failing tests should be fixed now. I'm worried about the output for one edge case, details below.
@@ -3021,6 +3021,18 @@ public void ParseSubsequentElseWithoutPrecedingIfStatement() | |||
public void ParseElseKeywordPlacedAsIfEmbeddedStatement() | |||
{ | |||
UsingStatement("if (a) else {}", | |||
// (1,8): error CS1003: Syntax error, 'if' expected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO interesting (potentially misleading). I understand the original issue was about lacking missing if
indication for misplaced else
s. This one however for "if (a) else {}"
outputs // (1,8): error CS1003: Syntax error, 'if' expected
as the first diagnostic. Isn't this sth unwanted?
This was added by following the last suggestion by @CyrusNajmabadi #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could customize the error message here. EatToken takes an optional error message. If we see an 'else' we could just call EatToken(SyntaxKind.IfToken, specialId);
where 'specialId' is the resource id for "'else' unexpected" or something like that. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or: "'else' cannot start a statement"
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I'll add the message #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is wrong with this error message? It sounds pretty clear to me. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, the example is if (a) else { }
? That is indeed odd (even though it makes sense). I would expect something like "missing statement". #Closed
…out-if-error-message * origin/master: (174 commits) Add Compilers filter for Roslyn (dotnet#30880) Remove NonNullTypes context and other unnecessary information stored in TypeSymbolWithAnnotations. (dotnet#30913) Update BoundCall method based on receiver nullability (dotnet#31000) Make nullabiulity inference associative and commutative (dotnet#30990) Async-streams: minimal test for IOperation and CFG. Improve diagnostics (dotnet#30363) Add src. Add test. Remove dead code Update the build status table Script for generating our build status tables Add parsing tests to compiler benchmarks (dotnet#31033) Fix Edit and Continue in CPS projects Add comment Sorting. Save work Address PR feedback only produce optprof data on an official build Add bunch of exclusions to dead code analysis for special methods Disable WinRT tests on Linux (dotnet#31026) Change prerelease version to beta2 (dotnet#31017) ... # Conflicts: # src/Compilers/CSharp/Portable/CSharpResources.resx # src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Ok, I changed the message. Now it's 'else' cannot start a statement. I'm still not convinced (i.e. maybe the previous version 'if' expected was actually better), as the original issue was about this: while(true)
{
if (false)
{
}
}
else {
} Expected Behavior: What's your thought? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 8)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 8)
Merged. Thanks @wachulski |
Note: this will first show up in dev16 preview3. |
[Note: there are merge commits in this PR, so it should not be squashed. -- jcouv]
Instead of a general syntax error } expected message we want
sth like Syntax error 'if' expected.
Fixes #27866