2151: Resolve (and complete) trait calls like `Vec::default()` r=flodiebold a=flodiebold

Similar to rustc, we do this using the same code as the method call resolution, just without doing autoderef (and considering more potential candidates).

(Btw, we currently don't complete methods with `self` in path notation, even though they'd be legal to use, so maybe we should -- on the other hand, that will usually not be the most interesting completions...)

Co-authored-by: Florian Diebold <flodiebold@gmail.com>
This commit is contained in:
bors[bot] 2019-11-01 19:03:05 +00:00 committed by GitHub
commit 9db97820f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 560 additions and 100 deletions

View File

@ -1053,4 +1053,13 @@ impl AssocItem {
AssocItem::TypeAlias(t) => t.module(db),
}
}
pub fn container(self, db: &impl DefDatabase) -> Container {
match self {
AssocItem::Function(f) => f.container(db),
AssocItem::Const(c) => c.container(db),
AssocItem::TypeAlias(t) => t.container(db),
}
.expect("AssocItem without container")
}
}

View File

@ -77,9 +77,10 @@ impl GenericParams {
let parent = match def {
GenericDef::Function(it) => it.container(db).map(GenericDef::from),
GenericDef::TypeAlias(it) => it.container(db).map(GenericDef::from),
GenericDef::Const(it) => it.container(db).map(GenericDef::from),
GenericDef::EnumVariant(it) => Some(it.parent_enum(db).into()),
GenericDef::Adt(_) | GenericDef::Trait(_) => None,
GenericDef::ImplBlock(_) | GenericDef::Const(_) => None,
GenericDef::ImplBlock(_) => None,
};
let mut generics = GenericParams {
def,

View File

@ -27,9 +27,9 @@ use crate::{
},
ids::LocationCtx,
resolve::{ScopeDef, TypeNs, ValueNs},
ty::method_resolution::implements_trait,
Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId, MacroDef, Module,
Name, Path, Resolver, Static, Struct, Ty,
ty::method_resolution::{self, implements_trait},
AssocItem, Const, DefWithBody, Either, Enum, FromSource, Function, HasBody, HirFileId,
MacroDef, Module, Name, Path, Resolver, Static, Struct, Ty,
};
fn try_get_resolver_for_node(
@ -327,16 +327,42 @@ impl SourceAnalyzer {
db: &impl HirDatabase,
ty: Ty,
name: Option<&Name>,
callback: impl FnMut(&Ty, Function) -> Option<T>,
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
) -> Option<T> {
// There should be no inference vars in types passed here
// FIXME check that?
// FIXME replace Unknown by bound vars here
let canonical = crate::ty::Canonical { value: ty, num_vars: 0 };
crate::ty::method_resolution::iterate_method_candidates(
method_resolution::iterate_method_candidates(
&canonical,
db,
&self.resolver,
name,
method_resolution::LookupMode::MethodCall,
|ty, it| match it {
AssocItem::Function(f) => callback(ty, f),
_ => None,
},
)
}
pub fn iterate_path_candidates<T>(
&self,
db: &impl HirDatabase,
ty: Ty,
name: Option<&Name>,
callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> {
// There should be no inference vars in types passed here
// FIXME check that?
// FIXME replace Unknown by bound vars here
let canonical = crate::ty::Canonical { value: ty, num_vars: 0 };
method_resolution::iterate_method_candidates(
&canonical,
db,
&self.resolver,
name,
method_resolution::LookupMode::Path,
callback,
)
}

View File

@ -385,13 +385,22 @@ impl SubstsBuilder {
self.param_count - self.vec.len()
}
pub fn fill_with_bound_vars(mut self, starting_from: u32) -> Self {
self.vec.extend((starting_from..starting_from + self.remaining() as u32).map(Ty::Bound));
self
pub fn fill_with_bound_vars(self, starting_from: u32) -> Self {
self.fill((starting_from..).map(Ty::Bound))
}
pub fn fill_with_unknown(mut self) -> Self {
self.vec.extend(iter::repeat(Ty::Unknown).take(self.remaining()));
pub fn fill_with_params(self) -> Self {
let start = self.vec.len() as u32;
self.fill((start..).map(|idx| Ty::Param { idx, name: Name::missing() }))
}
pub fn fill_with_unknown(self) -> Self {
self.fill(iter::repeat(Ty::Unknown))
}
pub fn fill(mut self, filler: impl Iterator<Item = Ty>) -> Self {
self.vec.extend(filler.take(self.remaining()));
assert_eq!(self.remaining(), 0);
self
}

View File

@ -6,8 +6,8 @@ use super::{ExprOrPatId, InferenceContext, TraitRef};
use crate::{
db::HirDatabase,
resolve::{ResolveValueResult, Resolver, TypeNs, ValueNs},
ty::{Substs, Ty, TypableDef, TypeWalk},
AssocItem, HasGenericParams, Namespace, Path,
ty::{method_resolution, Substs, Ty, TypableDef, TypeWalk},
AssocItem, Container, HasGenericParams, Name, Namespace, Path,
};
impl<'a, D: HirDatabase> InferenceContext<'a, D> {
@ -39,7 +39,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
let ty = Ty::from_type_relative_path(self.db, resolver, ty, remaining_segments_for_ty);
self.resolve_ty_assoc_item(
ty,
path.segments.last().expect("path had at least one segment"),
&path.segments.last().expect("path had at least one segment").name,
id,
)?
} else {
@ -122,10 +122,13 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
return None;
}
let ty = self.insert_type_vars(ty);
let ty = self.normalize_associated_types_in(ty);
let segment =
remaining_segments.last().expect("there should be at least one segment here");
self.resolve_ty_assoc_item(ty, segment, id)
self.resolve_ty_assoc_item(ty, &segment.name, id)
}
}
}
@ -162,7 +165,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
};
let substs = Substs::build_for_def(self.db, item)
.use_parent_substs(&trait_ref.substs)
.fill_with_unknown()
.fill_with_params()
.build();
self.write_assoc_resolution(id, item);
@ -172,44 +175,51 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
fn resolve_ty_assoc_item(
&mut self,
ty: Ty,
segment: &PathSegment,
name: &Name,
id: ExprOrPatId,
) -> Option<(ValueNs, Option<Substs>)> {
if let Ty::Unknown = ty {
return None;
}
let krate = self.resolver.krate()?;
let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone());
// Find impl
// FIXME: consider trait candidates
let item = ty.clone().iterate_impl_items(self.db, krate, |item| match item {
AssocItem::Function(func) => {
if segment.name == func.name(self.db) {
Some(AssocItem::Function(func))
} else {
None
}
}
method_resolution::iterate_method_candidates(
&canonical_ty.value,
self.db,
&self.resolver.clone(),
Some(name),
method_resolution::LookupMode::Path,
move |_ty, item| {
let def = match item {
AssocItem::Function(f) => ValueNs::Function(f),
AssocItem::Const(c) => ValueNs::Const(c),
AssocItem::TypeAlias(_) => unreachable!(),
};
let substs = match item.container(self.db) {
Container::ImplBlock(_) => self.find_self_types(&def, ty.clone()),
Container::Trait(t) => {
// we're picking this method
let trait_substs = Substs::build_for_def(self.db, t)
.push(ty.clone())
.fill(std::iter::repeat_with(|| self.new_type_var()))
.build();
let substs = Substs::build_for_def(self.db, item)
.use_parent_substs(&trait_substs)
.fill_with_params()
.build();
self.obligations.push(super::Obligation::Trait(TraitRef {
trait_: t,
substs: trait_substs,
}));
Some(substs)
}
};
AssocItem::Const(konst) => {
if konst.name(self.db).map_or(false, |n| n == segment.name) {
Some(AssocItem::Const(konst))
} else {
None
}
}
AssocItem::TypeAlias(_) => None,
})?;
let def = match item {
AssocItem::Function(f) => ValueNs::Function(f),
AssocItem::Const(c) => ValueNs::Const(c),
AssocItem::TypeAlias(_) => unreachable!(),
};
let substs = self.find_self_types(&def, ty);
self.write_assoc_resolution(id, item);
Some((def, substs))
self.write_assoc_resolution(id, item);
Some((def, substs))
},
)
}
fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option<Substs> {

View File

@ -166,37 +166,78 @@ pub(crate) fn lookup_method(
name: &Name,
resolver: &Resolver,
) -> Option<(Ty, Function)> {
iterate_method_candidates(ty, db, resolver, Some(name), |ty, f| Some((ty.clone(), f)))
iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| match f
{
AssocItem::Function(f) => Some((ty.clone(), f)),
_ => None,
})
}
/// Whether we're looking up a dotted method call (like `v.len()`) or a path
/// (like `Vec::new`).
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum LookupMode {
/// Looking up a method call like `v.len()`: We only consider candidates
/// that have a `self` parameter, and do autoderef.
MethodCall,
/// Looking up a path like `Vec::new` or `Vec::default`: We consider all
/// candidates including associated constants, but don't do autoderef.
Path,
}
// This would be nicer if it just returned an iterator, but that runs into
// lifetime problems, because we need to borrow temp `CrateImplBlocks`.
// FIXME add a context type here?
pub(crate) fn iterate_method_candidates<T>(
ty: &Canonical<Ty>,
db: &impl HirDatabase,
resolver: &Resolver,
name: Option<&Name>,
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
mode: LookupMode,
mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> {
// For method calls, rust first does any number of autoderef, and then one
// autoref (i.e. when the method takes &self or &mut self). We just ignore
// the autoref currently -- when we find a method matching the given name,
// we assume it fits.
// Also note that when we've got a receiver like &S, even if the method we
// find in the end takes &self, we still do the autoderef step (just as
// rustc does an autoderef and then autoref again).
let krate = resolver.krate()?;
for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) {
if let Some(result) = iterate_inherent_methods(&derefed_ty, db, name, krate, &mut callback)
{
return Some(result);
match mode {
LookupMode::MethodCall => {
// For method calls, rust first does any number of autoderef, and then one
// autoref (i.e. when the method takes &self or &mut self). We just ignore
// the autoref currently -- when we find a method matching the given name,
// we assume it fits.
// Also note that when we've got a receiver like &S, even if the method we
// find in the end takes &self, we still do the autoderef step (just as
// rustc does an autoderef and then autoref again).
for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) {
if let Some(result) =
iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback)
{
return Some(result);
}
if let Some(result) = iterate_trait_method_candidates(
&derefed_ty,
db,
resolver,
name,
mode,
&mut callback,
) {
return Some(result);
}
}
}
if let Some(result) =
iterate_trait_method_candidates(&derefed_ty, db, resolver, name, &mut callback)
{
return Some(result);
LookupMode::Path => {
// No autoderef for path lookups
if let Some(result) =
iterate_inherent_methods(&ty, db, name, mode, krate, &mut callback)
{
return Some(result);
}
if let Some(result) =
iterate_trait_method_candidates(&ty, db, resolver, name, mode, &mut callback)
{
return Some(result);
}
}
}
None
@ -207,7 +248,8 @@ fn iterate_trait_method_candidates<T>(
db: &impl HirDatabase,
resolver: &Resolver,
name: Option<&Name>,
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
mode: LookupMode,
mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> {
let krate = resolver.krate()?;
// FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that)
@ -231,22 +273,20 @@ fn iterate_trait_method_candidates<T>(
// trait, but if we find out it doesn't, we'll skip the rest of the
// iteration
let mut known_implemented = inherently_implemented;
for item in data.items() {
if let AssocItem::Function(m) = *item {
let data = m.data(db);
if name.map_or(true, |name| data.name() == name) && data.has_self_param() {
if !known_implemented {
let goal = generic_implements_goal(db, env.clone(), t, ty.clone());
if db.trait_solve(krate, goal).is_none() {
continue 'traits;
}
}
known_implemented = true;
if let Some(result) = callback(&ty.value, m) {
return Some(result);
}
for &item in data.items() {
if !is_valid_candidate(db, name, mode, item) {
continue;
}
if !known_implemented {
let goal = generic_implements_goal(db, env.clone(), t, ty.clone());
if db.trait_solve(krate, goal).is_none() {
continue 'traits;
}
}
known_implemented = true;
if let Some(result) = callback(&ty.value, item) {
return Some(result);
}
}
}
None
@ -256,21 +296,20 @@ fn iterate_inherent_methods<T>(
ty: &Canonical<Ty>,
db: &impl HirDatabase,
name: Option<&Name>,
mode: LookupMode,
krate: Crate,
mut callback: impl FnMut(&Ty, Function) -> Option<T>,
mut callback: impl FnMut(&Ty, AssocItem) -> Option<T>,
) -> Option<T> {
for krate in def_crates(db, krate, &ty.value)? {
let impls = db.impls_in_crate(krate);
for impl_block in impls.lookup_impl_blocks(&ty.value) {
for item in impl_block.items(db) {
if let AssocItem::Function(f) = item {
let data = f.data(db);
if name.map_or(true, |name| data.name() == name) && data.has_self_param() {
if let Some(result) = callback(&ty.value, f) {
return Some(result);
}
}
if !is_valid_candidate(db, name, mode, item) {
continue;
}
if let Some(result) = callback(&ty.value, item) {
return Some(result);
}
}
}
@ -278,6 +317,26 @@ fn iterate_inherent_methods<T>(
None
}
fn is_valid_candidate(
db: &impl HirDatabase,
name: Option<&Name>,
mode: LookupMode,
item: AssocItem,
) -> bool {
match item {
AssocItem::Function(m) => {
let data = m.data(db);
name.map_or(true, |name| data.name() == name)
&& (data.has_self_param() || mode == LookupMode::Path)
}
AssocItem::Const(c) => {
name.map_or(true, |name| Some(name) == c.name(db).as_ref())
&& (mode == LookupMode::Path)
}
_ => false,
}
}
pub(crate) fn implements_trait(
ty: &Canonical<Ty>,
db: &impl HirDatabase,

View File

@ -1841,8 +1841,8 @@ fn test() {
[243; 254) 'Struct::FOO': u32
[264; 265) 'y': u32
[268; 277) 'Enum::BAR': u32
[287; 288) 'z': {unknown}
[291; 304) 'TraitTest::ID': {unknown}
[287; 288) 'z': u32
[291; 304) 'TraitTest::ID': u32
"###
);
}
@ -2782,9 +2782,9 @@ fn test() {
[97; 99) 's1': S
[105; 121) 'Defaul...efault': fn default<S>() -> Self
[105; 123) 'Defaul...ault()': S
[133; 135) 's2': {unknown}
[138; 148) 'S::default': {unknown}
[138; 150) 'S::default()': {unknown}
[133; 135) 's2': S
[138; 148) 'S::default': fn default<S>() -> Self
[138; 150) 'S::default()': S
[160; 162) 's3': S
[165; 188) '<S as ...efault': fn default<S>() -> Self
[165; 190) '<S as ...ault()': S
@ -2792,6 +2792,153 @@ fn test() {
);
}
#[test]
fn infer_trait_assoc_method_generics_1() {
assert_snapshot!(
infer(r#"
trait Trait<T> {
fn make() -> T;
}
struct S;
impl Trait<u32> for S {}
struct G<T>;
impl<T> Trait<T> for G<T> {}
fn test() {
let a = S::make();
let b = G::<u64>::make();
let c: f64 = G::make();
}
"#),
@r###"
[127; 211) '{ ...e(); }': ()
[137; 138) 'a': u32
[141; 148) 'S::make': fn make<S, u32>() -> T
[141; 150) 'S::make()': u32
[160; 161) 'b': u64
[164; 178) 'G::<u64>::make': fn make<G<u64>, u64>() -> T
[164; 180) 'G::<u6...make()': u64
[190; 191) 'c': f64
[199; 206) 'G::make': fn make<G<f64>, f64>() -> T
[199; 208) 'G::make()': f64
"###
);
}
#[test]
fn infer_trait_assoc_method_generics_2() {
assert_snapshot!(
infer(r#"
trait Trait<T> {
fn make<U>() -> (T, U);
}
struct S;
impl Trait<u32> for S {}
struct G<T>;
impl<T> Trait<T> for G<T> {}
fn test() {
let a = S::make::<i64>();
let b: (_, i64) = S::make();
let c = G::<u32>::make::<i64>();
let d: (u32, _) = G::make::<i64>();
let e: (u32, i64) = G::make();
}
"#),
@r###"
[135; 313) '{ ...e(); }': ()
[145; 146) 'a': (u32, i64)
[149; 163) 'S::make::<i64>': fn make<S, u32, i64>() -> (T, U)
[149; 165) 'S::mak...i64>()': (u32, i64)
[175; 176) 'b': (u32, i64)
[189; 196) 'S::make': fn make<S, u32, i64>() -> (T, U)
[189; 198) 'S::make()': (u32, i64)
[208; 209) 'c': (u32, i64)
[212; 233) 'G::<u3...:<i64>': fn make<G<u32>, u32, i64>() -> (T, U)
[212; 235) 'G::<u3...i64>()': (u32, i64)
[245; 246) 'd': (u32, i64)
[259; 273) 'G::make::<i64>': fn make<G<u32>, u32, i64>() -> (T, U)
[259; 275) 'G::mak...i64>()': (u32, i64)
[285; 286) 'e': (u32, i64)
[301; 308) 'G::make': fn make<G<u32>, u32, i64>() -> (T, U)
[301; 310) 'G::make()': (u32, i64)
"###
);
}
#[test]
fn infer_trait_assoc_method_generics_3() {
assert_snapshot!(
infer(r#"
trait Trait<T> {
fn make() -> (Self, T);
}
struct S<T>;
impl Trait<i64> for S<i32> {}
fn test() {
let a = S::make();
}
"#),
@r###"
[101; 127) '{ ...e(); }': ()
[111; 112) 'a': (S<i32>, i64)
[115; 122) 'S::make': fn make<S<i32>, i64>() -> (Self, T)
[115; 124) 'S::make()': (S<i32>, i64)
"###
);
}
#[test]
fn infer_trait_assoc_method_generics_4() {
assert_snapshot!(
infer(r#"
trait Trait<T> {
fn make() -> (Self, T);
}
struct S<T>;
impl Trait<i64> for S<u64> {}
impl Trait<i32> for S<u32> {}
fn test() {
let a: (S<u64>, _) = S::make();
let b: (_, i32) = S::make();
}
"#),
@r###"
[131; 203) '{ ...e(); }': ()
[141; 142) 'a': (S<u64>, i64)
[158; 165) 'S::make': fn make<S<u64>, i64>() -> (Self, T)
[158; 167) 'S::make()': (S<u64>, i64)
[177; 178) 'b': (S<u32>, i32)
[191; 198) 'S::make': fn make<S<u32>, i32>() -> (Self, T)
[191; 200) 'S::make()': (S<u32>, i32)
"###
);
}
#[test]
fn infer_trait_assoc_method_generics_5() {
assert_snapshot!(
infer(r#"
trait Trait<T> {
fn make<U>() -> (Self, T, U);
}
struct S<T>;
impl Trait<i64> for S<u64> {}
fn test() {
let a = <S as Trait<i64>>::make::<u8>();
let b: (S<u64>, _, _) = Trait::<i64>::make::<u8>();
}
"#),
@r###"
[107; 211) '{ ...>(); }': ()
[117; 118) 'a': (S<u64>, i64, u8)
[121; 150) '<S as ...::<u8>': fn make<S<u64>, i64, u8>() -> (Self, T, U)
[121; 152) '<S as ...<u8>()': (S<u64>, i64, u8)
[162; 163) 'b': (S<u64>, i64, u8)
[182; 206) 'Trait:...::<u8>': fn make<S<u64>, i64, u8>() -> (Self, T, U)
[182; 208) 'Trait:...<u8>()': (S<u64>, i64, u8)
"###
);
}
#[test]
fn infer_from_bound_1() {
assert_snapshot!(
@ -3303,6 +3450,22 @@ fn test() { S.foo()<|>; }
assert_eq!(t, "u128");
}
#[ignore]
#[test]
fn method_resolution_by_value_before_autoref() {
let t = type_at(
r#"
//- /main.rs
trait Clone { fn clone(&self) -> Self; }
struct S;
impl Clone for S {}
impl Clone for &S {}
fn test() { (S.clone(), (&S).clone(), (&&S).clone())<|>; }
"#,
);
assert_eq!(t, "(S, S, &S)");
}
#[test]
fn method_resolution_trait_before_autoderef() {
let t = type_at(

View File

@ -50,23 +50,46 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
_ => unreachable!(),
};
ctx.analyzer.iterate_path_candidates(ctx.db, ty.clone(), None, |_ty, item| {
match item {
hir::AssocItem::Function(func) => {
let data = func.data(ctx.db);
if !data.has_self_param() {
acc.add_function(ctx, func);
}
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
None::<()>
});
// Iterate assoc types separately
// FIXME: complete T::AssocType
let krate = ctx.module.map(|m| m.krate());
if let Some(krate) = krate {
ty.iterate_impl_items(ctx.db, krate, |item| {
match item {
hir::AssocItem::Function(func) => {
let data = func.data(ctx.db);
if !data.has_self_param() {
acc.add_function(ctx, func);
}
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {}
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
None::<()>
});
}
}
hir::ModuleDef::Trait(t) => {
for item in t.items(ctx.db) {
match item {
hir::AssocItem::Function(func) => {
let data = func.data(ctx.db);
if !data.has_self_param() {
acc.add_function(ctx, func);
}
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
}
}
_ => {}
};
}
@ -558,6 +581,111 @@ mod tests {
);
}
#[test]
fn completes_trait_associated_method_1() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
fn foo() { let _ = Trait::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [73; 73),
delete: [73; 73),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_trait_associated_method_2() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
struct S;
impl Trait for S {}
fn foo() { let _ = S::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [99; 99),
delete: [99; 99),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_trait_associated_method_3() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Trait {
/// A trait method
fn m();
}
struct S;
impl Trait for S {}
fn foo() { let _ = <S as Trait>::<|> }
"
),
@r###"
[
CompletionItem {
label: "m()",
source_range: [110; 110),
delete: [110; 110),
insert: "m()$0",
kind: Function,
lookup: "m",
detail: "fn m()",
documentation: Documentation(
"A trait method",
),
},
]
"###
);
}
#[test]
fn completes_type_alias() {
assert_debug_snapshot!(

View File

@ -390,6 +390,61 @@ mod tests {
"spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)",
);
}
#[test]
fn goto_definition_works_for_ufcs_inherent_methods() {
check_goto(
"
//- /lib.rs
struct Foo;
impl Foo {
fn frobnicate() { }
}
fn bar(foo: &Foo) {
Foo::frobnicate<|>();
}
",
"frobnicate FN_DEF FileId(1) [27; 47) [30; 40)",
);
}
#[test]
fn goto_definition_works_for_ufcs_trait_methods_through_traits() {
check_goto(
"
//- /lib.rs
trait Foo {
fn frobnicate();
}
fn bar() {
Foo::frobnicate<|>();
}
",
"frobnicate FN_DEF FileId(1) [16; 32) [19; 29)",
);
}
#[test]
fn goto_definition_works_for_ufcs_trait_methods_through_self() {
check_goto(
"
//- /lib.rs
struct Foo;
trait Trait {
fn frobnicate();
}
impl Trait for Foo {}
fn bar() {
Foo::frobnicate<|>();
}
",
"frobnicate FN_DEF FileId(1) [30; 46) [33; 43)",
);
}
#[test]
fn goto_definition_on_self() {
check_goto(