-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Implement asymmetrical precedence for closures and jumps #134847
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
Conversation
I'm going to look at this in a few hours. |
One note on testing: the best approach I've found in Syn for thoroughly testing the pretty-printing algorithm and parenthesis insertion is the following pair of tests:
For the second test, a major difference between Syn's and rustc's is this: rust/tests/ui-fulldeps/pprust-expr-roundtrip.rs Lines 231 to 232 in ceb0441
Rustc's test just throws up its hands and says "sometimes the printer will just generate invalid syntax". (It is not just the one case mentioned in the comment.) Once the pretty-printer is in better shape, the goal will be to turn this into an unwrap, effectively asserting that the pretty-printer always produces syntactically valid Rust output for any arbitrary syntax tree input that a macro could have generated. That will provide exhaustive coverage of the edge cases. Syn's printer is tested this way. In rustc, for now there are still too many false positives and false negatives to easily make an exhaustive test of the set of expressions that currently happen to work. Fixing false positives (like this PR) is an important prerequisite to being able to fix certain false negatives without indiscriminately inserting way too many parentheses. Meanwhile, PRs like #134661 and #134600 have been fixing false negatives. |
☔ The latest upstream changes (presumably #127541) made this pull request unmergeable. Please resolve the merge conflicts. |
2bd5187
to
7764444
Compare
Rebased to resolve conflict with #127541 in tests/ui/invalid/invalid-rustc_legacy_const_generics-issue-123077.stderr. |
Hi @fmease, could you take another look at this PR when you get a chance? Thanks! |
Rebased to resolve conflicts with: |
☔ The latest upstream changes (presumably #140581) made this pull request unmergeable. Please resolve the merge conflicts. |
|
r? rust-lang/compiler |
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'm very sorry for the long wait. The impl looks good, thanks!
I've also performed a lot of testing locally and everything seems to check out.
| ExprKind::Yield(YieldKind::Prefix(..)) | ||
| ExprKind::Range(None, ..) = expr.kind | ||
{ | ||
return ExprPrecedence::Prefix; |
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 keep wondering if this could be relaxed to ExprPrecedence::Unambiguous
. I wasn't able to find any counterexamples for doing that so far but I'm sure I'm missing some very obvious cases
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.
Prefix
and Unambiguous
are interchangeable on this line. The only difference between Prefix
and Unambiguous
is for subexpressions that are not a rightmost subexpression:
$a.method()
// needs parens if $a is `&x` (Prefix)
// no parens if $a is `loop {}` (Unambiguous)
and if something is not a rightmost subexpression, then by definition next_operator_can_continue_expr
would be true, because the outer expression literally continues after that subexpression, so we would not be in this code.
But I still think Prefix
is the more natural value to return here compared to Unambiguous
if you consider it against the $a.method()
example — a subexpression like return false
behaves more like &a
than like loop {}
in that (return false).method()
needs the parenthesization, even if that behavior is ultimately not controlled by this return value (because next_operator_can_continue_expr
would be true).
@bors r+ rollup |
Rollup of 10 pull requests Successful merges: - #134847 (Implement asymmetrical precedence for closures and jumps) - #141491 (Delegate `<CStr as Debug>` to `ByteStr`) - #141770 (Merge `Cfg::render_long_html` and `Cfg::render_long_plain` methods common code) - #142069 (Introduce `-Zmacro-stats`) - #142158 (Tracking the old name of renamed unstable library features) - #142221 ([AIX] strip underlying xcoff object) - #142340 (miri: we can use apfloat's mul_add now) - #142379 (Add bootstrap option to compile a tool with features) - #142410 (intrinsics: rename min_align_of to align_of) - #142413 (rustc-dev-guide subtree update) r? `@ghost` `@rustbot` modify labels: rollup
Rollup merge of #134847 - dtolnay:asymmetrical, r=fmease Implement asymmetrical precedence for closures and jumps I have been through a series of asymmetrical precedence designs in Syn, and finally have one that I like and is worth backporting into rustc. It is based on just 2 bits of state: `next_operator_can_begin_expr` and `next_operator_can_continue_expr`. Asymmetrical precedence is the thing that enables `(return 1) + 1` to require parentheses while `1 + return 1` does not, despite `+` always having stronger precedence than `return` [according to the Rust Reference](https://doc.rust-lang.org/1.83.0/reference/expressions.html#expression-precedence). This is facilitated by `next_operator_can_continue_expr`. Relatedly, it is the thing that enables `(return) - 1` to require parentheses while `return + 1` does not, despite `+` and `-` having exactly the same precedence. This is facilitated by `next_operator_can_begin_expr`. **Example:** ```rust macro_rules! repro { ($e:expr) => { $e - $e; $e + $e; }; } fn main() { repro!{return} repro!{return 1} } ``` `-Zunpretty=expanded` **Before:** ```console fn main() { (return) - (return); (return) + (return); (return 1) - (return 1); (return 1) + (return 1); } ``` **After:** ```console fn main() { (return) - return; return + return; (return 1) - return 1; (return 1) + return 1; } ```
Rollup of 10 pull requests Successful merges: - rust-lang/rust#134847 (Implement asymmetrical precedence for closures and jumps) - rust-lang/rust#141491 (Delegate `<CStr as Debug>` to `ByteStr`) - rust-lang/rust#141770 (Merge `Cfg::render_long_html` and `Cfg::render_long_plain` methods common code) - rust-lang/rust#142069 (Introduce `-Zmacro-stats`) - rust-lang/rust#142158 (Tracking the old name of renamed unstable library features) - rust-lang/rust#142221 ([AIX] strip underlying xcoff object) - rust-lang/rust#142340 (miri: we can use apfloat's mul_add now) - rust-lang/rust#142379 (Add bootstrap option to compile a tool with features) - rust-lang/rust#142410 (intrinsics: rename min_align_of to align_of) - rust-lang/rust#142413 (rustc-dev-guide subtree update) r? `@ghost` `@rustbot` modify labels: rollup
I have been through a series of asymmetrical precedence designs in Syn, and finally have one that I like and is worth backporting into rustc. It is based on just 2 bits of state:
next_operator_can_begin_expr
andnext_operator_can_continue_expr
.Asymmetrical precedence is the thing that enables
(return 1) + 1
to require parentheses while1 + return 1
does not, despite+
always having stronger precedence thanreturn
according to the Rust Reference. This is facilitated bynext_operator_can_continue_expr
.Relatedly, it is the thing that enables
(return) - 1
to require parentheses whilereturn + 1
does not, despite+
and-
having exactly the same precedence. This is facilitated bynext_operator_can_begin_expr
.Example:
-Zunpretty=expanded
Before:After: