From 8c2670026a4c864a67a06bab654e203ed068f021 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 28 Apr 2020 00:40:32 +0200 Subject: Complete assoc. items on type parameters --- crates/ra_hir/src/code_model.rs | 10 + crates/ra_hir/src/semantics.rs | 47 +++- crates/ra_hir_ty/src/lib.rs | 3 +- crates/ra_hir_ty/src/lower.rs | 144 ++++++----- .../src/completion/complete_qualified_path.rs | 273 ++++++++++++++++++++- 5 files changed, 408 insertions(+), 69 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 3fb419571..af59aa1b6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -953,6 +953,16 @@ impl TypeParam { 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` diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 86bfb416c..687f83f60 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -9,6 +9,7 @@ use hir_def::{ AsMacroCall, TraitId, }; use hir_expand::ExpansionInfo; +use hir_ty::associated_types; use itertools::Itertools; use ra_db::{FileId, FileRange}; use ra_prof::profile; @@ -24,8 +25,9 @@ use crate::{ 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,49 @@ pub enum PathResolution { AssocItem(AssocItem), } +impl PathResolution { + fn in_type_ns(self) -> Option { + 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(_)) => None, + PathResolution::Def(ModuleDef::EnumVariant(_)) => None, + PathResolution::Def(ModuleDef::Function(_)) => None, + PathResolution::Def(ModuleDef::Module(_)) => None, + PathResolution::Def(ModuleDef::Static(_)) => None, + PathResolution::Def(ModuleDef::Trait(_)) => None, + PathResolution::Def(ModuleDef::TypeAlias(alias)) => { + Some(TypeNs::TypeAliasId(alias.into())) + } + PathResolution::Local(_) => None, + PathResolution::TypeParam(param) => Some(TypeNs::GenericParam(param.into())), + PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType(impl_def.into())), + PathResolution::Macro(_) => None, + PathResolution::AssocItem(AssocItem::Const(_)) => None, + 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( + &self, + db: &dyn HirDatabase, + mut cb: impl FnMut(TypeAlias) -> Option, + ) -> Option { + if let Some(res) = self.clone().in_type_ns() { + associated_types(db, res, |_, _, id| cb(id.into())) + } else { + None + } + } +} + /// Primary API to get semantic information, like types, from syntax trees. pub struct Semantics<'db, DB> { pub db: &'db DB, diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index a8ef32ec5..341a18683 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -66,7 +66,8 @@ pub use autoderef::autoderef; pub use infer::{InferTy, InferenceResult}; pub use lower::CallableDef; pub use lower::{ - callable_item_sig, ImplTraitLoweringMode, TyDefId, TyLoweringContext, ValueTyDefId, + associated_types, callable_item_sig, ImplTraitLoweringMode, TyDefId, TyLoweringContext, + ValueTyDefId, }; pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index a6f893037..67e5c1ccd 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -17,9 +17,9 @@ use hir_def::{ 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 @@ use crate::{ 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,37 @@ impl Ty { res: Option, 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), - }; - traits_.push(trait_ref); - } + if let Some(res) = res { + let ty = associated_types(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(), + }; + // 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 +671,61 @@ pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> PolyFnSig { } } +pub fn associated_types( + db: &dyn HirDatabase, + res: TypeNs, + mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option, +) -> Option { + 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, diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs index dd10f74e6..69e789a49 100644 --- a/crates/ra_ide/src/completion/complete_qualified_path.rs +++ b/crates/ra_ide/src/completion/complete_qualified_path.rs @@ -5,19 +5,30 @@ use ra_syntax::AstNode; 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 = if let Some(res) = scope.resolve_hir_path(&path) { + res + } else { + 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 +46,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 +58,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 +79,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 +92,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 `::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 +107,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 +889,211 @@ mod tests { ); } + #[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::<|> + } + " + ), + @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); + impl Super for Wrap {} + impl Sub for Wrap { + 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!( -- cgit v1.2.3