@@ -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,7 @@ 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) {
408- return None ;
409- }
407+ let doc_test_info = runnable_doc_test_info ( sema. db , & attrs) ?;
410408 let cfg = attrs. cfgs ( sema. db ) . cloned ( ) ;
411409 let nav = def. try_to_nav ( sema) ?. call_site ( ) ;
412410 let ty = def. self_ty ( sema. db ) ;
@@ -431,7 +429,7 @@ pub(crate) fn runnable_impl(
431429 Some ( Runnable {
432430 use_name_in_title : false ,
433431 nav,
434- kind : RunnableKind :: DocTest { test_id } ,
432+ kind : RunnableKind :: DocTest { test_id, has_compile_fail : doc_test_info . has_compile_fail } ,
435433 cfg,
436434 update_test,
437435 } )
@@ -510,9 +508,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op
510508 let display_target = krate
511509 . unwrap_or_else ( || ( * db. all_crates ( ) . last ( ) . expect ( "no crate graph present" ) ) . into ( ) )
512510 . to_display_target ( db) ;
513- if !has_runnable_doc_test ( db, & attrs) {
514- return None ;
515- }
511+ let doc_test_info = runnable_doc_test_info ( db, & attrs) ?;
516512 let def_name = def. name ( db) ?;
517513 let path = ( || {
518514 let mut path = String :: new ( ) ;
@@ -555,7 +551,7 @@ fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Op
555551 let res = Runnable {
556552 use_name_in_title : false ,
557553 nav,
558- kind : RunnableKind :: DocTest { test_id } ,
554+ kind : RunnableKind :: DocTest { test_id, has_compile_fail : doc_test_info . has_compile_fail } ,
559555 cfg : attrs. cfgs ( db) . cloned ( ) ,
560556 update_test : UpdateTest :: default ( ) ,
561557 } ;
@@ -573,32 +569,64 @@ impl TestAttr {
573569 }
574570}
575571
576- fn has_runnable_doc_test ( db : & RootDatabase , attrs : & hir:: AttrsWithOwner ) -> bool {
572+ #[ derive( Default , Clone , Copy ) ]
573+ struct RunnableDocTestInfo {
574+ has_compile_fail : bool ,
575+ }
576+
577+ fn runnable_doc_test_info (
578+ db : & RootDatabase ,
579+ attrs : & hir:: AttrsWithOwner ,
580+ ) -> Option < RunnableDocTestInfo > {
577581 const RUSTDOC_FENCES : [ & str ; 2 ] = [ "```" , "~~~" ] ;
578582 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE : & [ & str ] =
579- & [ "" , "rust" , "should_panic" , "edition2015" , "edition2018" , "edition2021" ] ;
583+ & [ "" , "rust" , "should_panic" , "edition2015" , "edition2018" , "edition2021" , "edition2024" ] ;
584+
585+ let doc = attrs. hir_docs ( db) ?;
586+ let mut info = RunnableDocTestInfo :: default ( ) ;
587+ let mut in_code_block = false ;
588+ let mut runnable_found = false ;
589+
590+ for line in doc. docs ( ) . lines ( ) {
591+ let trimmed_line = line. trim_start ( ) ;
592+ if let Some ( header) =
593+ RUSTDOC_FENCES . into_iter ( ) . find_map ( |fence| trimmed_line. strip_prefix ( fence) )
594+ {
595+ if in_code_block {
596+ in_code_block = false ;
597+ continue ;
598+ }
580599
581- attrs. hir_docs ( db) . is_some_and ( |doc| {
582- let mut in_code_block = false ;
600+ in_code_block = true ;
601+ let mut block_has_compile_fail = false ;
602+ let mut block_runnable = true ;
583603
584- for line in doc. docs ( ) . lines ( ) {
585- if let Some ( header) =
586- RUSTDOC_FENCES . into_iter ( ) . find_map ( |fence| line. strip_prefix ( fence) )
604+ for attr in header
605+ . split ( ',' )
606+ . flat_map ( |segment| segment. split_ascii_whitespace ( ) )
607+ . map ( str:: trim)
608+ . filter ( |attr| !attr. is_empty ( ) )
587609 {
588- in_code_block = !in_code_block;
610+ if attr. eq_ignore_ascii_case ( "compile_fail" ) {
611+ block_has_compile_fail = true ;
612+ block_runnable = false ;
613+ continue ;
614+ }
589615
590- if in_code_block
591- && header
592- . split ( ',' )
593- . all ( |sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE . contains ( & sub. trim ( ) ) )
616+ if !RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE
617+ . iter ( )
618+ . any ( |allowed| allowed. eq_ignore_ascii_case ( attr) )
594619 {
595- return true ;
620+ block_runnable = false ;
596621 }
597622 }
623+
624+ info. has_compile_fail |= block_has_compile_fail;
625+ runnable_found |= block_runnable;
598626 }
627+ }
599628
600- false
601- } )
629+ runnable_found. then_some ( info)
602630}
603631
604632// We could create runnables for modules with number_of_test_submodules > 0,
@@ -756,6 +784,7 @@ impl UpdateTest {
756784mod tests {
757785 use expect_test:: { Expect , expect} ;
758786
787+ use super :: RunnableKind ;
759788 use crate :: fixture;
760789
761790 fn check ( #[ rust_analyzer:: rust_fixture] ra_fixture : & str , expect : Expect ) {
@@ -944,6 +973,39 @@ impl Test for StructWithRunnable {}
944973 ) ;
945974 }
946975
976+ #[ test]
977+ fn doc_test_runnable_tracks_compile_fail_blocks ( ) {
978+ let ( analysis, position) = fixture:: position (
979+ r#"
980+ //- /lib.rs
981+ $0
982+ /// ```compile_fail
983+ /// let x = 5;
984+ /// x += 1;
985+ /// ```
986+ ///
987+ /// ```
988+ /// let x = 5;
989+ /// x + 1;
990+ /// ```
991+ fn add(left: u64, right: u64) -> u64 {
992+ left + right
993+ }
994+ "# ,
995+ ) ;
996+
997+ let runnables = analysis. runnables ( position. file_id ) . unwrap ( ) ;
998+ let doc_test = runnables
999+ . into_iter ( )
1000+ . find ( |runnable| matches ! ( runnable. kind, RunnableKind :: DocTest { .. } ) )
1001+ . expect ( "expected doctest runnable" ) ;
1002+
1003+ match doc_test. kind {
1004+ RunnableKind :: DocTest { has_compile_fail, .. } => assert ! ( has_compile_fail) ,
1005+ _ => panic ! ( "expected doctest runnable" ) ,
1006+ }
1007+ }
1008+
9471009 #[ test]
9481010 fn test_runnables_doc_test_in_impl ( ) {
9491011 check (
0 commit comments