Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-completion/src/context.rs')
| -rw-r--r-- | crates/ide-completion/src/context.rs | 290 |
1 files changed, 144 insertions, 146 deletions
diff --git a/crates/ide-completion/src/context.rs b/crates/ide-completion/src/context.rs index 6068a9eb32..02307def9e 100644 --- a/crates/ide-completion/src/context.rs +++ b/crates/ide-completion/src/context.rs @@ -43,12 +43,13 @@ pub(crate) enum Visible { No, } -#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub(super) enum PathKind { Expr { in_block_expr: bool, in_loop_body: bool, after_if_expr: bool, + ref_expr_parent: Option<ast::RefExpr>, }, Type { in_tuple_struct: bool, @@ -101,8 +102,6 @@ pub(crate) struct PathCompletionCtx { pub(super) is_absolute_path: bool, /// The qualifier of the current path if it exists. pub(super) qualifier: Option<PathQualifierCtx>, - #[allow(dead_code)] - // FIXME: use this /// The parent of the path we are completing. pub(super) parent: Option<ast::Path>, pub(super) kind: PathKind, @@ -110,6 +109,23 @@ pub(crate) struct PathCompletionCtx { pub(super) has_type_args: bool, } +impl PathCompletionCtx { + pub(super) fn is_trivial_path(&self) -> bool { + matches!( + self, + PathCompletionCtx { + has_call_parens: false, + has_macro_bang: false, + is_absolute_path: false, + qualifier: None, + parent: None, + has_type_args: false, + .. + } + ) + } +} + #[derive(Debug)] pub(crate) struct PathQualifierCtx { pub(crate) path: ast::Path, @@ -341,47 +357,14 @@ impl<'a> CompletionContext<'a> { matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_))) } - pub(crate) fn expects_ident_ref_expr(&self) -> bool { - matches!(self.completion_location, Some(ImmediateLocation::RefExpr)) - } - - // FIXME: This shouldn't exist - pub(crate) fn is_path_disallowed(&self) -> bool { - !self.qualifier_ctx.none() - || (matches!(self.name_ctx(), Some(NameContext { .. })) && self.pattern_ctx.is_none()) - || matches!(self.pattern_ctx, Some(PatternContext { record_pat: Some(_), .. })) - || matches!( - self.nameref_ctx(), - Some(NameRefContext { record_expr: Some((_, false)), .. }) - ) - } - pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> { self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref()) } - pub(crate) fn expects_expression(&self) -> bool { - matches!(self.path_context(), Some(PathCompletionCtx { kind: PathKind::Expr { .. }, .. })) - } - - pub(crate) fn is_non_trivial_path(&self) -> bool { - matches!( - self.path_context(), - Some( - PathCompletionCtx { is_absolute_path: true, .. } - | PathCompletionCtx { qualifier: Some(_), .. } - ) - ) - } - pub(crate) fn path_qual(&self) -> Option<&ast::Path> { self.path_context().and_then(|it| it.qualifier.as_ref().map(|it| &it.path)) } - pub(crate) fn path_kind(&self) -> Option<PathKind> { - self.path_context().map(|it| it.kind) - } - /// Checks if an item is visible and not `doc(hidden)` at the completion site. pub(crate) fn is_visible<I>(&self, item: &I) -> Visible where @@ -872,7 +855,7 @@ impl<'a> CompletionContext<'a> { find_node_at_offset(&file_with_fake_ident, offset) { let parent = name_ref.syntax().parent()?; - let (mut nameref_ctx, _) = + let (mut nameref_ctx, _, _) = Self::classify_name_ref(&self.sema, &original_file, name_ref, parent); if let Some(path_ctx) = &mut nameref_ctx.path_ctx { path_ctx.kind = PathKind::Derive; @@ -920,13 +903,23 @@ impl<'a> CompletionContext<'a> { self.impl_def = self .sema .token_ancestors_with_macros(self.token.clone()) - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::Impl::cast); + .take_while(|it| it.kind() != SOURCE_FILE) + .filter_map(ast::Item::cast) + .take(2) + .find_map(|it| match it { + ast::Item::Impl(impl_) => Some(impl_), + _ => None, + }); self.function_def = self .sema .token_ancestors_with_macros(self.token.clone()) .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::Fn::cast); + .filter_map(ast::Item::cast) + .take(2) + .find_map(|it| match it { + ast::Item::Fn(fn_) => Some(fn_), + _ => None, + }); match name_like { ast::NameLike::Lifetime(lifetime) => { @@ -938,50 +931,10 @@ impl<'a> CompletionContext<'a> { } ast::NameLike::NameRef(name_ref) => { let parent = name_ref.syntax().parent()?; - let (nameref_ctx, pat_ctx) = + let (nameref_ctx, pat_ctx, qualifier_ctx) = Self::classify_name_ref(&self.sema, &original_file, name_ref, parent.clone()); - // Extract qualifiers - if let Some(path_ctx) = &nameref_ctx.path_ctx { - if path_ctx.qualifier.is_none() { - let top = match path_ctx.kind { - PathKind::Expr { in_block_expr: true, .. } => parent - .ancestors() - .find(|it| ast::PathExpr::can_cast(it.kind())) - .and_then(|p| { - let parent = p.parent()?; - if ast::StmtList::can_cast(parent.kind()) { - Some(p) - } else if ast::ExprStmt::can_cast(parent.kind()) { - Some(parent) - } else { - None - } - }), - PathKind::Item { .. } => { - parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) - } - _ => None, - }; - if let Some(top) = top { - if let Some(NodeOrToken::Node(error_node)) = - syntax::algo::non_trivia_sibling( - top.into(), - syntax::Direction::Prev, - ) - { - if error_node.kind() == SyntaxKind::ERROR { - self.qualifier_ctx.unsafe_tok = error_node - .children_with_tokens() - .filter_map(NodeOrToken::into_token) - .find(|it| it.kind() == T![unsafe]); - self.qualifier_ctx.vis_node = - error_node.children().find_map(ast::Visibility::cast); - } - } - } - } - } + self.qualifier_ctx = qualifier_ctx; self.ident_ctx = IdentContext::NameRef(nameref_ctx); self.pattern_ctx = pat_ctx; } @@ -1070,40 +1023,44 @@ impl<'a> CompletionContext<'a> { original_file: &SyntaxNode, name_ref: ast::NameRef, parent: SyntaxNode, - ) -> (NameRefContext, Option<PatternContext>) { + ) -> (NameRefContext, Option<PatternContext>, QualifierCtx) { let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start()); - let mut nameref_ctx = NameRefContext { - dot_access: None, - path_ctx: None, - nameref, - record_expr: None, - keyword: None, - }; + let mut res = ( + NameRefContext { + dot_access: None, + path_ctx: None, + nameref, + record_expr: None, + keyword: None, + }, + None, + QualifierCtx::default(), + ); + let (nameref_ctx, pattern_ctx, qualifier_ctx) = &mut res; if let Some(record_field) = ast::RecordExprField::for_field_name(&name_ref) { nameref_ctx.record_expr = find_node_in_file_compensated(original_file, &record_field.parent_record_lit()) .zip(Some(false)); - return (nameref_ctx, None); + return res; } if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) { - let pat_ctx = - pattern_context_for(original_file, record_field.parent_record_pat().clone().into()); - return ( - nameref_ctx, - Some(PatternContext { - param_ctx: None, - has_type_ascription: false, - ref_token: None, - mut_token: None, - record_pat: find_node_in_file_compensated( - original_file, - &record_field.parent_record_pat(), - ), - ..pat_ctx - }), - ); + *pattern_ctx = Some(PatternContext { + param_ctx: None, + has_type_ascription: false, + ref_token: None, + mut_token: None, + record_pat: find_node_in_file_compensated( + original_file, + &record_field.parent_record_pat(), + ), + ..pattern_context_for( + original_file, + record_field.parent_record_pat().clone().into(), + ) + }); + return res; } let segment = match_ast! { @@ -1123,7 +1080,7 @@ impl<'a> CompletionContext<'a> { kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal }, receiver }); - return (nameref_ctx, None); + return res; }, ast::MethodCallExpr(method) => { let receiver = find_in_original_file(method.receiver(), original_file); @@ -1132,9 +1089,9 @@ impl<'a> CompletionContext<'a> { kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) }, receiver }); - return (nameref_ctx, None); + return res; }, - _ => return (nameref_ctx, None), + _ => return res, } }; @@ -1148,7 +1105,6 @@ impl<'a> CompletionContext<'a> { kind: PathKind::Item { kind: ItemListKind::SourceFile }, has_type_args: false, }; - let mut pat_ctx = None; let is_in_block = |it: &SyntaxNode| { it.parent() @@ -1205,9 +1161,9 @@ impl<'a> CompletionContext<'a> { None }; - let kind = path.syntax().ancestors().find_map(|it| { - // using Option<Option<PathKind>> as extra controlflow - let kind = match_ast! { + // Infer the path kind + let kind = path.syntax().parent().and_then(|it| { + match_ast! { match it { ast::PathType(it) => Some(PathKind::Type { in_tuple_struct: it.syntax().parent().map_or(false, |it| ast::TupleField::can_cast(it.kind())) @@ -1217,7 +1173,7 @@ impl<'a> CompletionContext<'a> { if ast::ExprStmt::can_cast(p.kind()) { if let Some(kind) = inbetween_body_and_decl_check(p) { nameref_ctx.keyword = Some(kind); - return Some(None); + return None; } } } @@ -1228,27 +1184,29 @@ impl<'a> CompletionContext<'a> { let in_block_expr = is_in_block(it.syntax()); let in_loop_body = is_in_loop_body(it.syntax()); let after_if_expr = after_if_expr(it.syntax().clone()); + let ref_expr_parent = path.as_single_name_ref() + .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); - Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr }) + Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent }) }, ast::TupleStructPat(it) => { path_ctx.has_call_parens = true; - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::RecordPat(it) => { path_ctx.has_call_parens = true; - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::PathPat(it) => { - pat_ctx = Some(pattern_context_for(original_file, it.into())); + *pattern_ctx = Some(pattern_context_for(original_file, it.into())); Some(PathKind::Pat) }, ast::MacroCall(it) => { if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) { nameref_ctx.keyword = Some(kind); - return Some(None); + return None; } path_ctx.has_macro_bang = it.excl_token().is_some(); @@ -1266,21 +1224,23 @@ impl<'a> CompletionContext<'a> { } else { ItemListKind::Impl }, - _ => return Some(None) + _ => return None } }, - None => return Some(None), + None => return None, } }), Some(SyntaxKind::EXTERN_ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), Some(SyntaxKind::SOURCE_FILE) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), _ => { - return Some(parent.and_then(ast::MacroExpr::cast).map(|it| { + return parent.and_then(ast::MacroExpr::cast).map(|it| { let in_loop_body = is_in_loop_body(it.syntax()); let in_block_expr = is_in_block(it.syntax()); let after_if_expr = after_if_expr(it.syntax().clone()); fill_record_expr(it.syntax()); - PathKind::Expr { in_block_expr, in_loop_body, after_if_expr } - })); + let ref_expr_parent = path.as_single_name_ref() + .and_then(|_| it.syntax().parent()).and_then(ast::RefExpr::cast); + PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent } + }); }, } }, @@ -1302,30 +1262,14 @@ impl<'a> CompletionContext<'a> { })(), ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }), ast::UseTree(_) => Some(PathKind::Use), - ast::ItemList(_) => Some(PathKind::Item { kind: ItemListKind::Module }), - ast::AssocItemList(it) => Some(PathKind::Item { kind: { - match_ast! { - match (it.syntax().parent()?) { - ast::Trait(_) => ItemListKind::Trait, - ast::Impl(it) => if it.trait_().is_some() { - ItemListKind::TraitImpl - } else { - ItemListKind::Impl - }, - _ => return None - } - } - }}), - ast::ExternItemList(_) => Some(PathKind::Item { kind: ItemListKind::ExternBlock }), - ast::SourceFile(_) => Some(PathKind::Item { kind: ItemListKind::SourceFile }), _ => return None, } - }; - Some(kind) - }).flatten(); + } + }); + match kind { Some(kind) => path_ctx.kind = kind, - None => return (nameref_ctx, pat_ctx), + None => return res, } path_ctx.has_type_args = segment.generic_arg_list().is_some(); @@ -1367,8 +1311,62 @@ impl<'a> CompletionContext<'a> { path_ctx.is_absolute_path = true; } } + + if path_ctx.is_trivial_path() { + // fetch the full expression that may have qualifiers attached to it + let top_node = match path_ctx.kind { + PathKind::Expr { in_block_expr: true, .. } => { + parent.ancestors().find(|it| ast::PathExpr::can_cast(it.kind())).and_then(|p| { + let parent = p.parent()?; + if ast::StmtList::can_cast(parent.kind()) { + Some(p) + } else if ast::ExprStmt::can_cast(parent.kind()) { + Some(parent) + } else { + None + } + }) + } + PathKind::Item { .. } => { + parent.ancestors().find(|it| ast::MacroCall::can_cast(it.kind())) + } + _ => None, + }; + if let Some(top) = top_node { + if let Some(NodeOrToken::Node(error_node)) = + syntax::algo::non_trivia_sibling(top.clone().into(), syntax::Direction::Prev) + { + if error_node.kind() == SyntaxKind::ERROR { + qualifier_ctx.unsafe_tok = error_node + .children_with_tokens() + .filter_map(NodeOrToken::into_token) + .find(|it| it.kind() == T![unsafe]); + qualifier_ctx.vis_node = + error_node.children().find_map(ast::Visibility::cast); + } + } + + if let PathKind::Item { .. } = path_ctx.kind { + if qualifier_ctx.none() { + if let Some(t) = top.first_token() { + if let Some(prev) = t + .prev_token() + .and_then(|t| syntax::algo::skip_trivia_token(t, Direction::Prev)) + { + if ![T![;], T!['}'], T!['{']].contains(&prev.kind()) { + // This was inferred to be an item position path, but it seems + // to be part of some other broken node which leaked into an item + // list, so return without setting the path context + return res; + } + } + } + } + } + } + } nameref_ctx.path_ctx = Some(path_ctx); - (nameref_ctx, pat_ctx) + res } } |