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

Inline ExprPrecedence::order into Expr::precedence #133140

Merged
merged 1 commit into from
Nov 27, 2024

Conversation

dtolnay
Copy link
Member

@dtolnay dtolnay commented Nov 17, 2024

The representation of expression precedence in rustc_ast has been an obstacle to further improvements in the pretty-printer (continuing from #119105 and #119427).

Previously the operation of "does this expression have lower precedence than that one" (relevant for parenthesis insertion in macro-generated syntax trees) consisted of 3 steps:

  1. Convert Expr to ExprPrecedence using .precedence()
  2. Convert ExprPrecedence to i8 using .order()
  3. Compare using <

As far as I can guess, the reason for the separation between precedence() and order() was so that both rustc_ast::Expr and rustc_hir::Expr could convert as straightforwardly as possible to the same ExprPrecedence enum, and then the more finicky logic performed by order could be present just once.

The mapping between Expr and ExprPrecedence was intended to be as straightforward as possible:

match self.kind {
    ExprKind::Closure(..) => ExprPrecedence::Closure,
    ...
}

although there were exceptions of both many-to-one, and one-to-many:

    ExprKind::Underscore => ExprPrecedence::Path,
    ExprKind::Path(..) => ExprPrecedence::Path,
    ...
    ExprKind::Match(_, _, MatchKind::Prefix) => ExprPrecedence::Match,
    ExprKind::Match(_, _, MatchKind::Postfix) => ExprPrecedence::PostfixMatch,

Where the nature of ExprPrecedence becomes problematic is when a single expression kind might be associated with multiple different precedence levels depending on context (outside the expression) and contents (inside the expression). For example consider what is the precedence of an ExprKind::Closure $closure. Well, on the left-hand side of a binary operator it would need parentheses in order to avoid the trailing binary operator being absorbed into the closure body: ($closure) + Rhs, so the precedence is something lower than that of +. But on the right-hand side of a binary operator, a closure is just a straightforward prefix expression like a unary op, which is a relatively high precedence level, higher than binops but lower than method calls: Lhs + $closure is fine without parens but ($closure).method() needs them. But as a third case, if the closure contains an explicit return type, then the precedence is an even higher level than that, never needing parenthesization even in a binop left-hand side or method call: || -> bool { false } + Rhs or || -> bool { false }.method().

You can see that trying to capture all of this resolution about expressions into ExprPrecedence violates the intention of ExprPrecedence being a straightforward one-to-one correspondence from each AST and HIR ExprKind variant. It would be possible to attempt that by doing stuff like ExprPrecedence::Closure(Side::Leading, ReturnType::No), but I don't foresee the original envisioned benefit of the precedence()/order() distinction being retained in this approach. Instead I want to move toward a model that Syn has been using successfully. In Syn, there is a Precedence enum but it differs from rustc in the following ways:

  • There are relatively few variants compared to rustc's ExprPrecedence. For example there is no distinction at the precedence level between returns and closures, or between loops and method calls.

  • We distinguish between leading and trailing precedence, taking into account an expression's context such as what token follows it (for various syntactic bail-outs in Rust's grammar, like ambiguities around break-with-value) and how it relates to operators from the surrounding syntax tree.

  • There are no hardcoded mysterious integer quantities like rustc's PREC_CLOSURE = -40. All precedence comparisons are performed via PartialOrd on a C-like enum.

This PR is just a first step in these changes. As you can tell from Syn, I definitely think there is value in having a dedicated type to represent precedence, instead of what order() is doing with i8. But that is a whole separate adventure because rustc_ast doesn't even agree consistently on i8 being the type for precedence order; AssocOp::precedence instead uses usize and there are casts in both directions. It is likely that a type called ExprPrecedence will re-appear, but it will look substantially different from the one that existed before this PR.

@rustbot
Copy link
Collaborator

rustbot commented Nov 17, 2024

r? @fmease

rustbot has assigned @fmease.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Nov 17, 2024
@dtolnay dtolnay added A-pretty Area: Pretty printing (including `-Z unpretty`) A-parser Area: The parsing of Rust source code to an AST labels Nov 17, 2024
@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Nov 17, 2024

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

Copy link
Member

@fmease fmease left a comment

Choose a reason for hiding this comment

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

This makes sense, thank you! Looking forward to all the follow-up PRs :)

@fmease
Copy link
Member

fmease commented Nov 25, 2024

@bors r+ rollup (refactoring)

@bors
Copy link
Contributor

bors commented Nov 25, 2024

📌 Commit e5f1555 has been approved by fmease

It is now in the queue for this repository.

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 25, 2024
jhpratt added a commit to jhpratt/rust that referenced this pull request Nov 26, 2024
Inline ExprPrecedence::order into Expr::precedence

The representation of expression precedence in rustc_ast has been an obstacle to further improvements in the pretty-printer (continuing from rust-lang#119105 and rust-lang#119427).

Previously the operation of *"does this expression have lower precedence than that one"* (relevant for parenthesis insertion in macro-generated syntax trees) consisted of 3 steps:

1. Convert `Expr` to `ExprPrecedence` using `.precedence()`
2. Convert `ExprPrecedence` to `i8` using `.order()`
3. Compare using `<`

As far as I can guess, the reason for the separation between `precedence()` and `order()` was so that both `rustc_ast::Expr` and `rustc_hir::Expr` could convert as straightforwardly as possible to the same `ExprPrecedence` enum, and then the more finicky logic performed by `order` could be present just once.

The mapping between `Expr` and `ExprPrecedence` was intended to be as straightforward as possible:

```rust
match self.kind {
    ExprKind::Closure(..) => ExprPrecedence::Closure,
    ...
}
```

although there were exceptions of both many-to-one, and one-to-many:

```rust
    ExprKind::Underscore => ExprPrecedence::Path,
    ExprKind::Path(..) => ExprPrecedence::Path,
    ...
    ExprKind::Match(_, _, MatchKind::Prefix) => ExprPrecedence::Match,
    ExprKind::Match(_, _, MatchKind::Postfix) => ExprPrecedence::PostfixMatch,
```

Where the nature of `ExprPrecedence` becomes problematic is when a single expression kind might be associated with multiple different precedence levels depending on context (outside the expression) and contents (inside the expression). For example consider what is the precedence of an ExprKind::Closure `$closure`. Well, on the left-hand side of a binary operator it would need parentheses in order to avoid the trailing binary operator being absorbed into the closure body: `($closure) + Rhs`, so the precedence is something lower than that of `+`. But on the right-hand side of a binary operator, a closure is just a straightforward prefix expression like a unary op, which is a relatively high precedence level, higher than binops but lower than method calls: `Lhs + $closure` is fine without parens but `($closure).method()` needs them. But as a third case, if the closure contains an explicit return type, then the precedence is an even higher level than that, never needing parenthesization even in a binop left-hand side or method call: `|| -> bool { false } + Rhs` or `|| -> bool { false }.method()`.

You can see that trying to capture all of this resolution about expressions into `ExprPrecedence` violates the intention of `ExprPrecedence` being a straightforward one-to-one correspondence from each AST and HIR `ExprKind` variant. It would be possible to attempt that by doing stuff like `ExprPrecedence::Closure(Side::Leading, ReturnType::No)`, but I don't foresee the original envisioned benefit of the `precedence()`/`order()` distinction being retained in this approach. Instead I want to move toward a model that Syn has been using successfully. In Syn, there is a Precedence enum but it differs from rustc in the following ways:

- There are [relatively few variants](https://github.com/dtolnay/syn/blob/2.0.87/src/precedence.rs#L11-L47) compared to rustc's `ExprPrecedence`. For example there is no distinction at the precedence level between returns and closures, or between loops and method calls.

- We distinguish between [leading](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L293) and [trailing](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L309) precedence, taking into account an expression's context such as what token follows it (for various syntactic bail-outs in Rust's grammar, like ambiguities around break-with-value) and how it relates to operators from the surrounding syntax tree.

- There are no hardcoded mysterious integer quantities like rustc's `PREC_CLOSURE = -40`. All precedence comparisons are performed via PartialOrd on a C-like enum.

This PR is just a first step in these changes. As you can tell from Syn, I definitely think there is value in having a dedicated type to represent precedence, instead of what `order()` is doing with `i8`. But that is a whole separate adventure because rustc_ast doesn't even agree consistently on `i8` being the type for precedence order; `AssocOp::precedence` instead uses `usize` and there are casts in both directions. It is likely that a type called `ExprPrecedence` will re-appear, but it will look substantially different from the one that existed before this PR.
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 26, 2024
Rollup of 28 pull requests

Successful merges:

 - rust-lang#132605 (CI: increase timeout from 4h to 6h)
 - rust-lang#133042 (btree: add `{Entry,VacantEntry}::insert_entry`)
 - rust-lang#133070 (Lexer tweaks)
 - rust-lang#133136 (Support ranges in `<[T]>::get_many_mut()`)
 - rust-lang#133140 (Inline ExprPrecedence::order into Expr::precedence)
 - rust-lang#133248 (CI: split x86_64-msvc-ext job)
 - rust-lang#133282 (Shorten the `MaybeUninit` `Debug` implementation)
 - rust-lang#133304 (Revert diagnostics hack to fix ICE 132920)
 - rust-lang#133326 (Remove the `DefinitelyInitializedPlaces` analysis.)
 - rust-lang#133362 (No need to re-sort existential preds in relate impl)
 - rust-lang#133367 (Simplify array length mismatch error reporting (to not try to turn consts into target usizes))
 - rust-lang#133394 (Bail on more errors in dyn ty lowering)
 - rust-lang#133410 (target check_consistency: ensure target feature string makes some basic sense)
 - rust-lang#133411 (the emscripten OS no longer exists on non-wasm targets)
 - rust-lang#133419 (Added a doc test for std::path::strip_prefix)
 - rust-lang#133430 (Tweak parameter mismatch explanation to not say `{unknown}`)
 - rust-lang#133435 (miri: disable test_downgrade_observe test on macOS)
 - rust-lang#133443 (Remove dead code stemming from the old effects desugaring (II))
 - rust-lang#133449 (std: expose `const_io_error!` as `const_error!`)
 - rust-lang#133450 (remove "onur-ozkan" from  users_on_vacation)
 - rust-lang#133454 (Update test expectations to accept LLVM 'initializes' attribute)
 - rust-lang#133458 (Fix `Result` and `Option` not getting a jump to def link generated)
 - rust-lang#133462 (Use ReadCache for archive reading in bootstrap)
 - rust-lang#133464 (std::thread: avoid leading whitespace in some panic messages)
 - rust-lang#133467 (tests: Add recursive associated type bound regression tests)
 - rust-lang#133470 (Cleanup: delete `//@ pretty-expanded` directive)
 - rust-lang#133473 (tests: Add regression test for recursive enum with Cow and Clone)
 - rust-lang#133481 (Disable `avr-rjmp-offset` on Windows for now)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 26, 2024
Rollup of 28 pull requests

Successful merges:

 - rust-lang#132605 (CI: increase timeout from 4h to 6h)
 - rust-lang#133042 (btree: add `{Entry,VacantEntry}::insert_entry`)
 - rust-lang#133070 (Lexer tweaks)
 - rust-lang#133136 (Support ranges in `<[T]>::get_many_mut()`)
 - rust-lang#133140 (Inline ExprPrecedence::order into Expr::precedence)
 - rust-lang#133248 (CI: split x86_64-msvc-ext job)
 - rust-lang#133282 (Shorten the `MaybeUninit` `Debug` implementation)
 - rust-lang#133304 (Revert diagnostics hack to fix ICE 132920)
 - rust-lang#133326 (Remove the `DefinitelyInitializedPlaces` analysis.)
 - rust-lang#133362 (No need to re-sort existential preds in relate impl)
 - rust-lang#133367 (Simplify array length mismatch error reporting (to not try to turn consts into target usizes))
 - rust-lang#133394 (Bail on more errors in dyn ty lowering)
 - rust-lang#133410 (target check_consistency: ensure target feature string makes some basic sense)
 - rust-lang#133411 (the emscripten OS no longer exists on non-wasm targets)
 - rust-lang#133419 (Added a doc test for std::path::strip_prefix)
 - rust-lang#133430 (Tweak parameter mismatch explanation to not say `{unknown}`)
 - rust-lang#133435 (miri: disable test_downgrade_observe test on macOS)
 - rust-lang#133443 (Remove dead code stemming from the old effects desugaring (II))
 - rust-lang#133449 (std: expose `const_io_error!` as `const_error!`)
 - rust-lang#133450 (remove "onur-ozkan" from  users_on_vacation)
 - rust-lang#133454 (Update test expectations to accept LLVM 'initializes' attribute)
 - rust-lang#133458 (Fix `Result` and `Option` not getting a jump to def link generated)
 - rust-lang#133462 (Use ReadCache for archive reading in bootstrap)
 - rust-lang#133464 (std::thread: avoid leading whitespace in some panic messages)
 - rust-lang#133467 (tests: Add recursive associated type bound regression tests)
 - rust-lang#133470 (Cleanup: delete `//@ pretty-expanded` directive)
 - rust-lang#133473 (tests: Add regression test for recursive enum with Cow and Clone)
 - rust-lang#133481 (Disable `avr-rjmp-offset` on Windows for now)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 26, 2024
Rollup of 28 pull requests

Successful merges:

 - rust-lang#132605 (CI: increase timeout from 4h to 6h)
 - rust-lang#133042 (btree: add `{Entry,VacantEntry}::insert_entry`)
 - rust-lang#133070 (Lexer tweaks)
 - rust-lang#133136 (Support ranges in `<[T]>::get_many_mut()`)
 - rust-lang#133140 (Inline ExprPrecedence::order into Expr::precedence)
 - rust-lang#133248 (CI: split x86_64-msvc-ext job)
 - rust-lang#133282 (Shorten the `MaybeUninit` `Debug` implementation)
 - rust-lang#133304 (Revert diagnostics hack to fix ICE 132920)
 - rust-lang#133326 (Remove the `DefinitelyInitializedPlaces` analysis.)
 - rust-lang#133362 (No need to re-sort existential preds in relate impl)
 - rust-lang#133367 (Simplify array length mismatch error reporting (to not try to turn consts into target usizes))
 - rust-lang#133394 (Bail on more errors in dyn ty lowering)
 - rust-lang#133410 (target check_consistency: ensure target feature string makes some basic sense)
 - rust-lang#133411 (the emscripten OS no longer exists on non-wasm targets)
 - rust-lang#133419 (Added a doc test for std::path::strip_prefix)
 - rust-lang#133430 (Tweak parameter mismatch explanation to not say `{unknown}`)
 - rust-lang#133435 (miri: disable test_downgrade_observe test on macOS)
 - rust-lang#133443 (Remove dead code stemming from the old effects desugaring (II))
 - rust-lang#133449 (std: expose `const_io_error!` as `const_error!`)
 - rust-lang#133450 (remove "onur-ozkan" from  users_on_vacation)
 - rust-lang#133454 (Update test expectations to accept LLVM 'initializes' attribute)
 - rust-lang#133458 (Fix `Result` and `Option` not getting a jump to def link generated)
 - rust-lang#133462 (Use ReadCache for archive reading in bootstrap)
 - rust-lang#133464 (std::thread: avoid leading whitespace in some panic messages)
 - rust-lang#133467 (tests: Add recursive associated type bound regression tests)
 - rust-lang#133470 (Cleanup: delete `//@ pretty-expanded` directive)
 - rust-lang#133473 (tests: Add regression test for recursive enum with Cow and Clone)
 - rust-lang#133481 (Disable `avr-rjmp-offset` on Windows for now)

r? `@ghost`
`@rustbot` modify labels: rollup
bors added a commit to rust-lang-ci/rust that referenced this pull request Nov 26, 2024
…mpiler-errors

Rollup of 12 pull requests

Successful merges:

 - rust-lang#133042 (btree: add `{Entry,VacantEntry}::insert_entry`)
 - rust-lang#133070 (Lexer tweaks)
 - rust-lang#133136 (Support ranges in `<[T]>::get_many_mut()`)
 - rust-lang#133140 (Inline ExprPrecedence::order into Expr::precedence)
 - rust-lang#133155 (Yet more `rustc_mir_dataflow` cleanups)
 - rust-lang#133282 (Shorten the `MaybeUninit` `Debug` implementation)
 - rust-lang#133326 (Remove the `DefinitelyInitializedPlaces` analysis.)
 - rust-lang#133362 (No need to re-sort existential preds in relate impl)
 - rust-lang#133367 (Simplify array length mismatch error reporting (to not try to turn consts into target usizes))
 - rust-lang#133394 (Bail on more errors in dyn ty lowering)
 - rust-lang#133410 (target check_consistency: ensure target feature string makes some basic sense)
 - rust-lang#133435 (miri: disable test_downgrade_observe test on macOS)

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 6e5bac1 into rust-lang:master Nov 27, 2024
6 checks passed
@rustbot rustbot added this to the 1.85.0 milestone Nov 27, 2024
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 27, 2024
Rollup merge of rust-lang#133140 - dtolnay:precedence, r=fmease

Inline ExprPrecedence::order into Expr::precedence

The representation of expression precedence in rustc_ast has been an obstacle to further improvements in the pretty-printer (continuing from rust-lang#119105 and rust-lang#119427).

Previously the operation of *"does this expression have lower precedence than that one"* (relevant for parenthesis insertion in macro-generated syntax trees) consisted of 3 steps:

1. Convert `Expr` to `ExprPrecedence` using `.precedence()`
2. Convert `ExprPrecedence` to `i8` using `.order()`
3. Compare using `<`

As far as I can guess, the reason for the separation between `precedence()` and `order()` was so that both `rustc_ast::Expr` and `rustc_hir::Expr` could convert as straightforwardly as possible to the same `ExprPrecedence` enum, and then the more finicky logic performed by `order` could be present just once.

The mapping between `Expr` and `ExprPrecedence` was intended to be as straightforward as possible:

```rust
match self.kind {
    ExprKind::Closure(..) => ExprPrecedence::Closure,
    ...
}
```

although there were exceptions of both many-to-one, and one-to-many:

```rust
    ExprKind::Underscore => ExprPrecedence::Path,
    ExprKind::Path(..) => ExprPrecedence::Path,
    ...
    ExprKind::Match(_, _, MatchKind::Prefix) => ExprPrecedence::Match,
    ExprKind::Match(_, _, MatchKind::Postfix) => ExprPrecedence::PostfixMatch,
```

Where the nature of `ExprPrecedence` becomes problematic is when a single expression kind might be associated with multiple different precedence levels depending on context (outside the expression) and contents (inside the expression). For example consider what is the precedence of an ExprKind::Closure `$closure`. Well, on the left-hand side of a binary operator it would need parentheses in order to avoid the trailing binary operator being absorbed into the closure body: `($closure) + Rhs`, so the precedence is something lower than that of `+`. But on the right-hand side of a binary operator, a closure is just a straightforward prefix expression like a unary op, which is a relatively high precedence level, higher than binops but lower than method calls: `Lhs + $closure` is fine without parens but `($closure).method()` needs them. But as a third case, if the closure contains an explicit return type, then the precedence is an even higher level than that, never needing parenthesization even in a binop left-hand side or method call: `|| -> bool { false } + Rhs` or `|| -> bool { false }.method()`.

You can see that trying to capture all of this resolution about expressions into `ExprPrecedence` violates the intention of `ExprPrecedence` being a straightforward one-to-one correspondence from each AST and HIR `ExprKind` variant. It would be possible to attempt that by doing stuff like `ExprPrecedence::Closure(Side::Leading, ReturnType::No)`, but I don't foresee the original envisioned benefit of the `precedence()`/`order()` distinction being retained in this approach. Instead I want to move toward a model that Syn has been using successfully. In Syn, there is a Precedence enum but it differs from rustc in the following ways:

- There are [relatively few variants](https://github.com/dtolnay/syn/blob/2.0.87/src/precedence.rs#L11-L47) compared to rustc's `ExprPrecedence`. For example there is no distinction at the precedence level between returns and closures, or between loops and method calls.

- We distinguish between [leading](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L293) and [trailing](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L309) precedence, taking into account an expression's context such as what token follows it (for various syntactic bail-outs in Rust's grammar, like ambiguities around break-with-value) and how it relates to operators from the surrounding syntax tree.

- There are no hardcoded mysterious integer quantities like rustc's `PREC_CLOSURE = -40`. All precedence comparisons are performed via PartialOrd on a C-like enum.

This PR is just a first step in these changes. As you can tell from Syn, I definitely think there is value in having a dedicated type to represent precedence, instead of what `order()` is doing with `i8`. But that is a whole separate adventure because rustc_ast doesn't even agree consistently on `i8` being the type for precedence order; `AssocOp::precedence` instead uses `usize` and there are casts in both directions. It is likely that a type called `ExprPrecedence` will re-appear, but it will look substantially different from the one that existed before this PR.
@dtolnay dtolnay deleted the precedence branch November 27, 2024 01:05
flip1995 pushed a commit to flip1995/rust that referenced this pull request Nov 28, 2024
Inline ExprPrecedence::order into Expr::precedence

The representation of expression precedence in rustc_ast has been an obstacle to further improvements in the pretty-printer (continuing from rust-lang#119105 and rust-lang#119427).

Previously the operation of *"does this expression have lower precedence than that one"* (relevant for parenthesis insertion in macro-generated syntax trees) consisted of 3 steps:

1. Convert `Expr` to `ExprPrecedence` using `.precedence()`
2. Convert `ExprPrecedence` to `i8` using `.order()`
3. Compare using `<`

As far as I can guess, the reason for the separation between `precedence()` and `order()` was so that both `rustc_ast::Expr` and `rustc_hir::Expr` could convert as straightforwardly as possible to the same `ExprPrecedence` enum, and then the more finicky logic performed by `order` could be present just once.

The mapping between `Expr` and `ExprPrecedence` was intended to be as straightforward as possible:

```rust
match self.kind {
    ExprKind::Closure(..) => ExprPrecedence::Closure,
    ...
}
```

although there were exceptions of both many-to-one, and one-to-many:

```rust
    ExprKind::Underscore => ExprPrecedence::Path,
    ExprKind::Path(..) => ExprPrecedence::Path,
    ...
    ExprKind::Match(_, _, MatchKind::Prefix) => ExprPrecedence::Match,
    ExprKind::Match(_, _, MatchKind::Postfix) => ExprPrecedence::PostfixMatch,
```

Where the nature of `ExprPrecedence` becomes problematic is when a single expression kind might be associated with multiple different precedence levels depending on context (outside the expression) and contents (inside the expression). For example consider what is the precedence of an ExprKind::Closure `$closure`. Well, on the left-hand side of a binary operator it would need parentheses in order to avoid the trailing binary operator being absorbed into the closure body: `($closure) + Rhs`, so the precedence is something lower than that of `+`. But on the right-hand side of a binary operator, a closure is just a straightforward prefix expression like a unary op, which is a relatively high precedence level, higher than binops but lower than method calls: `Lhs + $closure` is fine without parens but `($closure).method()` needs them. But as a third case, if the closure contains an explicit return type, then the precedence is an even higher level than that, never needing parenthesization even in a binop left-hand side or method call: `|| -> bool { false } + Rhs` or `|| -> bool { false }.method()`.

You can see that trying to capture all of this resolution about expressions into `ExprPrecedence` violates the intention of `ExprPrecedence` being a straightforward one-to-one correspondence from each AST and HIR `ExprKind` variant. It would be possible to attempt that by doing stuff like `ExprPrecedence::Closure(Side::Leading, ReturnType::No)`, but I don't foresee the original envisioned benefit of the `precedence()`/`order()` distinction being retained in this approach. Instead I want to move toward a model that Syn has been using successfully. In Syn, there is a Precedence enum but it differs from rustc in the following ways:

- There are [relatively few variants](https://github.com/dtolnay/syn/blob/2.0.87/src/precedence.rs#L11-L47) compared to rustc's `ExprPrecedence`. For example there is no distinction at the precedence level between returns and closures, or between loops and method calls.

- We distinguish between [leading](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L293) and [trailing](https://github.com/dtolnay/syn/blob/2.0.87/src/fixup.rs#L309) precedence, taking into account an expression's context such as what token follows it (for various syntactic bail-outs in Rust's grammar, like ambiguities around break-with-value) and how it relates to operators from the surrounding syntax tree.

- There are no hardcoded mysterious integer quantities like rustc's `PREC_CLOSURE = -40`. All precedence comparisons are performed via PartialOrd on a C-like enum.

This PR is just a first step in these changes. As you can tell from Syn, I definitely think there is value in having a dedicated type to represent precedence, instead of what `order()` is doing with `i8`. But that is a whole separate adventure because rustc_ast doesn't even agree consistently on `i8` being the type for precedence order; `AssocOp::precedence` instead uses `usize` and there are casts in both directions. It is likely that a type called `ExprPrecedence` will re-appear, but it will look substantially different from the one that existed before this PR.
GuillaumeGomez added a commit to GuillaumeGomez/rust that referenced this pull request Dec 2, 2024
Eliminate magic numbers from expression precedence

Context: see rust-lang#133140.

This PR continues on backporting Syn's expression precedence design into rustc. Rustc's design used mysterious integer quantities represented variously as `i8` or `usize` (e.g. `PREC_CLOSURE = -40i8`), a special significance around `0` that is never named, and an extra `PREC_FORCE_PAREN` precedence level that does not correspond to any expression. Syn's design uses a C-like enum with variants that clearly correspond to specific sets of expression kinds.

This PR is a refactoring that has no intended behavior change on its own, but it unblocks other precedence work that rustc's precedence design was poorly suited to accommodate.

- Asymmetrical precedence, so that a pretty-printer can tell `(return 1) + 1` needs parens but `1 + return 1` does not.

- Squashing the `Closure` and `Jump` cases into a single precedence level.

- Numerous remaining false positives and false negatives in rustc pretty-printer's parenthesization of macro metavariables, for example in `$e < rhs` where $e is `lhs as Thing<T>`.

FYI `@fmease` &mdash; you don't need to review if rustbot picks someone else, but you mentioned being interested in the followup PRs.
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Dec 2, 2024
Rollup merge of rust-lang#133603 - dtolnay:precedence, r=lcnr

Eliminate magic numbers from expression precedence

Context: see rust-lang#133140.

This PR continues on backporting Syn's expression precedence design into rustc. Rustc's design used mysterious integer quantities represented variously as `i8` or `usize` (e.g. `PREC_CLOSURE = -40i8`), a special significance around `0` that is never named, and an extra `PREC_FORCE_PAREN` precedence level that does not correspond to any expression. Syn's design uses a C-like enum with variants that clearly correspond to specific sets of expression kinds.

This PR is a refactoring that has no intended behavior change on its own, but it unblocks other precedence work that rustc's precedence design was poorly suited to accommodate.

- Asymmetrical precedence, so that a pretty-printer can tell `(return 1) + 1` needs parens but `1 + return 1` does not.

- Squashing the `Closure` and `Jump` cases into a single precedence level.

- Numerous remaining false positives and false negatives in rustc pretty-printer's parenthesization of macro metavariables, for example in `$e < rhs` where $e is `lhs as Thing<T>`.

FYI `@fmease` &mdash; you don't need to review if rustbot picks someone else, but you mentioned being interested in the followup PRs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-parser Area: The parsing of Rust source code to an AST A-pretty Area: Pretty printing (including `-Z unpretty`) S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants