about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJonas Marcello <jonas.marcello@esbme.com>2023-02-19 22:07:33 +0100
committerJonas Marcello <jonas.marcello@esbme.com>2023-02-27 18:08:17 +0100
commit9942cc425ba31cf47ebf9498ed3cd58f064b2b89 (patch)
tree94034fcbe184759f728f29a409da6586e7aea42c
parentc867cbf9b6aea8d73c433ed85c6619e7714f3f7f (diff)
downloadrust-9942cc425ba31cf47ebf9498ed3cd58f064b2b89.tar.gz
rust-9942cc425ba31cf47ebf9498ed3cd58f064b2b89.zip
Fix 14142: Annotate lifetime paramaters in doctest runnables
-rw-r--r--crates/hir-def/src/resolver.rs7
-rw-r--r--crates/hir/src/lib.rs37
-rw-r--r--crates/ide/src/runnables.rs117
3 files changed, 155 insertions, 6 deletions
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<GenericParams>> {
+        self.scopes().find_map(|scope| match scope {
+            Scope::GenericParams { params, .. } => Some(params),
+            _ => None,
+        })
+    }
+
     pub fn body_owner(&self) -> Option<DefWithBodyId> {
         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 6206a541c1e..28d87e14e15 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::{TypeOrConstParamData, TypeParamProvenance, LifetimeParamData},
     item_tree::ItemTreeNode,
     lang_item::{LangItem, LangItemTarget},
     layout::{Layout, LayoutError, ReprOptions},
@@ -1170,6 +1170,22 @@ impl Adt {
         }
     }
 
+    /// Returns the lifetime of the DataType
+    pub fn lifetime(&self, db: &dyn HirDatabase) -> Option<LifetimeParamData> {
+        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<Enum> {
         if let Self::Enum(v) = self {
             Some(*v)
@@ -3339,6 +3355,25 @@ impl Type {
             .map(move |ty| self.derived(ty))
     }
 
+    /// Combines lifetime indicators and type arguments into a single `Vec<SmolStr>`
+    pub fn lifetime_and_type_arguments<'a>(&'a self, db: &'a dyn HirDatabase) -> Vec<SmolStr> {
+        let mut names = if let Some(lt) = self
+            .as_adt()
+            .and_then(|a| {
+                a.lifetime(db)
+                .and_then(|lt| Some((&lt.name).to_smol_str().clone()))
+            }) {
+                vec![lt]
+            } else {
+                vec![]
+            };
+
+        for ty in self.type_arguments() {
+            names.push(SmolStr::new(ty.display(db).to_string()))
+        }
+        names
+    }
+
     pub fn iterate_method_candidates_with_traits<T>(
         &self,
         db: &dyn HirDatabase,
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index af53adee898..2e8f3906afc 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.lifetime_and_type_arguments(sema.db).into_iter().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,13 +436,13 @@ fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
                 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.lifetime_and_type_arguments(db).into_iter().peekable();
                     format_to!(path, "{}", name);
                     if ty_args.peek().is_some() {
                         format_to!(
                             path,
                             "<{}>",
-                            ty_args.format_with(",", |ty, cb| cb(&ty.display(db)))
+                            ty_args.format_with(",", |ty, cb| cb(&ty))
                         );
                     }
                     format_to!(path, "::{}", def_name);
@@ -1000,6 +1000,113 @@ 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<T, U> 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_module() {
         check(
             r#"