Skip to content

Commit b73a71c

Browse files
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.
2 parents 945ccbd + a2b6b6b commit b73a71c

File tree

6 files changed

+11
-11
lines changed

6 files changed

+11
-11
lines changed

clippy_lints/src/dereference.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -963,7 +963,7 @@ fn report<'tcx>(
963963
// expr_str (the suggestion) is never shown if is_final_ufcs is true, since it's
964964
// `expr.kind == ExprKind::Call`. Therefore, this is, afaik, always unnecessary.
965965
/*
966-
expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence().order() < PREC_PREFIX {
966+
expr_str = if !expr_is_macro_call && is_final_ufcs && expr.precedence() < PREC_PREFIX {
967967
Cow::Owned(format!("({expr_str})"))
968968
} else {
969969
expr_str
@@ -1003,7 +1003,7 @@ fn report<'tcx>(
10031003
Node::Expr(e) => match e.kind {
10041004
ExprKind::Call(callee, _) if callee.hir_id != data.first_expr.hir_id => (0, false),
10051005
ExprKind::Call(..) => (PREC_UNAMBIGUOUS, matches!(expr.kind, ExprKind::Field(..))),
1006-
_ => (e.precedence().order(), false),
1006+
_ => (e.precedence(), false),
10071007
},
10081008
_ => (0, false),
10091009
};
@@ -1016,7 +1016,7 @@ fn report<'tcx>(
10161016
);
10171017

10181018
let sugg = if !snip_is_macro
1019-
&& (calls_field || expr.precedence().order() < precedence)
1019+
&& (calls_field || expr.precedence() < precedence)
10201020
&& !has_enclosing_paren(&snip)
10211021
&& !is_in_tuple
10221022
{
@@ -1071,7 +1071,7 @@ fn report<'tcx>(
10711071
let (snip, snip_is_macro) =
10721072
snippet_with_context(cx, expr.span, data.first_expr.span.ctxt(), "..", &mut app);
10731073
let sugg =
1074-
if !snip_is_macro && expr.precedence().order() < precedence && !has_enclosing_paren(&snip) {
1074+
if !snip_is_macro && expr.precedence() < precedence && !has_enclosing_paren(&snip) {
10751075
format!("{prefix}({snip})")
10761076
} else {
10771077
format!("{prefix}{snip}")
@@ -1158,7 +1158,7 @@ impl<'tcx> Dereferencing<'tcx> {
11581158
},
11591159
Some(parent) if !parent.span.from_expansion() => {
11601160
// Double reference might be needed at this point.
1161-
if parent.precedence().order() == PREC_UNAMBIGUOUS {
1161+
if parent.precedence() == PREC_UNAMBIGUOUS {
11621162
// Parentheses would be needed here, don't lint.
11631163
*outer_pat = None;
11641164
} else {

clippy_lints/src/loops/single_element_loop.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub(super) fn check<'tcx>(
8484
if !prefix.is_empty()
8585
&& (
8686
// Precedence of internal expression is less than or equal to precedence of `&expr`.
87-
arg_expression.precedence().order() <= PREC_PREFIX || is_range_literal(arg_expression)
87+
arg_expression.precedence() <= PREC_PREFIX || is_range_literal(arg_expression)
8888
)
8989
{
9090
arg_snip = format!("({arg_snip})").into();

clippy_lints/src/matches/manual_utils.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ where
117117
// it's being passed by value.
118118
let scrutinee = peel_hir_expr_refs(scrutinee).0;
119119
let (scrutinee_str, _) = snippet_with_context(cx, scrutinee.span, expr_ctxt, "..", &mut app);
120-
let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence().order() < PREC_UNAMBIGUOUS {
120+
let scrutinee_str = if scrutinee.span.eq_ctxt(expr.span) && scrutinee.precedence() < PREC_UNAMBIGUOUS {
121121
format!("({scrutinee_str})")
122122
} else {
123123
scrutinee_str.into()

clippy_lints/src/neg_multiply.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
5858
{
5959
let mut applicability = Applicability::MachineApplicable;
6060
let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability);
61-
let suggestion = if !from_macro && exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
61+
let suggestion = if !from_macro && exp.precedence() < PREC_PREFIX && !has_enclosing_paren(&snip) {
6262
format!("-({snip})")
6363
} else {
6464
format!("-{snip}")

clippy_lints/src/redundant_slicing.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
8585
let (expr_ty, expr_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(expr));
8686
let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
8787
let parent_expr = get_parent_expr(cx, expr);
88-
let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence().order() > PREC_PREFIX);
88+
let needs_parens_for_prefix = parent_expr.is_some_and(|parent| parent.precedence() > PREC_PREFIX);
8989

9090
if expr_ty == indexed_ty {
9191
if expr_ref_count > indexed_ref_count {

clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::sugg::Sugg;
4-
use rustc_ast::ExprPrecedence;
4+
use rustc_ast::util::parser::AssocOp;
55
use rustc_errors::Applicability;
66
use rustc_hir::{Expr, Node};
77
use rustc_hir_typeck::cast::check_cast;
@@ -44,7 +44,7 @@ pub(super) fn check<'tcx>(
4444
};
4545

4646
if let Node::Expr(parent) = cx.tcx.parent_hir_node(e.hir_id)
47-
&& parent.precedence().order() > ExprPrecedence::Cast.order()
47+
&& parent.precedence() > AssocOp::As.precedence() as i8
4848
{
4949
sugg = format!("({sugg})");
5050
}

0 commit comments

Comments
 (0)