Skip to content

Commit f73cd39

Browse files
committed
Auto merge of rust-lang#15383 - max-heller:issue-12568, r=Veykril
Suggest type completions for type arguments and constant completions for constant arguments When determining completions for generic arguments, suggest only types or only constants if the corresponding generic parameter is a type parameter or constant parameter. Closes rust-lang#12568
2 parents 0fa822d + fb98f52 commit f73cd39

File tree

5 files changed

+503
-75
lines changed

5 files changed

+503
-75
lines changed

crates/ide-completion/src/completions.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,9 @@ pub(super) fn complete_name_ref(
703703
TypeLocation::TypeAscription(ascription) => {
704704
r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
705705
}
706-
TypeLocation::GenericArgList(_)
706+
TypeLocation::GenericArg { .. }
707+
| TypeLocation::AssocConstEq
708+
| TypeLocation::AssocTypeEq
707709
| TypeLocation::TypeBound
708710
| TypeLocation::ImplTarget
709711
| TypeLocation::ImplTrait

crates/ide-completion/src/completions/type.rs

+30-57
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Completion of names from the current scope in type position.
22
33
use hir::{HirDisplay, ScopeDef};
4-
use syntax::{ast, AstNode, SyntaxKind};
4+
use syntax::{ast, AstNode};
55

66
use crate::{
77
context::{PathCompletionCtx, Qualified, TypeAscriptionTarget, TypeLocation},
@@ -20,16 +20,15 @@ pub(crate) fn complete_type_path(
2020
let scope_def_applicable = |def| {
2121
use hir::{GenericParam::*, ModuleDef::*};
2222
match def {
23-
ScopeDef::GenericParam(LifetimeParam(_)) | ScopeDef::Label(_) => false,
23+
ScopeDef::GenericParam(LifetimeParam(_)) => location.complete_lifetimes(),
24+
ScopeDef::Label(_) => false,
2425
// no values in type places
2526
ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false,
2627
// unless its a constant in a generic arg list position
2728
ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => {
28-
matches!(location, TypeLocation::GenericArgList(_))
29-
}
30-
ScopeDef::ImplSelfType(_) => {
31-
!matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
29+
location.complete_consts()
3230
}
31+
ScopeDef::ImplSelfType(_) => location.complete_self_type(),
3332
// Don't suggest attribute macros and derives.
3433
ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db),
3534
// Type things are fine
@@ -38,12 +37,12 @@ pub(crate) fn complete_type_path(
3837
)
3938
| ScopeDef::AdtSelfType(_)
4039
| ScopeDef::Unknown
41-
| ScopeDef::GenericParam(TypeParam(_)) => true,
40+
| ScopeDef::GenericParam(TypeParam(_)) => location.complete_types(),
4241
}
4342
};
4443

4544
let add_assoc_item = |acc: &mut Completions, item| match item {
46-
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => {
45+
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArg { .. }) => {
4746
acc.add_const(ctx, ct)
4847
}
4948
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
@@ -157,56 +156,30 @@ pub(crate) fn complete_type_path(
157156
});
158157
return;
159158
}
160-
TypeLocation::GenericArgList(Some(arg_list)) => {
161-
let in_assoc_type_arg = ctx
162-
.original_token
163-
.parent_ancestors()
164-
.any(|node| node.kind() == SyntaxKind::ASSOC_TYPE_ARG);
165-
166-
if !in_assoc_type_arg {
167-
if let Some(path_seg) =
168-
arg_list.syntax().parent().and_then(ast::PathSegment::cast)
169-
{
170-
if path_seg
171-
.syntax()
172-
.ancestors()
173-
.find_map(ast::TypeBound::cast)
174-
.is_some()
175-
{
176-
if let Some(hir::PathResolution::Def(hir::ModuleDef::Trait(
177-
trait_,
178-
))) = ctx.sema.resolve_path(&path_seg.parent_path())
179-
{
180-
let arg_idx = arg_list
181-
.generic_args()
182-
.filter(|arg| {
183-
arg.syntax().text_range().end()
184-
< ctx.original_token.text_range().start()
185-
})
186-
.count();
187-
188-
let n_required_params =
189-
trait_.type_or_const_param_count(ctx.sema.db, true);
190-
if arg_idx >= n_required_params {
191-
trait_
192-
.items_with_supertraits(ctx.sema.db)
193-
.into_iter()
194-
.for_each(|it| {
195-
if let hir::AssocItem::TypeAlias(alias) = it {
196-
cov_mark::hit!(
197-
complete_assoc_type_in_generics_list
198-
);
199-
acc.add_type_alias_with_eq(ctx, alias);
200-
}
201-
});
202-
203-
let n_params =
204-
trait_.type_or_const_param_count(ctx.sema.db, false);
205-
if arg_idx >= n_params {
206-
return; // only show assoc types
207-
}
208-
}
159+
TypeLocation::GenericArg {
160+
args: Some(arg_list), of_trait: Some(trait_), ..
161+
} => {
162+
if arg_list.syntax().ancestors().find_map(ast::TypeBound::cast).is_some() {
163+
let arg_idx = arg_list
164+
.generic_args()
165+
.filter(|arg| {
166+
arg.syntax().text_range().end()
167+
< ctx.original_token.text_range().start()
168+
})
169+
.count();
170+
171+
let n_required_params = trait_.type_or_const_param_count(ctx.sema.db, true);
172+
if arg_idx >= n_required_params {
173+
trait_.items_with_supertraits(ctx.sema.db).into_iter().for_each(|it| {
174+
if let hir::AssocItem::TypeAlias(alias) = it {
175+
cov_mark::hit!(complete_assoc_type_in_generics_list);
176+
acc.add_type_alias_with_eq(ctx, alias);
209177
}
178+
});
179+
180+
let n_params = trait_.type_or_const_param_count(ctx.sema.db, false);
181+
if arg_idx >= n_params {
182+
return; // only show assoc types
210183
}
211184
}
212185
}

crates/ide-completion/src/context.rs

+51-1
Original file line numberDiff line numberDiff line change
@@ -155,13 +155,63 @@ pub(crate) struct ExprCtx {
155155
pub(crate) enum TypeLocation {
156156
TupleField,
157157
TypeAscription(TypeAscriptionTarget),
158-
GenericArgList(Option<ast::GenericArgList>),
158+
/// Generic argument position e.g. `Foo<$0>`
159+
GenericArg {
160+
/// The generic argument list containing the generic arg
161+
args: Option<ast::GenericArgList>,
162+
/// `Some(trait_)` if `trait_` is being instantiated with `args`
163+
of_trait: Option<hir::Trait>,
164+
/// The generic parameter being filled in by the generic arg
165+
corresponding_param: Option<ast::GenericParam>,
166+
},
167+
/// Associated type equality constraint e.g. `Foo<Bar = $0>`
168+
AssocTypeEq,
169+
/// Associated constant equality constraint e.g. `Foo<X = $0>`
170+
AssocConstEq,
159171
TypeBound,
160172
ImplTarget,
161173
ImplTrait,
162174
Other,
163175
}
164176

177+
impl TypeLocation {
178+
pub(crate) fn complete_lifetimes(&self) -> bool {
179+
matches!(
180+
self,
181+
TypeLocation::GenericArg {
182+
corresponding_param: Some(ast::GenericParam::LifetimeParam(_)),
183+
..
184+
}
185+
)
186+
}
187+
188+
pub(crate) fn complete_consts(&self) -> bool {
189+
match self {
190+
TypeLocation::GenericArg {
191+
corresponding_param: Some(ast::GenericParam::ConstParam(_)),
192+
..
193+
} => true,
194+
TypeLocation::AssocConstEq => true,
195+
_ => false,
196+
}
197+
}
198+
199+
pub(crate) fn complete_types(&self) -> bool {
200+
match self {
201+
TypeLocation::GenericArg { corresponding_param: Some(param), .. } => {
202+
matches!(param, ast::GenericParam::TypeParam(_))
203+
}
204+
TypeLocation::AssocConstEq => false,
205+
TypeLocation::AssocTypeEq => true,
206+
_ => true,
207+
}
208+
}
209+
210+
pub(crate) fn complete_self_type(&self) -> bool {
211+
self.complete_types() && !matches!(self, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
212+
}
213+
}
214+
165215
#[derive(Clone, Debug, PartialEq, Eq)]
166216
pub(crate) enum TypeAscriptionTarget {
167217
Let(Option<ast::Pat>),

crates/ide-completion/src/context/analysis.rs

+137-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! Module responsible for analyzing the code surrounding the cursor for completion.
22
use std::iter;
33

4-
use hir::{Semantics, Type, TypeInfo, Variant};
4+
use hir::{HasSource, Semantics, Type, TypeInfo, Variant};
55
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
66
use syntax::{
77
algo::{find_node_at_offset, non_trivia_sibling},
8-
ast::{self, AttrKind, HasArgList, HasLoopBody, HasName, NameOrNameRef},
8+
ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
99
match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
1010
SyntaxToken, TextRange, TextSize, T,
1111
};
@@ -719,6 +719,136 @@ fn classify_name_ref(
719719
None
720720
};
721721

722+
let generic_arg_location = |arg: ast::GenericArg| {
723+
let mut override_location = None;
724+
let location = find_opt_node_in_file_compensated(
725+
sema,
726+
original_file,
727+
arg.syntax().parent().and_then(ast::GenericArgList::cast),
728+
)
729+
.map(|args| {
730+
let mut in_trait = None;
731+
let param = (|| {
732+
let parent = args.syntax().parent()?;
733+
let params = match_ast! {
734+
match parent {
735+
ast::PathSegment(segment) => {
736+
match sema.resolve_path(&segment.parent_path().top_path())? {
737+
hir::PathResolution::Def(def) => match def {
738+
hir::ModuleDef::Function(func) => {
739+
func.source(sema.db)?.value.generic_param_list()
740+
}
741+
hir::ModuleDef::Adt(adt) => {
742+
adt.source(sema.db)?.value.generic_param_list()
743+
}
744+
hir::ModuleDef::Variant(variant) => {
745+
variant.parent_enum(sema.db).source(sema.db)?.value.generic_param_list()
746+
}
747+
hir::ModuleDef::Trait(trait_) => {
748+
if let ast::GenericArg::AssocTypeArg(arg) = &arg {
749+
let arg_name = arg.name_ref()?;
750+
let arg_name = arg_name.text();
751+
for item in trait_.items_with_supertraits(sema.db) {
752+
match item {
753+
hir::AssocItem::TypeAlias(assoc_ty) => {
754+
if assoc_ty.name(sema.db).as_str()? == arg_name {
755+
override_location = Some(TypeLocation::AssocTypeEq);
756+
return None;
757+
}
758+
},
759+
hir::AssocItem::Const(const_) => {
760+
if const_.name(sema.db)?.as_str()? == arg_name {
761+
override_location = Some(TypeLocation::AssocConstEq);
762+
return None;
763+
}
764+
},
765+
_ => (),
766+
}
767+
}
768+
return None;
769+
} else {
770+
in_trait = Some(trait_);
771+
trait_.source(sema.db)?.value.generic_param_list()
772+
}
773+
}
774+
hir::ModuleDef::TraitAlias(trait_) => {
775+
trait_.source(sema.db)?.value.generic_param_list()
776+
}
777+
hir::ModuleDef::TypeAlias(ty_) => {
778+
ty_.source(sema.db)?.value.generic_param_list()
779+
}
780+
_ => None,
781+
},
782+
_ => None,
783+
}
784+
},
785+
ast::MethodCallExpr(call) => {
786+
let func = sema.resolve_method_call(&call)?;
787+
func.source(sema.db)?.value.generic_param_list()
788+
},
789+
ast::AssocTypeArg(arg) => {
790+
let trait_ = ast::PathSegment::cast(arg.syntax().parent()?.parent()?)?;
791+
match sema.resolve_path(&trait_.parent_path().top_path())? {
792+
hir::PathResolution::Def(def) => match def {
793+
hir::ModuleDef::Trait(trait_) => {
794+
let arg_name = arg.name_ref()?;
795+
let arg_name = arg_name.text();
796+
let trait_items = trait_.items_with_supertraits(sema.db);
797+
let assoc_ty = trait_items.iter().find_map(|item| match item {
798+
hir::AssocItem::TypeAlias(assoc_ty) => {
799+
(assoc_ty.name(sema.db).as_str()? == arg_name)
800+
.then_some(assoc_ty)
801+
},
802+
_ => None,
803+
})?;
804+
assoc_ty.source(sema.db)?.value.generic_param_list()
805+
}
806+
_ => None,
807+
},
808+
_ => None,
809+
}
810+
},
811+
_ => None,
812+
}
813+
}?;
814+
// Determine the index of the argument in the `GenericArgList` and match it with
815+
// the corresponding parameter in the `GenericParamList`. Since lifetime parameters
816+
// are often omitted, ignore them for the purposes of matching the argument with
817+
// its parameter unless a lifetime argument is provided explicitly. That is, for
818+
// `struct S<'a, 'b, T>`, match `S::<$0>` to `T` and `S::<'a, $0, _>` to `'b`.
819+
// FIXME: This operates on the syntax tree and will produce incorrect results when
820+
// generic parameters are disabled by `#[cfg]` directives. It should operate on the
821+
// HIR, but the functionality necessary to do so is not exposed at the moment.
822+
let mut explicit_lifetime_arg = false;
823+
let arg_idx = arg
824+
.syntax()
825+
.siblings(Direction::Prev)
826+
// Skip the node itself
827+
.skip(1)
828+
.map(|arg| if ast::LifetimeArg::can_cast(arg.kind()) { explicit_lifetime_arg = true })
829+
.count();
830+
let param_idx = if explicit_lifetime_arg {
831+
arg_idx
832+
} else {
833+
// Lifetimes parameters always precede type and generic parameters,
834+
// so offset the argument index by the total number of lifetime params
835+
arg_idx + params.lifetime_params().count()
836+
};
837+
params.generic_params().nth(param_idx)
838+
})();
839+
(args, in_trait, param)
840+
});
841+
let (arg_list, of_trait, corresponding_param) = match location {
842+
Some((arg_list, of_trait, param)) => (Some(arg_list), of_trait, param),
843+
_ => (None, None, None),
844+
};
845+
override_location.unwrap_or(TypeLocation::GenericArg {
846+
args: arg_list,
847+
of_trait,
848+
corresponding_param,
849+
})
850+
};
851+
722852
let type_location = |node: &SyntaxNode| {
723853
let parent = node.parent()?;
724854
let res = match_ast! {
@@ -774,9 +904,12 @@ fn classify_name_ref(
774904
ast::TypeBound(_) => TypeLocation::TypeBound,
775905
// is this case needed?
776906
ast::TypeBoundList(_) => TypeLocation::TypeBound,
777-
ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
907+
ast::GenericArg(it) => generic_arg_location(it),
778908
// is this case needed?
779-
ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
909+
ast::GenericArgList(it) => {
910+
let args = find_opt_node_in_file_compensated(sema, original_file, Some(it));
911+
TypeLocation::GenericArg { args, of_trait: None, corresponding_param: None }
912+
},
780913
ast::TupleField(_) => TypeLocation::TupleField,
781914
_ => return None,
782915
}

0 commit comments

Comments
 (0)