blob: 802e9bc3a8077498715ab253c31bbf29ca5ea589 [file] [log] [blame]
//! Completion of names from the current scope in expression position.
use hir::ScopeDef;
use syntax::ast;
use crate::{
completions::record::add_default_update,
context::{BreakableKind, PathCompletionCtx, PathExprCtx, Qualified},
CompletionContext, Completions,
};
pub(crate) fn complete_expr_path(
acc: &mut Completions,
ctx: &CompletionContext<'_>,
path_ctx @ PathCompletionCtx { qualified, .. }: &PathCompletionCtx,
expr_ctx: &PathExprCtx,
) {
let _p = tracing::span!(tracing::Level::INFO, "complete_expr_path").entered();
if !ctx.qualifier_ctx.none() {
return;
}
let &PathExprCtx {
in_block_expr,
in_breakable,
after_if_expr,
in_condition,
incomplete_let,
ref ref_expr_parent,
ref is_func_update,
ref innermost_ret_ty,
ref impl_,
in_match_guard,
..
} = expr_ctx;
let wants_mut_token =
ref_expr_parent.as_ref().map(|it| it.mut_token().is_none()).unwrap_or(false);
let scope_def_applicable = |def| match def {
ScopeDef::GenericParam(hir::GenericParam::LifetimeParam(_)) | ScopeDef::Label(_) => false,
ScopeDef::ModuleDef(hir::ModuleDef::Macro(mac)) => mac.is_fn_like(ctx.db),
_ => true,
};
let add_assoc_item = |acc: &mut Completions, item| match item {
hir::AssocItem::Function(func) => acc.add_function(ctx, path_ctx, func, None),
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
};
match qualified {
Qualified::TypeAnchor { ty: None, trait_: None } => ctx
.traits_in_scope()
.iter()
.flat_map(|&it| hir::Trait::from(it).items(ctx.sema.db))
.for_each(|item| add_assoc_item(acc, item)),
Qualified::TypeAnchor { trait_: Some(trait_), .. } => {
trait_.items(ctx.sema.db).into_iter().for_each(|item| add_assoc_item(acc, item))
}
Qualified::TypeAnchor { ty: Some(ty), trait_: None } => {
if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
cov_mark::hit!(completes_variant_through_alias);
acc.add_enum_variants(ctx, path_ctx, e);
}
ctx.iterate_path_candidates(ty, |item| {
add_assoc_item(acc, item);
});
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
Qualified::With { resolution: None, .. } => {}
Qualified::With { resolution: Some(resolution), .. } => {
// Add associated types on type parameters and `Self`.
ctx.scope.assoc_type_shorthand_candidates(resolution, |_, alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
match resolution {
hir::PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, Some(ctx.module));
for (name, def) in module_scope {
if scope_def_applicable(def) {
acc.add_path_resolution(
ctx,
path_ctx,
name,
def,
ctx.doc_aliases_in_scope(def),
);
}
}
}
hir::PathResolution::Def(
def @ (hir::ModuleDef::Adt(_)
| hir::ModuleDef::TypeAlias(_)
| hir::ModuleDef::BuiltinType(_)),
) => {
let ty = match def {
hir::ModuleDef::Adt(adt) => adt.ty(ctx.db),
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
hir::ModuleDef::BuiltinType(builtin) => {
cov_mark::hit!(completes_primitive_assoc_const);
builtin.ty(ctx.db)
}
_ => return,
};
if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
cov_mark::hit!(completes_variant_through_alias);
acc.add_enum_variants(ctx, path_ctx, e);
}
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
// (where AssocType is defined on a trait, not an inherent impl)
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
// Iterate assoc types separately
ty.iterate_assoc_items(ctx.db, ctx.krate, |item| {
if let hir::AssocItem::TypeAlias(ty) = item {
acc.add_type_alias(ctx, ty)
}
None::<()>
});
}
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, item);
}
}
hir::PathResolution::TypeParam(_) | hir::PathResolution::SelfType(_) => {
let ty = match resolution {
hir::PathResolution::TypeParam(param) => param.ty(ctx.db),
hir::PathResolution::SelfType(impl_def) => impl_def.self_ty(ctx.db),
_ => return,
};
if let Some(hir::Adt::Enum(e)) = ty.as_adt() {
cov_mark::hit!(completes_variant_through_self);
acc.add_enum_variants(ctx, path_ctx, e);
}
ctx.iterate_path_candidates(&ty, |item| {
add_assoc_item(acc, item);
});
}
_ => (),
}
}
Qualified::Absolute => acc.add_crate_roots(ctx, path_ctx),
Qualified::No => {
acc.add_nameref_keywords_with_colon(ctx);
if let Some(adt) =
ctx.expected_type.as_ref().and_then(|ty| ty.strip_references().as_adt())
{
let self_ty = (|| ctx.sema.to_def(impl_.as_ref()?)?.self_ty(ctx.db).as_adt())();
let complete_self = self_ty == Some(adt);
match adt {
hir::Adt::Struct(strukt) => {
let path = ctx
.module
.find_use_path(
ctx.db,
hir::ModuleDef::from(strukt),
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
)
.filter(|it| it.len() > 1);
acc.add_struct_literal(ctx, path_ctx, strukt, path, None);
if complete_self {
acc.add_struct_literal(
ctx,
path_ctx,
strukt,
None,
Some(hir::known::SELF_TYPE),
);
}
}
hir::Adt::Union(un) => {
let path = ctx
.module
.find_use_path(
ctx.db,
hir::ModuleDef::from(un),
ctx.config.prefer_no_std,
ctx.config.prefer_prelude,
)
.filter(|it| it.len() > 1);
acc.add_union_literal(ctx, un, path, None);
if complete_self {
acc.add_union_literal(ctx, un, None, Some(hir::known::SELF_TYPE));
}
}
hir::Adt::Enum(e) => {
super::enum_variants_with_paths(
acc,
ctx,
e,
impl_,
|acc, ctx, variant, path| {
acc.add_qualified_enum_variant(ctx, path_ctx, variant, path)
},
);
}
}
}
ctx.process_all_names(&mut |name, def, doc_aliases| match def {
ScopeDef::ModuleDef(hir::ModuleDef::Trait(t)) => {
let assocs = t.items_with_supertraits(ctx.db);
match &*assocs {
// traits with no assoc items are unusable as expressions since
// there is no associated item path that can be constructed with them
[] => (),
// FIXME: Render the assoc item with the trait qualified
&[_item] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
// FIXME: Append `::` to the thing here, since a trait on its own won't work
[..] => acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases),
}
}
_ if scope_def_applicable(def) => {
acc.add_path_resolution(ctx, path_ctx, name, def, doc_aliases)
}
_ => (),
});
match is_func_update {
Some(record_expr) => {
let ty = ctx.sema.type_of_expr(&ast::Expr::RecordExpr(record_expr.clone()));
match ty.as_ref().and_then(|t| t.original.as_adt()) {
Some(hir::Adt::Union(_)) => (),
_ => {
cov_mark::hit!(functional_update);
let missing_fields =
ctx.sema.record_literal_missing_fields(record_expr);
if !missing_fields.is_empty() {
add_default_update(acc, ctx, ty);
}
}
};
}
None => {
let mut add_keyword = |kw, snippet| {
acc.add_keyword_snippet_expr(ctx, incomplete_let, kw, snippet)
};
if !in_block_expr {
add_keyword("unsafe", "unsafe {\n $0\n}");
}
add_keyword("match", "match $1 {\n $0\n}");
add_keyword("while", "while $1 {\n $0\n}");
add_keyword("while let", "while let $1 = $2 {\n $0\n}");
add_keyword("loop", "loop {\n $0\n}");
if in_match_guard {
add_keyword("if", "if $0");
} else {
add_keyword("if", "if $1 {\n $0\n}");
}
add_keyword("if let", "if let $1 = $2 {\n $0\n}");
add_keyword("for", "for $1 in $2 {\n $0\n}");
add_keyword("true", "true");
add_keyword("false", "false");
if in_condition || in_block_expr {
add_keyword("let", "let");
}
if after_if_expr {
add_keyword("else", "else {\n $0\n}");
add_keyword("else if", "else if $1 {\n $0\n}");
}
if wants_mut_token {
add_keyword("mut", "mut ");
}
if in_breakable != BreakableKind::None {
if in_block_expr {
add_keyword("continue", "continue;");
add_keyword("break", "break;");
} else {
add_keyword("continue", "continue");
add_keyword("break", "break");
}
}
if let Some(ret_ty) = innermost_ret_ty {
add_keyword(
"return",
match (ret_ty.is_unit(), in_block_expr) {
(true, true) => {
cov_mark::hit!(return_unit_block);
"return;"
}
(true, false) => {
cov_mark::hit!(return_unit_no_block);
"return"
}
(false, true) => {
cov_mark::hit!(return_value_block);
"return $0;"
}
(false, false) => {
cov_mark::hit!(return_value_no_block);
"return $0"
}
},
);
}
}
}
}
}
}
pub(crate) fn complete_expr(acc: &mut Completions, ctx: &CompletionContext<'_>) {
let _p = tracing::span!(tracing::Level::INFO, "complete_expr").entered();
if !ctx.config.enable_term_search {
return;
}
if !ctx.qualifier_ctx.none() {
return;
}
if let Some(ty) = &ctx.expected_type {
// Ignore unit types as they are not very interesting
if ty.is_unit() || ty.is_unknown() {
return;
}
let term_search_ctx = hir::term_search::TermSearchCtx {
sema: &ctx.sema,
scope: &ctx.scope,
goal: ty.clone(),
config: hir::term_search::TermSearchConfig {
enable_borrowcheck: false,
many_alternatives_threshold: 1,
depth: 6,
},
};
let exprs = hir::term_search::term_search(&term_search_ctx);
for expr in exprs {
// Expand method calls
match expr {
hir::term_search::Expr::Method { func, generics, target, params }
if target.is_many() =>
{
let target_ty = target.ty(ctx.db);
let term_search_ctx =
hir::term_search::TermSearchCtx { goal: target_ty, ..term_search_ctx };
let target_exprs = hir::term_search::term_search(&term_search_ctx);
for expr in target_exprs {
let expanded_expr = hir::term_search::Expr::Method {
func,
generics: generics.clone(),
target: Box::new(expr),
params: params.clone(),
};
acc.add_expr(ctx, &expanded_expr)
}
}
_ => acc.add_expr(ctx, &expr),
}
}
}
}