mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 21:43:37 +00:00
Auto merge of #14185 - anergictcell:fix_14142, r=HKalbasi
Fix: Run doctests for structs with lifetime parameters from IDE Fixes #14142: Doctests can't be triggered for structs with lifetimes This MR adds lifetime parameters to the structs path for runnables so that they can be triggered from an IDE as well. This is my first MR for rust-analyzer, please let me know if I should change something, either in code or the description here.
This commit is contained in:
commit
c386316fe0
3 changed files with 370 additions and 10 deletions
|
@ -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),
|
||||
|
|
|
@ -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<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)
|
||||
|
@ -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<T, U>
|
||||
///
|
||||
/// impl<U> Foo<String, U>
|
||||
/// ```
|
||||
///
|
||||
/// It iterates:
|
||||
/// ```text
|
||||
/// - "String"
|
||||
/// - "U"
|
||||
/// ```
|
||||
pub fn type_arguments(&self) -> impl Iterator<Item = Type> + '_ {
|
||||
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<T, const U: usize, const X: usize>
|
||||
///
|
||||
/// impl<U> Foo<String, U, 12>
|
||||
/// ```
|
||||
///
|
||||
/// It iterates:
|
||||
/// ```text
|
||||
/// - "String"
|
||||
/// - "U"
|
||||
/// - "12"
|
||||
/// ```
|
||||
pub fn type_and_const_arguments<'a>(
|
||||
&'a self,
|
||||
db: &'a dyn HirDatabase,
|
||||
) -> impl Iterator<Item = SmolStr> + '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<Item = SmolStr> + '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<T>(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
|
|
|
@ -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<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.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<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_doc_test_in_impl_with_const() {
|
||||
check(
|
||||
r#"
|
||||
//- /lib.rs
|
||||
$0
|
||||
fn main() {}
|
||||
|
||||
struct Data<const N: usize>;
|
||||
impl<const N: usize> Data<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: 79..133,
|
||||
name: "foo",
|
||||
},
|
||||
kind: DocTest {
|
||||
test_id: Path(
|
||||
"Data<N>::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<A, C, const D: u32> 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(
|
||||
|
|
Loading…
Reference in a new issue