From ff6c12ba3b90586fa4f24601007b98f847aedb02 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Sat, 20 Dec 2025 13:36:11 +0900 Subject: [PATCH 1/2] fix clippy --- crates/ide/src/runnables.rs | 112 ++++++++++++++++++------ crates/rust-analyzer/src/target_spec.rs | 7 +- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index c562a9b30b..136ce6ac1e 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -57,7 +57,7 @@ pub enum RunnableKind { TestMod { path: String }, Test { test_id: TestId, attr: TestAttr }, Bench { test_id: TestId }, - DocTest { test_id: TestId }, + DocTest { test_id: TestId, has_compile_fail: bool }, Bin, } @@ -404,9 +404,7 @@ pub(crate) fn runnable_impl( let display_target = def.module(sema.db).krate(sema.db).to_display_target(sema.db); let edition = display_target.edition; let attrs = def.attrs(sema.db); - if !has_runnable_doc_test(sema.db, &attrs) { - return None; - } + let doc_test_info = runnable_doc_test_info(sema.db, &attrs)?; let cfg = attrs.cfgs(sema.db).cloned(); let nav = def.try_to_nav(sema)?.call_site(); let ty = def.self_ty(sema.db); @@ -429,7 +427,7 @@ pub(crate) fn runnable_impl( Some(Runnable { use_name_in_title: false, nav, - kind: RunnableKind::DocTest { test_id }, + kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail }, cfg, update_test, }) @@ -508,9 +506,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op let display_target = krate .unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into()) .to_display_target(db); - if !has_runnable_doc_test(db, &attrs) { - return None; - } + let doc_test_info = runnable_doc_test_info(db, &attrs)?; let def_name = def.name(db)?; let path = (|| { let mut path = String::new(); @@ -551,7 +547,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op let res = Runnable { use_name_in_title: false, nav, - kind: RunnableKind::DocTest { test_id }, + kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail }, cfg: attrs.cfgs(db).cloned(), update_test: UpdateTest::default(), }; @@ -569,32 +565,64 @@ impl TestAttr { } } -fn has_runnable_doc_test(db: &RootDatabase, attrs: &hir::AttrsWithOwner) -> bool { +#[derive(Default, Clone, Copy)] +struct RunnableDocTestInfo { + has_compile_fail: bool, +} + +fn runnable_doc_test_info( + db: &RootDatabase, + attrs: &hir::AttrsWithOwner, +) -> Option { const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = - &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"]; + &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021", "edition2024"]; - attrs.hir_docs(db).is_some_and(|doc| { - let mut in_code_block = false; + let doc = attrs.hir_docs(db)?; + let mut info = RunnableDocTestInfo::default(); + let mut in_code_block = false; + let mut runnable_found = false; - for line in doc.docs().lines() { - if let Some(header) = - RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence)) + for line in doc.docs().lines() { + let trimmed_line = line.trim_start(); + if let Some(header) = + RUSTDOC_FENCES.into_iter().find_map(|fence| trimmed_line.strip_prefix(fence)) + { + if in_code_block { + in_code_block = false; + continue; + } + + in_code_block = true; + let mut block_has_compile_fail = false; + let mut block_runnable = true; + + for attr in header + .split(',') + .flat_map(|segment| segment.split_ascii_whitespace()) + .map(str::trim) + .filter(|attr| !attr.is_empty()) { - in_code_block = !in_code_block; + if attr.eq_ignore_ascii_case("compile_fail") { + block_has_compile_fail = true; + block_runnable = false; + continue; + } - if in_code_block - && header - .split(',') - .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim())) + if !RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE + .iter() + .any(|allowed| allowed.eq_ignore_ascii_case(attr)) { - return true; + block_runnable = false; } } - } - false - }) + info.has_compile_fail |= block_has_compile_fail; + runnable_found |= block_runnable; + } + } + + runnable_found.then_some(info) } // We could create runnables for modules with number_of_test_submodules > 0, @@ -752,6 +780,7 @@ impl UpdateTest { mod tests { use expect_test::{Expect, expect}; + use super::RunnableKind; use crate::fixture; fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { @@ -940,6 +969,39 @@ impl Test for StructWithRunnable {} ); } + #[test] + fn doc_test_runnable_tracks_compile_fail_blocks() { + let (analysis, position) = fixture::position( + r#" +//- /lib.rs +$0 +/// ```compile_fail +/// let x = 5; +/// x += 1; +/// ``` +/// +/// ``` +/// let x = 5; +/// x + 1; +/// ``` +fn add(left: u64, right: u64) -> u64 { + left + right +} +"#, + ); + + let runnables = analysis.runnables(position.file_id).unwrap(); + let doc_test = runnables + .into_iter() + .find(|runnable| matches!(runnable.kind, RunnableKind::DocTest { .. })) + .expect("expected doctest runnable"); + + match doc_test.kind { + RunnableKind::DocTest { has_compile_fail, .. } => assert!(has_compile_fail), + _ => panic!("expected doctest runnable"), + } + } + #[test] fn test_runnables_doc_test_in_impl() { check( diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs index e532d15553..f47df6e5c9 100644 --- a/crates/rust-analyzer/src/target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -116,7 +116,7 @@ impl CargoTargetSpec { cfg: &Option, ) -> (Vec, Vec) { let config = snap.config.runnables(None); - let extra_test_binary_args = config.extra_test_binary_args; + let mut extra_test_binary_args = config.extra_test_binary_args; let mut cargo_args = Vec::new(); let mut executable_args = Vec::new(); @@ -146,10 +146,13 @@ impl CargoTargetSpec { } executable_args.extend(extra_test_binary_args); } - RunnableKind::DocTest { test_id } => { + RunnableKind::DocTest { test_id, has_compile_fail } => { cargo_args.push("test".to_owned()); cargo_args.push("--doc".to_owned()); executable_args.push(test_id.to_string()); + if *has_compile_fail { + extra_test_binary_args.retain(|arg| arg != "--nocapture"); + } executable_args.extend(extra_test_binary_args); } RunnableKind::Bin => { From 7bfa62e99f1ccdb7f15c579fc4c7de7e472ebd28 Mon Sep 17 00:00:00 2001 From: Asuka Minato Date: Mon, 22 Dec 2025 22:20:33 +0900 Subject: [PATCH 2/2] update based on comment --- crates/ide/src/runnables.rs | 64 +++++++++++-------------- crates/rust-analyzer/src/target_spec.rs | 7 +-- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index 136ce6ac1e..6798504378 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -57,7 +57,7 @@ pub enum RunnableKind { TestMod { path: String }, Test { test_id: TestId, attr: TestAttr }, Bench { test_id: TestId }, - DocTest { test_id: TestId, has_compile_fail: bool }, + DocTest { test_id: TestId }, Bin, } @@ -404,7 +404,9 @@ pub(crate) fn runnable_impl( let display_target = def.module(sema.db).krate(sema.db).to_display_target(sema.db); let edition = display_target.edition; let attrs = def.attrs(sema.db); - let doc_test_info = runnable_doc_test_info(sema.db, &attrs)?; + if !has_runnable_doc_test(sema.db, &attrs) { + return None; + } let cfg = attrs.cfgs(sema.db).cloned(); let nav = def.try_to_nav(sema)?.call_site(); let ty = def.self_ty(sema.db); @@ -427,7 +429,7 @@ pub(crate) fn runnable_impl( Some(Runnable { use_name_in_title: false, nav, - kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail }, + kind: RunnableKind::DocTest { test_id }, cfg, update_test, }) @@ -506,7 +508,9 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op let display_target = krate .unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into()) .to_display_target(db); - let doc_test_info = runnable_doc_test_info(db, &attrs)?; + if !has_runnable_doc_test(db, &attrs) { + return None; + } let def_name = def.name(db)?; let path = (|| { let mut path = String::new(); @@ -547,7 +551,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op let res = Runnable { use_name_in_title: false, nav, - kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail }, + kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfgs(db).cloned(), update_test: UpdateTest::default(), }; @@ -565,23 +569,18 @@ impl TestAttr { } } -#[derive(Default, Clone, Copy)] -struct RunnableDocTestInfo { - has_compile_fail: bool, -} - -fn runnable_doc_test_info( - db: &RootDatabase, - attrs: &hir::AttrsWithOwner, -) -> Option { +fn has_runnable_doc_test(db: &RootDatabase, attrs: &hir::AttrsWithOwner) -> bool { const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"]; const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] = &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021", "edition2024"]; - let doc = attrs.hir_docs(db)?; - let mut info = RunnableDocTestInfo::default(); + let doc = match attrs.hir_docs(db) { + Some(doc) => doc, + None => return false, + }; let mut in_code_block = false; let mut runnable_found = false; + let mut has_compile_fail = false; for line in doc.docs().lines() { let trimmed_line = line.trim_start(); @@ -606,7 +605,7 @@ fn runnable_doc_test_info( if attr.eq_ignore_ascii_case("compile_fail") { block_has_compile_fail = true; block_runnable = false; - continue; + break; } if !RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE @@ -617,12 +616,12 @@ fn runnable_doc_test_info( } } - info.has_compile_fail |= block_has_compile_fail; + has_compile_fail |= block_has_compile_fail; runnable_found |= block_runnable; } } - runnable_found.then_some(info) + runnable_found && !has_compile_fail } // We could create runnables for modules with number_of_test_submodules > 0, @@ -780,7 +779,6 @@ impl UpdateTest { mod tests { use expect_test::{Expect, expect}; - use super::RunnableKind; use crate::fixture; fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) { @@ -970,11 +968,13 @@ impl Test for StructWithRunnable {} } #[test] - fn doc_test_runnable_tracks_compile_fail_blocks() { - let (analysis, position) = fixture::position( + fn doc_test_with_compile_fail_blocks_is_skipped() { + check( r#" //- /lib.rs $0 +fn main() {} + /// ```compile_fail /// let x = 5; /// x += 1; @@ -984,22 +984,14 @@ $0 /// let x = 5; /// x + 1; /// ``` -fn add(left: u64, right: u64) -> u64 { - left + right -} +fn add() {} "#, + expect![[r#" + [ + "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })", + ] + "#]], ); - - let runnables = analysis.runnables(position.file_id).unwrap(); - let doc_test = runnables - .into_iter() - .find(|runnable| matches!(runnable.kind, RunnableKind::DocTest { .. })) - .expect("expected doctest runnable"); - - match doc_test.kind { - RunnableKind::DocTest { has_compile_fail, .. } => assert!(has_compile_fail), - _ => panic!("expected doctest runnable"), - } } #[test] diff --git a/crates/rust-analyzer/src/target_spec.rs b/crates/rust-analyzer/src/target_spec.rs index f47df6e5c9..e532d15553 100644 --- a/crates/rust-analyzer/src/target_spec.rs +++ b/crates/rust-analyzer/src/target_spec.rs @@ -116,7 +116,7 @@ impl CargoTargetSpec { cfg: &Option, ) -> (Vec, Vec) { let config = snap.config.runnables(None); - let mut extra_test_binary_args = config.extra_test_binary_args; + let extra_test_binary_args = config.extra_test_binary_args; let mut cargo_args = Vec::new(); let mut executable_args = Vec::new(); @@ -146,13 +146,10 @@ impl CargoTargetSpec { } executable_args.extend(extra_test_binary_args); } - RunnableKind::DocTest { test_id, has_compile_fail } => { + RunnableKind::DocTest { test_id } => { cargo_args.push("test".to_owned()); cargo_args.push("--doc".to_owned()); executable_args.push(test_id.to_string()); - if *has_compile_fail { - extra_test_binary_args.retain(|arg| arg != "--nocapture"); - } executable_args.extend(extra_test_binary_args); } RunnableKind::Bin => {