@@ -8,7 +8,7 @@ use rustc_ast::token::{Delimiter, TokenKind};
88use rustc_ast:: tokenstream:: TokenTree ;
99use rustc_ast:: { self as ast, AttrStyle , HasAttrs , StmtKind } ;
1010use rustc_errors:: emitter:: SilentEmitter ;
11- use rustc_errors:: { DiagCtxt , DiagCtxtHandle } ;
11+ use rustc_errors:: { Diag , DiagCtxt , DiagCtxtHandle } ;
1212use rustc_parse:: lexer:: StripTokens ;
1313use rustc_parse:: new_parser_from_source_str;
1414use rustc_session:: parse:: ParseSess ;
@@ -136,16 +136,21 @@ impl<'a> BuildDocTestBuilder<'a> {
136136 maybe_crate_attrs,
137137 } ) ) = result
138138 else {
139- // If the AST returned an error, we don't want this doctest to be merged with the
140- // others.
141- return DocTestBuilder :: invalid (
142- Vec :: new ( ) ,
143- String :: new ( ) ,
144- String :: new ( ) ,
145- String :: new ( ) ,
146- source. to_string ( ) ,
139+ // If we failed to parse the doctest, create a dummy doctest.
140+ return DocTestBuilder {
141+ supports_color : false ,
142+ has_main_fn : false ,
143+ global_crate_attrs : Vec :: new ( ) ,
144+ crate_attrs : String :: new ( ) ,
145+ maybe_crate_attrs : String :: new ( ) ,
146+ crates : String :: new ( ) ,
147+ everything_else : source. to_string ( ) ,
148+ already_has_extern_crate : false ,
147149 test_id,
148- ) ;
150+ failed_to_parse : true ,
151+ // We certainly don't want to merge such a doctest with others.
152+ can_be_merged : false ,
153+ } ;
149154 } ;
150155
151156 debug ! ( "crate_attrs:\n {crate_attrs}{maybe_crate_attrs}" ) ;
@@ -182,7 +187,7 @@ impl<'a> BuildDocTestBuilder<'a> {
182187 everything_else,
183188 already_has_extern_crate,
184189 test_id,
185- invalid_ast : false ,
190+ failed_to_parse : false ,
186191 can_be_merged,
187192 }
188193 }
@@ -202,7 +207,7 @@ pub(crate) struct DocTestBuilder {
202207 pub ( crate ) crates : String ,
203208 pub ( crate ) everything_else : String ,
204209 pub ( crate ) test_id : Option < String > ,
205- pub ( crate ) invalid_ast : bool ,
210+ pub ( crate ) failed_to_parse : bool ,
206211 pub ( crate ) can_be_merged : bool ,
207212}
208213
@@ -281,29 +286,6 @@ impl std::string::ToString for DocTestWrapResult {
281286}
282287
283288impl DocTestBuilder {
284- fn invalid (
285- global_crate_attrs : Vec < String > ,
286- crate_attrs : String ,
287- maybe_crate_attrs : String ,
288- crates : String ,
289- everything_else : String ,
290- test_id : Option < String > ,
291- ) -> Self {
292- Self {
293- supports_color : false ,
294- has_main_fn : false ,
295- global_crate_attrs,
296- crate_attrs,
297- maybe_crate_attrs,
298- crates,
299- everything_else,
300- already_has_extern_crate : false ,
301- test_id,
302- invalid_ast : true ,
303- can_be_merged : false ,
304- }
305- }
306-
307289 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
308290 /// lines before the test code begins.
309291 pub ( crate ) fn generate_unique_doctest (
@@ -313,10 +295,10 @@ impl DocTestBuilder {
313295 opts : & GlobalTestOptions ,
314296 crate_name : Option < & str > ,
315297 ) -> ( DocTestWrapResult , usize ) {
316- if self . invalid_ast {
317- // If the AST failed to compile, no need to go generate a complete doctest, the error
318- // will be better this way.
319- debug ! ( "invalid AST :\n {test_code}" ) ;
298+ if self . failed_to_parse {
299+ // If we failed to parse the doctest, there's no need to go generate a complete doctest,
300+ // the diagnostics will be better this way.
301+ debug ! ( "failed to parse doctest :\n {test_code}" ) ;
320302 return ( DocTestWrapResult :: SyntaxError ( test_code. to_string ( ) ) , 0 ) ;
321303 }
322304 let mut line_offset = 0 ;
@@ -438,16 +420,6 @@ impl DocTestBuilder {
438420 }
439421}
440422
441- fn reset_error_count ( psess : & ParseSess ) {
442- // Reset errors so that they won't be reported as compiler bugs when dropping the
443- // dcx. Any errors in the tests will be reported when the test file is compiled,
444- // Note that we still need to cancel the errors above otherwise `Diag` will panic on
445- // drop.
446- psess. dcx ( ) . reset_err_count ( ) ;
447- }
448-
449- const DOCTEST_CODE_WRAPPER : & str = "fn f(){" ;
450-
451423fn parse_source (
452424 source : & str ,
453425 crate_name : & Option < & str > ,
@@ -457,15 +429,17 @@ fn parse_source(
457429 let mut info =
458430 ParseSourceInfo { already_has_extern_crate : crate_name. is_none ( ) , ..Default :: default ( ) } ;
459431
432+ const DOCTEST_CODE_WRAPPER : & str = "fn f(){" ;
460433 let wrapped_source = format ! ( "{DOCTEST_CODE_WRAPPER}{source}\n }}" ) ;
461434
462435 // Any parse errors should also appear when the doctest is compiled for real,
463436 // so just suppress them here.
464437 let emitter = SilentEmitter { translator : rustc_driver:: default_translator ( ) } ;
465438
466- // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
467439 let dcx = DiagCtxt :: new ( Box :: new ( emitter) ) . disable_warnings ( ) ;
468440 let psess = ParseSess :: with_dcx ( dcx, Arc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ) ;
441+ // Reset errors at the end so that we don't panic on delayed bugs.
442+ let _defer = rustc_data_structures:: defer ( || psess. dcx ( ) . reset_err_count ( ) ) ;
469443
470444 // Don't strip any tokens; it wouldn't matter anyway because the source is wrapped in a function.
471445 let mut parser = match new_parser_from_source_str (
@@ -477,7 +451,6 @@ fn parse_source(
477451 Ok ( p) => p,
478452 Err ( errs) => {
479453 errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
480- reset_error_count ( & psess) ;
481454 return Err ( ( ) ) ;
482455 }
483456 } ;
@@ -526,124 +499,120 @@ fn parse_source(
526499 is_extern_crate
527500 }
528501
529- let mut prev_span_hi = 0 ;
530502 let not_crate_attrs = & [ sym:: forbid, sym:: allow, sym:: warn, sym:: deny, sym:: expect] ;
531- let parsed = parser. parse_item ( rustc_parse:: parser:: ForceCollect :: No ) ;
532-
533- let result = match parsed {
534- Ok ( Some ( ref item) )
535- if let ast:: ItemKind :: Fn ( ref fn_item) = item. kind
536- && let Some ( ref body) = fn_item. body
537- // The parser might've recovered from some syntax errors and thus returned `Ok(_)`.
538- // However, since we've suppressed diagnostic emission above, we must ensure that
539- // we mark the doctest as syntactically invalid. Otherwise, we can end up generating
540- // a runnable doctest that's free of any errors even though the source was malformed.
541- && psess. dcx ( ) . has_errors ( ) . is_none ( ) =>
542- {
543- for attr in & item. attrs {
544- if attr. style == AttrStyle :: Outer || attr. has_any_name ( not_crate_attrs) {
545- // There is one exception to these attributes:
546- // `#![allow(internal_features)]`. If this attribute is used, we need to
547- // consider it only as a crate-level attribute.
548- if attr. has_name ( sym:: allow)
549- && let Some ( list) = attr. meta_item_list ( )
550- && list. iter ( ) . any ( |sub_attr| sub_attr. has_name ( sym:: internal_features) )
551- {
552- push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
553- } else {
554- push_to_s (
555- & mut info. maybe_crate_attrs ,
556- source,
557- attr. span ,
558- & mut prev_span_hi,
559- ) ;
560- }
561- } else {
562- push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
563- }
503+ let mut prev_span_hi = 0 ;
504+
505+ let item = parser. parse_item ( rustc_parse:: parser:: ForceCollect :: No ) . map_err ( Diag :: cancel) ?;
506+
507+ // The parser might've recovered from some syntax errors and thus returned `Ok(_)`.
508+ // However, since we've suppressed diagnostic emission above, we must ensure that
509+ // we mark the doctest as syntactically invalid. Otherwise, we can end up generating
510+ // a runnable doctest that's free of any errors even though the source was malformed.
511+ if psess. dcx ( ) . has_errors ( ) . is_some ( ) {
512+ return Err ( ( ) ) ;
513+ }
514+
515+ let Some ( box ast:: Item {
516+ attrs,
517+ kind : ast:: ItemKind :: Fn ( box ast:: Fn { body : Some ( body) , .. } ) ,
518+ ..
519+ } ) = item
520+ else {
521+ // We know that this *has* to be a function with a body
522+ // because we've wrapped the whole source in one above.
523+ unreachable ! ( )
524+ } ;
525+
526+ for attr in attrs {
527+ if attr. style == AttrStyle :: Outer || attr. has_any_name ( not_crate_attrs) {
528+ // There is one exception to these attributes:
529+ // `#![allow(internal_features)]`. If this attribute is used, we need to
530+ // consider it only as a crate-level attribute.
531+ if attr. has_name ( sym:: allow)
532+ && let Some ( list) = attr. meta_item_list ( )
533+ && list. iter ( ) . any ( |sub_attr| sub_attr. has_name ( sym:: internal_features) )
534+ {
535+ push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
536+ } else {
537+ push_to_s ( & mut info. maybe_crate_attrs , source, attr. span , & mut prev_span_hi) ;
564538 }
565- let mut has_non_items = false ;
566- for stmt in & body . stmts {
567- let mut is_extern_crate = false ;
568- match stmt . kind {
569- StmtKind :: Item ( ref item ) => {
570- is_extern_crate = check_item ( item , & mut info , crate_name ) ;
571- }
572- // We assume that the macro calls will expand to item(s) even though they could
573- // expand to statements and expressions.
574- StmtKind :: MacCall ( ref mac_call ) => {
575- if ! info. has_main_fn {
576- // For backward compatibility, we look for the token sequence `fn main(…)`
577- // in the macro input (!) to crudely detect main functions "masked by a
578- // wrapper macro". For the record, this is a horrible heuristic!
579- // See <https://github.com/rust-lang/rust/issues/56898>.
580- let mut iter = mac_call . mac . args . tokens . iter ( ) ;
581- while let Some ( token) = iter . next ( ) {
582- if let TokenTree :: Token ( token , _ ) = token
583- && let TokenKind :: Ident ( kw :: Fn , _ ) = token . kind
584- && let Some ( TokenTree :: Token ( ident , _ ) ) = iter . peek ( )
585- && let TokenKind :: Ident ( sym :: main , _ ) = ident . kind
586- && let Some ( TokenTree :: Delimited ( .. , Delimiter :: Parenthesis , _ ) ) = {
587- iter . next ( ) ;
588- iter . peek ( )
589- }
590- {
591- info . has_main_fn = true ;
592- break ;
593- }
539+ } else {
540+ push_to_s ( & mut info . crate_attrs , source , attr . span , & mut prev_span_hi ) ;
541+ }
542+ }
543+
544+ let mut has_non_items = false ;
545+ for stmt in & body . stmts {
546+ let mut is_extern_crate = false ;
547+ match stmt . kind {
548+ StmtKind :: Item ( ref item ) => {
549+ is_extern_crate = check_item ( item , & mut info, crate_name ) ;
550+ }
551+ // We assume that the macro calls will expand to item(s) even though they could
552+ // expand to statements and expressions.
553+ StmtKind :: MacCall ( ref mac_call ) => {
554+ if !info . has_main_fn {
555+ // For backward compatibility, we look for the token sequence `fn main(…)`
556+ // in the macro input (!) to crudely detect main functions "masked by a
557+ // wrapper macro". For the record, this is a horrible heuristic!
558+ // See <https://github.com/rust-lang/rust/issues/56898>.
559+ let mut iter = mac_call . mac . args . tokens . iter ( ) ;
560+ while let Some ( token ) = iter . next ( ) {
561+ if let TokenTree :: Token ( token , _ ) = token
562+ && let TokenKind :: Ident ( kw :: Fn , _ ) = token . kind
563+ && let Some ( TokenTree :: Token ( ident , _ ) ) = iter . peek ( )
564+ && let TokenKind :: Ident ( sym :: main , _ ) = ident . kind
565+ && let Some ( TokenTree :: Delimited ( .. , Delimiter :: Parenthesis , _ ) ) = {
566+ iter . next ( ) ;
567+ iter . peek ( )
594568 }
569+ {
570+ info. has_main_fn = true ;
571+ break ;
595572 }
596573 }
597- StmtKind :: Let ( _) | StmtKind :: Expr ( _) | StmtKind :: Semi ( _) | StmtKind :: Empty => {
598- has_non_items = true
599- }
600- }
601-
602- // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
603- // tweak the span to include the attributes as well.
604- let mut span = stmt. span ;
605- if let Some ( attr) =
606- stmt. kind . attrs ( ) . iter ( ) . find ( |attr| attr. style == AttrStyle :: Outer )
607- {
608- span = span. with_lo ( attr. span . lo ( ) ) ;
609- }
610- if info. everything_else . is_empty ( )
611- && ( !info. maybe_crate_attrs . is_empty ( ) || !info. crate_attrs . is_empty ( ) )
612- {
613- // To keep the doctest code "as close as possible" to the original, we insert
614- // all the code located between this new span and the previous span which
615- // might contain code comments and backlines.
616- push_to_s ( & mut info. crates , source, span. shrink_to_lo ( ) , & mut prev_span_hi) ;
617- }
618- if !is_extern_crate {
619- push_to_s ( & mut info. everything_else , source, span, & mut prev_span_hi) ;
620- } else {
621- push_to_s ( & mut info. crates , source, span, & mut prev_span_hi) ;
622574 }
623575 }
624- if has_non_items {
625- if info. has_main_fn
626- && let Some ( dcx) = parent_dcx
627- && !span. is_dummy ( )
628- {
629- dcx. span_warn (
630- span,
631- "the `main` function of this doctest won't be run as it contains \
632- expressions at the top level, meaning that the whole doctest code will be \
633- wrapped in a function",
634- ) ;
635- }
636- info. has_main_fn = false ;
576+ StmtKind :: Let ( _) | StmtKind :: Expr ( _) | StmtKind :: Semi ( _) | StmtKind :: Empty => {
577+ has_non_items = true
637578 }
638- Ok ( info)
639579 }
640- Err ( e) => {
641- e. cancel ( ) ;
642- Err ( ( ) )
580+
581+ // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
582+ // tweak the span to include the attributes as well.
583+ let mut span = stmt. span ;
584+ if let Some ( attr) = stmt. kind . attrs ( ) . iter ( ) . find ( |attr| attr. style == AttrStyle :: Outer ) {
585+ span = span. with_lo ( attr. span . lo ( ) ) ;
643586 }
644- _ => Err ( ( ) ) ,
645- } ;
587+ if info. everything_else . is_empty ( )
588+ && ( !info. maybe_crate_attrs . is_empty ( ) || !info. crate_attrs . is_empty ( ) )
589+ {
590+ // To keep the doctest code "as close as possible" to the original, we insert
591+ // all the code located between this new span and the previous span which
592+ // might contain code comments and backlines.
593+ push_to_s ( & mut info. crates , source, span. shrink_to_lo ( ) , & mut prev_span_hi) ;
594+ }
595+ if !is_extern_crate {
596+ push_to_s ( & mut info. everything_else , source, span, & mut prev_span_hi) ;
597+ } else {
598+ push_to_s ( & mut info. crates , source, span, & mut prev_span_hi) ;
599+ }
600+ }
601+
602+ if has_non_items {
603+ if info. has_main_fn
604+ && let Some ( dcx) = parent_dcx
605+ && !span. is_dummy ( )
606+ {
607+ dcx. span_warn (
608+ span,
609+ "the `main` function of this doctest won't be run as it contains \
610+ expressions at the top level, meaning that the whole doctest code will be \
611+ wrapped in a function",
612+ ) ;
613+ }
614+ info. has_main_fn = false ;
615+ }
646616
647- reset_error_count ( & psess) ;
648- result
617+ Ok ( info)
649618}
0 commit comments