@@ -378,7 +378,7 @@ pub fn make_test(s: &str,
378378 dont_insert_main : bool ,
379379 opts : & TestOptions )
380380 -> ( String , usize ) {
381- let ( crate_attrs, everything_else) = partition_source ( s) ;
381+ let ( crate_attrs, everything_else, crates ) = partition_source ( s) ;
382382 let everything_else = everything_else. trim ( ) ;
383383 let mut line_offset = 0 ;
384384 let mut prog = String :: new ( ) ;
@@ -402,30 +402,91 @@ pub fn make_test(s: &str,
402402 // are intended to be crate attributes.
403403 prog. push_str ( & crate_attrs) ;
404404
405+ // Uses libsyntax to parse the doctest and find if there's a main fn and the extern
406+ // crate already is included.
407+ let ( already_has_main, already_has_extern_crate) = crate :: syntax:: with_globals ( || {
408+ use crate :: syntax:: { ast, parse:: { self , ParseSess } , source_map:: FilePathMapping } ;
409+ use crate :: syntax_pos:: FileName ;
410+ use errors:: emitter:: EmitterWriter ;
411+ use errors:: Handler ;
412+
413+ let filename = FileName :: Anon ;
414+ let source = crates + & everything_else;
415+
416+ // any errors in parsing should also appear when the doctest is compiled for real, so just
417+ // send all the errors that libsyntax emits directly into a Sink instead of stderr
418+ let cm = Lrc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ;
419+ let emitter = EmitterWriter :: new ( box io:: sink ( ) , None , false , false ) ;
420+ let handler = Handler :: with_emitter ( false , false , box emitter) ;
421+ let sess = ParseSess :: with_span_handler ( handler, cm) ;
422+
423+ debug ! ( "about to parse: \n {}" , source) ;
424+
425+ let mut found_main = false ;
426+ let mut found_extern_crate = cratename. is_none ( ) ;
427+
428+ let mut parser = match parse:: maybe_new_parser_from_source_str ( & sess, filename, source) {
429+ Ok ( p) => p,
430+ Err ( errs) => {
431+ for mut err in errs {
432+ err. cancel ( ) ;
433+ }
434+
435+ return ( found_main, found_extern_crate) ;
436+ }
437+ } ;
438+
439+ loop {
440+ match parser. parse_item ( ) {
441+ Ok ( Some ( item) ) => {
442+ if !found_main {
443+ if let ast:: ItemKind :: Fn ( ..) = item. node {
444+ if item. ident . as_str ( ) == "main" {
445+ found_main = true ;
446+ }
447+ }
448+ }
449+
450+ if !found_extern_crate {
451+ if let ast:: ItemKind :: ExternCrate ( original) = item. node {
452+ // This code will never be reached if `cratename` is none because
453+ // `found_extern_crate` is initialized to `true` if it is none.
454+ let cratename = cratename. unwrap ( ) ;
455+
456+ match original {
457+ Some ( name) => found_extern_crate = name. as_str ( ) == cratename,
458+ None => found_extern_crate = item. ident . as_str ( ) == cratename,
459+ }
460+ }
461+ }
462+
463+ if found_main && found_extern_crate {
464+ break ;
465+ }
466+ }
467+ Ok ( None ) => break ,
468+ Err ( mut e) => {
469+ e. cancel ( ) ;
470+ break ;
471+ }
472+ }
473+ }
474+
475+ ( found_main, found_extern_crate)
476+ } ) ;
477+
405478 // Don't inject `extern crate std` because it's already injected by the
406479 // compiler.
407- if !s . contains ( "extern crate" ) && !opts. no_crate_inject && cratename != Some ( "std" ) {
480+ if !already_has_extern_crate && !opts. no_crate_inject && cratename != Some ( "std" ) {
408481 if let Some ( cratename) = cratename {
482+ // Make sure its actually used if not included.
409483 if s. contains ( cratename) {
410484 prog. push_str ( & format ! ( "extern crate {};\n " , cratename) ) ;
411485 line_offset += 1 ;
412486 }
413487 }
414488 }
415489
416- // FIXME (#21299): prefer libsyntax or some other actual parser over this
417- // best-effort ad hoc approach
418- let already_has_main = s. lines ( )
419- . map ( |line| {
420- let comment = line. find ( "//" ) ;
421- if let Some ( comment_begins) = comment {
422- & line[ 0 ..comment_begins]
423- } else {
424- line
425- }
426- } )
427- . any ( |code| code. contains ( "fn main" ) ) ;
428-
429490 if dont_insert_main || already_has_main {
430491 prog. push_str ( everything_else) ;
431492 } else {
@@ -441,9 +502,10 @@ pub fn make_test(s: &str,
441502}
442503
443504// FIXME(aburka): use a real parser to deal with multiline attributes
444- fn partition_source ( s : & str ) -> ( String , String ) {
505+ fn partition_source ( s : & str ) -> ( String , String , String ) {
445506 let mut after_header = false ;
446507 let mut before = String :: new ( ) ;
508+ let mut crates = String :: new ( ) ;
447509 let mut after = String :: new ( ) ;
448510
449511 for line in s. lines ( ) {
@@ -457,12 +519,17 @@ fn partition_source(s: &str) -> (String, String) {
457519 after. push_str ( line) ;
458520 after. push_str ( "\n " ) ;
459521 } else {
522+ if trimline. starts_with ( "#[macro_use] extern crate" )
523+ || trimline. starts_with ( "extern crate" ) {
524+ crates. push_str ( line) ;
525+ crates. push_str ( "\n " ) ;
526+ }
460527 before. push_str ( line) ;
461528 before. push_str ( "\n " ) ;
462529 }
463530 }
464531
465- ( before, after)
532+ ( before, after, crates )
466533}
467534
468535pub trait Tester {
@@ -1014,4 +1081,38 @@ assert_eq!(2+2, 4);
10141081 let output = make_test ( input, None , false , & opts) ;
10151082 assert_eq ! ( output, ( expected, 1 ) ) ;
10161083 }
1084+
1085+ #[ test]
1086+ fn make_test_issues_21299_33731 ( ) {
1087+ let opts = TestOptions :: default ( ) ;
1088+
1089+ let input =
1090+ "// fn main
1091+ assert_eq!(2+2, 4);" ;
1092+
1093+ let expected =
1094+ "#![allow(unused)]
1095+ fn main() {
1096+ // fn main
1097+ assert_eq!(2+2, 4);
1098+ }" . to_string ( ) ;
1099+
1100+ let output = make_test ( input, None , false , & opts) ;
1101+ assert_eq ! ( output, ( expected, 2 ) ) ;
1102+
1103+ let input =
1104+ "extern crate hella_qwop;
1105+ assert_eq!(asdf::foo, 4);" ;
1106+
1107+ let expected =
1108+ "#![allow(unused)]
1109+ extern crate hella_qwop;
1110+ extern crate asdf;
1111+ fn main() {
1112+ assert_eq!(asdf::foo, 4);
1113+ }" . to_string ( ) ;
1114+
1115+ let output = make_test ( input, Some ( "asdf" ) , false , & opts) ;
1116+ assert_eq ! ( output, ( expected, 3 ) ) ;
1117+ }
10171118}
0 commit comments