mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-02-13 12:43:38 +00:00
Auto merge of #12560 - Veykril:completion, r=Veykril
internal: More completions refactoring This gets rid of the remaining `ImmediateLocation` bits
This commit is contained in:
commit
6269137760
14 changed files with 334 additions and 345 deletions
crates/ide-completion/src
|
@ -4,7 +4,8 @@ use ide_db::FxHashSet;
|
|||
|
||||
use crate::{
|
||||
context::{
|
||||
CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind,
|
||||
CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind,
|
||||
PathCompletionCtx, PathKind,
|
||||
},
|
||||
CompletionItem, CompletionItemKind, Completions,
|
||||
};
|
||||
|
@ -13,7 +14,8 @@ use crate::{
|
|||
pub(crate) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let (dot_access, receiver_ty) = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext {
|
||||
dot_access: Some(access @ DotAccess { receiver_ty: Some(receiver_ty), .. }),
|
||||
kind:
|
||||
Some(NameRefKind::DotAccess(access @ DotAccess { receiver_ty: Some(receiver_ty), .. })),
|
||||
..
|
||||
}) => (access, &receiver_ty.original),
|
||||
_ => return complete_undotted_self(acc, ctx),
|
||||
|
|
|
@ -5,7 +5,7 @@ use ide_db::FxHashSet;
|
|||
use syntax::T;
|
||||
|
||||
use crate::{
|
||||
context::{NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx},
|
||||
context::{NameRefContext, NameRefKind, PathCompletionCtx, PathKind, PathQualifierCtx},
|
||||
CompletionContext, Completions,
|
||||
};
|
||||
|
||||
|
@ -21,24 +21,29 @@ pub(crate) fn complete_expr_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
after_if_expr,
|
||||
wants_mut_token,
|
||||
) = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext {
|
||||
path_ctx:
|
||||
Some(PathCompletionCtx {
|
||||
Some(&NameRefContext {
|
||||
kind:
|
||||
Some(NameRefKind::Path(PathCompletionCtx {
|
||||
kind:
|
||||
PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent },
|
||||
PathKind::Expr {
|
||||
in_block_expr,
|
||||
in_loop_body,
|
||||
after_if_expr,
|
||||
ref ref_expr_parent,
|
||||
ref is_func_update,
|
||||
},
|
||||
is_absolute_path,
|
||||
qualifier,
|
||||
ref qualifier,
|
||||
..
|
||||
}),
|
||||
record_expr,
|
||||
})),
|
||||
..
|
||||
}) if ctx.qualifier_ctx.none() => (
|
||||
*is_absolute_path,
|
||||
is_absolute_path,
|
||||
qualifier,
|
||||
*in_block_expr,
|
||||
*in_loop_body,
|
||||
record_expr.as_ref().map_or(false, |&(_, it)| it),
|
||||
*after_if_expr,
|
||||
in_block_expr,
|
||||
in_loop_body,
|
||||
is_func_update.is_some(),
|
||||
after_if_expr,
|
||||
ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false),
|
||||
),
|
||||
_ => return,
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
//! Completion of field list position.
|
||||
|
||||
use crate::{
|
||||
context::{IdentContext, NameContext, NameKind, NameRefContext, PathCompletionCtx, PathKind},
|
||||
context::{
|
||||
IdentContext, NameContext, NameKind, NameRefContext, NameRefKind, PathCompletionCtx,
|
||||
PathKind, TypeLocation,
|
||||
},
|
||||
CompletionContext, Completions,
|
||||
};
|
||||
|
||||
|
@ -9,16 +12,16 @@ pub(crate) fn complete_field_list(acc: &mut Completions, ctx: &CompletionContext
|
|||
match &ctx.ident_ctx {
|
||||
IdentContext::Name(NameContext { kind: NameKind::RecordField, .. })
|
||||
| IdentContext::NameRef(NameRefContext {
|
||||
path_ctx:
|
||||
Some(PathCompletionCtx {
|
||||
kind:
|
||||
Some(NameRefKind::Path(PathCompletionCtx {
|
||||
has_macro_bang: false,
|
||||
is_absolute_path: false,
|
||||
qualifier: None,
|
||||
parent: None,
|
||||
kind: PathKind::Type { in_tuple_struct: true },
|
||||
kind: PathKind::Type { location: TypeLocation::TupleField },
|
||||
has_type_args: false,
|
||||
..
|
||||
}),
|
||||
})),
|
||||
..
|
||||
}) => {
|
||||
if ctx.qualifier_ctx.vis_node.is_none() {
|
||||
|
|
|
@ -8,8 +8,10 @@ use itertools::Itertools;
|
|||
use syntax::{AstNode, SyntaxNode, T};
|
||||
|
||||
use crate::{
|
||||
context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PatternContext},
|
||||
patterns::ImmediateLocation,
|
||||
context::{
|
||||
CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx, PathKind,
|
||||
PatternContext, TypeLocation,
|
||||
},
|
||||
render::{render_resolution_with_import, RenderContext},
|
||||
};
|
||||
|
||||
|
@ -111,19 +113,20 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
|
|||
return None;
|
||||
}
|
||||
let path_kind = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext { path_ctx: Some(PathCompletionCtx { kind, .. }), .. })
|
||||
if matches!(
|
||||
kind,
|
||||
PathKind::Expr { .. }
|
||||
| PathKind::Type { .. }
|
||||
| PathKind::Attr { .. }
|
||||
| PathKind::Derive
|
||||
| PathKind::Pat
|
||||
) =>
|
||||
{
|
||||
Some(kind)
|
||||
}
|
||||
Some(NameRefContext { dot_access: Some(_), .. }) => None,
|
||||
Some(NameRefContext {
|
||||
kind:
|
||||
Some(NameRefKind::Path(PathCompletionCtx {
|
||||
kind:
|
||||
kind @ (PathKind::Expr { .. }
|
||||
| PathKind::Type { .. }
|
||||
| PathKind::Attr { .. }
|
||||
| PathKind::Derive
|
||||
| PathKind::Pat),
|
||||
..
|
||||
})),
|
||||
..
|
||||
}) => Some(kind),
|
||||
Some(NameRefContext { kind: Some(NameRefKind::DotAccess(_)), .. }) => None,
|
||||
None if matches!(ctx.pattern_ctx, Some(PatternContext { record_pat: None, .. })) => {
|
||||
Some(&PathKind::Pat)
|
||||
}
|
||||
|
@ -173,8 +176,8 @@ pub(crate) fn import_on_the_fly(acc: &mut Completions, ctx: &CompletionContext)
|
|||
(PathKind::Pat, ItemInNs::Types(_)) => true,
|
||||
(PathKind::Pat, ItemInNs::Values(def)) => matches!(def, hir::ModuleDef::Const(_)),
|
||||
|
||||
(PathKind::Type { .. }, ItemInNs::Types(ty)) => {
|
||||
if matches!(ctx.completion_location, Some(ImmediateLocation::TypeBound)) {
|
||||
(PathKind::Type { location }, ItemInNs::Types(ty)) => {
|
||||
if matches!(location, TypeLocation::TypeBound) {
|
||||
matches!(ty, ModuleDef::Trait(_))
|
||||
} else {
|
||||
true
|
||||
|
|
|
@ -44,8 +44,8 @@ use text_edit::TextEdit;
|
|||
|
||||
use crate::{
|
||||
context::{
|
||||
IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, PathCompletionCtx,
|
||||
PathKind,
|
||||
IdentContext, ItemListKind, NameContext, NameKind, NameRefContext, NameRefKind,
|
||||
PathCompletionCtx, PathKind,
|
||||
},
|
||||
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
|
||||
};
|
||||
|
@ -106,14 +106,13 @@ fn completion_match(ctx: &CompletionContext) -> Option<(ImplCompletionKind, Text
|
|||
}
|
||||
IdentContext::NameRef(NameRefContext {
|
||||
nameref,
|
||||
path_ctx:
|
||||
Some(
|
||||
kind:
|
||||
Some(NameRefKind::Path(
|
||||
path_ctx @ PathCompletionCtx {
|
||||
kind: PathKind::Item { kind: ItemListKind::TraitImpl },
|
||||
..
|
||||
},
|
||||
),
|
||||
..
|
||||
)),
|
||||
}) if path_ctx.is_trivial_path() => Some((
|
||||
ImplCompletionKind::All,
|
||||
match nameref {
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
|
||||
use syntax::ast::Item;
|
||||
|
||||
use crate::{context::NameRefContext, CompletionContext, Completions};
|
||||
use crate::{
|
||||
context::{NameRefContext, NameRefKind},
|
||||
CompletionContext, Completions,
|
||||
};
|
||||
|
||||
pub(crate) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let item = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext { keyword: Some(item), record_expr: None, .. }) => item,
|
||||
Some(NameRefContext { kind: Some(NameRefKind::Keyword(item)), .. }) => item,
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use text_edit::TextEdit;
|
|||
|
||||
use crate::{
|
||||
completions::postfix::format_like::add_format_like_completions,
|
||||
context::{CompletionContext, DotAccess, DotAccessKind, NameRefContext},
|
||||
context::{CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind},
|
||||
item::{Builder, CompletionRelevancePostfixMatch},
|
||||
CompletionItem, CompletionItemKind, CompletionRelevance, Completions, SnippetScope,
|
||||
};
|
||||
|
@ -25,7 +25,13 @@ pub(crate) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) {
|
|||
|
||||
let (dot_receiver, receiver_ty, receiver_is_ambiguous_float_literal) = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext {
|
||||
dot_access: Some(DotAccess { receiver_ty: Some(ty), receiver: Some(it), kind, .. }),
|
||||
kind:
|
||||
Some(NameRefKind::DotAccess(DotAccess {
|
||||
receiver_ty: Some(ty),
|
||||
receiver: Some(it),
|
||||
kind,
|
||||
..
|
||||
})),
|
||||
..
|
||||
}) => (
|
||||
it,
|
||||
|
|
|
@ -3,7 +3,7 @@ use ide_db::SymbolKind;
|
|||
use syntax::{ast::Expr, T};
|
||||
|
||||
use crate::{
|
||||
context::{NameRefContext, PatternContext},
|
||||
context::{NameRefContext, NameRefKind, PathCompletionCtx, PathKind, PatternContext},
|
||||
CompletionContext, CompletionItem, CompletionItemKind, CompletionRelevance,
|
||||
CompletionRelevancePostfixMatch, Completions,
|
||||
};
|
||||
|
@ -13,8 +13,18 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
|
|||
&ctx.pattern_ctx
|
||||
{
|
||||
ctx.sema.record_pattern_missing_fields(record_pat)
|
||||
} else if let Some(NameRefContext { record_expr: Some((record_expr, _)), .. }) =
|
||||
ctx.nameref_ctx()
|
||||
} else if let Some(NameRefContext {
|
||||
kind:
|
||||
Some(
|
||||
NameRefKind::RecordExpr(record_expr)
|
||||
| NameRefKind::Path(PathCompletionCtx {
|
||||
kind: PathKind::Expr { is_func_update: Some(record_expr), .. },
|
||||
qualifier: None,
|
||||
..
|
||||
}),
|
||||
),
|
||||
..
|
||||
}) = ctx.nameref_ctx()
|
||||
{
|
||||
let ty = ctx.sema.type_of_expr(&Expr::RecordExpr(record_expr.clone()));
|
||||
|
||||
|
@ -39,7 +49,7 @@ pub(crate) fn complete_record(acc: &mut Completions, ctx: &CompletionContext) ->
|
|||
ty.original.impls_trait(ctx.db, default_trait, &[])
|
||||
});
|
||||
|
||||
if impl_default_trait && !missing_fields.is_empty() && ctx.path_qual().is_none() {
|
||||
if impl_default_trait && !missing_fields.is_empty() {
|
||||
let completion_text = "..Default::default()";
|
||||
let mut item =
|
||||
CompletionItem::new(SymbolKind::Field, ctx.source_range(), completion_text);
|
||||
|
|
|
@ -5,8 +5,7 @@ use ide_db::FxHashSet;
|
|||
use syntax::{ast, AstNode};
|
||||
|
||||
use crate::{
|
||||
context::{PathCompletionCtx, PathKind, PathQualifierCtx},
|
||||
patterns::{ImmediateLocation, TypeAnnotation},
|
||||
context::{PathCompletionCtx, PathKind, PathQualifierCtx, TypeAscriptionTarget, TypeLocation},
|
||||
render::render_type_inference,
|
||||
CompletionContext, Completions,
|
||||
};
|
||||
|
@ -14,13 +13,13 @@ use crate::{
|
|||
pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let _p = profile::span("complete_type_path");
|
||||
|
||||
let (&is_absolute_path, qualifier) = match ctx.path_context() {
|
||||
let (&is_absolute_path, location, qualifier) = match ctx.path_context() {
|
||||
Some(PathCompletionCtx {
|
||||
kind: PathKind::Type { .. },
|
||||
kind: PathKind::Type { location },
|
||||
is_absolute_path,
|
||||
qualifier,
|
||||
..
|
||||
}) => (is_absolute_path, qualifier),
|
||||
}) => (is_absolute_path, location, qualifier),
|
||||
_ => return,
|
||||
};
|
||||
|
||||
|
@ -32,7 +31,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false,
|
||||
// unless its a constant in a generic arg list position
|
||||
ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => {
|
||||
ctx.expects_generic_arg()
|
||||
matches!(location, TypeLocation::GenericArgList(_))
|
||||
}
|
||||
ScopeDef::ImplSelfType(_) => {
|
||||
!ctx.previous_token_is(syntax::T![impl]) && !ctx.previous_token_is(syntax::T![for])
|
||||
|
@ -47,6 +46,14 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
}
|
||||
};
|
||||
|
||||
let add_assoc_item = |acc: &mut Completions, item| match item {
|
||||
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => {
|
||||
acc.add_const(ctx, ct)
|
||||
}
|
||||
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
};
|
||||
|
||||
match qualifier {
|
||||
Some(PathQualifierCtx { is_infer_qualifier, resolution, .. }) => {
|
||||
if *is_infer_qualifier {
|
||||
|
@ -54,7 +61,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
.0
|
||||
.into_iter()
|
||||
.flat_map(|it| hir::Trait::from(it).items(ctx.sema.db))
|
||||
.for_each(|item| add_assoc_item(acc, ctx, item));
|
||||
.for_each(|item| add_assoc_item(acc, item));
|
||||
return;
|
||||
}
|
||||
let resolution = match resolution {
|
||||
|
@ -98,7 +105,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
Some(ctx.module),
|
||||
None,
|
||||
|item| {
|
||||
add_assoc_item(acc, ctx, item);
|
||||
add_assoc_item(acc, item);
|
||||
None::<()>
|
||||
},
|
||||
);
|
||||
|
@ -114,7 +121,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
hir::PathResolution::Def(hir::ModuleDef::Trait(t)) => {
|
||||
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
|
||||
for item in t.items(ctx.db) {
|
||||
add_assoc_item(acc, ctx, item);
|
||||
add_assoc_item(acc, item);
|
||||
}
|
||||
}
|
||||
hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
|
||||
|
@ -135,7 +142,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
// We might iterate candidates of a trait multiple times here, so deduplicate
|
||||
// them.
|
||||
if seen.insert(item) {
|
||||
add_assoc_item(acc, ctx, item);
|
||||
add_assoc_item(acc, item);
|
||||
}
|
||||
None::<()>
|
||||
},
|
||||
|
@ -147,7 +154,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
None if is_absolute_path => acc.add_crate_roots(ctx),
|
||||
None => {
|
||||
acc.add_nameref_keywords_with_colon(ctx);
|
||||
if let Some(ImmediateLocation::TypeBound) = &ctx.completion_location {
|
||||
if let TypeLocation::TypeBound = location {
|
||||
ctx.process_all_names(&mut |name, res| {
|
||||
let add_resolution = match res {
|
||||
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
|
||||
|
@ -162,7 +169,7 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
});
|
||||
return;
|
||||
}
|
||||
if let Some(ImmediateLocation::GenericArgList(arg_list)) = &ctx.completion_location {
|
||||
if let TypeLocation::GenericArgList(Some(arg_list)) = location {
|
||||
if let Some(path_seg) = arg_list.syntax().parent().and_then(ast::PathSegment::cast)
|
||||
{
|
||||
if path_seg.syntax().ancestors().find_map(ast::TypeBound::cast).is_some() {
|
||||
|
@ -189,25 +196,25 @@ pub(crate) fn complete_type_path(acc: &mut Completions, ctx: &CompletionContext)
|
|||
}
|
||||
|
||||
pub(crate) fn complete_inferred_type(acc: &mut Completions, ctx: &CompletionContext) -> Option<()> {
|
||||
use TypeAnnotation::*;
|
||||
let pat = match &ctx.completion_location {
|
||||
Some(ImmediateLocation::TypeAnnotation(t)) => t,
|
||||
let pat = match ctx.path_context() {
|
||||
Some(
|
||||
ctx @ PathCompletionCtx {
|
||||
kind: PathKind::Type { location: TypeLocation::TypeAscription(ascription), .. },
|
||||
..
|
||||
},
|
||||
) if ctx.is_trivial_path() => ascription,
|
||||
_ => return None,
|
||||
};
|
||||
let x = match pat {
|
||||
Let(pat) | FnParam(pat) => ctx.sema.type_of_pat(pat.as_ref()?),
|
||||
Const(exp) | RetType(exp) => ctx.sema.type_of_expr(exp.as_ref()?),
|
||||
TypeAscriptionTarget::Let(pat) | TypeAscriptionTarget::FnParam(pat) => {
|
||||
ctx.sema.type_of_pat(pat.as_ref()?)
|
||||
}
|
||||
TypeAscriptionTarget::Const(exp) | TypeAscriptionTarget::RetType(exp) => {
|
||||
ctx.sema.type_of_expr(exp.as_ref()?)
|
||||
}
|
||||
}?
|
||||
.adjusted();
|
||||
let ty_string = x.display_source_code(ctx.db, ctx.module.into()).ok()?;
|
||||
acc.add(render_type_inference(ty_string, ctx));
|
||||
None
|
||||
}
|
||||
|
||||
fn add_assoc_item(acc: &mut Completions, ctx: &CompletionContext, item: hir::AssocItem) {
|
||||
match item {
|
||||
hir::AssocItem::Const(ct) if ctx.expects_generic_arg() => acc.add_const(ctx, ct),
|
||||
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
|
||||
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,10 @@ use ide_db::{FxHashSet, SymbolKind};
|
|||
use syntax::{ast, AstNode};
|
||||
|
||||
use crate::{
|
||||
context::{CompletionContext, NameRefContext, PathCompletionCtx, PathKind, PathQualifierCtx},
|
||||
context::{
|
||||
CompletionContext, NameRefContext, NameRefKind, PathCompletionCtx, PathKind,
|
||||
PathQualifierCtx,
|
||||
},
|
||||
item::Builder,
|
||||
CompletionItem, CompletionItemKind, CompletionRelevance, Completions,
|
||||
};
|
||||
|
@ -13,8 +16,13 @@ use crate::{
|
|||
pub(crate) fn complete_use_tree(acc: &mut Completions, ctx: &CompletionContext) {
|
||||
let (&is_absolute_path, qualifier, name_ref) = match ctx.nameref_ctx() {
|
||||
Some(NameRefContext {
|
||||
path_ctx:
|
||||
Some(PathCompletionCtx { kind: PathKind::Use, is_absolute_path, qualifier, .. }),
|
||||
kind:
|
||||
Some(NameRefKind::Path(PathCompletionCtx {
|
||||
kind: PathKind::Use,
|
||||
is_absolute_path,
|
||||
qualifier,
|
||||
..
|
||||
})),
|
||||
nameref,
|
||||
..
|
||||
}) => (is_absolute_path, qualifier, nameref),
|
||||
|
|
|
@ -22,10 +22,7 @@ use syntax::{
|
|||
use text_edit::Indel;
|
||||
|
||||
use crate::{
|
||||
patterns::{
|
||||
determine_location, is_in_loop_body, is_in_token_of_for_loop, previous_token,
|
||||
ImmediateLocation,
|
||||
},
|
||||
patterns::{is_in_loop_body, is_in_token_of_for_loop, previous_token},
|
||||
CompletionConfig,
|
||||
};
|
||||
|
||||
|
@ -43,43 +40,7 @@ pub(crate) enum Visible {
|
|||
No,
|
||||
}
|
||||
|
||||
#[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,
|
||||
},
|
||||
Attr {
|
||||
kind: AttrKind,
|
||||
annotated_item_kind: Option<SyntaxKind>,
|
||||
},
|
||||
Derive,
|
||||
/// Path in item position, that is inside an (Assoc)ItemList
|
||||
Item {
|
||||
kind: ItemListKind,
|
||||
},
|
||||
Pat,
|
||||
Vis {
|
||||
has_in_token: bool,
|
||||
},
|
||||
Use,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) enum ItemListKind {
|
||||
SourceFile,
|
||||
Module,
|
||||
Impl,
|
||||
TraitImpl,
|
||||
Trait,
|
||||
ExternBlock,
|
||||
}
|
||||
|
||||
/// Existing qualifiers for the thing we are currently completing.
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct QualifierCtx {
|
||||
pub(super) unsafe_tok: Option<SyntaxToken>,
|
||||
|
@ -92,6 +53,7 @@ impl QualifierCtx {
|
|||
}
|
||||
}
|
||||
|
||||
/// The state of the path we are currently completing.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PathCompletionCtx {
|
||||
/// If this is a call with () already there (or {} in case of record patterns)
|
||||
|
@ -126,6 +88,65 @@ impl PathCompletionCtx {
|
|||
}
|
||||
}
|
||||
|
||||
/// The kind of path we are completing right now.
|
||||
#[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>,
|
||||
is_func_update: Option<ast::RecordExpr>,
|
||||
},
|
||||
Type {
|
||||
location: TypeLocation,
|
||||
},
|
||||
Attr {
|
||||
kind: AttrKind,
|
||||
annotated_item_kind: Option<SyntaxKind>,
|
||||
},
|
||||
Derive,
|
||||
/// Path in item position, that is inside an (Assoc)ItemList
|
||||
Item {
|
||||
kind: ItemListKind,
|
||||
},
|
||||
Pat,
|
||||
Vis {
|
||||
has_in_token: bool,
|
||||
},
|
||||
Use,
|
||||
}
|
||||
|
||||
/// Original file ast nodes
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TypeLocation {
|
||||
TupleField,
|
||||
TypeAscription(TypeAscriptionTarget),
|
||||
GenericArgList(Option<ast::GenericArgList>),
|
||||
TypeBound,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TypeAscriptionTarget {
|
||||
Let(Option<ast::Pat>),
|
||||
FnParam(Option<ast::Pat>),
|
||||
RetType(Option<ast::Expr>),
|
||||
Const(Option<ast::Expr>),
|
||||
}
|
||||
|
||||
/// The kind of item list a [`PathKind::Item`] belongs to.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub(super) enum ItemListKind {
|
||||
SourceFile,
|
||||
Module,
|
||||
Impl,
|
||||
TraitImpl,
|
||||
Trait,
|
||||
ExternBlock,
|
||||
}
|
||||
|
||||
/// The path qualifier state of the path we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PathQualifierCtx {
|
||||
pub(crate) path: ast::Path,
|
||||
|
@ -138,6 +159,7 @@ pub(crate) struct PathQualifierCtx {
|
|||
pub(crate) is_infer_qualifier: bool,
|
||||
}
|
||||
|
||||
/// The state of the pattern we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct PatternContext {
|
||||
pub(super) refutability: PatternRefutability,
|
||||
|
@ -150,12 +172,14 @@ pub(super) struct PatternContext {
|
|||
pub(super) record_pat: Option<ast::RecordPat>,
|
||||
}
|
||||
|
||||
/// The state of the lifetime we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct LifetimeContext {
|
||||
pub(super) lifetime: Option<ast::Lifetime>,
|
||||
pub(super) kind: LifetimeKind,
|
||||
}
|
||||
|
||||
/// The kind of lifetime we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum LifetimeKind {
|
||||
LifetimeParam { is_decl: bool, param: ast::LifetimeParam },
|
||||
|
@ -164,6 +188,7 @@ pub(super) enum LifetimeKind {
|
|||
LabelDef,
|
||||
}
|
||||
|
||||
/// The state of the name we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct NameContext {
|
||||
#[allow(dead_code)]
|
||||
|
@ -171,6 +196,7 @@ pub(super) struct NameContext {
|
|||
pub(super) kind: NameKind,
|
||||
}
|
||||
|
||||
/// The kind of the name we are completing.
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)]
|
||||
pub(super) enum NameKind {
|
||||
|
@ -195,34 +221,46 @@ pub(super) enum NameKind {
|
|||
Variant,
|
||||
}
|
||||
|
||||
/// The state of the NameRef we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct NameRefContext {
|
||||
/// NameRef syntax in the original file
|
||||
pub(super) nameref: Option<ast::NameRef>,
|
||||
// FIXME: these fields are actually disjoint -> enum
|
||||
pub(super) dot_access: Option<DotAccess>,
|
||||
pub(super) path_ctx: Option<PathCompletionCtx>,
|
||||
/// Position where we are only interested in keyword completions
|
||||
pub(super) keyword: Option<ast::Item>,
|
||||
/// The record expression this nameref is a field of
|
||||
pub(super) record_expr: Option<(ast::RecordExpr, bool)>,
|
||||
// FIXME: This shouldn't be an Option
|
||||
pub(super) kind: Option<NameRefKind>,
|
||||
}
|
||||
|
||||
/// The kind of the NameRef we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum NameRefKind {
|
||||
Path(PathCompletionCtx),
|
||||
DotAccess(DotAccess),
|
||||
/// Position where we are only interested in keyword completions
|
||||
Keyword(ast::Item),
|
||||
/// The record expression this nameref is a field of
|
||||
RecordExpr(ast::RecordExpr),
|
||||
}
|
||||
|
||||
/// The identifier we are currently completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) enum IdentContext {
|
||||
Name(NameContext),
|
||||
NameRef(NameRefContext),
|
||||
Lifetime(LifetimeContext),
|
||||
/// Original token, fake token
|
||||
/// The string the cursor is currently inside
|
||||
String {
|
||||
/// original token
|
||||
original: ast::String,
|
||||
/// fake token
|
||||
expanded: Option<ast::String>,
|
||||
},
|
||||
/// Set if we are currently completing in an unexpanded attribute, this usually implies a builtin attribute like `allow($0)`
|
||||
UnexpandedAttrTT {
|
||||
fake_attribute_under_caret: Option<ast::Attr>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Information about the field or method access we are completing.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct DotAccess {
|
||||
pub(super) receiver: Option<ast::Expr>,
|
||||
|
@ -278,9 +316,9 @@ pub(crate) struct CompletionContext<'a> {
|
|||
/// The parent impl of the cursor position if it exists.
|
||||
pub(super) impl_def: Option<ast::Impl>,
|
||||
/// Are we completing inside a let statement with a missing semicolon?
|
||||
// FIXME: This should be part of PathKind::Expr
|
||||
pub(super) incomplete_let: bool,
|
||||
|
||||
pub(super) completion_location: Option<ImmediateLocation>,
|
||||
pub(super) previous_token: Option<SyntaxToken>,
|
||||
|
||||
pub(super) ident_ctx: IdentContext,
|
||||
|
@ -341,9 +379,10 @@ impl<'a> CompletionContext<'a> {
|
|||
|
||||
pub(crate) fn dot_receiver(&self) -> Option<&ast::Expr> {
|
||||
match self.nameref_ctx() {
|
||||
Some(NameRefContext { dot_access: Some(DotAccess { receiver, .. }), .. }) => {
|
||||
receiver.as_ref()
|
||||
}
|
||||
Some(NameRefContext {
|
||||
kind: Some(NameRefKind::DotAccess(DotAccess { receiver, .. })),
|
||||
..
|
||||
}) => receiver.as_ref(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -352,13 +391,11 @@ impl<'a> CompletionContext<'a> {
|
|||
self.dot_receiver().is_some()
|
||||
}
|
||||
|
||||
// FIXME: This shouldn't exist
|
||||
pub(crate) fn expects_generic_arg(&self) -> bool {
|
||||
matches!(self.completion_location, Some(ImmediateLocation::GenericArgList(_)))
|
||||
}
|
||||
|
||||
pub(crate) fn path_context(&self) -> Option<&PathCompletionCtx> {
|
||||
self.nameref_ctx().and_then(|ctx| ctx.path_ctx.as_ref())
|
||||
self.nameref_ctx().and_then(|ctx| match &ctx.kind {
|
||||
Some(NameRefKind::Path(path)) => Some(path),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn path_qual(&self) -> Option<&ast::Path> {
|
||||
|
@ -505,7 +542,6 @@ impl<'a> CompletionContext<'a> {
|
|||
function_def: None,
|
||||
impl_def: None,
|
||||
incomplete_let: false,
|
||||
completion_location: None,
|
||||
previous_token: None,
|
||||
// dummy value, will be overwritten
|
||||
ident_ctx: IdentContext::UnexpandedAttrTT { fake_attribute_under_caret: None },
|
||||
|
@ -857,7 +893,7 @@ impl<'a> CompletionContext<'a> {
|
|||
let parent = name_ref.syntax().parent()?;
|
||||
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 {
|
||||
if let Some(NameRefKind::Path(path_ctx)) = &mut nameref_ctx.kind {
|
||||
path_ctx.kind = PathKind::Derive;
|
||||
}
|
||||
self.ident_ctx = IdentContext::NameRef(nameref_ctx);
|
||||
|
@ -898,8 +934,6 @@ impl<'a> CompletionContext<'a> {
|
|||
return Some(());
|
||||
}
|
||||
};
|
||||
self.completion_location =
|
||||
determine_location(&self.sema, original_file, offset, &name_like);
|
||||
self.impl_def = self
|
||||
.sema
|
||||
.token_ancestors_with_macros(self.token.clone())
|
||||
|
@ -1026,23 +1060,13 @@ impl<'a> CompletionContext<'a> {
|
|||
) -> (NameRefContext, Option<PatternContext>, QualifierCtx) {
|
||||
let nameref = find_node_at_offset(&original_file, name_ref.syntax().text_range().start());
|
||||
|
||||
let mut res = (
|
||||
NameRefContext {
|
||||
dot_access: None,
|
||||
path_ctx: None,
|
||||
nameref,
|
||||
record_expr: None,
|
||||
keyword: None,
|
||||
},
|
||||
None,
|
||||
QualifierCtx::default(),
|
||||
);
|
||||
let mut res = (NameRefContext { nameref, kind: 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 =
|
||||
nameref_ctx.kind =
|
||||
find_node_in_file_compensated(original_file, &record_field.parent_record_lit())
|
||||
.zip(Some(false));
|
||||
.map(NameRefKind::RecordExpr);
|
||||
return res;
|
||||
}
|
||||
if let Some(record_field) = ast::RecordPatField::for_field_name_ref(&name_ref) {
|
||||
|
@ -1067,7 +1091,7 @@ impl<'a> CompletionContext<'a> {
|
|||
match parent {
|
||||
ast::PathSegment(segment) => segment,
|
||||
ast::FieldExpr(field) => {
|
||||
let receiver = find_in_original_file(field.expr(), original_file);
|
||||
let receiver = find_opt_node_in_file(original_file, field.expr());
|
||||
let receiver_is_ambiguous_float_literal = match &receiver {
|
||||
Some(ast::Expr::Literal(l)) => matches! {
|
||||
l.kind(),
|
||||
|
@ -1075,20 +1099,20 @@ impl<'a> CompletionContext<'a> {
|
|||
},
|
||||
_ => false,
|
||||
};
|
||||
nameref_ctx.dot_access = Some(DotAccess {
|
||||
nameref_ctx.kind = Some(NameRefKind::DotAccess(DotAccess {
|
||||
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
|
||||
kind: DotAccessKind::Field { receiver_is_ambiguous_float_literal },
|
||||
receiver
|
||||
});
|
||||
}));
|
||||
return res;
|
||||
},
|
||||
ast::MethodCallExpr(method) => {
|
||||
let receiver = find_in_original_file(method.receiver(), original_file);
|
||||
nameref_ctx.dot_access = Some(DotAccess {
|
||||
let receiver = find_opt_node_in_file(original_file, method.receiver());
|
||||
nameref_ctx.kind = Some(NameRefKind::DotAccess(DotAccess {
|
||||
receiver_ty: receiver.as_ref().and_then(|it| sema.type_of_expr(it)),
|
||||
kind: DotAccessKind::Method { has_parens: method.arg_list().map_or(false, |it| it.l_paren_token().is_some()) },
|
||||
receiver
|
||||
});
|
||||
}));
|
||||
return res;
|
||||
},
|
||||
_ => return res,
|
||||
|
@ -1113,10 +1137,11 @@ impl<'a> CompletionContext<'a> {
|
|||
})
|
||||
.unwrap_or(false)
|
||||
};
|
||||
let mut fill_record_expr = |syn: &SyntaxNode| {
|
||||
let func_update_record = |syn: &SyntaxNode| {
|
||||
if let Some(record_expr) = syn.ancestors().nth(2).and_then(ast::RecordExpr::cast) {
|
||||
nameref_ctx.record_expr =
|
||||
find_node_in_file_compensated(original_file, &record_expr).zip(Some(true));
|
||||
find_node_in_file_compensated(original_file, &record_expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
let after_if_expr = |node: SyntaxNode| {
|
||||
|
@ -1161,33 +1186,91 @@ impl<'a> CompletionContext<'a> {
|
|||
None
|
||||
};
|
||||
|
||||
let type_location = |it: Option<SyntaxNode>| {
|
||||
let parent = it?;
|
||||
let res = match_ast! {
|
||||
match parent {
|
||||
ast::Const(it) => {
|
||||
let name = find_opt_node_in_file(original_file, it.name())?;
|
||||
let original = ast::Const::cast(name.syntax().parent()?)?;
|
||||
TypeLocation::TypeAscription(TypeAscriptionTarget::Const(original.body()))
|
||||
},
|
||||
ast::RetType(it) => {
|
||||
if it.thin_arrow_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
let parent = match ast::Fn::cast(parent.parent()?) {
|
||||
Some(x) => x.param_list(),
|
||||
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
|
||||
};
|
||||
|
||||
let parent = find_opt_node_in_file(original_file, parent)?.syntax().parent()?;
|
||||
TypeLocation::TypeAscription(TypeAscriptionTarget::RetType(match_ast! {
|
||||
match parent {
|
||||
ast::ClosureExpr(it) => {
|
||||
it.body()
|
||||
},
|
||||
ast::Fn(it) => {
|
||||
it.body().map(ast::Expr::BlockExpr)
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
}))
|
||||
},
|
||||
ast::Param(it) => {
|
||||
if it.colon_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
TypeLocation::TypeAscription(TypeAscriptionTarget::FnParam(find_opt_node_in_file(original_file, it.pat())))
|
||||
},
|
||||
ast::LetStmt(it) => {
|
||||
if it.colon_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
TypeLocation::TypeAscription(TypeAscriptionTarget::Let(find_opt_node_in_file(original_file, it.pat())))
|
||||
},
|
||||
ast::TypeBound(_) => TypeLocation::TypeBound,
|
||||
// is this case needed?
|
||||
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))),
|
||||
// is this case needed?
|
||||
ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(original_file, Some(it))),
|
||||
ast::TupleField(_) => TypeLocation::TupleField,
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
Some(res)
|
||||
};
|
||||
|
||||
// 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()))
|
||||
}),
|
||||
ast::PathType(it) => {
|
||||
let location = type_location(it.syntax().parent());
|
||||
Some(PathKind::Type {
|
||||
location: location.unwrap_or(TypeLocation::Other),
|
||||
})
|
||||
},
|
||||
ast::PathExpr(it) => {
|
||||
if let Some(p) = it.syntax().parent() {
|
||||
if ast::ExprStmt::can_cast(p.kind()) {
|
||||
if let Some(kind) = inbetween_body_and_decl_check(p) {
|
||||
nameref_ctx.keyword = Some(kind);
|
||||
nameref_ctx.kind = Some(NameRefKind::Keyword(kind));
|
||||
return None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fill_record_expr(it.syntax());
|
||||
|
||||
path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
|
||||
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);
|
||||
let is_func_update = func_update_record(it.syntax());
|
||||
|
||||
Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent })
|
||||
Some(PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent, is_func_update })
|
||||
},
|
||||
ast::TupleStructPat(it) => {
|
||||
path_ctx.has_call_parens = true;
|
||||
|
@ -1205,7 +1288,7 @@ impl<'a> CompletionContext<'a> {
|
|||
},
|
||||
ast::MacroCall(it) => {
|
||||
if let Some(kind) = inbetween_body_and_decl_check(it.syntax().clone()) {
|
||||
nameref_ctx.keyword = Some(kind);
|
||||
nameref_ctx.kind = Some(NameRefKind::Keyword(kind));
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -1213,7 +1296,12 @@ impl<'a> CompletionContext<'a> {
|
|||
let parent = it.syntax().parent();
|
||||
match parent.as_ref().map(|it| it.kind()) {
|
||||
Some(SyntaxKind::MACRO_PAT) => Some(PathKind::Pat),
|
||||
Some(SyntaxKind::MACRO_TYPE) => Some(PathKind::Type { in_tuple_struct: false }),
|
||||
Some(SyntaxKind::MACRO_TYPE) => {
|
||||
let location = type_location(parent.unwrap().parent());
|
||||
Some(PathKind::Type {
|
||||
location: location.unwrap_or(TypeLocation::Other),
|
||||
})
|
||||
},
|
||||
Some(SyntaxKind::ITEM_LIST) => Some(PathKind::Item { kind: ItemListKind::Module }),
|
||||
Some(SyntaxKind::ASSOC_ITEM_LIST) => Some(PathKind::Item { kind: match parent.and_then(|it| it.parent()) {
|
||||
Some(it) => match_ast! {
|
||||
|
@ -1236,10 +1324,10 @@ impl<'a> CompletionContext<'a> {
|
|||
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());
|
||||
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 }
|
||||
let is_func_update = func_update_record(it.syntax());
|
||||
PathKind::Expr { in_block_expr, in_loop_body, after_if_expr, ref_expr_parent, is_func_update }
|
||||
});
|
||||
},
|
||||
}
|
||||
|
@ -1365,7 +1453,7 @@ impl<'a> CompletionContext<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
nameref_ctx.path_ctx = Some(path_ctx);
|
||||
nameref_ctx.kind = Some(NameRefKind::Path(path_ctx));
|
||||
res
|
||||
}
|
||||
}
|
||||
|
@ -1422,15 +1510,14 @@ fn pattern_context_for(original_file: &SyntaxNode, pat: ast::Pat) -> PatternCont
|
|||
}
|
||||
}
|
||||
|
||||
fn find_in_original_file<N: AstNode>(x: Option<N>, original_file: &SyntaxNode) -> Option<N> {
|
||||
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
|
||||
let range = syntax.text_range().intersect(range)?;
|
||||
syntax.covering_element(range).ancestors().find_map(N::cast)
|
||||
}
|
||||
x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r))
|
||||
/// Attempts to find `node` inside `syntax` via `node`'s text range.
|
||||
/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
|
||||
fn find_opt_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: Option<N>) -> Option<N> {
|
||||
find_node_in_file(syntax, &node?)
|
||||
}
|
||||
|
||||
/// Attempts to find `node` inside `syntax` via `node`'s text range.
|
||||
/// If the fake identifier has been inserted after this node or inside of this node use the `_compensated` version instead.
|
||||
fn find_node_in_file<N: AstNode>(syntax: &SyntaxNode, node: &N) -> Option<N> {
|
||||
let syntax_range = syntax.text_range();
|
||||
let range = node.syntax().text_range();
|
||||
|
@ -1449,11 +1536,21 @@ fn find_node_in_file_compensated<N: AstNode>(syntax: &SyntaxNode, node: &N) -> O
|
|||
return None;
|
||||
}
|
||||
let range = TextRange::new(range.start(), end);
|
||||
// our inserted ident could cause `range` to be 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)?;
|
||||
syntax.covering_element(intersection).ancestors().find_map(N::cast)
|
||||
}
|
||||
|
||||
/// Attempts to find `node` inside `syntax` via `node`'s text range while compensating
|
||||
/// for the offset introduced by the fake ident..
|
||||
/// 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>(
|
||||
syntax: &SyntaxNode,
|
||||
node: Option<N>,
|
||||
) -> Option<N> {
|
||||
find_node_in_file_compensated(syntax, &node?)
|
||||
}
|
||||
|
||||
fn path_or_use_tree_qualifier(path: &ast::Path) -> Option<(ast::Path, bool)> {
|
||||
if let Some(qual) = path.qualifier() {
|
||||
return Some((qual, false));
|
||||
|
|
|
@ -4,171 +4,16 @@
|
|||
//! This means we for example expand a NameRef token to its outermost Path node, as semantically these act in the same location
|
||||
//! and the completions usually query for path specific things on the Path context instead. This simplifies some location handling.
|
||||
|
||||
use hir::Semantics;
|
||||
use ide_db::RootDatabase;
|
||||
use syntax::{
|
||||
ast::{self, HasLoopBody, HasName},
|
||||
ast::{self, HasLoopBody},
|
||||
match_ast, AstNode, SyntaxElement,
|
||||
SyntaxKind::*,
|
||||
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||
SyntaxNode, SyntaxToken,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use crate::tests::check_pattern_is_applicable;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum TypeAnnotation {
|
||||
Let(Option<ast::Pat>),
|
||||
FnParam(Option<ast::Pat>),
|
||||
RetType(Option<ast::Expr>),
|
||||
Const(Option<ast::Expr>),
|
||||
}
|
||||
|
||||
/// Direct parent "thing" of what we are currently completing.
|
||||
///
|
||||
/// This may contain nodes of the fake file as well as the original, comments on the variants specify
|
||||
/// from which file the nodes are.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ImmediateLocation {
|
||||
TypeBound,
|
||||
/// Original file ast node
|
||||
TypeAnnotation(TypeAnnotation),
|
||||
// Only set from a type arg
|
||||
/// Original file ast node
|
||||
GenericArgList(ast::GenericArgList),
|
||||
}
|
||||
|
||||
pub(crate) fn determine_location(
|
||||
sema: &Semantics<RootDatabase>,
|
||||
original_file: &SyntaxNode,
|
||||
offset: TextSize,
|
||||
name_like: &ast::NameLike,
|
||||
) -> Option<ImmediateLocation> {
|
||||
let node = match name_like {
|
||||
ast::NameLike::NameRef(name_ref) => maximize_name_ref(name_ref),
|
||||
ast::NameLike::Name(name) => name.syntax().clone(),
|
||||
ast::NameLike::Lifetime(lt) => lt.syntax().clone(),
|
||||
};
|
||||
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::TypeBoundList(_it) => return Some(ImmediateLocation::TypeBound),
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
|
||||
let parent = match node.parent() {
|
||||
Some(parent) => match ast::MacroCall::cast(parent.clone()) {
|
||||
// When a path is being typed in an (Assoc)ItemList the parser will always emit a macro_call.
|
||||
// This is usually fine as the node expansion code above already accounts for that with
|
||||
// the ancestors call, but there is one exception to this which is that when an attribute
|
||||
// precedes it the code above will not walk the Path to the parent MacroCall as their ranges differ.
|
||||
// FIXME path expr and statement have a similar problem
|
||||
Some(call)
|
||||
if call.excl_token().is_none()
|
||||
&& call.token_tree().is_none()
|
||||
&& call.semicolon_token().is_none() =>
|
||||
{
|
||||
call.syntax().parent()?
|
||||
}
|
||||
_ => parent,
|
||||
},
|
||||
// SourceFile
|
||||
None => return None,
|
||||
};
|
||||
|
||||
let res = match_ast! {
|
||||
match parent {
|
||||
ast::TypeBound(_) => ImmediateLocation::TypeBound,
|
||||
ast::TypeBoundList(_) => ImmediateLocation::TypeBound,
|
||||
ast::GenericArgList(_) => sema
|
||||
.find_node_at_offset_with_macros(original_file, offset)
|
||||
.map(ImmediateLocation::GenericArgList)?,
|
||||
ast::Const(it) => {
|
||||
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
|
||||
return None;
|
||||
}
|
||||
let name = find_in_original_file(it.name(), original_file)?;
|
||||
let original = ast::Const::cast(name.syntax().parent()?)?;
|
||||
ImmediateLocation::TypeAnnotation(TypeAnnotation::Const(original.body()))
|
||||
},
|
||||
ast::RetType(it) => {
|
||||
if it.thin_arrow_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
|
||||
return None;
|
||||
}
|
||||
let parent = match ast::Fn::cast(parent.parent()?) {
|
||||
Some(x) => x.param_list(),
|
||||
None => ast::ClosureExpr::cast(parent.parent()?)?.param_list(),
|
||||
};
|
||||
let parent = find_in_original_file(parent, original_file)?.syntax().parent()?;
|
||||
ImmediateLocation::TypeAnnotation(TypeAnnotation::RetType(match_ast! {
|
||||
match parent {
|
||||
ast::ClosureExpr(it) => {
|
||||
it.body()
|
||||
},
|
||||
ast::Fn(it) => {
|
||||
it.body().map(ast::Expr::BlockExpr)
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
}))
|
||||
},
|
||||
ast::Param(it) => {
|
||||
if it.colon_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
|
||||
return None;
|
||||
}
|
||||
ImmediateLocation::TypeAnnotation(TypeAnnotation::FnParam(find_in_original_file(it.pat(), original_file)))
|
||||
},
|
||||
ast::LetStmt(it) => {
|
||||
if it.colon_token().is_none() {
|
||||
return None;
|
||||
}
|
||||
if !it.ty().map_or(false, |x| x.syntax().text_range().contains(offset)) {
|
||||
return None;
|
||||
}
|
||||
ImmediateLocation::TypeAnnotation(TypeAnnotation::Let(find_in_original_file(it.pat(), original_file)))
|
||||
},
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
fn find_in_original_file<N: AstNode>(x: Option<N>, original_file: &SyntaxNode) -> Option<N> {
|
||||
x.map(|e| e.syntax().text_range()).and_then(|r| find_node_with_range(original_file, r))
|
||||
}
|
||||
Some(res)
|
||||
}
|
||||
|
||||
/// Maximize a nameref to its enclosing path if its the last segment of said path.
|
||||
/// That is, when completing a [`NameRef`] we actually handle it as the path it is part of when determining
|
||||
/// its location.
|
||||
fn maximize_name_ref(name_ref: &ast::NameRef) -> SyntaxNode {
|
||||
if let Some(segment) = name_ref.syntax().parent().and_then(ast::PathSegment::cast) {
|
||||
let p = segment.parent_path();
|
||||
if p.parent_path().is_none() {
|
||||
// Get rid of PathExpr, PathType, etc...
|
||||
let path = p
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.take_while(|it| it.text_range() == p.syntax().text_range())
|
||||
.last();
|
||||
if let Some(it) = path {
|
||||
return it;
|
||||
}
|
||||
}
|
||||
}
|
||||
name_ref.syntax().clone()
|
||||
}
|
||||
|
||||
fn find_node_with_range<N: AstNode>(syntax: &SyntaxNode, range: TextRange) -> Option<N> {
|
||||
let range = syntax.text_range().intersect(range)?;
|
||||
syntax.covering_element(range).ancestors().find_map(N::cast)
|
||||
}
|
||||
|
||||
pub(crate) fn previous_token(element: SyntaxElement) -> Option<SyntaxToken> {
|
||||
element.into_token().and_then(previous_non_trivia_token)
|
||||
}
|
||||
|
|
|
@ -8,7 +8,8 @@ use syntax::SmolStr;
|
|||
|
||||
use crate::{
|
||||
context::{
|
||||
CompletionContext, DotAccess, DotAccessKind, NameRefContext, PathCompletionCtx, PathKind,
|
||||
CompletionContext, DotAccess, DotAccessKind, NameRefContext, NameRefKind,
|
||||
PathCompletionCtx, PathKind,
|
||||
},
|
||||
item::{Builder, CompletionItem, CompletionItemKind, CompletionRelevance},
|
||||
render::{compute_exact_name_match, compute_ref_match, compute_type_match, RenderContext},
|
||||
|
@ -212,7 +213,10 @@ fn should_add_parens(ctx: &CompletionContext) -> bool {
|
|||
if matches!(
|
||||
ctx.nameref_ctx(),
|
||||
Some(NameRefContext {
|
||||
dot_access: Some(DotAccess { kind: DotAccessKind::Method { has_parens: true }, .. }),
|
||||
kind: Some(NameRefKind::DotAccess(DotAccess {
|
||||
kind: DotAccessKind::Method { has_parens: true },
|
||||
..
|
||||
})),
|
||||
..
|
||||
})
|
||||
) {
|
||||
|
|
|
@ -105,7 +105,6 @@ fn foo(f: Struct) {
|
|||
#[test]
|
||||
fn functional_update() {
|
||||
// FIXME: This should filter out all completions that do not have the type `Foo`
|
||||
// FIXME: Fields should not show up after `.`
|
||||
check(
|
||||
r#"
|
||||
//- minicore:default
|
||||
|
@ -192,8 +191,6 @@ fn main() {
|
|||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
fd foo1 u32
|
||||
fd foo2 u32
|
||||
fn default() (as Default) fn() -> Self
|
||||
"#]],
|
||||
);
|
||||
|
|
Loading…
Add table
Reference in a new issue