@@ -14,6 +14,7 @@ use rustc_middle::ty;
14
14
use rustc_parse:: maybe_new_parser_from_source_str;
15
15
use rustc_session:: parse:: ParseSess ;
16
16
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
17
+ use rustc_span:: edition:: Edition ;
17
18
use rustc_span:: source_map:: { BytePos , FilePathMapping , MultiSpan , SourceMap , Span } ;
18
19
use rustc_span:: { sym, FileName , Pos } ;
19
20
use std:: io;
@@ -377,7 +378,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
377
378
check_doc ( cx, valid_idents, events, & spans)
378
379
}
379
380
380
- const RUST_CODE : & [ & str ] = & [ "rust" , "no_run" , "should_panic" , "compile_fail" , "edition2018" ] ;
381
+ const RUST_CODE : & [ & str ] = & [ "rust" , "no_run" , "should_panic" , "compile_fail" ] ;
381
382
382
383
fn check_doc < ' a , Events : Iterator < Item = ( pulldown_cmark:: Event < ' a > , Range < usize > ) > > (
383
384
cx : & LateContext < ' _ > ,
@@ -400,13 +401,24 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
400
401
let mut in_link = None ;
401
402
let mut in_heading = false ;
402
403
let mut is_rust = false ;
404
+ let mut edition = None ;
403
405
for ( event, range) in events {
404
406
match event {
405
407
Start ( CodeBlock ( ref kind) ) => {
406
408
in_code = true ;
407
409
if let CodeBlockKind :: Fenced ( lang) = kind {
408
- is_rust =
409
- lang. is_empty ( ) || !lang. contains ( "ignore" ) && lang. split ( ',' ) . any ( |i| RUST_CODE . contains ( & i) ) ;
410
+ for item in lang. split ( ',' ) {
411
+ if item == "ignore" {
412
+ is_rust = false ;
413
+ break ;
414
+ }
415
+ if let Some ( stripped) = item. strip_prefix ( "edition" ) {
416
+ is_rust = true ;
417
+ edition = stripped. parse :: < Edition > ( ) . ok ( ) ;
418
+ } else if item. is_empty ( ) || RUST_CODE . contains ( & item) {
419
+ is_rust = true ;
420
+ }
421
+ }
410
422
}
411
423
} ,
412
424
End ( CodeBlock ( _) ) => {
@@ -436,7 +448,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
436
448
let ( begin, span) = spans[ index] ;
437
449
if in_code {
438
450
if is_rust {
439
- check_code ( cx, & text, span) ;
451
+ let edition = edition. unwrap_or_else ( || cx. tcx . sess . edition ( ) ) ;
452
+ check_code ( cx, & text, edition, span) ;
440
453
}
441
454
} else {
442
455
// Adjust for the beginning of the current `Event`
@@ -450,67 +463,73 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
450
463
headers
451
464
}
452
465
453
- fn check_code ( cx : & LateContext < ' _ > , text : & str , span : Span ) {
454
- fn has_needless_main ( code : & str ) -> bool {
455
- let filename = FileName :: anon_source_code ( code) ;
456
-
457
- let sm = Lrc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ;
458
- let emitter = EmitterWriter :: new ( box io:: sink ( ) , None , false , false , false , None , false ) ;
459
- let handler = Handler :: with_emitter ( false , None , box emitter) ;
460
- let sess = ParseSess :: with_span_handler ( handler, sm) ;
461
-
462
- let mut parser = match maybe_new_parser_from_source_str ( & sess, filename, code. into ( ) ) {
463
- Ok ( p) => p,
464
- Err ( errs) => {
465
- for mut err in errs {
466
- err. cancel ( ) ;
467
- }
468
- return false ;
469
- } ,
470
- } ;
471
-
472
- let mut relevant_main_found = false ;
473
- loop {
474
- match parser. parse_item ( ) {
475
- Ok ( Some ( item) ) => match & item. kind {
476
- // Tests with one of these items are ignored
477
- ItemKind :: Static ( ..)
478
- | ItemKind :: Const ( ..)
479
- | ItemKind :: ExternCrate ( ..)
480
- | ItemKind :: ForeignMod ( ..) => return false ,
481
- // We found a main function ...
482
- ItemKind :: Fn ( _, sig, _, Some ( block) ) if item. ident . name == sym:: main => {
483
- let is_async = matches ! ( sig. header. asyncness, Async :: Yes { .. } ) ;
484
- let returns_nothing = match & sig. decl . output {
485
- FnRetTy :: Default ( ..) => true ,
486
- FnRetTy :: Ty ( ty) if ty. kind . is_unit ( ) => true ,
487
- _ => false ,
488
- } ;
489
-
490
- if returns_nothing && !is_async && !block. stmts . is_empty ( ) {
491
- // This main function should be linted, but only if there are no other functions
492
- relevant_main_found = true ;
493
- } else {
494
- // This main function should not be linted, we're done
495
- return false ;
466
+ fn check_code ( cx : & LateContext < ' _ > , text : & str , edition : Edition , span : Span ) {
467
+ fn has_needless_main ( code : & str , edition : Edition ) -> bool {
468
+ rustc_driver:: catch_fatal_errors ( || {
469
+ rustc_span:: with_session_globals ( edition, || {
470
+ let filename = FileName :: anon_source_code ( code) ;
471
+
472
+ let sm = Lrc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ;
473
+ let emitter = EmitterWriter :: new ( box io:: sink ( ) , None , false , false , false , None , false ) ;
474
+ let handler = Handler :: with_emitter ( false , None , box emitter) ;
475
+ let sess = ParseSess :: with_span_handler ( handler, sm) ;
476
+
477
+ let mut parser = match maybe_new_parser_from_source_str ( & sess, filename, code. into ( ) ) {
478
+ Ok ( p) => p,
479
+ Err ( errs) => {
480
+ for mut err in errs {
481
+ err. cancel ( ) ;
496
482
}
483
+ return false ;
497
484
} ,
498
- // Another function was found; this case is ignored too
499
- ItemKind :: Fn ( ..) => return false ,
500
- _ => { } ,
501
- } ,
502
- Ok ( None ) => break ,
503
- Err ( mut e) => {
504
- e. cancel ( ) ;
505
- return false ;
506
- } ,
507
- }
508
- }
485
+ } ;
486
+
487
+ let mut relevant_main_found = false ;
488
+ loop {
489
+ match parser. parse_item ( ) {
490
+ Ok ( Some ( item) ) => match & item. kind {
491
+ // Tests with one of these items are ignored
492
+ ItemKind :: Static ( ..)
493
+ | ItemKind :: Const ( ..)
494
+ | ItemKind :: ExternCrate ( ..)
495
+ | ItemKind :: ForeignMod ( ..) => return false ,
496
+ // We found a main function ...
497
+ ItemKind :: Fn ( _, sig, _, Some ( block) ) if item. ident . name == sym:: main => {
498
+ let is_async = matches ! ( sig. header. asyncness, Async :: Yes { .. } ) ;
499
+ let returns_nothing = match & sig. decl . output {
500
+ FnRetTy :: Default ( ..) => true ,
501
+ FnRetTy :: Ty ( ty) if ty. kind . is_unit ( ) => true ,
502
+ _ => false ,
503
+ } ;
504
+
505
+ if returns_nothing && !is_async && !block. stmts . is_empty ( ) {
506
+ // This main function should be linted, but only if there are no other functions
507
+ relevant_main_found = true ;
508
+ } else {
509
+ // This main function should not be linted, we're done
510
+ return false ;
511
+ }
512
+ } ,
513
+ // Another function was found; this case is ignored too
514
+ ItemKind :: Fn ( ..) => return false ,
515
+ _ => { } ,
516
+ } ,
517
+ Ok ( None ) => break ,
518
+ Err ( mut e) => {
519
+ e. cancel ( ) ;
520
+ return false ;
521
+ } ,
522
+ }
523
+ }
509
524
510
- relevant_main_found
525
+ relevant_main_found
526
+ } )
527
+ } )
528
+ . ok ( )
529
+ . unwrap_or_default ( )
511
530
}
512
531
513
- if has_needless_main ( text) {
532
+ if has_needless_main ( text, edition ) {
514
533
span_lint ( cx, NEEDLESS_DOCTEST_MAIN , span, "needless `fn main` in doctest" ) ;
515
534
}
516
535
}
0 commit comments