Skip to content

Commit b89c4f0

Browse files
committed
Implement postfix adjustment hints
I'd say "First stab at implementing..." but I've been working on this for a month already lol
1 parent ae65912 commit b89c4f0

File tree

7 files changed

+246
-27
lines changed

7 files changed

+246
-27
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub struct InlayHintsConfig {
3535
pub parameter_hints: bool,
3636
pub chaining_hints: bool,
3737
pub adjustment_hints: AdjustmentHints,
38+
pub adjustment_hints_postfix: bool,
3839
pub adjustment_hints_hide_outside_unsafe: bool,
3940
pub closure_return_type_hints: ClosureReturnTypeHints,
4041
pub binding_mode_hints: bool,
@@ -82,6 +83,7 @@ pub enum InlayKind {
8283
ClosureReturnTypeHint,
8384
GenericParamListHint,
8485
AdjustmentHint,
86+
AdjustmentHintPostfix,
8587
LifetimeHint,
8688
ParameterHint,
8789
TypeHint,
@@ -446,6 +448,7 @@ mod tests {
446448
lifetime_elision_hints: LifetimeElisionHints::Never,
447449
closure_return_type_hints: ClosureReturnTypeHints::Never,
448450
adjustment_hints: AdjustmentHints::Never,
451+
adjustment_hints_postfix: false,
449452
adjustment_hints_hide_outside_unsafe: false,
450453
binding_mode_hints: false,
451454
hide_named_constructor_hints: false,

crates/ide/src/inlay_hints/adjustment.rs

Lines changed: 225 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
//! ```
66
use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics};
77
use ide_db::RootDatabase;
8-
use syntax::ast::{self, AstNode};
8+
9+
use syntax::{
10+
ast::{self, make, AstNode},
11+
ted,
12+
};
913

1014
use crate::{AdjustmentHints, InlayHint, InlayHintsConfig, InlayKind};
1115

@@ -32,36 +36,47 @@ pub(super) fn hints(
3236
return None;
3337
}
3438

35-
let parent = expr.syntax().parent().and_then(ast::Expr::cast);
3639
let descended = sema.descend_node_into_attributes(expr.clone()).pop();
3740
let desc_expr = descended.as_ref().unwrap_or(expr);
3841
let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
39-
let needs_parens = match parent {
40-
Some(parent) => {
41-
match parent {
42-
ast::Expr::AwaitExpr(_)
43-
| ast::Expr::CallExpr(_)
44-
| ast::Expr::CastExpr(_)
45-
| ast::Expr::FieldExpr(_)
46-
| ast::Expr::MethodCallExpr(_)
47-
| ast::Expr::TryExpr(_) => true,
48-
// FIXME: shorthands need special casing, though not sure if adjustments are even valid there
49-
ast::Expr::RecordExpr(_) => false,
50-
ast::Expr::IndexExpr(index) => index.base().as_ref() == Some(expr),
51-
_ => false,
52-
}
53-
}
54-
None => false,
55-
};
56-
if needs_parens {
42+
43+
let (needs_outer_parens, needs_inner_parens) =
44+
needs_parens_for_adjustment_hints(expr, config.adjustment_hints_postfix);
45+
46+
if needs_outer_parens {
5747
acc.push(InlayHint {
5848
range: expr.syntax().text_range(),
5949
kind: InlayKind::OpeningParenthesis,
6050
label: "(".into(),
6151
tooltip: None,
6252
});
6353
}
64-
for adjustment in adjustments.into_iter().rev() {
54+
55+
if config.adjustment_hints_postfix && needs_inner_parens {
56+
acc.push(InlayHint {
57+
range: expr.syntax().text_range(),
58+
kind: InlayKind::OpeningParenthesis,
59+
label: "(".into(),
60+
tooltip: None,
61+
});
62+
acc.push(InlayHint {
63+
range: expr.syntax().text_range(),
64+
kind: InlayKind::ClosingParenthesis,
65+
label: ")".into(),
66+
tooltip: None,
67+
});
68+
}
69+
70+
let (mut tmp0, mut tmp1);
71+
let iter: &mut dyn Iterator<Item = _> = if config.adjustment_hints_postfix {
72+
tmp0 = adjustments.into_iter();
73+
&mut tmp0
74+
} else {
75+
tmp1 = adjustments.into_iter().rev();
76+
&mut tmp1
77+
};
78+
79+
for adjustment in iter {
6580
if adjustment.source == adjustment.target {
6681
continue;
6782
}
@@ -97,12 +112,34 @@ pub(super) fn hints(
97112
};
98113
acc.push(InlayHint {
99114
range: expr.syntax().text_range(),
100-
kind: InlayKind::AdjustmentHint,
101-
label: text.into(),
115+
kind: if config.adjustment_hints_postfix {
116+
InlayKind::AdjustmentHintPostfix
117+
} else {
118+
InlayKind::AdjustmentHint
119+
},
120+
label: if config.adjustment_hints_postfix {
121+
format!(".{}", text.trim_end()).into()
122+
} else {
123+
text.into()
124+
},
102125
tooltip: None,
103126
});
104127
}
105-
if needs_parens {
128+
if !config.adjustment_hints_postfix && needs_inner_parens {
129+
acc.push(InlayHint {
130+
range: expr.syntax().text_range(),
131+
kind: InlayKind::OpeningParenthesis,
132+
label: "(".into(),
133+
tooltip: None,
134+
});
135+
acc.push(InlayHint {
136+
range: expr.syntax().text_range(),
137+
kind: InlayKind::ClosingParenthesis,
138+
label: ")".into(),
139+
tooltip: None,
140+
});
141+
}
142+
if needs_outer_parens {
106143
acc.push(InlayHint {
107144
range: expr.syntax().text_range(),
108145
kind: InlayKind::ClosingParenthesis,
@@ -113,6 +150,69 @@ pub(super) fn hints(
113150
Some(())
114151
}
115152

153+
/// Returns whatever we need to add paretheses on the inside and/or outside of `expr`,
154+
/// if we are going to add (`postfix`) adjustments hints to it.
155+
fn needs_parens_for_adjustment_hints(expr: &ast::Expr, postfix: bool) -> (bool, bool) {
156+
// This is a very miserable pile of hacks...
157+
//
158+
// `Expr::needs_parens_in` requires that the expression is the child of the other expression,
159+
// that is supposed to be its parent.
160+
//
161+
// But we want to check what would happen if we add `*`/`.*` to the inner expression.
162+
// To check for inner we need `` expr.needs_parens_in(`*expr`) ``,
163+
// to check for outer we need `` `*expr`.needs_parens_in(parent) ``,
164+
// where "expr" is the `expr` parameter, `*expr` is the editted `expr`,
165+
// and "parent" is the parent of the original expression...
166+
//
167+
// For this we utilize mutable mutable trees, which is a HACK, but it works.
168+
169+
// Make `&expr`/`expr?`
170+
let dummy_expr = {
171+
// `make::*` function go through a string, so they parse wrongly.
172+
// for example `` make::expr_try(`|| a`) `` would result in a
173+
// `|| (a?)` and not `(|| a)?`.
174+
//
175+
// Thus we need dummy parens to preserve the relationship we want.
176+
// The parens are then simply ignored by the following code.
177+
let dummy_paren = make::expr_paren(expr.clone());
178+
if postfix {
179+
make::expr_try(dummy_paren)
180+
} else {
181+
make::expr_ref(dummy_paren, false)
182+
}
183+
};
184+
185+
// Do the dark mutable tree magic.
186+
// This essentially makes `dummy_expr` and `expr` switch places (families),
187+
// so that `expr`'s parent is not `dummy_expr`'s parent.
188+
let dummy_expr = dummy_expr.clone_for_update();
189+
let expr = expr.clone_for_update();
190+
ted::replace(expr.syntax(), dummy_expr.syntax());
191+
192+
let parent = dummy_expr.syntax().parent();
193+
let expr = if postfix {
194+
let ast::Expr::TryExpr(e) = &dummy_expr else { unreachable!() };
195+
let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
196+
197+
e.expr().unwrap()
198+
} else {
199+
let ast::Expr::RefExpr(e) = &dummy_expr else { unreachable!() };
200+
let Some(ast::Expr::ParenExpr(e)) = e.expr() else { unreachable!() };
201+
202+
e.expr().unwrap()
203+
};
204+
205+
// At this point
206+
// - `parent` is the parrent of the original expression
207+
// - `dummy_expr` is the original expression wrapped in the operator we want (`*`/`.*`)
208+
// - `expr` is the clone of the original expression (with `dummy_expr` as the parent)
209+
210+
let needs_outer_parens = parent.map_or(false, |p| dummy_expr.needs_parens_in(p));
211+
let needs_inner_parens = expr.needs_parens_in(dummy_expr.syntax().clone());
212+
213+
(needs_outer_parens, needs_inner_parens)
214+
}
215+
116216
#[cfg(test)]
117217
mod tests {
118218
use crate::{
@@ -125,7 +225,7 @@ mod tests {
125225
check_with_config(
126226
InlayHintsConfig { adjustment_hints: AdjustmentHints::Always, ..DISABLED_CONFIG },
127227
r#"
128-
//- minicore: coerce_unsized
228+
//- minicore: coerce_unsized, fn
129229
fn main() {
130230
let _: u32 = loop {};
131231
//^^^^^^^<never-to-any>
@@ -148,12 +248,16 @@ fn main() {
148248
//^^^^<fn-item-to-fn-pointer>
149249
let _: unsafe fn() = main as fn();
150250
//^^^^^^^^^^^^<safe-fn-pointer-to-unsafe-fn-pointer>
251+
//^^^^^^^^^^^^(
252+
//^^^^^^^^^^^^)
151253
let _: fn() = || {};
152254
//^^^^^<closure-to-fn-pointer>
153255
let _: unsafe fn() = || {};
154256
//^^^^^<closure-to-unsafe-fn-pointer>
155257
let _: *const u32 = &mut 0u32 as *mut u32;
156258
//^^^^^^^^^^^^^^^^^^^^^<mut-ptr-to-const-ptr>
259+
//^^^^^^^^^^^^^^^^^^^^^(
260+
//^^^^^^^^^^^^^^^^^^^^^)
157261
let _: &mut [_] = &mut [0; 0];
158262
//^^^^^^^^^^^<unsize>
159263
//^^^^^^^^^^^&mut $
@@ -206,6 +310,11 @@ fn main() {
206310
//^^^^^^^<unsize>
207311
//^^^^^^^&mut $
208312
//^^^^^^^*
313+
314+
let _: &mut dyn Fn() = &mut || ();
315+
//^^^^^^^^^^<unsize>
316+
//^^^^^^^^^^&mut $
317+
//^^^^^^^^^^*
209318
}
210319
211320
#[derive(Copy, Clone)]
@@ -215,12 +324,101 @@ impl Struct {
215324
fn by_ref(&self) {}
216325
fn by_ref_mut(&mut self) {}
217326
}
218-
trait Trait {}
219-
impl Trait for Struct {}
220327
"#,
221328
)
222329
}
223330

331+
#[test]
332+
fn adjustment_hints_postfix() {
333+
check_with_config(
334+
InlayHintsConfig {
335+
adjustment_hints: AdjustmentHints::Always,
336+
adjustment_hints_postfix: true,
337+
..DISABLED_CONFIG
338+
},
339+
r#"
340+
//- minicore: coerce_unsized, fn
341+
fn main() {
342+
343+
Struct.consume();
344+
Struct.by_ref();
345+
//^^^^^^.&
346+
Struct.by_ref_mut();
347+
//^^^^^^.&mut
348+
349+
(&Struct).consume();
350+
//^^^^^^^(
351+
//^^^^^^^)
352+
//^^^^^^^.*
353+
(&Struct).by_ref();
354+
355+
(&mut Struct).consume();
356+
//^^^^^^^^^^^(
357+
//^^^^^^^^^^^)
358+
//^^^^^^^^^^^.*
359+
(&mut Struct).by_ref();
360+
//^^^^^^^^^^^(
361+
//^^^^^^^^^^^)
362+
//^^^^^^^^^^^.*
363+
//^^^^^^^^^^^.&
364+
(&mut Struct).by_ref_mut();
365+
366+
// Check that block-like expressions don't duplicate hints
367+
let _: &mut [u32] = (&mut []);
368+
//^^^^^^^(
369+
//^^^^^^^)
370+
//^^^^^^^.*
371+
//^^^^^^^.&mut
372+
//^^^^^^^.<unsize>
373+
let _: &mut [u32] = { &mut [] };
374+
//^^^^^^^(
375+
//^^^^^^^)
376+
//^^^^^^^.*
377+
//^^^^^^^.&mut
378+
//^^^^^^^.<unsize>
379+
let _: &mut [u32] = unsafe { &mut [] };
380+
//^^^^^^^(
381+
//^^^^^^^)
382+
//^^^^^^^.*
383+
//^^^^^^^.&mut
384+
//^^^^^^^.<unsize>
385+
let _: &mut [u32] = if true {
386+
&mut []
387+
//^^^^^^^(
388+
//^^^^^^^)
389+
//^^^^^^^.*
390+
//^^^^^^^.&mut
391+
//^^^^^^^.<unsize>
392+
} else {
393+
loop {}
394+
//^^^^^^^.<never-to-any>
395+
};
396+
let _: &mut [u32] = match () { () => &mut [] }
397+
//^^^^^^^(
398+
//^^^^^^^)
399+
//^^^^^^^.*
400+
//^^^^^^^.&mut
401+
//^^^^^^^.<unsize>
402+
403+
let _: &mut dyn Fn() = &mut || ();
404+
//^^^^^^^^^^(
405+
//^^^^^^^^^^)
406+
//^^^^^^^^^^.*
407+
//^^^^^^^^^^.&mut
408+
//^^^^^^^^^^.<unsize>
409+
}
410+
411+
#[derive(Copy, Clone)]
412+
struct Struct;
413+
impl Struct {
414+
fn consume(self) {}
415+
fn by_ref(&self) {}
416+
fn by_ref_mut(&mut self) {}
417+
}
418+
"#,
419+
);
420+
}
421+
224422
#[test]
225423
fn never_to_never_is_never_shown() {
226424
check_with_config(

crates/ide/src/static_index.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ impl StaticIndex<'_> {
115115
closure_return_type_hints: crate::ClosureReturnTypeHints::WithBlock,
116116
lifetime_elision_hints: crate::LifetimeElisionHints::Never,
117117
adjustment_hints: crate::AdjustmentHints::Never,
118+
adjustment_hints_postfix: false,
118119
adjustment_hints_hide_outside_unsafe: false,
119120
hide_named_constructor_hints: false,
120121
hide_closure_initialization_hints: false,

crates/rust-analyzer/src/config.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,8 @@ config_data! {
333333
inlayHints_expressionAdjustmentHints_enable: AdjustmentHintsDef = "\"never\"",
334334
/// Whether to hide inlay hints for type adjustments outside of `unsafe` blocks.
335335
inlayHints_expressionAdjustmentHints_hideOutsideUnsafe: bool = "false",
336+
/// Whether to show inlay hints for type adjustments as postfix ops (`.*` instead of `*`, etc).
337+
inlayHints_expressionAdjustmentHints_postfix: bool = "false",
336338
/// Whether to show inlay type hints for elided lifetimes in function signatures.
337339
inlayHints_lifetimeElisionHints_enable: LifetimeElisionDef = "\"never\"",
338340
/// Whether to prefer using parameter names as the name for elided lifetime hints if possible.
@@ -1252,6 +1254,7 @@ impl Config {
12521254
},
12531255
AdjustmentHintsDef::Reborrow => ide::AdjustmentHints::ReborrowOnly,
12541256
},
1257+
adjustment_hints_postfix: self.data.inlayHints_expressionAdjustmentHints_postfix,
12551258
adjustment_hints_hide_outside_unsafe: self
12561259
.data
12571260
.inlayHints_expressionAdjustmentHints_hideOutsideUnsafe,

0 commit comments

Comments
 (0)