Skip to content
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

suggest ! instead of erroneous not on if/while block parse failure #48858

Closed
wants to merge 1 commit into from

Conversation

zackmdavis
Copy link
Member

not

Impressing confused Python users with magical diagnostics is probably
worth this very slight (only 25ish lines) extra complexity in the
parser?

FIXME comments have been left to note that the formatting after
autofixing the suggestion (with rustfix or an RLS-powered IDE) won't
be optimal. (It doesn't look like any of the existing CodeMap methods
make it easy to adjust the span.)

Unfortunately, the parser doesn't seem smart enough to know which of the "try placing this code inside a block" and ! suggestions is correct. (Also, the function that sets the "inside a block" suggestion doesn't and shouldn't know about the condition, so canceling that suggestion if the ! suggestion is issued would require a new clear-suggestions method on Diagnostic/DiagnosticBuilder, which I didn't want to write until we're convinced it's necessary.) The last function in the new UI test demonstrates a case in which the "block" suggestion is right and the ! suggestion is wrong.

Resolves #46836.

r? @estebank

Impressing confused Python users with magical diagnostics is probably
worth this very slight (only 25ish lines) extra complexity in the
parser?

FIXME comments have been left to note that the formatting after
autofixing the suggestion (with `rustfix` or an RLS-powered IDE) won't
be optimal. (It doesn't look like any of the existing `CodeMap` methods
make it easy to adjust the span.)

Resolves rust-lang#46836.
@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 9, 2018
@petrochenkov petrochenkov self-assigned this Mar 9, 2018
Copy link
Contributor

@estebank estebank left a comment

Choose a reason for hiding this comment

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

LGTM, minor suggestions.

LL | | println!("pass");
LL | | //~^ ERROR expected one of
LL | | }
| |_____- help: try placing this code inside a block: `{ department{println,}; }`
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you also modify parse_block so that this suggestion is not given if the next token is an open brace, like in these cases? The suggestion as is is more likely to be wrong than right.

.map_err(|mut err| {
if self.is_identifier_not(&cond) {
let msg = "try replacing identifier `not` with the negation operator";
// FIXME: replaced span doesn't include trailing whitespace
Copy link
Contributor

Choose a reason for hiding this comment

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

You could merge the cond.span with the next span, get the snippet and synthesize a span that does include the whitespace (in a similar way as suggested in #47574 (comment)).

@@ -3232,6 +3247,11 @@ impl<'a> Parser<'a> {
}
let not_block = self.token != token::OpenDelim(token::Brace);
let thn = self.parse_block().map_err(|mut err| {
if self.is_identifier_not(&cond) {
err.span_suggestion(cond.span, // FIXME: replaced span doesn't incl. whitespace
Copy link
Contributor

Choose a reason for hiding this comment

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

re: span, same as below

@bjorn3
Copy link
Member

bjorn3 commented Mar 14, 2018

Could you please place the try replacing identifier `not` with the negation operator help message first, as I think it is the most likely solution when you use not.

@estebank estebank added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Mar 16, 2018
@petrochenkov
Copy link
Contributor

This is a quite limited ad hoc way to check for not used as !.
I'd expect it to work as normal parsing would work.

  • You start parsing an expression (priority is important, should be the same as for ! EXPR, i.e. parse_prefix_expr).
  • If the current token is not and ...
  • The next token is not in the "can continue expression after ident" set (you can look through enum Token and make this set, or I can do it tomorrow), then ...
  • Report a non-fatal error, recover not as ! and continue parsing.

This would cover tests from this PR and more.

@@ -3232,6 +3247,11 @@ impl<'a> Parser<'a> {
}
let not_block = self.token != token::OpenDelim(token::Brace);
let thn = self.parse_block().map_err(|mut err| {
if self.is_identifier_not(&cond) {
err.span_suggestion(cond.span, // FIXME: replaced span doesn't incl. whitespace
"try replacing identifier `not` with the negation operator",
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks a bit too wordy for a label, I'd personally use something "try replacing not with !", but as a minimum the word "identifier" can be removed here.

Copy link
Member Author

Choose a reason for hiding this comment

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

The motivation was to emphasize that not has no special meaning in Rust-the-language (it's just another ordinary variable/identifier), even if rustc-the-compiler is (after this PR) nice enough to try to figure out notice what you meant, but I agree that ceteris paribus, longer messages are worse.

@petrochenkov
Copy link
Contributor

The next token is not in the "can continue expression after ident" set

So, the set (already inverted) is Pound | Ident(..) | Literal(..) | NtIdent(..) | NtExpr(..) | NtBlock(..) | NtPath(..) (this is a subset of fn can_begin_expr).

@zackmdavis
Copy link
Member Author

This is a quite limited ad hoc way to check for not used as !. I'd expect it to work as normal parsing would work.

Yeah, that's a good point.

You start parsing an expression [...] So, the set (already inverted) is

Thanks for the pointer! I can probably look into doing it this way later today or tomorrow.

@zackmdavis
Copy link
Member Author

I've made some exciting progress on the parse-prefix approach, but in addition to producing awesome diagnostics, my code also misparses some legitimate uses of not as a variable name. 😞

I'm pretty busy with a new dayjob lately, so I likely won't be able to look at this again for a week.

@petrochenkov
Copy link
Contributor

@zackmdavis

also misparses some legitimate uses of not as a variable name

For example?
I certainly could miss something when making that token list.
Maybe something in macros?

@zackmdavis
Copy link
Member Author

@petrochenkov

For example?

zmd@ReflectiveCoherence:~/Code/rust$ cat correct.rs 
fn main() {
    let not = 2;
    let _really = not;
}
zmd@ReflectiveCoherence:~/Code/rust$ rustc correct.rs # sane rustc
zmd@ReflectiveCoherence:~/Code/rust$ echo $?
0
zmd@ReflectiveCoherence:~/Code/rust$ rustc +stage1 correct.rs # my delusional work-in-progress build
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, or an operator, found `}`
 --> correct.rs:4:1
  |
3 |     let _really = not;
  |                      - expected one of 7 possible tokens here
4 | }
  | ^ unexpected token

error: aborting due to previous error

I certainly could miss something when making that token list.

I think it's more likely to be an off-by-one-token error in my recovery logic, an error which we can pray will be revealed in the cold light of info! statements and careful thought. (But not tonight.)

(This commit is only using ident/literal/pound as the can't-continue-expression-after-ident set, because of the conjunction of my not feeling confident that I understand how the Nts work, and their being a different enum which would require another match and the fact that leaving tokens out of the set should only decrease the number of situations in which we recover without affecting the correctness of the cases where we do try to recover.)

@petrochenkov
Copy link
Contributor

an off-by-one-token error

Ah, of course, the next token lookup is done with look_ahead, not next_tok.

@zackmdavis
Copy link
Member Author

continued: #49258

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

better syntax error for "not <condition>" instead "!<condition>"
5 participants