Skip to content

Commit 9c30fec

Browse files
authored
Merge pull request rust-lang#19070 from Veykril/push-wpqzmznymtrn
Remove mutable syntax tree shenanigans from adjustment hints
2 parents ce8bace + 0d9355e commit 9c30fec

File tree

2 files changed

+148
-71
lines changed

2 files changed

+148
-71
lines changed

src/tools/rust-analyzer/crates/ide/src/inlay_hints/adjustment.rs

Lines changed: 32 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@ use ide_db::famous_defs::FamousDefs;
1313

1414
use ide_db::text_edit::TextEditBuilder;
1515
use span::EditionedFileId;
16-
use stdx::never;
17-
use syntax::{
18-
ast::{self, make, AstNode},
19-
ted,
20-
};
16+
use syntax::ast::{self, prec::ExprPrecedence, AstNode};
2117

2218
use crate::{
2319
AdjustmentHints, AdjustmentHintsMode, InlayHint, InlayHintLabel, InlayHintLabelPart,
@@ -104,12 +100,14 @@ pub(super) fn hints(
104100
};
105101
let iter: &mut dyn Iterator<Item = _> = iter.as_mut().either(|it| it as _, |it| it as _);
106102

103+
let mut has_adjustments = false;
107104
let mut allow_edit = !postfix;
108105
for Adjustment { source, target, kind } in iter {
109106
if source == target {
110107
cov_mark::hit!(same_type_adjustment);
111108
continue;
112109
}
110+
has_adjustments = true;
113111

114112
// FIXME: Add some nicer tooltips to each of these
115113
let (text, coercion) = match kind {
@@ -172,6 +170,10 @@ pub(super) fn hints(
172170
};
173171
if postfix { &mut post } else { &mut pre }.label.append_part(label);
174172
}
173+
if !has_adjustments {
174+
return None;
175+
}
176+
175177
if !postfix && needs_inner_parens {
176178
pre.label.append_str("(");
177179
}
@@ -254,71 +256,31 @@ fn mode_and_needs_parens_for_adjustment_hints(
254256
/// Returns whatever we need to add parentheses on the inside and/or outside of `expr`,
255257
/// if we are going to add (`postfix`) adjustments hints to it.
256258
fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
257-
// This is a very miserable pile of hacks...
258-
//
259-
// `Expr::needs_parens_in` requires that the expression is the child of the other expression,
260-
// that is supposed to be its parent.
261-
//
262-
// But we want to check what would happen if we add `*`/`.*` to the inner expression.
263-
// To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
264-
// to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
265-
// where "expr" is the `expr` parameter, `*expr` is the edited `expr`,
266-
// and "parent" is the parent of the original expression...
267-
//
268-
// For this we utilize mutable trees, which is a HACK, but it works.
269-
//
270-
// FIXME: comeup with a better API for `needs_parens_in`, so that we don't have to do *this*
271-
272-
// Make `&expr`/`expr?`
273-
let dummy_expr = {
274-
// `make::*` function go through a string, so they parse wrongly.
275-
// for example `` make::expr_try(`|| a`) `` would result in a
276-
// `|| (a?)` and not `(|| a)?`.
277-
//
278-
// Thus we need dummy parens to preserve the relationship we want.
279-
// The parens are then simply ignored by the following code.
280-
let dummy_paren = make::expr_paren(expr.clone());
281-
if postfix {
282-
make::expr_try(dummy_paren)
283-
} else {
284-
make::expr_ref(dummy_paren, false)
285-
}
286-
};
287-
288-
// Do the dark mutable tree magic.
289-
// This essentially makes `dummy_expr` and `expr` switch places (families),
290-
// so that `expr`'s parent is not `dummy_expr`'s parent.
291-
let dummy_expr = dummy_expr.clone_for_update();
292-
let expr = expr.clone_for_update();
293-
ted::replace(expr.syntax(), dummy_expr.syntax());
294-
295-
let parent = dummy_expr.syntax().parent();
296-
let Some(expr) = (|| {
297-
if postfix {
298-
let ast::Expr::TryExpr(e) = &dummy_expr else { return None };
299-
let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
300-
301-
e.expr()
302-
} else {
303-
let ast::Expr::RefExpr(e) = &dummy_expr else { return None };
304-
let Some(ast::Expr::ParenExpr(e)) = e.expr() else { return None };
305-
306-
e.expr()
307-
}
308-
})() else {
309-
never!("broken syntax tree?\n{:?}\n{:?}", expr, dummy_expr);
310-
return (true, true);
311-
};
312-
313-
// At this point
314-
// - `parent` is the parent of the original expression
315-
// - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
316-
// - `expr` is the clone of the original expression (with `dummy_expr` as the parent)
317-
318-
let needs_outer_parens = parent.is_some_and(|p| dummy_expr.needs_parens_in(p));
319-
let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
320-
321-
(needs_outer_parens, needs_inner_parens)
259+
let prec = expr.precedence();
260+
if postfix {
261+
// postfix ops have higher precedence than any other operator, so we need to wrap
262+
// any inner expression that is below (except for jumps if they don't have a value)
263+
let needs_inner_parens = prec < ExprPrecedence::Unambiguous && {
264+
prec != ExprPrecedence::Jump || !expr.is_ret_like_with_no_value()
265+
};
266+
// given we are the higher precedence, no parent expression will have stronger requirements
267+
let needs_outer_parens = false;
268+
(needs_outer_parens, needs_inner_parens)
269+
} else {
270+
// We need to wrap all binary like things, thats everything below prefix except for jumps
271+
let needs_inner_parens = prec < ExprPrecedence::Prefix && prec != ExprPrecedence::Jump;
272+
let parent = expr
273+
.syntax()
274+
.parent()
275+
.and_then(ast::Expr::cast)
276+
// if we are already wrapped, great, no need to wrap again
277+
.filter(|it| !matches!(it, ast::Expr::ParenExpr(_)))
278+
.map(|it| it.precedence());
279+
// if we have no parent, we don't need outer parens to disambiguate
280+
// otherwise anything with higher precedence than what we insert needs to wrap us
281+
let needs_outer_parens = parent.is_some_and(|prec| prec > ExprPrecedence::Prefix);
282+
(needs_outer_parens, needs_inner_parens)
283+
}
322284
}
323285

324286
#[cfg(test)]

src/tools/rust-analyzer/crates/syntax/src/ast/prec.rs

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,122 @@ use crate::{
55
match_ast, AstNode, SyntaxNode,
66
};
77

8+
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
9+
pub enum ExprPrecedence {
10+
// return, break, yield, closures
11+
Jump,
12+
// = += -= *= /= %= &= |= ^= <<= >>=
13+
Assign,
14+
// .. ..=
15+
Range,
16+
// ||
17+
LOr,
18+
// &&
19+
LAnd,
20+
// == != < > <= >=
21+
Compare,
22+
// |
23+
BitOr,
24+
// ^
25+
BitXor,
26+
// &
27+
BitAnd,
28+
// << >>
29+
Shift,
30+
// + -
31+
Sum,
32+
// * / %
33+
Product,
34+
// as
35+
Cast,
36+
// unary - * ! & &mut
37+
Prefix,
38+
// paths, loops, function calls, array indexing, field expressions, method calls
39+
Unambiguous,
40+
}
41+
42+
#[derive(PartialEq, Debug)]
43+
pub enum Fixity {
44+
/// The operator is left-associative
45+
Left,
46+
/// The operator is right-associative
47+
Right,
48+
/// The operator is not associative
49+
None,
50+
}
51+
52+
pub fn precedence(expr: &ast::Expr) -> ExprPrecedence {
53+
match expr {
54+
Expr::ClosureExpr(closure) => match closure.ret_type() {
55+
None => ExprPrecedence::Jump,
56+
Some(_) => ExprPrecedence::Unambiguous,
57+
},
58+
59+
Expr::BreakExpr(_)
60+
| Expr::ContinueExpr(_)
61+
| Expr::ReturnExpr(_)
62+
| Expr::YeetExpr(_)
63+
| Expr::YieldExpr(_) => ExprPrecedence::Jump,
64+
65+
Expr::RangeExpr(..) => ExprPrecedence::Range,
66+
67+
Expr::BinExpr(bin_expr) => match bin_expr.op_kind() {
68+
Some(it) => match it {
69+
BinaryOp::LogicOp(logic_op) => match logic_op {
70+
ast::LogicOp::And => ExprPrecedence::LAnd,
71+
ast::LogicOp::Or => ExprPrecedence::LOr,
72+
},
73+
BinaryOp::ArithOp(arith_op) => match arith_op {
74+
ast::ArithOp::Add | ast::ArithOp::Sub => ExprPrecedence::Sum,
75+
ast::ArithOp::Div | ast::ArithOp::Rem | ast::ArithOp::Mul => {
76+
ExprPrecedence::Product
77+
}
78+
ast::ArithOp::Shl | ast::ArithOp::Shr => ExprPrecedence::Shift,
79+
ast::ArithOp::BitXor => ExprPrecedence::BitXor,
80+
ast::ArithOp::BitOr => ExprPrecedence::BitOr,
81+
ast::ArithOp::BitAnd => ExprPrecedence::BitAnd,
82+
},
83+
BinaryOp::CmpOp(_) => ExprPrecedence::Compare,
84+
BinaryOp::Assignment { .. } => ExprPrecedence::Assign,
85+
},
86+
None => ExprPrecedence::Unambiguous,
87+
},
88+
Expr::CastExpr(_) => ExprPrecedence::Cast,
89+
90+
Expr::LetExpr(_) | Expr::PrefixExpr(_) | Expr::RefExpr(_) => ExprPrecedence::Prefix,
91+
92+
Expr::ArrayExpr(_)
93+
| Expr::AsmExpr(_)
94+
| Expr::AwaitExpr(_)
95+
| Expr::BecomeExpr(_)
96+
| Expr::BlockExpr(_)
97+
| Expr::CallExpr(_)
98+
| Expr::FieldExpr(_)
99+
| Expr::ForExpr(_)
100+
| Expr::FormatArgsExpr(_)
101+
| Expr::IfExpr(_)
102+
| Expr::IndexExpr(_)
103+
| Expr::Literal(_)
104+
| Expr::LoopExpr(_)
105+
| Expr::MacroExpr(_)
106+
| Expr::MatchExpr(_)
107+
| Expr::MethodCallExpr(_)
108+
| Expr::OffsetOfExpr(_)
109+
| Expr::ParenExpr(_)
110+
| Expr::PathExpr(_)
111+
| Expr::RecordExpr(_)
112+
| Expr::TryExpr(_)
113+
| Expr::TupleExpr(_)
114+
| Expr::UnderscoreExpr(_)
115+
| Expr::WhileExpr(_) => ExprPrecedence::Unambiguous,
116+
}
117+
}
118+
8119
impl Expr {
120+
pub fn precedence(&self) -> ExprPrecedence {
121+
precedence(self)
122+
}
123+
9124
// Implementation is based on
10125
// - https://doc.rust-lang.org/reference/expressions.html#expression-precedence
11126
// - https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
@@ -261,7 +376,7 @@ impl Expr {
261376
}
262377

263378
/// Returns true if self is one of `return`, `break`, `continue` or `yield` with **no associated value**.
264-
fn is_ret_like_with_no_value(&self) -> bool {
379+
pub fn is_ret_like_with_no_value(&self) -> bool {
265380
use Expr::*;
266381

267382
match self {

0 commit comments

Comments
 (0)