Skip to content

Commit 191cfba

Browse files
committed
Split inlay hints into modules
1 parent ccbf8fe commit 191cfba

10 files changed

+1096
-1017
lines changed

crates/ide/src/inlay_hints.rs

Lines changed: 25 additions & 1017 deletions
Large diffs are not rendered by default.
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use hir::{Adjust, AutoBorrow, Mutability, OverloadedDeref, PointerCast, Safety, Semantics};
2+
use ide_db::RootDatabase;
3+
4+
use syntax::ast::{self, AstNode};
5+
6+
use crate::{AdjustmentHints, InlayHint, InlayHintsConfig, InlayKind};
7+
8+
pub(super) fn hints(
9+
acc: &mut Vec<InlayHint>,
10+
sema: &Semantics<'_, RootDatabase>,
11+
config: &InlayHintsConfig,
12+
expr: &ast::Expr,
13+
) -> Option<()> {
14+
if config.adjustment_hints == AdjustmentHints::Never {
15+
return None;
16+
}
17+
18+
// These inherit from the inner expression which would result in duplicate hints
19+
if let ast::Expr::ParenExpr(_)
20+
| ast::Expr::IfExpr(_)
21+
| ast::Expr::BlockExpr(_)
22+
| ast::Expr::MatchExpr(_) = expr
23+
{
24+
return None;
25+
}
26+
27+
let parent = expr.syntax().parent().and_then(ast::Expr::cast);
28+
let descended = sema.descend_node_into_attributes(expr.clone()).pop();
29+
let desc_expr = descended.as_ref().unwrap_or(expr);
30+
let adjustments = sema.expr_adjustments(desc_expr).filter(|it| !it.is_empty())?;
31+
let needs_parens = match parent {
32+
Some(parent) => {
33+
match parent {
34+
ast::Expr::AwaitExpr(_)
35+
| ast::Expr::CallExpr(_)
36+
| ast::Expr::CastExpr(_)
37+
| ast::Expr::FieldExpr(_)
38+
| ast::Expr::MethodCallExpr(_)
39+
| ast::Expr::TryExpr(_) => true,
40+
// FIXME: shorthands need special casing, though not sure if adjustments are even valid there
41+
ast::Expr::RecordExpr(_) => false,
42+
ast::Expr::IndexExpr(index) => index.base().as_ref() == Some(expr),
43+
_ => false,
44+
}
45+
}
46+
None => false,
47+
};
48+
if needs_parens {
49+
acc.push(InlayHint {
50+
range: expr.syntax().text_range(),
51+
kind: InlayKind::OpeningParenthesis,
52+
label: "(".into(),
53+
tooltip: None,
54+
});
55+
}
56+
for adjustment in adjustments.into_iter().rev() {
57+
// FIXME: Add some nicer tooltips to each of these
58+
let text = match adjustment {
59+
Adjust::NeverToAny if config.adjustment_hints == AdjustmentHints::Always => {
60+
"<never-to-any>"
61+
}
62+
Adjust::Deref(None) => "*",
63+
Adjust::Deref(Some(OverloadedDeref(Mutability::Mut))) => "*",
64+
Adjust::Deref(Some(OverloadedDeref(Mutability::Shared))) => "*",
65+
Adjust::Borrow(AutoBorrow::Ref(Mutability::Shared)) => "&",
66+
Adjust::Borrow(AutoBorrow::Ref(Mutability::Mut)) => "&mut ",
67+
Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Shared)) => "&raw const ",
68+
Adjust::Borrow(AutoBorrow::RawPtr(Mutability::Mut)) => "&raw mut ",
69+
// some of these could be represented via `as` casts, but that's not too nice and
70+
// handling everything as a prefix expr makes the `(` and `)` insertion easier
71+
Adjust::Pointer(cast) if config.adjustment_hints == AdjustmentHints::Always => {
72+
match cast {
73+
PointerCast::ReifyFnPointer => "<fn-item-to-fn-pointer>",
74+
PointerCast::UnsafeFnPointer => "<safe-fn-pointer-to-unsafe-fn-pointer>",
75+
PointerCast::ClosureFnPointer(Safety::Unsafe) => {
76+
"<closure-to-unsafe-fn-pointer>"
77+
}
78+
PointerCast::ClosureFnPointer(Safety::Safe) => "<closure-to-fn-pointer>",
79+
PointerCast::MutToConstPointer => "<mut-ptr-to-const-ptr>",
80+
PointerCast::ArrayToPointer => "<array-ptr-to-element-ptr>",
81+
PointerCast::Unsize => "<unsize>",
82+
}
83+
}
84+
_ => continue,
85+
};
86+
acc.push(InlayHint {
87+
range: expr.syntax().text_range(),
88+
kind: InlayKind::AdjustmentHint,
89+
label: text.into(),
90+
tooltip: None,
91+
});
92+
}
93+
if needs_parens {
94+
acc.push(InlayHint {
95+
range: expr.syntax().text_range(),
96+
kind: InlayKind::ClosingParenthesis,
97+
label: ")".into(),
98+
tooltip: None,
99+
});
100+
}
101+
Some(())
102+
}
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use hir::{HirDisplay, Semantics, TypeInfo};
2+
use ide_db::{base_db::FileId, famous_defs::FamousDefs, RootDatabase};
3+
4+
use itertools::Itertools;
5+
use syntax::{
6+
ast::{self, AstNode, HasName},
7+
match_ast,
8+
};
9+
10+
use crate::{
11+
inlay_hints::{closure_has_block_body, hint_iterator},
12+
InlayHint, InlayHintsConfig, InlayKind, InlayTooltip,
13+
};
14+
15+
pub(super) fn hints(
16+
acc: &mut Vec<InlayHint>,
17+
sema: &Semantics<'_, RootDatabase>,
18+
config: &InlayHintsConfig,
19+
file_id: FileId,
20+
pat: &ast::IdentPat,
21+
) -> Option<()> {
22+
if !config.type_hints {
23+
return None;
24+
}
25+
26+
let descended = sema.descend_node_into_attributes(pat.clone()).pop();
27+
let desc_pat = descended.as_ref().unwrap_or(pat);
28+
let ty = sema.type_of_pat(&desc_pat.clone().into())?.original;
29+
30+
if should_not_display_type_hint(sema, config, pat, &ty) {
31+
return None;
32+
}
33+
34+
let krate = sema.scope(desc_pat.syntax())?.krate();
35+
let famous_defs = FamousDefs(sema, krate);
36+
let label = hint_iterator(sema, &famous_defs, config, &ty);
37+
38+
let label = match label {
39+
Some(label) => label,
40+
None => {
41+
let ty_name = ty.display_truncated(sema.db, config.max_length).to_string();
42+
if config.hide_named_constructor_hints
43+
&& is_named_constructor(sema, pat, &ty_name).is_some()
44+
{
45+
return None;
46+
}
47+
ty_name
48+
}
49+
};
50+
51+
acc.push(InlayHint {
52+
range: match pat.name() {
53+
Some(name) => name.syntax().text_range(),
54+
None => pat.syntax().text_range(),
55+
},
56+
kind: InlayKind::TypeHint,
57+
label: label.into(),
58+
tooltip: pat
59+
.name()
60+
.map(|it| it.syntax().text_range())
61+
.map(|it| InlayTooltip::HoverRanged(file_id, it)),
62+
});
63+
64+
Some(())
65+
}
66+
67+
fn should_not_display_type_hint(
68+
sema: &Semantics<'_, RootDatabase>,
69+
config: &InlayHintsConfig,
70+
bind_pat: &ast::IdentPat,
71+
pat_ty: &hir::Type,
72+
) -> bool {
73+
let db = sema.db;
74+
75+
if pat_ty.is_unknown() {
76+
return true;
77+
}
78+
79+
if let Some(hir::Adt::Struct(s)) = pat_ty.as_adt() {
80+
if s.fields(db).is_empty() && s.name(db).to_smol_str() == bind_pat.to_string() {
81+
return true;
82+
}
83+
}
84+
85+
if config.hide_closure_initialization_hints {
86+
if let Some(parent) = bind_pat.syntax().parent() {
87+
if let Some(it) = ast::LetStmt::cast(parent.clone()) {
88+
if let Some(ast::Expr::ClosureExpr(closure)) = it.initializer() {
89+
if closure_has_block_body(&closure) {
90+
return true;
91+
}
92+
}
93+
}
94+
}
95+
}
96+
97+
for node in bind_pat.syntax().ancestors() {
98+
match_ast! {
99+
match node {
100+
ast::LetStmt(it) => return it.ty().is_some(),
101+
// FIXME: We might wanna show type hints in parameters for non-top level patterns as well
102+
ast::Param(it) => return it.ty().is_some(),
103+
ast::MatchArm(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
104+
ast::LetExpr(_) => return pat_is_enum_variant(db, bind_pat, pat_ty),
105+
ast::IfExpr(_) => return false,
106+
ast::WhileExpr(_) => return false,
107+
ast::ForExpr(it) => {
108+
// We *should* display hint only if user provided "in {expr}" and we know the type of expr (and it's not unit).
109+
// Type of expr should be iterable.
110+
return it.in_token().is_none() ||
111+
it.iterable()
112+
.and_then(|iterable_expr| sema.type_of_expr(&iterable_expr))
113+
.map(TypeInfo::original)
114+
.map_or(true, |iterable_ty| iterable_ty.is_unknown() || iterable_ty.is_unit())
115+
},
116+
_ => (),
117+
}
118+
}
119+
}
120+
false
121+
}
122+
123+
fn is_named_constructor(
124+
sema: &Semantics<'_, RootDatabase>,
125+
pat: &ast::IdentPat,
126+
ty_name: &str,
127+
) -> Option<()> {
128+
let let_node = pat.syntax().parent()?;
129+
let expr = match_ast! {
130+
match let_node {
131+
ast::LetStmt(it) => it.initializer(),
132+
ast::LetExpr(it) => it.expr(),
133+
_ => None,
134+
}
135+
}?;
136+
137+
let expr = sema.descend_node_into_attributes(expr.clone()).pop().unwrap_or(expr);
138+
// unwrap postfix expressions
139+
let expr = match expr {
140+
ast::Expr::TryExpr(it) => it.expr(),
141+
ast::Expr::AwaitExpr(it) => it.expr(),
142+
expr => Some(expr),
143+
}?;
144+
let expr = match expr {
145+
ast::Expr::CallExpr(call) => match call.expr()? {
146+
ast::Expr::PathExpr(path) => path,
147+
_ => return None,
148+
},
149+
ast::Expr::PathExpr(path) => path,
150+
_ => return None,
151+
};
152+
let path = expr.path()?;
153+
154+
let callable = sema.type_of_expr(&ast::Expr::PathExpr(expr))?.original.as_callable(sema.db);
155+
let callable_kind = callable.map(|it| it.kind());
156+
let qual_seg = match callable_kind {
157+
Some(hir::CallableKind::Function(_) | hir::CallableKind::TupleEnumVariant(_)) => {
158+
path.qualifier()?.segment()
159+
}
160+
_ => path.segment(),
161+
}?;
162+
163+
let ctor_name = match qual_seg.kind()? {
164+
ast::PathSegmentKind::Name(name_ref) => {
165+
match qual_seg.generic_arg_list().map(|it| it.generic_args()) {
166+
Some(generics) => format!("{}<{}>", name_ref, generics.format(", ")),
167+
None => name_ref.to_string(),
168+
}
169+
}
170+
ast::PathSegmentKind::Type { type_ref: Some(ty), trait_ref: None } => ty.to_string(),
171+
_ => return None,
172+
};
173+
(ctor_name == ty_name).then(|| ())
174+
}
175+
176+
fn pat_is_enum_variant(db: &RootDatabase, bind_pat: &ast::IdentPat, pat_ty: &hir::Type) -> bool {
177+
if let Some(hir::Adt::Enum(enum_data)) = pat_ty.as_adt() {
178+
let pat_text = bind_pat.to_string();
179+
enum_data
180+
.variants(db)
181+
.into_iter()
182+
.map(|variant| variant.name(db).to_smol_str())
183+
.any(|enum_name| enum_name == pat_text)
184+
} else {
185+
false
186+
}
187+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use hir::{Mutability, Semantics};
2+
use ide_db::RootDatabase;
3+
4+
use syntax::ast::{self, AstNode};
5+
6+
use crate::{InlayHint, InlayHintsConfig, InlayKind, InlayTooltip};
7+
8+
pub(super) fn hints(
9+
acc: &mut Vec<InlayHint>,
10+
sema: &Semantics<'_, RootDatabase>,
11+
config: &InlayHintsConfig,
12+
pat: &ast::Pat,
13+
) -> Option<()> {
14+
if !config.binding_mode_hints {
15+
return None;
16+
}
17+
18+
let outer_paren_pat = pat
19+
.syntax()
20+
.ancestors()
21+
.skip(1)
22+
.map_while(ast::Pat::cast)
23+
.map_while(|pat| match pat {
24+
ast::Pat::ParenPat(pat) => Some(pat),
25+
_ => None,
26+
})
27+
.last();
28+
let range =
29+
outer_paren_pat.as_ref().map_or_else(|| pat.syntax(), |it| it.syntax()).text_range();
30+
sema.pattern_adjustments(&pat).iter().for_each(|ty| {
31+
let reference = ty.is_reference();
32+
let mut_reference = ty.is_mutable_reference();
33+
let r = match (reference, mut_reference) {
34+
(true, true) => "&mut",
35+
(true, false) => "&",
36+
_ => return,
37+
};
38+
acc.push(InlayHint {
39+
range,
40+
kind: InlayKind::BindingModeHint,
41+
label: r.to_string().into(),
42+
tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
43+
});
44+
});
45+
match pat {
46+
ast::Pat::IdentPat(pat) if pat.ref_token().is_none() && pat.mut_token().is_none() => {
47+
let bm = sema.binding_mode_of_pat(pat)?;
48+
let bm = match bm {
49+
hir::BindingMode::Move => return None,
50+
hir::BindingMode::Ref(Mutability::Mut) => "ref mut",
51+
hir::BindingMode::Ref(Mutability::Shared) => "ref",
52+
};
53+
acc.push(InlayHint {
54+
range: pat.syntax().text_range(),
55+
kind: InlayKind::BindingModeHint,
56+
label: bm.to_string().into(),
57+
tooltip: Some(InlayTooltip::String("Inferred binding mode".into())),
58+
});
59+
}
60+
ast::Pat::OrPat(pat) if outer_paren_pat.is_none() => {
61+
acc.push(InlayHint {
62+
range: pat.syntax().text_range(),
63+
kind: InlayKind::OpeningParenthesis,
64+
label: "(".into(),
65+
tooltip: None,
66+
});
67+
acc.push(InlayHint {
68+
range: pat.syntax().text_range(),
69+
kind: InlayKind::ClosingParenthesis,
70+
label: ")".into(),
71+
tooltip: None,
72+
});
73+
}
74+
_ => (),
75+
}
76+
77+
Some(())
78+
}

0 commit comments

Comments
 (0)