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) => { TypeLocation::TypeAscription(ascription) => {
r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription); r#type::complete_ascribed_type(acc, ctx, path_ctx, ascription);
} }
TypeLocation::GenericArgList(_) TypeLocation::GenericArg { .. }
| TypeLocation::AssocConstEq
| TypeLocation::AssocTypeEq
| TypeLocation::TypeBound | TypeLocation::TypeBound
| TypeLocation::ImplTarget | TypeLocation::ImplTarget
| TypeLocation::ImplTrait | TypeLocation::ImplTrait

View file

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

View file

@ -155,13 +155,63 @@ pub(crate) struct ExprCtx {
pub(crate) enum TypeLocation { pub(crate) enum TypeLocation {
TupleField, TupleField,
TypeAscription(TypeAscriptionTarget), 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, TypeBound,
ImplTarget, ImplTarget,
ImplTrait, ImplTrait,
Other, 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)] #[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum TypeAscriptionTarget { pub(crate) enum TypeAscriptionTarget {
Let(Option<ast::Pat>), Let(Option<ast::Pat>),

View file

@ -1,11 +1,11 @@
//! Module responsible for analyzing the code surrounding the cursor for completion. //! Module responsible for analyzing the code surrounding the cursor for completion.
use std::iter; use std::iter;
use hir::{Semantics, Type, TypeInfo, Variant}; use hir::{HasSource, Semantics, Type, TypeInfo, Variant};
use ide_db::{active_parameter::ActiveParameter, RootDatabase}; use ide_db::{active_parameter::ActiveParameter, RootDatabase};
use syntax::{ use syntax::{
algo::{find_node_at_offset, non_trivia_sibling}, 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, match_ast, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode,
SyntaxToken, TextRange, TextSize, T, SyntaxToken, TextRange, TextSize, T,
}; };
@ -719,6 +719,136 @@ fn classify_name_ref(
None 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 type_location = |node: &SyntaxNode| {
let parent = node.parent()?; let parent = node.parent()?;
let res = match_ast! { let res = match_ast! {
@ -774,9 +904,12 @@ fn classify_name_ref(
ast::TypeBound(_) => TypeLocation::TypeBound, ast::TypeBound(_) => TypeLocation::TypeBound,
// is this case needed? // is this case needed?
ast::TypeBoundList(_) => TypeLocation::TypeBound, 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? // 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, ast::TupleField(_) => TypeLocation::TupleField,
_ => return None, _ => return None,
} }

View file

@ -384,8 +384,6 @@ trait Trait2<T>: Trait1 {
fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {} fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
"#, "#,
expect![[r#" expect![[r#"
ct CONST
cp CONST_PARAM
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -404,14 +402,13 @@ fn foo<'lt, T: Trait2<$0>, const CONST_PARAM: usize>(_: T) {}
); );
check( check(
r#" r#"
trait Trait2 { trait Trait2<T> {
type Foo; type Foo;
} }
fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {} fn foo<'lt, T: Trait2<self::$0>, const CONST_PARAM: usize>(_: T) {}
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -437,7 +434,6 @@ trait Tr<T> {
impl Tr<$0 impl Tr<$0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -485,7 +481,6 @@ trait MyTrait<T, U> {
fn f(t: impl MyTrait<u$0 fn f(t: impl MyTrait<u$0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -511,7 +506,6 @@ trait MyTrait<T, U> {
fn f(t: impl MyTrait<u8, u$0 fn f(t: impl MyTrait<u8, u$0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -555,7 +549,6 @@ trait MyTrait<T, U = u8> {
fn f(t: impl MyTrait<u$0 fn f(t: impl MyTrait<u$0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -581,7 +574,6 @@ trait MyTrait<T, U = u8> {
fn f(t: impl MyTrait<u8, u$0 fn f(t: impl MyTrait<u8, u$0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -627,7 +619,6 @@ trait MyTrait {
fn f(t: impl MyTrait<Item1 = $0 fn f(t: impl MyTrait<Item1 = $0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -653,7 +644,6 @@ trait MyTrait {
fn f(t: impl MyTrait<Item1 = u8, Item2 = $0 fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
"#, "#,
expect![[r#" expect![[r#"
ct CONST
en Enum en Enum
ma makro!() macro_rules! makro ma makro!() macro_rules! makro
md module md module
@ -668,6 +658,22 @@ fn f(t: impl MyTrait<Item1 = u8, Item2 = $0
kw self:: 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] #[test]
@ -719,3 +725,267 @@ pub struct S;
"#]], "#]],
) )
} }
#[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::
"#]],
);
}