diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index 86958e3daea..b2323915c1f 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -459,6 +459,13 @@ impl Resolver { }) } + pub fn generic_params(&self) -> Option<&Interned> { + self.scopes().find_map(|scope| match scope { + Scope::GenericParams { params, .. } => Some(params), + _ => None, + }) + } + pub fn body_owner(&self) -> Option { self.scopes().find_map(|scope| match scope { Scope::ExprScope(it) => Some(it.owner), diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index eca37149f0a..921e64ad933 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -42,7 +42,7 @@ use hir_def::{ adt::VariantData, body::{BodyDiagnostic, SyntheticSyntax}, expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId}, - generics::{TypeOrConstParamData, TypeParamProvenance}, + generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance}, item_tree::ItemTreeNode, lang_item::{LangItem, LangItemTarget}, layout::{Layout, LayoutError, ReprOptions}, @@ -1170,6 +1170,25 @@ impl Adt { } } + /// Returns the lifetime of the DataType + pub fn lifetime(&self, db: &dyn HirDatabase) -> Option { + let resolver = match self { + Adt::Struct(s) => s.id.resolver(db.upcast()), + Adt::Union(u) => u.id.resolver(db.upcast()), + Adt::Enum(e) => e.id.resolver(db.upcast()), + }; + resolver + .generic_params() + .and_then(|gp| { + (&gp.lifetimes) + .iter() + // there should only be a single lifetime + // but `Arena` requires to use an iterator + .nth(0) + }) + .map(|arena| arena.1.clone()) + } + pub fn as_enum(&self) -> Option { if let Self::Enum(v) = self { Some(*v) @@ -3332,6 +3351,24 @@ impl Type { } } + /// Iterates its type arguments + /// + /// It iterates the actual type arguments when concrete types are used + /// and otherwise the generic names. + /// It does not include `const` arguments. + /// + /// For code, such as: + /// ```text + /// struct Foo + /// + /// impl Foo + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// ``` pub fn type_arguments(&self) -> impl Iterator + '_ { self.ty .strip_references() @@ -3342,6 +3379,58 @@ impl Type { .map(move |ty| self.derived(ty)) } + /// Iterates its type and const arguments + /// + /// It iterates the actual type and const arguments when concrete types + /// are used and otherwise the generic names. + /// + /// For code, such as: + /// ```text + /// struct Foo + /// + /// impl Foo + /// ``` + /// + /// It iterates: + /// ```text + /// - "String" + /// - "U" + /// - "12" + /// ``` + pub fn type_and_const_arguments<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator + 'a { + self.ty + .strip_references() + .as_adt() + .into_iter() + .flat_map(|(_, substs)| substs.iter(Interner)) + .filter_map(|arg| { + // arg can be either a `Ty` or `constant` + if let Some(ty) = arg.ty(Interner) { + Some(SmolStr::new(ty.display(db).to_string())) + } else if let Some(const_) = arg.constant(Interner) { + Some(SmolStr::new_inline(&const_.display(db).to_string())) + } else { + None + } + }) + } + + /// Combines lifetime indicators, type and constant parameters into a single `Iterator` + pub fn generic_parameters<'a>( + &'a self, + db: &'a dyn HirDatabase, + ) -> impl Iterator + 'a { + // iterate the lifetime + self.as_adt() + .and_then(|a| a.lifetime(db).and_then(|lt| Some((<.name).to_smol_str()))) + .into_iter() + // add the type and const paramaters + .chain(self.type_and_const_arguments(db)) + } + pub fn iterate_method_candidates_with_traits( &self, db: &dyn HirDatabase, diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index af53adee898..77aef710ad1 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -2,7 +2,7 @@ use std::fmt; use ast::HasName; use cfg::CfgExpr; -use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics}; +use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; use ide_assists::utils::test_related_attribute; use ide_db::{ base_db::{FilePosition, FileRange}, @@ -370,9 +370,9 @@ pub(crate) fn runnable_impl( let nav = def.try_to_nav(sema.db)?; let ty = def.self_ty(sema.db); let adt_name = ty.as_adt()?.name(sema.db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.generic_parameters(sema.db).peekable(); let params = if ty_args.peek().is_some() { - format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty.display(sema.db)))) + format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))) } else { String::new() }; @@ -436,14 +436,10 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option { let ty = imp.self_ty(db); if let Some(adt) = ty.as_adt() { let name = adt.name(db); - let mut ty_args = ty.type_arguments().peekable(); + let mut ty_args = ty.generic_parameters(db).peekable(); format_to!(path, "{}", name); if ty_args.peek().is_some() { - format_to!( - path, - "<{}>", - ty_args.format_with(",", |ty, cb| cb(&ty.display(db))) - ); + format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty))); } format_to!(path, "::{}", def_name); path.retain(|c| c != ' '); @@ -999,6 +995,221 @@ impl Data { ); } + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a>; +impl Data<'a> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 52..106, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_and_types() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, U>; +impl Data<'a, T, U> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 70..124, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,U>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data; +impl Data { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 79..133, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_types_and_const() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, T, const N: usize>; +impl<'a, T, const N: usize> Data<'a, T, N> { + /// ``` + /// let x = 5; + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 100..154, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,T,N>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } #[test] fn test_runnables_module() { check( @@ -2061,6 +2272,59 @@ mod tests { ); } + #[test] + fn test_runnables_doc_test_in_impl_with_lifetime_type_const_value() { + check( + r#" +//- /lib.rs +$0 +fn main() {} + +struct Data<'a, A, const B: usize, C, const D: u32>; +impl Data<'a, A, 12, C, D> { + /// ``` + /// ``` + fn foo() {} +} +"#, + &[Bin, DocTest], + expect![[r#" + [ + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 1..13, + focus_range: 4..8, + name: "main", + kind: Function, + }, + kind: Bin, + cfg: None, + }, + Runnable { + use_name_in_title: false, + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 121..156, + name: "foo", + }, + kind: DocTest { + test_id: Path( + "Data<'a,A,12,C,D>::foo", + ), + }, + cfg: None, + }, + ] + "#]], + ); + } + #[test] fn doc_test_type_params() { check(