Skip to content

Commit 6918009

Browse files
committed
Auto merge of rust-lang#13638 - DesmondWillowbrook:hover-rest-pat-mvp, r=Veykril
feat: adds hover hint to ".." in record pattern Hovering on the "rest" pattern in struct destructuring, ```rust struct Baz { a: u32, b: u32, c: u32, d: u32 } let Baz { a, b, ..$0} = Baz { a: 1, b: 2, c: 3, d: 4 }; ``` shows: ``` .., c: u32, d: u32 ``` Currently only works with struct patterns. ![image](https://user-images.githubusercontent.com/51814158/202837115-f424cc26-c2d7-4027-8eea-eeb7749ad146.png)
2 parents 1e6a49a + a26aef9 commit 6918009

File tree

3 files changed

+140
-44
lines changed

3 files changed

+140
-44
lines changed

crates/ide/src/hover.rs

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ pub(crate) fn hover(
127127
original_token.parent().and_then(ast::TokenTree::cast),
128128
Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
129129
);
130+
130131
// prefer descending the same token kind in attribute expansions, in normal macros text
131132
// equivalency is more important
132133
let descended = if in_attr {
@@ -135,54 +136,67 @@ pub(crate) fn hover(
135136
sema.descend_into_macros_with_same_text(original_token.clone())
136137
};
137138

138-
// FIXME: Definition should include known lints and the like instead of having this special case here
139-
let hovered_lint = descended.iter().find_map(|token| {
140-
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
141-
render::try_for_lint(&attr, token)
142-
});
143-
if let Some(res) = hovered_lint {
144-
return Some(RangeInfo::new(original_token.text_range(), res));
145-
}
146-
139+
// try lint hover
147140
let result = descended
148141
.iter()
149-
.filter_map(|token| {
150-
let node = token.parent()?;
151-
let class = IdentClass::classify_token(sema, token)?;
152-
if let IdentClass::Operator(OperatorClass::Await(_)) = class {
153-
// It's better for us to fall back to the keyword hover here,
154-
// rendering poll is very confusing
155-
return None;
156-
}
157-
Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
142+
.find_map(|token| {
143+
// FIXME: Definition should include known lints and the like instead of having this special case here
144+
let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
145+
render::try_for_lint(&attr, token)
158146
})
159-
.flatten()
160-
.unique_by(|&(def, _)| def)
161-
.filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
162-
.reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
163-
acc.actions.extend(actions);
164-
acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
165-
acc
166-
});
147+
// try item definitions
148+
.or_else(|| {
149+
descended
150+
.iter()
151+
.filter_map(|token| {
152+
let node = token.parent()?;
153+
let class = IdentClass::classify_token(sema, token)?;
154+
if let IdentClass::Operator(OperatorClass::Await(_)) = class {
155+
// It's better for us to fall back to the keyword hover here,
156+
// rendering poll is very confusing
157+
return None;
158+
}
159+
Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
160+
})
161+
.flatten()
162+
.unique_by(|&(def, _)| def)
163+
.filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
164+
.reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
165+
acc.actions.extend(actions);
166+
acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
167+
acc
168+
})
169+
})
170+
// try keywords
171+
.or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
172+
// try rest item hover
173+
.or_else(|| {
174+
descended.iter().find_map(|token| {
175+
if token.kind() != DOT2 {
176+
return None;
177+
}
167178

168-
if result.is_none() {
169-
// fallbacks, show keywords or types
179+
let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
180+
let record_pat_field_list =
181+
rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
170182

171-
let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
172-
if let Some(res) = res {
173-
return Some(RangeInfo::new(original_token.text_range(), res));
174-
}
175-
let res = descended
176-
.iter()
177-
.find_map(|token| hover_type_fallback(sema, config, token, &original_token));
178-
if let Some(_) = res {
179-
return res;
180-
}
181-
}
182-
result.map(|mut res: HoverResult| {
183-
res.actions = dedupe_or_merge_hover_actions(res.actions);
184-
RangeInfo::new(original_token.text_range(), res)
185-
})
183+
let record_pat =
184+
record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
185+
186+
Some(render::struct_rest_pat(sema, config, &record_pat))
187+
})
188+
});
189+
190+
result
191+
.map(|mut res: HoverResult| {
192+
res.actions = dedupe_or_merge_hover_actions(res.actions);
193+
RangeInfo::new(original_token.text_range(), res)
194+
})
195+
// fallback to type hover if there aren't any other suggestions
196+
// this finds its own range instead of using the closest token's range
197+
.or_else(|| {
198+
descended.iter().find_map(|token| hover_type_fallback(sema, config, token, &token))
199+
})
186200
}
187201

188202
pub(crate) fn hover_for_definition(
@@ -269,6 +283,7 @@ fn hover_type_fallback(
269283
};
270284

271285
let res = render::type_info(sema, config, &expr_or_pat)?;
286+
272287
let range = sema
273288
.original_range_opt(&node)
274289
.map(|frange| frange.range)

crates/ide/src/hover/render.rs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ use ide_db::{
1414
use itertools::Itertools;
1515
use stdx::format_to;
1616
use syntax::{
17-
algo, ast, match_ast, AstNode, Direction,
17+
algo,
18+
ast::{self, RecordPat},
19+
match_ast, AstNode, Direction,
1820
SyntaxKind::{LET_EXPR, LET_STMT},
1921
SyntaxToken, T,
2022
};
@@ -250,6 +252,50 @@ pub(super) fn keyword(
250252
Some(HoverResult { markup, actions })
251253
}
252254

255+
/// Returns missing types in a record pattern.
256+
/// Only makes sense when there's a rest pattern in the record pattern.
257+
/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
258+
pub(super) fn struct_rest_pat(
259+
sema: &Semantics<'_, RootDatabase>,
260+
config: &HoverConfig,
261+
pattern: &RecordPat,
262+
) -> HoverResult {
263+
let missing_fields = sema.record_pattern_missing_fields(pattern);
264+
265+
// if there are no missing fields, the end result is a hover that shows ".."
266+
// should be left in to indicate that there are no more fields in the pattern
267+
// example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
268+
269+
let mut res = HoverResult::default();
270+
let mut targets: Vec<hir::ModuleDef> = Vec::new();
271+
let mut push_new_def = |item: hir::ModuleDef| {
272+
if !targets.contains(&item) {
273+
targets.push(item);
274+
}
275+
};
276+
for (_, t) in &missing_fields {
277+
walk_and_push_ty(sema.db, &t, &mut push_new_def);
278+
}
279+
280+
res.markup = {
281+
let mut s = String::from(".., ");
282+
for (f, _) in &missing_fields {
283+
s += f.display(sema.db).to_string().as_ref();
284+
s += ", ";
285+
}
286+
// get rid of trailing comma
287+
s.truncate(s.len() - 2);
288+
289+
if config.markdown() {
290+
Markup::fenced_block(&s)
291+
} else {
292+
s.into()
293+
}
294+
};
295+
res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
296+
res
297+
}
298+
253299
pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
254300
let (path, tt) = attr.as_simple_call()?;
255301
if !tt.syntax().text_range().contains(token.text_range().start()) {

crates/ide/src/hover/tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5307,3 +5307,38 @@ fn main() { $0V; }
53075307
"#]],
53085308
);
53095309
}
5310+
5311+
#[test]
5312+
fn hover_rest_pat() {
5313+
check(
5314+
r#"
5315+
struct Struct {a: u32, b: u32, c: u8, d: u16};
5316+
5317+
fn main() {
5318+
let Struct {a, c, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
5319+
}
5320+
"#,
5321+
expect![[r#"
5322+
*..*
5323+
```rust
5324+
.., b: u32, d: u16
5325+
```
5326+
"#]],
5327+
);
5328+
5329+
check(
5330+
r#"
5331+
struct Struct {a: u32, b: u32, c: u8, d: u16};
5332+
5333+
fn main() {
5334+
let Struct {a, b, c, d, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
5335+
}
5336+
"#,
5337+
expect![[r#"
5338+
*..*
5339+
```rust
5340+
..
5341+
```
5342+
"#]],
5343+
);
5344+
}

0 commit comments

Comments
 (0)