@@ -22,6 +22,9 @@ pub(crate) struct DocTest {
22
22
pub ( crate ) already_has_extern_crate : bool ,
23
23
pub ( crate ) has_main_fn : bool ,
24
24
pub ( crate ) crate_attrs : String ,
25
+ /// If this is a merged doctest, it will be put into `everything_else`, otherwise it will
26
+ /// put into `crate_attrs`.
27
+ pub ( crate ) maybe_crate_attrs : String ,
25
28
pub ( crate ) crates : String ,
26
29
pub ( crate ) everything_else : String ,
27
30
pub ( crate ) test_id : Option < String > ,
@@ -38,7 +41,14 @@ impl DocTest {
38
41
// If `test_id` is `None`, it means we're generating code for a code example "run" link.
39
42
test_id : Option < String > ,
40
43
) -> Self {
41
- let ( crate_attrs, everything_else, crates) = partition_source ( source, edition) ;
44
+ let SourceInfo {
45
+ crate_attrs,
46
+ maybe_crate_attrs,
47
+ crates,
48
+ everything_else,
49
+ has_features,
50
+ has_no_std,
51
+ } = partition_source ( source, edition) ;
42
52
let mut supports_color = false ;
43
53
44
54
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
@@ -56,10 +66,11 @@ impl DocTest {
56
66
else {
57
67
// If the parser panicked due to a fatal error, pass the test code through unchanged.
58
68
// The error will be reported during compilation.
59
- return DocTest {
69
+ return Self {
60
70
supports_color : false ,
61
71
has_main_fn : false ,
62
72
crate_attrs,
73
+ maybe_crate_attrs,
63
74
crates,
64
75
everything_else,
65
76
already_has_extern_crate : false ,
@@ -72,14 +83,15 @@ impl DocTest {
72
83
supports_color,
73
84
has_main_fn,
74
85
crate_attrs,
86
+ maybe_crate_attrs,
75
87
crates,
76
88
everything_else,
77
89
already_has_extern_crate,
78
90
test_id,
79
91
failed_ast : false ,
80
92
// If the AST returned an error, we don't want this doctest to be merged with the
81
- // others.
82
- can_be_merged : !failed_ast,
93
+ // others. Same if it contains `#[feature]` or `#[no_std]`.
94
+ can_be_merged : !failed_ast && !has_no_std && !has_features ,
83
95
}
84
96
}
85
97
@@ -118,6 +130,7 @@ impl DocTest {
118
130
// Now push any outer attributes from the example, assuming they
119
131
// are intended to be crate attributes.
120
132
prog. push_str ( & self . crate_attrs ) ;
133
+ prog. push_str ( & self . maybe_crate_attrs ) ;
121
134
prog. push_str ( & self . crates ) ;
122
135
123
136
// Don't inject `extern crate std` because it's already injected by the
@@ -405,11 +418,22 @@ fn check_for_main_and_extern_crate(
405
418
Ok ( ( has_main_fn, already_has_extern_crate, parsing_result != ParsingResult :: Ok ) )
406
419
}
407
420
408
- fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> bool {
421
+ enum AttrKind {
422
+ CrateAttr ,
423
+ Attr ,
424
+ Feature ,
425
+ NoStd ,
426
+ }
427
+
428
+ /// Returns `Some` if the attribute is complete and `Some(true)` if it is an attribute that can be
429
+ /// placed at the crate root.
430
+ fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> Option < AttrKind > {
409
431
if source. is_empty ( ) {
410
432
// Empty content so nothing to check in here...
411
- return true ;
433
+ return None ;
412
434
}
435
+ let not_crate_attrs = [ sym:: forbid, sym:: allow, sym:: warn, sym:: deny] ;
436
+
413
437
rustc_driver:: catch_fatal_errors ( || {
414
438
rustc_span:: create_session_if_not_set_then ( edition, |_| {
415
439
use rustc_errors:: emitter:: HumanEmitter ;
@@ -435,33 +459,77 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
435
459
errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
436
460
// If there is an unclosed delimiter, an error will be returned by the
437
461
// tokentrees.
438
- return false ;
462
+ return None ;
439
463
}
440
464
} ;
441
465
// If a parsing error happened, it's very likely that the attribute is incomplete.
442
- if let Err ( e) = parser. parse_attribute ( InnerAttrPolicy :: Permitted ) {
443
- e. cancel ( ) ;
444
- return false ;
445
- }
446
- true
466
+ let ret = match parser. parse_attribute ( InnerAttrPolicy :: Permitted ) {
467
+ Ok ( attr) => {
468
+ let attr_name = attr. name_or_empty ( ) ;
469
+
470
+ if attr_name == sym:: feature {
471
+ Some ( AttrKind :: Feature )
472
+ } else if attr_name == sym:: no_std {
473
+ Some ( AttrKind :: NoStd )
474
+ } else if not_crate_attrs. contains ( & attr_name) {
475
+ Some ( AttrKind :: Attr )
476
+ } else {
477
+ Some ( AttrKind :: CrateAttr )
478
+ }
479
+ }
480
+ Err ( e) => {
481
+ e. cancel ( ) ;
482
+ None
483
+ }
484
+ } ;
485
+ ret
447
486
} )
448
487
} )
449
- . unwrap_or ( false )
488
+ . unwrap_or ( None )
450
489
}
451
490
452
- /// Returns `(crate_attrs, content, crates)`.
453
- fn partition_source ( s : & str , edition : Edition ) -> ( String , String , String ) {
491
+ fn handle_attr ( mod_attr_pending : & mut String , source_info : & mut SourceInfo , edition : Edition ) {
492
+ if let Some ( attr_kind) = check_if_attr_is_complete ( mod_attr_pending, edition) {
493
+ let push_to = match attr_kind {
494
+ AttrKind :: CrateAttr => & mut source_info. crate_attrs ,
495
+ AttrKind :: Attr => & mut source_info. maybe_crate_attrs ,
496
+ AttrKind :: Feature => {
497
+ source_info. has_features = true ;
498
+ & mut source_info. crate_attrs
499
+ }
500
+ AttrKind :: NoStd => {
501
+ source_info. has_no_std = true ;
502
+ & mut source_info. crate_attrs
503
+ }
504
+ } ;
505
+ push_to. push_str ( mod_attr_pending) ;
506
+ push_to. push ( '\n' ) ;
507
+ // If it's complete, then we can clear the pending content.
508
+ mod_attr_pending. clear ( ) ;
509
+ } else if mod_attr_pending. ends_with ( '\\' ) {
510
+ mod_attr_pending. push ( 'n' ) ;
511
+ }
512
+ }
513
+
514
+ #[ derive( Default ) ]
515
+ struct SourceInfo {
516
+ crate_attrs : String ,
517
+ maybe_crate_attrs : String ,
518
+ crates : String ,
519
+ everything_else : String ,
520
+ has_features : bool ,
521
+ has_no_std : bool ,
522
+ }
523
+
524
+ fn partition_source ( s : & str , edition : Edition ) -> SourceInfo {
454
525
#[ derive( Copy , Clone , PartialEq ) ]
455
526
enum PartitionState {
456
527
Attrs ,
457
528
Crates ,
458
529
Other ,
459
530
}
531
+ let mut source_info = SourceInfo :: default ( ) ;
460
532
let mut state = PartitionState :: Attrs ;
461
- let mut crate_attrs = String :: new ( ) ;
462
- let mut crates = String :: new ( ) ;
463
- let mut after = String :: new ( ) ;
464
-
465
533
let mut mod_attr_pending = String :: new ( ) ;
466
534
467
535
for line in s. lines ( ) {
@@ -472,12 +540,9 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
472
540
match state {
473
541
PartitionState :: Attrs => {
474
542
state = if trimline. starts_with ( "#![" ) {
475
- if !check_if_attr_is_complete ( line, edition) {
476
- mod_attr_pending = line. to_owned ( ) ;
477
- } else {
478
- mod_attr_pending. clear ( ) ;
479
- }
480
- PartitionState :: Attrs
543
+ mod_attr_pending = line. to_owned ( ) ;
544
+ handle_attr ( & mut mod_attr_pending, & mut source_info, edition) ;
545
+ continue ;
481
546
} else if trimline. chars ( ) . all ( |c| c. is_whitespace ( ) )
482
547
|| ( trimline. starts_with ( "//" ) && !trimline. starts_with ( "///" ) )
483
548
{
@@ -492,15 +557,10 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
492
557
// If not, then we append the new line into the pending attribute to check
493
558
// if this time it's complete...
494
559
mod_attr_pending. push_str ( line) ;
495
- if !trimline. is_empty ( )
496
- && check_if_attr_is_complete ( & mod_attr_pending, edition)
497
- {
498
- // If it's complete, then we can clear the pending content.
499
- mod_attr_pending. clear ( ) ;
560
+ if !trimline. is_empty ( ) {
561
+ handle_attr ( & mut mod_attr_pending, & mut source_info, edition) ;
500
562
}
501
- // In any case, this is considered as `PartitionState::Attrs` so it's
502
- // prepended before rustdoc's inserts.
503
- PartitionState :: Attrs
563
+ continue ;
504
564
} else {
505
565
PartitionState :: Other
506
566
}
@@ -522,23 +582,25 @@ fn partition_source(s: &str, edition: Edition) -> (String, String, String) {
522
582
523
583
match state {
524
584
PartitionState :: Attrs => {
525
- crate_attrs. push_str ( line) ;
526
- crate_attrs. push ( '\n' ) ;
585
+ source_info . crate_attrs . push_str ( line) ;
586
+ source_info . crate_attrs . push ( '\n' ) ;
527
587
}
528
588
PartitionState :: Crates => {
529
- crates. push_str ( line) ;
530
- crates. push ( '\n' ) ;
589
+ source_info . crates . push_str ( line) ;
590
+ source_info . crates . push ( '\n' ) ;
531
591
}
532
592
PartitionState :: Other => {
533
- after . push_str ( line) ;
534
- after . push ( '\n' ) ;
593
+ source_info . everything_else . push_str ( line) ;
594
+ source_info . everything_else . push ( '\n' ) ;
535
595
}
536
596
}
537
597
}
538
598
539
- debug ! ( "before:\n {before}" ) ;
540
- debug ! ( "crates:\n {crates}" ) ;
541
- debug ! ( "after:\n {after}" ) ;
599
+ source_info. everything_else = source_info. everything_else . trim ( ) . to_string ( ) ;
600
+
601
+ debug ! ( "crate_attrs:\n {}{}" , source_info. crate_attrs, source_info. maybe_crate_attrs) ;
602
+ debug ! ( "crates:\n {}" , source_info. crates) ;
603
+ debug ! ( "after:\n {}" , source_info. everything_else) ;
542
604
543
- ( before , after . trim ( ) . to_owned ( ) , crates )
605
+ source_info
544
606
}
0 commit comments