4162: Complete assoc. items on type parameters r=jonas-schievink a=jonas-schievink

This is fairly messy and seems to leak a lot through the `ra_hir` abstraction (`TypeNs`, `AssocItemId`, ...), so I'd be glad for any advise for how to improve this.

Co-authored-by: Jonas Schievink <jonasschievink@gmail.com>
This commit is contained in:
bors[bot] 2020-04-29 22:24:17 +00:00 committed by GitHub
commit 913eff5ad7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 402 additions and 68 deletions

View File

@ -953,6 +953,16 @@ pub fn name(self, db: &dyn HirDatabase) -> Name {
pub fn module(self, db: &dyn HirDatabase) -> Module {
self.id.parent.module(db.upcast()).into()
}
pub fn ty(self, db: &dyn HirDatabase) -> Type {
let resolver = self.id.parent.resolver(db.upcast());
let environment = TraitEnvironment::lower(db, &resolver);
let ty = Ty::Placeholder(self.id);
Type {
krate: self.id.parent.module(db.upcast()).krate,
ty: InEnvironment { value: ty, environment },
}
}
}
// FIXME: rename from `ImplDef` to `Impl`

View File

@ -9,6 +9,7 @@
AsMacroCall, TraitId,
};
use hir_expand::ExpansionInfo;
use hir_ty::associated_type_shorthand_candidates;
use itertools::Itertools;
use ra_db::{FileId, FileRange};
use ra_prof::profile;
@ -24,8 +25,9 @@
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, SourceAnalyzer},
AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef,
Name, Origin, Path, ScopeDef, Trait, Type, TypeParam,
Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam,
};
use resolver::TypeNs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathResolution {
@ -40,6 +42,44 @@ pub enum PathResolution {
AssocItem(AssocItem),
}
impl PathResolution {
fn in_type_ns(&self) -> Option<TypeNs> {
match self {
PathResolution::Def(ModuleDef::Adt(adt)) => Some(TypeNs::AdtId((*adt).into())),
PathResolution::Def(ModuleDef::BuiltinType(builtin)) => {
Some(TypeNs::BuiltinType(*builtin))
}
PathResolution::Def(ModuleDef::Const(_))
| PathResolution::Def(ModuleDef::EnumVariant(_))
| PathResolution::Def(ModuleDef::Function(_))
| PathResolution::Def(ModuleDef::Module(_))
| PathResolution::Def(ModuleDef::Static(_))
| PathResolution::Def(ModuleDef::Trait(_)) => None,
PathResolution::Def(ModuleDef::TypeAlias(alias)) => {
Some(TypeNs::TypeAliasId((*alias).into()))
}
PathResolution::Local(_) | PathResolution::Macro(_) => None,
PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())),
PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())),
PathResolution::AssocItem(AssocItem::Const(_))
| PathResolution::AssocItem(AssocItem::Function(_)) => None,
PathResolution::AssocItem(AssocItem::TypeAlias(alias)) => {
Some(TypeNs::TypeAliasId((*alias).into()))
}
}
}
/// Returns an iterator over associated types that may be specified after this path (using
/// `Ty::Assoc` syntax).
pub fn assoc_type_shorthand_candidates<R>(
&self,
db: &dyn HirDatabase,
mut cb: impl FnMut(TypeAlias) -> Option<R>,
) -> Option<R> {
associated_type_shorthand_candidates(db, self.in_type_ns()?, |_, _, id| cb(id.into()))
}
}
/// Primary API to get semantic information, like types, from syntax trees.
pub struct Semantics<'db, DB> {
pub db: &'db DB,

View File

@ -66,7 +66,8 @@ fn from(it: $sv) -> $e {
pub use infer::{InferTy, InferenceResult};
pub use lower::CallableDef;
pub use lower::{
callable_item_sig, ImplTraitLoweringMode, TyDefId, TyLoweringContext, ValueTyDefId,
associated_type_shorthand_candidates, callable_item_sig, ImplTraitLoweringMode, TyDefId,
TyLoweringContext, ValueTyDefId,
};
pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment};

View File

@ -17,9 +17,9 @@
path::{GenericArg, Path, PathSegment, PathSegments},
resolver::{HasResolver, Resolver, TypeNs},
type_ref::{TypeBound, TypeRef},
AdtId, AssocContainerId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule,
ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
VariantId,
AdtId, AssocContainerId, AssocItemId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId,
HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId,
UnionId, VariantId,
};
use ra_arena::map::ArenaMap;
use ra_db::CrateId;
@ -34,6 +34,7 @@
Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, PolyFnSig, ProjectionPredicate,
ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
};
use hir_expand::name::Name;
#[derive(Debug)]
pub struct TyLoweringContext<'a> {
@ -383,61 +384,38 @@ fn select_associated_type(
res: Option<TypeNs>,
segment: PathSegment<'_>,
) -> Ty {
let traits_from_env: Vec<_> = match res {
Some(TypeNs::SelfType(impl_id)) => match ctx.db.impl_trait(impl_id) {
None => return Ty::Unknown,
Some(trait_ref) => vec![trait_ref.value],
},
Some(TypeNs::GenericParam(param_id)) => {
let predicates = ctx.db.generic_predicates_for_param(param_id);
let mut traits_: Vec<_> = predicates
.iter()
.filter_map(|pred| match &pred.value {
GenericPredicate::Implemented(tr) => Some(tr.clone()),
_ => None,
})
.collect();
// Handle `Self::Type` referring to own associated type in trait definitions
if let GenericDefId::TraitId(trait_id) = param_id.parent {
let generics = generics(ctx.db.upcast(), trait_id.into());
if generics.params.types[param_id.local_id].provenance
== TypeParamProvenance::TraitSelf
{
let trait_ref = TraitRef {
trait_: trait_id,
substs: Substs::bound_vars(&generics, DebruijnIndex::INNERMOST),
if let Some(res) = res {
let ty =
associated_type_shorthand_candidates(ctx.db, res, move |name, t, associated_ty| {
if name == segment.name {
let substs = match ctx.type_param_mode {
TypeParamLoweringMode::Placeholder => {
// if we're lowering to placeholders, we have to put
// them in now
let s = Substs::type_params(
ctx.db,
ctx.resolver.generic_def().expect(
"there should be generics if there's a generic param",
),
);
t.substs.clone().subst_bound_vars(&s)
}
TypeParamLoweringMode::Variable => t.substs.clone(),
};
traits_.push(trait_ref);
// FIXME handle type parameters on the segment
return Some(Ty::Projection(ProjectionTy {
associated_ty,
parameters: substs,
}));
}
}
traits_
}
_ => return Ty::Unknown,
};
let traits = traits_from_env.into_iter().flat_map(|t| all_super_trait_refs(ctx.db, t));
for t in traits {
if let Some(associated_ty) =
ctx.db.trait_data(t.trait_).associated_type_by_name(&segment.name)
{
let substs = match ctx.type_param_mode {
TypeParamLoweringMode::Placeholder => {
// if we're lowering to placeholders, we have to put
// them in now
let s = Substs::type_params(
ctx.db,
ctx.resolver
.generic_def()
.expect("there should be generics if there's a generic param"),
);
t.substs.subst_bound_vars(&s)
}
TypeParamLoweringMode::Variable => t.substs,
};
// FIXME handle (forbid) type parameters on the segment
return Ty::Projection(ProjectionTy { associated_ty, parameters: substs });
}
None
});
ty.unwrap_or(Ty::Unknown)
} else {
Ty::Unknown
}
Ty::Unknown
}
fn from_hir_path_inner(
@ -694,6 +672,61 @@ pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> PolyFnSig {
}
}
pub fn associated_type_shorthand_candidates<R>(
db: &dyn HirDatabase,
res: TypeNs,
mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
) -> Option<R> {
let traits_from_env: Vec<_> = match res {
TypeNs::SelfType(impl_id) => match db.impl_trait(impl_id) {
None => vec![],
Some(trait_ref) => vec![trait_ref.value],
},
TypeNs::GenericParam(param_id) => {
let predicates = db.generic_predicates_for_param(param_id);
let mut traits_: Vec<_> = predicates
.iter()
.filter_map(|pred| match &pred.value {
GenericPredicate::Implemented(tr) => Some(tr.clone()),
_ => None,
})
.collect();
// Handle `Self::Type` referring to own associated type in trait definitions
if let GenericDefId::TraitId(trait_id) = param_id.parent {
let generics = generics(db.upcast(), trait_id.into());
if generics.params.types[param_id.local_id].provenance
== TypeParamProvenance::TraitSelf
{
let trait_ref = TraitRef {
trait_: trait_id,
substs: Substs::bound_vars(&generics, DebruijnIndex::INNERMOST),
};
traits_.push(trait_ref);
}
}
traits_
}
_ => vec![],
};
for t in traits_from_env.into_iter().flat_map(move |t| all_super_trait_refs(db, t)) {
let data = db.trait_data(t.trait_);
for (name, assoc_id) in &data.items {
match assoc_id {
AssocItemId::TypeAliasId(alias) => {
if let Some(result) = cb(name, &t, *alias) {
return Some(result);
}
}
AssocItemId::FunctionId(_) | AssocItemId::ConstId(_) => {}
}
}
}
None
}
/// Build the type of all specific fields of a struct or enum variant.
pub(crate) fn field_types_query(
db: &dyn HirDatabase,

View File

@ -5,19 +5,29 @@
use test_utils::tested_by;
use crate::completion::{CompletionContext, Completions};
use rustc_hash::FxHashSet;
pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
let path = match &ctx.path_prefix {
Some(path) => path.clone(),
_ => return,
};
let def = match ctx.scope().resolve_hir_path(&path) {
Some(PathResolution::Def(def)) => def,
_ => return,
let scope = ctx.scope();
let context_module = scope.module();
let res = match scope.resolve_hir_path(&path) {
Some(res) => res,
None => return,
};
let context_module = ctx.scope().module();
match def {
hir::ModuleDef::Module(module) => {
// Add associated types on type parameters and `Self`.
res.assoc_type_shorthand_candidates(ctx.db, |alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
match res {
PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, context_module);
for (name, def) in module_scope {
if ctx.use_item_syntax.is_some() {
@ -35,7 +45,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
acc.add_resolution(ctx, name.to_string(), &def);
}
}
hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => {
PathResolution::Def(def @ hir::ModuleDef::Adt(_))
| PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => {
if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
for variant in e.variants(ctx.db) {
acc.add_enum_variant(ctx, variant, None);
@ -46,8 +57,10 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
_ => unreachable!(),
};
// Iterate assoc types separately
// FIXME: complete T::AssocType
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
// (where AssocType is defined on a trait, not an inherent impl)
let krate = ctx.krate;
if let Some(krate) = krate {
let traits_in_scope = ctx.scope().traits_in_scope();
@ -65,6 +78,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
None::<()>
});
// Iterate assoc types separately
ty.iterate_impl_items(ctx.db, krate, |item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
@ -77,7 +91,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
});
}
}
hir::ModuleDef::Trait(t) => {
PathResolution::Def(hir::ModuleDef::Trait(t)) => {
// Handles `Trait::assoc` as well as `<Ty as Trait>::assoc`.
for item in t.items(ctx.db) {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
continue;
@ -91,8 +106,38 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
}
}
}
PathResolution::TypeParam(_) | PathResolution::SelfType(_) => {
if let Some(krate) = ctx.krate {
let ty = match res {
PathResolution::TypeParam(param) => param.ty(ctx.db),
PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db),
_ => return,
};
let traits_in_scope = ctx.scope().traits_in_scope();
let mut seen = FxHashSet::default();
ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
}
// We might iterate candidates of a trait multiple times here, so deduplicate
// them.
if seen.insert(item) {
match item {
hir::AssocItem::Function(func) => {
acc.add_function(ctx, func, None);
}
hir::AssocItem::Const(ct) => acc.add_const(ctx, ct),
hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty),
}
}
None::<()>
});
}
}
_ => {}
};
}
}
#[cfg(test)]
@ -843,6 +888,211 @@ fn foo() { let _ = <S as Trait>::<|> }
);
}
#[test]
fn completes_ty_param_assoc_ty() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Super {
type Ty;
const CONST: u8;
fn func() {}
fn method(&self) {}
}
trait Sub: Super {
type SubTy;
const C2: ();
fn subfunc() {}
fn submethod(&self) {}
}
fn foo<T: Sub>() {
T::<|>
}
"
),
@r###"
[
CompletionItem {
label: "C2",
source_range: 219..219,
delete: 219..219,
insert: "C2",
kind: Const,
detail: "const C2: ();",
},
CompletionItem {
label: "CONST",
source_range: 219..219,
delete: 219..219,
insert: "CONST",
kind: Const,
detail: "const CONST: u8;",
},
CompletionItem {
label: "SubTy",
source_range: 219..219,
delete: 219..219,
insert: "SubTy",
kind: TypeAlias,
detail: "type SubTy;",
},
CompletionItem {
label: "Ty",
source_range: 219..219,
delete: 219..219,
insert: "Ty",
kind: TypeAlias,
detail: "type Ty;",
},
CompletionItem {
label: "func()",
source_range: 219..219,
delete: 219..219,
insert: "func()$0",
kind: Function,
lookup: "func",
detail: "fn func()",
},
CompletionItem {
label: "method()",
source_range: 219..219,
delete: 219..219,
insert: "method()$0",
kind: Method,
lookup: "method",
detail: "fn method(&self)",
},
CompletionItem {
label: "subfunc()",
source_range: 219..219,
delete: 219..219,
insert: "subfunc()$0",
kind: Function,
lookup: "subfunc",
detail: "fn subfunc()",
},
CompletionItem {
label: "submethod()",
source_range: 219..219,
delete: 219..219,
insert: "submethod()$0",
kind: Method,
lookup: "submethod",
detail: "fn submethod(&self)",
},
]
"###
);
}
#[test]
fn completes_self_param_assoc_ty() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Super {
type Ty;
const CONST: u8 = 0;
fn func() {}
fn method(&self) {}
}
trait Sub: Super {
type SubTy;
const C2: () = ();
fn subfunc() {}
fn submethod(&self) {}
}
struct Wrap<T>(T);
impl<T> Super for Wrap<T> {}
impl<T> Sub for Wrap<T> {
fn subfunc() {
// Should be able to assume `Self: Sub + Super`
Self::<|>
}
}
"
),
@r###"
[
CompletionItem {
label: "C2",
source_range: 365..365,
delete: 365..365,
insert: "C2",
kind: Const,
detail: "const C2: () = ();",
},
CompletionItem {
label: "CONST",
source_range: 365..365,
delete: 365..365,
insert: "CONST",
kind: Const,
detail: "const CONST: u8 = 0;",
},
CompletionItem {
label: "SubTy",
source_range: 365..365,
delete: 365..365,
insert: "SubTy",
kind: TypeAlias,
detail: "type SubTy;",
},
CompletionItem {
label: "Ty",
source_range: 365..365,
delete: 365..365,
insert: "Ty",
kind: TypeAlias,
detail: "type Ty;",
},
CompletionItem {
label: "func()",
source_range: 365..365,
delete: 365..365,
insert: "func()$0",
kind: Function,
lookup: "func",
detail: "fn func()",
},
CompletionItem {
label: "method()",
source_range: 365..365,
delete: 365..365,
insert: "method()$0",
kind: Method,
lookup: "method",
detail: "fn method(&self)",
},
CompletionItem {
label: "subfunc()",
source_range: 365..365,
delete: 365..365,
insert: "subfunc()$0",
kind: Function,
lookup: "subfunc",
detail: "fn subfunc()",
},
CompletionItem {
label: "submethod()",
source_range: 365..365,
delete: 365..365,
insert: "submethod()$0",
kind: Method,
lookup: "submethod",
detail: "fn submethod(&self)",
},
]
"###
);
}
#[test]
fn completes_type_alias() {
assert_debug_snapshot!(