Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #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)
bors 2022-11-25
parent 1e6a49a · parent a26aef9 · commit 6918009
-rw-r--r--crates/ide/src/hover.rs101
-rw-r--r--crates/ide/src/hover/render.rs48
-rw-r--r--crates/ide/src/hover/tests.rs35
3 files changed, 140 insertions, 44 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs
index 838fb18c3d..9cbfed4763 100644
--- a/crates/ide/src/hover.rs
+++ b/crates/ide/src/hover.rs
@@ -127,6 +127,7 @@ pub(crate) fn hover(
original_token.parent().and_then(ast::TokenTree::cast),
Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind()))
);
+
// prefer descending the same token kind in attribute expansions, in normal macros text
// equivalency is more important
let descended = if in_attr {
@@ -135,54 +136,67 @@ pub(crate) fn hover(
sema.descend_into_macros_with_same_text(original_token.clone())
};
- // FIXME: Definition should include known lints and the like instead of having this special case here
- let hovered_lint = descended.iter().find_map(|token| {
- let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
- render::try_for_lint(&attr, token)
- });
- if let Some(res) = hovered_lint {
- return Some(RangeInfo::new(original_token.text_range(), res));
- }
-
+ // try lint hover
let result = descended
.iter()
- .filter_map(|token| {
- let node = token.parent()?;
- let class = IdentClass::classify_token(sema, token)?;
- if let IdentClass::Operator(OperatorClass::Await(_)) = class {
- // It's better for us to fall back to the keyword hover here,
- // rendering poll is very confusing
- return None;
- }
- Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ .find_map(|token| {
+ // FIXME: Definition should include known lints and the like instead of having this special case here
+ let attr = token.parent_ancestors().find_map(ast::Attr::cast)?;
+ render::try_for_lint(&attr, token)
})
- .flatten()
- .unique_by(|&(def, _)| def)
- .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
- .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
- acc.actions.extend(actions);
- acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
- acc
- });
+ // try item definitions
+ .or_else(|| {
+ descended
+ .iter()
+ .filter_map(|token| {
+ let node = token.parent()?;
+ let class = IdentClass::classify_token(sema, token)?;
+ if let IdentClass::Operator(OperatorClass::Await(_)) = class {
+ // It's better for us to fall back to the keyword hover here,
+ // rendering poll is very confusing
+ return None;
+ }
+ Some(class.definitions().into_iter().zip(iter::once(node).cycle()))
+ })
+ .flatten()
+ .unique_by(|&(def, _)| def)
+ .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config))
+ .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| {
+ acc.actions.extend(actions);
+ acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup));
+ acc
+ })
+ })
+ // try keywords
+ .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token)))
+ // try rest item hover
+ .or_else(|| {
+ descended.iter().find_map(|token| {
+ if token.kind() != DOT2 {
+ return None;
+ }
- if result.is_none() {
- // fallbacks, show keywords or types
+ let rest_pat = token.parent().and_then(ast::RestPat::cast)?;
+ let record_pat_field_list =
+ rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?;
- let res = descended.iter().find_map(|token| render::keyword(sema, config, token));
- if let Some(res) = res {
- return Some(RangeInfo::new(original_token.text_range(), res));
- }
- let res = descended
- .iter()
- .find_map(|token| hover_type_fallback(sema, config, token, &original_token));
- if let Some(_) = res {
- return res;
- }
- }
- result.map(|mut res: HoverResult| {
- res.actions = dedupe_or_merge_hover_actions(res.actions);
- RangeInfo::new(original_token.text_range(), res)
- })
+ let record_pat =
+ record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;
+
+ Some(render::struct_rest_pat(sema, config, &record_pat))
+ })
+ });
+
+ result
+ .map(|mut res: HoverResult| {
+ res.actions = dedupe_or_merge_hover_actions(res.actions);
+ RangeInfo::new(original_token.text_range(), res)
+ })
+ // fallback to type hover if there aren't any other suggestions
+ // this finds its own range instead of using the closest token's range
+ .or_else(|| {
+ descended.iter().find_map(|token| hover_type_fallback(sema, config, token, &token))
+ })
}
pub(crate) fn hover_for_definition(
@@ -269,6 +283,7 @@ fn hover_type_fallback(
};
let res = render::type_info(sema, config, &expr_or_pat)?;
+
let range = sema
.original_range_opt(&node)
.map(|frange| frange.range)
diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs
index d109c07691..fb00a40f96 100644
--- a/crates/ide/src/hover/render.rs
+++ b/crates/ide/src/hover/render.rs
@@ -14,7 +14,9 @@ use ide_db::{
use itertools::Itertools;
use stdx::format_to;
use syntax::{
- algo, ast, match_ast, AstNode, Direction,
+ algo,
+ ast::{self, RecordPat},
+ match_ast, AstNode, Direction,
SyntaxKind::{LET_EXPR, LET_STMT},
SyntaxToken, T,
};
@@ -250,6 +252,50 @@ pub(super) fn keyword(
Some(HoverResult { markup, actions })
}
+/// Returns missing types in a record pattern.
+/// Only makes sense when there's a rest pattern in the record pattern.
+/// i.e. `let S {a, ..} = S {a: 1, b: 2}`
+pub(super) fn struct_rest_pat(
+ sema: &Semantics<'_, RootDatabase>,
+ config: &HoverConfig,
+ pattern: &RecordPat,
+) -> HoverResult {
+ let missing_fields = sema.record_pattern_missing_fields(pattern);
+
+ // if there are no missing fields, the end result is a hover that shows ".."
+ // should be left in to indicate that there are no more fields in the pattern
+ // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32}
+
+ let mut res = HoverResult::default();
+ let mut targets: Vec<hir::ModuleDef> = Vec::new();
+ let mut push_new_def = |item: hir::ModuleDef| {
+ if !targets.contains(&item) {
+ targets.push(item);
+ }
+ };
+ for (_, t) in &missing_fields {
+ walk_and_push_ty(sema.db, &t, &mut push_new_def);
+ }
+
+ res.markup = {
+ let mut s = String::from(".., ");
+ for (f, _) in &missing_fields {
+ s += f.display(sema.db).to_string().as_ref();
+ s += ", ";
+ }
+ // get rid of trailing comma
+ s.truncate(s.len() - 2);
+
+ if config.markdown() {
+ Markup::fenced_block(&s)
+ } else {
+ s.into()
+ }
+ };
+ res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets));
+ res
+}
+
pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option<HoverResult> {
let (path, tt) = attr.as_simple_call()?;
if !tt.syntax().text_range().contains(token.text_range().start()) {
diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs
index eb997e6fef..f8be4cfb04 100644
--- a/crates/ide/src/hover/tests.rs
+++ b/crates/ide/src/hover/tests.rs
@@ -5307,3 +5307,38 @@ fn main() { $0V; }
"#]],
);
}
+
+#[test]
+fn hover_rest_pat() {
+ check(
+ r#"
+struct Struct {a: u32, b: u32, c: u8, d: u16};
+
+fn main() {
+ let Struct {a, c, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
+}
+"#,
+ expect![[r#"
+ *..*
+ ```rust
+ .., b: u32, d: u16
+ ```
+ "#]],
+ );
+
+ check(
+ r#"
+struct Struct {a: u32, b: u32, c: u8, d: u16};
+
+fn main() {
+ let Struct {a, b, c, d, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4};
+}
+"#,
+ expect![[r#"
+ *..*
+ ```rust
+ ..
+ ```
+ "#]],
+ );
+}