From 0ffb361eb943cdfaa6c21b761a7906161fd3b041 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 11:10:47 +0530 Subject: [PATCH 01/10] feat: adds hover hint to ".." in record pattern currently only works with struct pattern --- crates/ide/src/hover.rs | 7 +++-- crates/ide/src/hover/render.rs | 54 +++++++++++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 3 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 838fb18c3d5..966daad1358 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -87,7 +87,7 @@ pub struct HoverResult { // Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code. // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. // -// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] +// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif pub(crate) fn hover( db: &RootDatabase, FileRange { file_id, range }: FileRange, @@ -268,7 +268,10 @@ fn hover_type_fallback( } }; - let res = render::type_info(sema, config, &expr_or_pat)?; + let res = + render::type_info(sema, config, &expr_or_pat) + .or_else(|| render::struct_rest_pat(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 d109c076919..3b561f65da9 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -14,7 +14,7 @@ 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 +250,58 @@ pub(super) fn keyword( Some(HoverResult { markup, actions }) } +pub(super) fn struct_rest_pat( + sema: &Semantics<'_, RootDatabase>, + config: &HoverConfig, + expr_or_pat: &Either, +) -> Option { + let pat = expr_or_pat.as_ref().right()?; + + let mut ancestors = sema.ancestors_with_macros(pat.syntax().clone()); + let _record_pat_field_list = ancestors.next()?; + let record_pat = ancestors.next()?; + let pattern = sema + .find_nodes_at_offset_with_descend::( + &record_pat, + record_pat.text_range().start()) + .next()?; + + 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 = 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 + if s.len() > 0 {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)); + Some(res) +} + pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option { let (path, tt) = attr.as_simple_call()?; if !tt.syntax().text_range().contains(token.text_range().start()) { From a778203db9d23d2c6dbbd1bd038d6ef2d5368b94 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 11:17:43 +0530 Subject: [PATCH 02/10] simplify ancestor climbing to not consider macros --- crates/ide/src/hover/render.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 3b561f65da9..b5320c6b6b2 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -257,7 +257,7 @@ pub(super) fn struct_rest_pat( ) -> Option { let pat = expr_or_pat.as_ref().right()?; - let mut ancestors = sema.ancestors_with_macros(pat.syntax().clone()); + let mut ancestors = pat.syntax().ancestors(); let _record_pat_field_list = ancestors.next()?; let record_pat = ancestors.next()?; let pattern = sema From 8b176810588734bdba3b5ea3374d39046f184f45 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 11:34:49 +0530 Subject: [PATCH 03/10] fix formatting and remove unnecessary check --- crates/ide/src/hover.rs | 3 +-- crates/ide/src/hover/render.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 966daad1358..cc64b0c3db3 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -268,8 +268,7 @@ fn hover_type_fallback( } }; - let res = - render::type_info(sema, config, &expr_or_pat) + let res = render::type_info(sema, config, &expr_or_pat) .or_else(|| render::struct_rest_pat(sema, config, &expr_or_pat))?; let range = sema diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index b5320c6b6b2..b66c029257d 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::{self, RecordPat}, match_ast, AstNode, Direction, + algo, + ast::{self, RecordPat}, + match_ast, AstNode, Direction, SyntaxKind::{LET_EXPR, LET_STMT}, SyntaxToken, T, }; @@ -263,7 +265,8 @@ pub(super) fn struct_rest_pat( let pattern = sema .find_nodes_at_offset_with_descend::( &record_pat, - record_pat.text_range().start()) + record_pat.text_range().start(), + ) .next()?; let missing_fields = sema.record_pattern_missing_fields(&pattern); @@ -290,7 +293,7 @@ pub(super) fn struct_rest_pat( s += ", "; } // get rid of trailing comma - if s.len() > 0 {s.truncate(s.len() - 2);} + s.truncate(s.len() - 2); if config.markdown() { Markup::fenced_block(&s) From 87658c81731cd99b80bcd32d2188890f2ca22291 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 21:22:10 +0530 Subject: [PATCH 04/10] refactor hover change struct_rest_pat to guarentee a HoverResult Move token and node matching out of struct_rest_pat into hover --- crates/ide/src/hover.rs | 104 +++++++++++++++++++-------------- crates/ide/src/hover/render.rs | 23 +++----- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index cc64b0c3db3..1e07e7dbac9 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,68 @@ 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; + } + + let record_pat_field_list = + token.parent_ancestors().find_map(ast::RecordPatFieldList::cast)?; + + let record_pat = + record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; + + Some(render::struct_rest_pat(sema, config, &record_pat)) + }) }); - if result.is_none() { - // fallbacks, show keywords or types - - 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) - }) + 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( @@ -268,8 +283,7 @@ fn hover_type_fallback( } }; - let res = render::type_info(sema, config, &expr_or_pat) - .or_else(|| render::struct_rest_pat(sema, config, &expr_or_pat))?; + let res = render::type_info(sema, config, &expr_or_pat)?; let range = sema .original_range_opt(&node) diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index b66c029257d..fb00a40f961 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -252,24 +252,15 @@ 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, - expr_or_pat: &Either, -) -> Option { - let pat = expr_or_pat.as_ref().right()?; - - let mut ancestors = pat.syntax().ancestors(); - let _record_pat_field_list = ancestors.next()?; - let record_pat = ancestors.next()?; - let pattern = sema - .find_nodes_at_offset_with_descend::( - &record_pat, - record_pat.text_range().start(), - ) - .next()?; - - let missing_fields = sema.record_pattern_missing_fields(&pattern); + 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 @@ -302,7 +293,7 @@ pub(super) fn struct_rest_pat( } }; res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); - Some(res) + res } pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option { From 29951f9766dc8ffc2b661b78cdff44ceafef3d57 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Sat, 19 Nov 2022 21:23:33 +0530 Subject: [PATCH 05/10] fix formatting --- crates/ide/src/hover.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 1e07e7dbac9..9751dc11db6 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -127,7 +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 { @@ -168,9 +168,7 @@ pub(crate) fn hover( }) }) // try keywords - .or_else(|| { - descended.iter().find_map(|token| render::keyword(sema, config, token)) - }) + .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token))) // try rest item hover .or_else(|| { descended.iter().find_map(|token| { From f64feeb11ac78db9c634339465d973ceb9cb3c97 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Thu, 24 Nov 2022 22:32:15 -0800 Subject: [PATCH 06/10] Correct node traversal to look at parent instead Co-authored-by: Lukas Wirth --- crates/ide/src/hover.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 9751dc11db6..5e812a1569c 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -176,8 +176,8 @@ pub(crate) fn hover( return None; } - let record_pat_field_list = - token.parent_ancestors().find_map(ast::RecordPatFieldList::cast)?; + let rest_pat = token.syntax().parent().and_then(ast::RestPat::cast)?; + let record_pat_field_list = rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; let record_pat = record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; From 132d5ffc7d86e77ee80a45545b8e9f503b59a8c4 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Fri, 25 Nov 2022 12:03:49 +0530 Subject: [PATCH 07/10] add back [] in hover documentation --- crates/ide/src/hover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 5e812a1569c..cbf61efba05 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -87,7 +87,7 @@ pub struct HoverResult { // Shows additional information, like the type of an expression or the documentation for a definition when "focusing" code. // Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. // -// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif +// image::https://user-images.githubusercontent.com/48062697/113020658-b5f98b80-917a-11eb-9f88-3dbc27320c95.gif[] pub(crate) fn hover( db: &RootDatabase, FileRange { file_id, range }: FileRange, From e86d451484bf06a452a330af4b1b0c00de286715 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Fri, 25 Nov 2022 12:20:34 +0530 Subject: [PATCH 08/10] fix token method call --- crates/ide/src/hover.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index cbf61efba05..016567311c8 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -176,7 +176,7 @@ pub(crate) fn hover( return None; } - let rest_pat = token.syntax().parent().and_then(ast::RestPat::cast)?; + 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 record_pat = From 91e7624de01fc28e4abe2400a6f945b430ab0ab1 Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Fri, 25 Nov 2022 12:20:38 +0530 Subject: [PATCH 09/10] add hover tests --- crates/ide/src/hover/tests.rs | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/ide/src/hover/tests.rs b/crates/ide/src/hover/tests.rs index eb997e6fef8..f8be4cfb04c 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 + .. + ``` + "#]], + ); +} From a26aef9055e116d790ea63c37118aefcf93d3d0f Mon Sep 17 00:00:00 2001 From: Kartavya Vashishtha Date: Fri, 25 Nov 2022 12:22:06 +0530 Subject: [PATCH 10/10] fix formatting --- crates/ide/src/hover.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 016567311c8..9cbfed4763b 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -177,7 +177,8 @@ pub(crate) fn hover( } 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 record_pat_field_list = + rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; let record_pat = record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?;