Skip to content

Commit

Permalink
Auto merge of #13638 - DesmondWillowbrook:hover-rest-pat-mvp, r=Veykril
Browse files Browse the repository at this point in the history
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)
  • Loading branch information
bors committed Nov 25, 2022
2 parents 1e6a49a + a26aef9 commit 6918009
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 44 deletions.
101 changes: 58 additions & 43 deletions crates/ide/src/hover.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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(
Expand Down Expand Up @@ -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)
Expand Down
48 changes: 47 additions & 1 deletion crates/ide/src/hover/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand Down Expand Up @@ -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()) {
Expand Down
35 changes: 35 additions & 0 deletions crates/ide/src/hover/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
..
```
"#]],
);
}

0 comments on commit 6918009

Please sign in to comment.