Skip to content

Commit d639195

Browse files
fix
1 parent ea1d299 commit d639195

File tree

2 files changed

+93
-24
lines changed

2 files changed

+93
-24
lines changed

crates/ide/src/runnables.rs

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub enum RunnableKind {
5757
TestMod { path: String },
5858
Test { test_id: TestId, attr: TestAttr },
5959
Bench { test_id: TestId },
60-
DocTest { test_id: TestId },
60+
DocTest { test_id: TestId, has_compile_fail: bool },
6161
Bin,
6262
}
6363

@@ -404,9 +404,9 @@ pub(crate) fn runnable_impl(
404404
let display_target = def.module(sema.db).krate(sema.db).to_display_target(sema.db);
405405
let edition = display_target.edition;
406406
let attrs = def.attrs(sema.db);
407-
if !has_runnable_doc_test(sema.db, &attrs) {
407+
let Some(doc_test_info) = runnable_doc_test_info(sema.db, &attrs) else {
408408
return None;
409-
}
409+
};
410410
let cfg = attrs.cfgs(sema.db).cloned();
411411
let nav = def.try_to_nav(sema)?.call_site();
412412
let ty = def.self_ty(sema.db);
@@ -431,7 +431,7 @@ pub(crate) fn runnable_impl(
431431
Some(Runnable {
432432
use_name_in_title: false,
433433
nav,
434-
kind: RunnableKind::DocTest { test_id },
434+
kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail },
435435
cfg,
436436
update_test,
437437
})
@@ -510,9 +510,9 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op
510510
let display_target = krate
511511
.unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into())
512512
.to_display_target(db);
513-
if !has_runnable_doc_test(db, &attrs) {
513+
let Some(doc_test_info) = runnable_doc_test_info(db, &attrs) else {
514514
return None;
515-
}
515+
};
516516
let def_name = def.name(db)?;
517517
let path = (|| {
518518
let mut path = String::new();
@@ -555,7 +555,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op
555555
let res = Runnable {
556556
use_name_in_title: false,
557557
nav,
558-
kind: RunnableKind::DocTest { test_id },
558+
kind: RunnableKind::DocTest { test_id, has_compile_fail: doc_test_info.has_compile_fail },
559559
cfg: attrs.cfgs(db).cloned(),
560560
update_test: UpdateTest::default(),
561561
};
@@ -573,32 +573,64 @@ impl TestAttr {
573573
}
574574
}
575575

576-
fn has_runnable_doc_test(db: &RootDatabase, attrs: &hir::AttrsWithOwner) -> bool {
576+
#[derive(Default, Clone, Copy)]
577+
struct RunnableDocTestInfo {
578+
has_compile_fail: bool,
579+
}
580+
581+
fn runnable_doc_test_info(
582+
db: &RootDatabase,
583+
attrs: &hir::AttrsWithOwner,
584+
) -> Option<RunnableDocTestInfo> {
577585
const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
578586
const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
579-
&["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
587+
&["", "rust", "should_panic", "edition2015", "edition2018", "edition2021", "edition2024"];
588+
589+
let doc = attrs.hir_docs(db)?;
590+
let mut info = RunnableDocTestInfo::default();
591+
let mut in_code_block = false;
592+
let mut runnable_found = false;
593+
594+
for line in doc.docs().lines() {
595+
let trimmed_line = line.trim_start();
596+
if let Some(header) =
597+
RUSTDOC_FENCES.into_iter().find_map(|fence| trimmed_line.strip_prefix(fence))
598+
{
599+
if in_code_block {
600+
in_code_block = false;
601+
continue;
602+
}
580603

581-
attrs.hir_docs(db).is_some_and(|doc| {
582-
let mut in_code_block = false;
604+
in_code_block = true;
605+
let mut block_has_compile_fail = false;
606+
let mut block_runnable = true;
583607

584-
for line in doc.docs().lines() {
585-
if let Some(header) =
586-
RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence))
608+
for attr in header
609+
.split(',')
610+
.flat_map(|segment| segment.split_ascii_whitespace())
611+
.map(str::trim)
612+
.filter(|attr| !attr.is_empty())
587613
{
588-
in_code_block = !in_code_block;
614+
if attr.eq_ignore_ascii_case("compile_fail") {
615+
block_has_compile_fail = true;
616+
block_runnable = false;
617+
continue;
618+
}
589619

590-
if in_code_block
591-
&& header
592-
.split(',')
593-
.all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
620+
if !RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE
621+
.iter()
622+
.any(|allowed| allowed.eq_ignore_ascii_case(attr))
594623
{
595-
return true;
624+
block_runnable = false;
596625
}
597626
}
627+
628+
info.has_compile_fail |= block_has_compile_fail;
629+
runnable_found |= block_runnable;
598630
}
631+
}
599632

600-
false
601-
})
633+
runnable_found.then_some(info)
602634
}
603635

604636
// We could create runnables for modules with number_of_test_submodules > 0,
@@ -756,6 +788,7 @@ impl UpdateTest {
756788
mod tests {
757789
use expect_test::{Expect, expect};
758790

791+
use super::RunnableKind;
759792
use crate::fixture;
760793

761794
fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
@@ -944,6 +977,39 @@ impl Test for StructWithRunnable {}
944977
);
945978
}
946979

980+
#[test]
981+
fn doc_test_runnable_tracks_compile_fail_blocks() {
982+
let (analysis, position) = fixture::position(
983+
r#"
984+
//- /lib.rs
985+
$0
986+
/// ```compile_fail
987+
/// let x = 5;
988+
/// x += 1;
989+
/// ```
990+
///
991+
/// ```
992+
/// let x = 5;
993+
/// x + 1;
994+
/// ```
995+
fn add(left: u64, right: u64) -> u64 {
996+
left + right
997+
}
998+
"#,
999+
);
1000+
1001+
let runnables = analysis.runnables(position.file_id).unwrap();
1002+
let doc_test = runnables
1003+
.into_iter()
1004+
.find(|runnable| matches!(runnable.kind, RunnableKind::DocTest { .. }))
1005+
.expect("expected doctest runnable");
1006+
1007+
match doc_test.kind {
1008+
RunnableKind::DocTest { has_compile_fail, .. } => assert!(has_compile_fail),
1009+
_ => panic!("expected doctest runnable"),
1010+
}
1011+
}
1012+
9471013
#[test]
9481014
fn test_runnables_doc_test_in_impl() {
9491015
check(

crates/rust-analyzer/src/target_spec.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl CargoTargetSpec {
116116
cfg: &Option<CfgExpr>,
117117
) -> (Vec<String>, Vec<String>) {
118118
let config = snap.config.runnables(None);
119-
let extra_test_binary_args = config.extra_test_binary_args;
119+
let mut extra_test_binary_args = config.extra_test_binary_args;
120120

121121
let mut cargo_args = Vec::new();
122122
let mut executable_args = Vec::new();
@@ -146,10 +146,13 @@ impl CargoTargetSpec {
146146
}
147147
executable_args.extend(extra_test_binary_args);
148148
}
149-
RunnableKind::DocTest { test_id } => {
149+
RunnableKind::DocTest { test_id, has_compile_fail } => {
150150
cargo_args.push("test".to_owned());
151151
cargo_args.push("--doc".to_owned());
152152
executable_args.push(test_id.to_string());
153+
if *has_compile_fail {
154+
extra_test_binary_args.retain(|arg| arg != "--nocapture");
155+
}
153156
executable_args.extend(extra_test_binary_args);
154157
}
155158
RunnableKind::Bin => {

0 commit comments

Comments
 (0)