Auto merge of #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 #12568
This commit is contained in:
bors 2023-08-15 06:39:50 +00:00
commit f73cd39f7b
5 changed files with 502 additions and 74 deletions

View File

@ -703,7 +703,9 @@ pub(super) fn complete_name_ref(
TypeLocation::TypeAscription(ascription) => {
r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
}
TypeLocation::GenericArgList(_)
TypeLocation::GenericArg { .. }
| TypeLocation::AssocConstEq
| TypeLocation::AssocTypeEq
| TypeLocation::TypeBound
| TypeLocation::ImplTarget
| TypeLocation::ImplTrait

View File

@ -1,7 +1,7 @@
//! Completion of names from the current scope in type position.
use hir::{HirDisplay, ScopeDef};
use syntax::{ast, AstNode, SyntaxKind};
use syntax::{ast, AstNode};
use crate::{
context::{PathCompletionCtx, Qualified, TypeAscriptionTarget, TypeLocation},
@ -20,16 +20,15 @@ pub(crate) fn complete_type_path(
let scope_def_applicable = |def| {
use hir::{GenericParam::*, ModuleDef::*};
match def {
ScopeDef::GenericParam(LifetimeParam(_)) | ScopeDef::Label(_) => false,
ScopeDef::GenericParam(LifetimeParam(_)) => location.complete_lifetimes(),
ScopeDef::Label(_) => false,
// no values in type places
ScopeDef::ModuleDef(Function(_) | Variant(_) | Static(_)) | ScopeDef::Local(_) => false,
// unless its a constant in a generic arg list position
ScopeDef::ModuleDef(Const(_)) | ScopeDef::GenericParam(ConstParam(_)) => {
matches!(location, TypeLocation::GenericArgList(_))
}
ScopeDef::ImplSelfType(_) => {
!matches!(location, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
location.complete_consts()
}
ScopeDef::ImplSelfType(_) => location.complete_self_type(),
// Don't suggest attribute macros and derives.
ScopeDef::ModuleDef(Macro(mac)) => mac.is_fn_like(ctx.db),
// Type things are fine
@ -38,12 +37,12 @@ pub(crate) fn complete_type_path(
)
| ScopeDef::AdtSelfType(_)
| ScopeDef::Unknown
| ScopeDef::GenericParam(TypeParam(_)) => true,
| ScopeDef::GenericParam(TypeParam(_)) => location.complete_types(),
}
};
let add_assoc_item = |acc: &mut Completions, item| match item {
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArgList(_)) => {
hir::AssocItem::Const(ct) if matches!(location, TypeLocation::GenericArg { .. }) => {
acc.add_const(ctx, ct)
}
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => (),
@ -157,56 +156,30 @@ pub(crate) fn complete_type_path(
});
return;
}
TypeLocation::GenericArgList(Some(arg_list)) => {
let in_assoc_type_arg = ctx
.original_token
.parent_ancestors()
.any(|node| node.kind() == SyntaxKind::ASSOC_TYPE_ARG);
TypeLocation::GenericArg {
args: Some(arg_list), of_trait: Some(trait_), ..
} => {
if arg_list.syntax().ancestors().find_map(ast::TypeBound::cast).is_some() {
let arg_idx = arg_list
.generic_args()
.filter(|arg| {
arg.syntax().text_range().end()
< ctx.original_token.text_range().start()
})
.count();
if !in_assoc_type_arg {
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()
{
if let Some(hir::PathResolution::Def(hir::ModuleDef::Trait(
trait_,
))) = ctx.sema.resolve_path(&path_seg.parent_path())
{
let arg_idx = arg_list
.generic_args()
.filter(|arg| {
arg.syntax().text_range().end()
< ctx.original_token.text_range().start()
})
.count();
let n_required_params =
trait_.type_or_const_param_count(ctx.sema.db, true);
if arg_idx >= n_required_params {
trait_
.items_with_supertraits(ctx.sema.db)
.into_iter()
.for_each(|it| {
if let hir::AssocItem::TypeAlias(alias) = it {
cov_mark::hit!(
complete_assoc_type_in_generics_list
);
acc.add_type_alias_with_eq(ctx, alias);
}
});
let n_params =
trait_.type_or_const_param_count(ctx.sema.db, false);
if arg_idx >= n_params {
return; // only show assoc types
}
}
let n_required_params = trait_.type_or_const_param_count(ctx.sema.db, true);
if arg_idx >= n_required_params {
trait_.items_with_supertraits(ctx.sema.db).into_iter().for_each(|it| {
if let hir::AssocItem::TypeAlias(alias) = it {
cov_mark::hit!(complete_assoc_type_in_generics_list);
acc.add_type_alias_with_eq(ctx, alias);
}
});
let n_params = trait_.type_or_const_param_count(ctx.sema.db, false);
if arg_idx >= n_params {
return; // only show assoc types
}
}
}

View File

@ -155,13 +155,63 @@ pub(crate) struct ExprCtx {
pub(crate) enum TypeLocation {
TupleField,
TypeAscription(TypeAscriptionTarget),
GenericArgList(Option<ast::GenericArgList>),
/// Generic argument position e.g. `Foo<$0>`
GenericArg {
/// The generic argument list containing the generic arg
args: Option<ast::GenericArgList>,
/// `Some(trait_)` if `trait_` is being instantiated with `args`
of_trait: Option<hir::Trait>,
/// The generic parameter being filled in by the generic arg
corresponding_param: Option<ast::GenericParam>,
},
/// Associated type equality constraint e.g. `Foo<Bar = $0>`
AssocTypeEq,
/// Associated constant equality constraint e.g. `Foo<X = $0>`
AssocConstEq,
TypeBound,
ImplTarget,
ImplTrait,
Other,
}
impl TypeLocation {
pub(crate) fn complete_lifetimes(&self) -> bool {
matches!(
self,
TypeLocation::GenericArg {
corresponding_param: Some(ast::GenericParam::LifetimeParam(_)),
..
}
)
}
pub(crate) fn complete_consts(&self) -> bool {
match self {
TypeLocation::GenericArg {
corresponding_param: Some(ast::GenericParam::ConstParam(_)),
..
} => true,
TypeLocation::AssocConstEq => true,
_ => false,
}
}
pub(crate) fn complete_types(&self) -> bool {
match self {
TypeLocation::GenericArg { corresponding_param: Some(param), .. } => {
matches!(param, ast::GenericParam::TypeParam(_))
}
TypeLocation::AssocConstEq => false,
TypeLocation::AssocTypeEq => true,
_ => true,
}
}
pub(crate) fn complete_self_type(&self) -> bool {
self.complete_types() && !matches!(self, TypeLocation::ImplTarget | TypeLocation::ImplTrait)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeAscriptionTarget {
Let(Option<ast::Pat>),

View File

@ -1,11 +1,11 @@
//! Module responsible for analyzing the code surrounding the cursor for completion.
use std::iter;
use hir::{Semantics, Type, TypeInfo, Variant};
use hir::{HasSource, Semantics, Type, TypeInfo, Variant};
use ide_db::{active_parameter::ActiveParameter, RootDatabase};
use syntax::{
algo::{find_node_at_offset, non_trivia_sibling},
ast::{self, AttrKind, HasArgList, HasLoopBody, HasName, NameOrNameRef},
ast::{self, AttrKind, HasArgList, HasGenericParams, HasLoopBody, HasName, NameOrNameRef},
match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
SyntaxToken, TextRange, TextSize, T,
};
@ -719,6 +719,136 @@ fn classify_name_ref(
None
};
let generic_arg_location = |arg: ast::GenericArg| {
let mut override_location = None;
let location = find_opt_node_in_file_compensated(
sema,
original_file,
arg.syntax().parent().and_then(ast::GenericArgList::cast),
)
.map(|args| {
let mut in_trait = None;
let param = (|| {
let parent = args.syntax().parent()?;
let params = match_ast! {
match parent {
ast::PathSegment(segment) => {
match sema.resolve_path(&segment.parent_path().top_path())? {
hir::PathResolution::Def(def) => match def {
hir::ModuleDef::Function(func) => {
func.source(sema.db)?.value.generic_param_list()
}
hir::ModuleDef::Adt(adt) => {
adt.source(sema.db)?.value.generic_param_list()
}
hir::ModuleDef::Variant(variant) => {
variant.parent_enum(sema.db).source(sema.db)?.value.generic_param_list()
}
hir::ModuleDef::Trait(trait_) => {
if let ast::GenericArg::AssocTypeArg(arg) = &arg {
let arg_name = arg.name_ref()?;
let arg_name = arg_name.text();
for item in trait_.items_with_supertraits(sema.db) {
match item {
hir::AssocItem::TypeAlias(assoc_ty) => {
if assoc_ty.name(sema.db).as_str()? == arg_name {
override_location = Some(TypeLocation::AssocTypeEq);
return None;
}
},
hir::AssocItem::Const(const_) => {
if const_.name(sema.db)?.as_str()? == arg_name {
override_location = Some(TypeLocation::AssocConstEq);
return None;
}
},
_ => (),
}
}
return None;
} else {
in_trait = Some(trait_);
trait_.source(sema.db)?.value.generic_param_list()
}
}
hir::ModuleDef::TraitAlias(trait_) => {
trait_.source(sema.db)?.value.generic_param_list()
}
hir::ModuleDef::TypeAlias(ty_) => {
ty_.source(sema.db)?.value.generic_param_list()
}
_ => None,
},
_ => None,
}
},
ast::MethodCallExpr(call) => {
let func = sema.resolve_method_call(&call)?;
func.source(sema.db)?.value.generic_param_list()
},
ast::AssocTypeArg(arg) => {
let trait_ = ast::PathSegment::cast(arg.syntax().parent()?.parent()?)?;
match sema.resolve_path(&trait_.parent_path().top_path())? {
hir::PathResolution::Def(def) => match def {
hir::ModuleDef::Trait(trait_) => {
let arg_name = arg.name_ref()?;
let arg_name = arg_name.text();
let trait_items = trait_.items_with_supertraits(sema.db);
let assoc_ty = trait_items.iter().find_map(|item| match item {
hir::AssocItem::TypeAlias(assoc_ty) => {
(assoc_ty.name(sema.db).as_str()? == arg_name)
.then_some(assoc_ty)
},
_ => None,
})?;
assoc_ty.source(sema.db)?.value.generic_param_list()
}
_ => None,
},
_ => None,
}
},
_ => None,
}
}?;
// Determine the index of the argument in the `GenericArgList` and match it with
// the corresponding parameter in the `GenericParamList`. Since lifetime parameters
// are often omitted, ignore them for the purposes of matching the argument with
// its parameter unless a lifetime argument is provided explicitly. That is, for
// `struct S<'a, 'b, T>`, match `S::<$0>` to `T` and `S::<'a, $0, _>` to `'b`.
// FIXME: This operates on the syntax tree and will produce incorrect results when
// generic parameters are disabled by `#[cfg]` directives. It should operate on the
// HIR, but the functionality necessary to do so is not exposed at the moment.
let mut explicit_lifetime_arg = false;
let arg_idx = arg
.syntax()
.siblings(Direction::Prev)
// Skip the node itself
.skip(1)
.map(|arg| if ast::LifetimeArg::can_cast(arg.kind()) { explicit_lifetime_arg = true })
.count();
let param_idx = if explicit_lifetime_arg {
arg_idx
} else {
// Lifetimes parameters always precede type and generic parameters,
// so offset the argument index by the total number of lifetime params
arg_idx + params.lifetime_params().count()
};
params.generic_params().nth(param_idx)
})();
(args, in_trait, param)
});
let (arg_list, of_trait, corresponding_param) = match location {
Some((arg_list, of_trait, param)) => (Some(arg_list), of_trait, param),
_ => (None, None, None),
};
override_location.unwrap_or(TypeLocation::GenericArg {
args: arg_list,
of_trait,
corresponding_param,
})
};
let type_location = |node: &SyntaxNode| {
let parent = node.parent()?;
let res = match_ast! {
@ -774,9 +904,12 @@ fn classify_name_ref(
ast::TypeBound(_) => TypeLocation::TypeBound,
// is this case needed?
ast::TypeBoundList(_) => TypeLocation::TypeBound,
ast::GenericArg(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, it.syntax().parent().and_then(ast::GenericArgList::cast))),
ast::GenericArg(it) => generic_arg_location(it),
// is this case needed?
ast::GenericArgList(it) => TypeLocation::GenericArgList(find_opt_node_in_file_compensated(sema, original_file, Some(it))),
ast::GenericArgList(it) => {
let args = find_opt_node_in_file_compensated(sema, original_file, Some(it));
TypeLocation::GenericArg { args, of_trait: None, corresponding_param: None }
},
ast::TupleField(_) => TypeLocation::TupleField,
_ => return None,
}

View File

@ -384,10 +384,8 @@ trait Trait2<T>: Trait1 {
fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
"#,
expect![[r#"
ct CONST
cp CONST_PARAM
en Enum
ma makro!() macro_rules! makro
ma makro!() macro_rules! makro
md module
st Record
st Tuple
@ -404,14 +402,13 @@ fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
);
check(
r#"
trait Trait2 {
trait Trait2<T> {
type Foo;
}
fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {}
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -437,7 +434,6 @@ trait Tr<T> {
impl Tr<$0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -485,7 +481,6 @@ trait MyTrait<T, U> {
fn f(t: impl MyTrait<u$0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -511,7 +506,6 @@ trait MyTrait<T, U> {
fn f(t: impl MyTrait<u8, u$0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -555,7 +549,6 @@ trait MyTrait<T, U = u8> {
fn f(t: impl MyTrait<u$0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -581,7 +574,6 @@ trait MyTrait<T, U = u8> {
fn f(t: impl MyTrait<u8, u$0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -627,7 +619,6 @@ trait MyTrait {
fn f(t: impl MyTrait<Item1 = $0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -653,7 +644,6 @@ trait MyTrait {
fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
"#,
expect![[r#"
ct CONST
en Enum
ma makro!() macro_rules! makro
md module
@ -668,6 +658,22 @@ fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
kw self::
"#]],
);
check(
r#"
trait MyTrait {
const C: usize;
};
fn f(t: impl MyTrait<C = $0
"#,
expect![[r#"
ct CONST
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
}
#[test]
@ -719,3 +725,267 @@ struct Foo {
"#]],
)
}
#[test]
fn completes_const_and_type_generics_separately() {
// Function generic params
check(
r#"
struct Foo;
const X: usize = 0;
fn foo<T, const N: usize>() {}
fn main() {
foo::<F$0, _>();
}
"#,
expect![[r#"
en Enum
ma makro!() macro_rules! makro
md module
st Foo
st Record
st Tuple
st Unit
tt Trait
un Union
bt u32
kw crate::
kw self::
"#]],
);
// FIXME: This should probably also suggest completions for types, at least those that have
// associated constants usable in this position. For example, a user could be typing
// `foo::<_, { usize::MAX }>()`, but we currently don't suggest `usize` in constant position.
check(
r#"
struct Foo;
const X: usize = 0;
fn foo<T, const N: usize>() {}
fn main() {
foo::<_, $0>();
}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Method generic params
check(
r#"
const X: usize = 0;
struct Foo;
impl Foo { fn bar<const N: usize, T>(self) {} }
fn main() {
Foo.bar::<_, $0>();
}
"#,
expect![[r#"
en Enum
ma makro!() macro_rules! makro
md module
st Foo
st Record
st Tuple
st Unit
tt Trait
un Union
bt u32
kw crate::
kw self::
"#]],
);
check(
r#"
const X: usize = 0;
struct Foo;
impl Foo { fn bar<const N: usize, T>(self) {} }
fn main() {
Foo.bar::<X$0, _>();
}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Associated type generic params
check(
r#"
const X: usize = 0;
struct Foo;
trait Bar {
type Baz<T, const X: usize>;
}
fn foo(_: impl Bar<Baz<F$0, 0> = ()>) {}
"#,
expect![[r#"
en Enum
ma makro!() macro_rules! makro
md module
st Foo
st Record
st Tuple
st Unit
tt Bar
tt Trait
un Union
bt u32
kw crate::
kw self::
"#]],
);
check(
r#"
const X: usize = 0;
struct Foo;
trait Bar {
type Baz<T, const X: usize>;
}
fn foo<T: Bar<Baz<(), $0> = ()>>() {}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Type generic params
check(
r#"
const X: usize = 0;
struct Foo<T, const N: usize>(T);
fn main() {
let _: Foo::<_, $0> = Foo(());
}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Type alias generic params
check(
r#"
const X: usize = 0;
struct Foo<T, const N: usize>(T);
type Bar<const X: usize, U> = Foo<U, X>;
fn main() {
let _: Bar::<X$0, _> = Bar(());
}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Enum variant params
check(
r#"
const X: usize = 0;
enum Foo<T, const N: usize> { A(T), B }
fn main() {
Foo::B::<(), $0>;
}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Trait params
check(
r#"
const X: usize = 0;
trait Foo<T, const N: usize> {}
impl Foo<(), $0> for () {}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Trait alias params
check(
r#"
#![feature(trait_alias)]
const X: usize = 0;
trait Foo<T, const N: usize> {}
trait Bar<const M: usize, U> = Foo<U, M>;
fn foo<T: Bar<X$0, ()>>() {}
"#,
expect![[r#"
ct CONST
ct X
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Omitted lifetime params
check(
r#"
struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>);
fn foo<'a>() { S::<F$0, _>; }
"#,
expect![[r#"
ct CONST
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
// Explicit lifetime params
check(
r#"
struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>);
fn foo<'a>() { S::<'static, 'static, F$0, _>; }
"#,
expect![[r#"
ct CONST
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
check(
r#"
struct S<'a, 'b, const C: usize, T>(core::marker::PhantomData<&'a &'b T>);
fn foo<'a>() { S::<'static, F$0, _, _>; }
"#,
expect![[r#"
lt 'a
ma makro!() macro_rules! makro
kw crate::
kw self::
"#]],
);
}