Consider walking up macro expansions when searching for surrounding entities in completion analysis

This commit is contained in:
Lukas Wirth 2022-06-18 11:19:36 +02:00
parent c1446a2743
commit f271b18129

View file

@ -456,7 +456,7 @@ impl<'a> CompletionContext<'a> {
ast::IdentPat(bind_pat) => { ast::IdentPat(bind_pat) => {
let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into()); let mut pat_ctx = pattern_context_for(sema, original_file, bind_pat.into());
if let Some(record_field) = ast::RecordPatField::for_field_name(&name) { if let Some(record_field) = ast::RecordPatField::for_field_name(&name) {
pat_ctx.record_pat = find_node_in_file_compensated(original_file, &record_field.parent_record_pat()); pat_ctx.record_pat = find_node_in_file_compensated(sema, original_file, &record_field.parent_record_pat());
} }
NameKind::IdentPat(pat_ctx) NameKind::IdentPat(pat_ctx)
@ -493,9 +493,13 @@ impl<'a> CompletionContext<'a> {
|kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default()); |kind| (NameRefContext { nameref: nameref.clone(), kind }, Default::default());
if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) {
return find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) return find_node_in_file_compensated(
.map(NameRefKind::RecordExpr) sema,
.map(make_res); original_file,
&record_field.parent_record_lit(),
)
.map(NameRefKind::RecordExpr)
.map(make_res);
} }
if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
let kind = NameRefKind::Pattern(PatternContext { let kind = NameRefKind::Pattern(PatternContext {
@ -504,6 +508,7 @@ impl<'a> CompletionContext<'a> {
ref_token: None, ref_token: None,
mut_token: None, mut_token: None,
record_pat: find_node_in_file_compensated( record_pat: find_node_in_file_compensated(
sema,
original_file, original_file,
&record_field.parent_record_pat(), &record_field.parent_record_pat(),
), ),
@ -568,7 +573,7 @@ impl<'a> CompletionContext<'a> {
}; };
let func_update_record = |syn: &SyntaxNode| { let func_update_record = |syn: &SyntaxNode| {
if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) { if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
find_node_in_file_compensated(original_file, &record_expr) find_node_in_file_compensated(sema, original_file, &record_expr)
} else { } else {
None None
} }
@ -670,9 +675,9 @@ impl<'a> CompletionContext<'a> {
ast::TypeBound(_) => TypeLocation::TypeBound, ast::TypeBound(_) => TypeLocation::TypeBound,
// is this case needed? // is this case needed?
ast::TypeBoundList(_) => TypeLocation::TypeBound, ast::TypeBoundList(_) => TypeLocation::TypeBound,
ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))), ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
// is this case needed? // is this case needed?
ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, Some(it))), ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
ast::TupleField(_) => TypeLocation::TupleField, ast::TupleField(_) => TypeLocation::TupleField,
_ => return None, _ => return None,
} }
@ -734,7 +739,7 @@ impl<'a> CompletionContext<'a> {
_ => Some(None), _ => Some(None),
}; };
match dbg!(find_node_in_file_compensated(original_file, &expr)) { match find_node_in_file_compensated(sema, original_file, &expr) {
Some(it) => { Some(it) => {
let innermost_ret_ty = sema let innermost_ret_ty = sema
.ancestors_with_macros(it.syntax().clone()) .ancestors_with_macros(it.syntax().clone())
@ -757,7 +762,7 @@ impl<'a> CompletionContext<'a> {
.parent() .parent()
.and_then(ast::LetStmt::cast) .and_then(ast::LetStmt::cast)
.map_or(false, |it| it.semicolon_token().is_none()); .map_or(false, |it| it.semicolon_token().is_none());
let impl_ = fetch_immediate_impl(sema, original_file, &expr); let impl_ = fetch_immediate_impl(sema, original_file, expr.syntax());
PathKind::Expr { PathKind::Expr {
in_block_expr, in_block_expr,
@ -826,7 +831,7 @@ impl<'a> CompletionContext<'a> {
match it { match it {
ast::Trait(_) => ItemListKind::Trait, ast::Trait(_) => ItemListKind::Trait,
ast::Impl(it) => if it.trait_().is_some() { ast::Impl(it) => if it.trait_().is_some() {
ItemListKind::TraitImpl(find_node_in_file_compensated(original_file, &it)) ItemListKind::TraitImpl(find_node_in_file_compensated(sema, original_file, &it))
} else { } else {
ItemListKind::Impl ItemListKind::Impl
}, },
@ -983,7 +988,7 @@ fn pattern_context_for(
let has_type_ascription = param.ty().is_some(); let has_type_ascription = param.ty().is_some();
is_param = (|| { is_param = (|| {
let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?; let fake_param_list = param.syntax().parent().and_then(ast::ParamList::cast)?;
let param_list = find_node_in_file_compensated(original_file, &fake_param_list)?; let param_list = find_node_in_file_compensated(sema, original_file, &fake_param_list)?;
let param_list_owner = param_list.syntax().parent()?; let param_list_owner = param_list.syntax().parent()?;
let kind = match_ast! { let kind = match_ast! {
match param_list_owner { match param_list_owner {
@ -1017,42 +1022,25 @@ fn pattern_context_for(
mut_token, mut_token,
ref_token, ref_token,
record_pat: None, record_pat: None,
impl_: fetch_immediate_impl(sema, original_file, &pat), impl_: fetch_immediate_impl(sema, original_file, pat.syntax()),
} }
} }
fn fetch_immediate_impl( fn fetch_immediate_impl(
sema: &Semantics<RootDatabase>, sema: &Semantics<RootDatabase>,
original_file: &SyntaxNode, original_file: &SyntaxNode,
node: &impl AstNode, node: &SyntaxNode,
) -> Option<ast::Impl> { ) -> Option<ast::Impl> {
// FIXME: The fallback here could be done better let mut ancestors = ancestors_in_file_compensated(sema, original_file, node)?
let (f, s) = match find_node_in_file_compensated(original_file, node) { .filter_map(ast::Item::cast)
Some(node) => { .filter(|it| !matches!(it, ast::Item::MacroCall(_)));
let mut items = sema
.ancestors_with_macros(node.syntax().clone())
.filter_map(ast::Item::cast)
.filter(|it| !matches!(it, ast::Item::MacroCall(_)))
.take(2);
(items.next(), items.next())
}
None => {
let mut items = node
.syntax()
.ancestors()
.filter_map(ast::Item::cast)
.filter(|it| !matches!(it, ast::Item::MacroCall(_)))
.take(2);
(items.next(), items.next())
}
};
match f? { match ancestors.next()? {
ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (), ast::Item::Const(_) | ast::Item::Fn(_) | ast::Item::TypeAlias(_) => (),
ast::Item::Impl(it) => return Some(it), ast::Item::Impl(it) => return Some(it),
_ => return None, _ => return None,
} }
match s? { match ancestors.next()? {
ast::Item::Impl(it) => Some(it), ast::Item::Impl(it) => Some(it),
_ => None, _ => None,
} }
@ -1076,9 +1064,21 @@ fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating /// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
/// for the offset introduced by the fake ident. /// for the offset introduced by the fake ident.
/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead. /// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> { fn find_node_in_file_compensated<N: AstNode>(
let syntax_range = syntax.text_range(); sema: &Semantics<RootDatabase>,
let range = node.syntax().text_range(); in_file: &SyntaxNode,
node: &N,
) -> Option<N> {
ancestors_in_file_compensated(sema, in_file, node.syntax())?.find_map(N::cast)
}
fn ancestors_in_file_compensated<'sema>(
sema: &'sema Semantics<RootDatabase>,
in_file: &SyntaxNode,
node: &SyntaxNode,
) -> Option<impl Iterator<Item = SyntaxNode> + 'sema> {
let syntax_range = in_file.text_range();
let range = node.text_range();
let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?; let end = range.end().checked_sub(TextSize::try_from(COMPLETION_MARKER.len()).ok()?)?;
if end < range.start() { if end < range.start() {
return None; return None;
@ -1086,17 +1086,22 @@ fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> O
let range = TextRange::new(range.start(), end); let range = TextRange::new(range.start(), end);
// our inserted ident could cause `range` to go outside of the original syntax, so cap it // our inserted ident could cause `range` to go outside of the original syntax, so cap it
let intersection = range.intersect(syntax_range)?; let intersection = range.intersect(syntax_range)?;
syntax.covering_element(intersection).ancestors().find_map(N::cast) let node = match in_file.covering_element(intersection) {
NodeOrToken::Node(node) => node,
NodeOrToken::Token(tok) => tok.parent()?,
};
Some(sema.ancestors_with_macros(node))
} }
/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating /// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
/// for the offset introduced by the fake ident.. /// for the offset introduced by the fake ident..
/// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead. /// This is wrong if `node` comes before the insertion point! Use `find_node_in_file` instead.
fn find_opt_node_in_file_compensated<N: AstNode>( fn find_opt_node_in_file_compensated<N: AstNode>(
sema: &Semantics<RootDatabase>,
syntax: &SyntaxNode, syntax: &SyntaxNode,
node: Option<N>, node: Option<N>,
) -> Option<N> { ) -> Option<N> {
find_node_in_file_compensated(syntax, &node?) find_node_in_file_compensated(sema, syntax, &node?)
} }
fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> { fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {