@@ -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 ;
@@ -139,16 +139,21 @@ impl<'a> BuildDocTestBuilder<'a> {
139139 maybe_crate_attrs,
140140 } ) ) = result
141141 else {
142- // If the AST returned an error, we don't want this doctest to be merged with the
143- // others.
144- return DocTestBuilder :: invalid (
145- Vec :: new ( ) ,
146- String :: new ( ) ,
147- String :: new ( ) ,
148- String :: new ( ) ,
149- source. to_string ( ) ,
142+ // If we failed to parse the doctest, create a dummy doctest.
143+ return DocTestBuilder {
144+ supports_color : false ,
145+ has_main_fn : false ,
146+ global_crate_attrs : Vec :: new ( ) ,
147+ crate_attrs : String :: new ( ) ,
148+ maybe_crate_attrs : String :: new ( ) ,
149+ crates : String :: new ( ) ,
150+ everything_else : source. to_string ( ) ,
151+ already_has_extern_crate : false ,
150152 test_id,
151- ) ;
153+ failed_to_parse : true ,
154+ // We certainly don't want to merge such a doctest with others.
155+ can_be_merged : false ,
156+ } ;
152157 } ;
153158
154159 debug ! ( "crate_attrs:\n {crate_attrs}{maybe_crate_attrs}" ) ;
@@ -172,7 +177,7 @@ impl<'a> BuildDocTestBuilder<'a> {
172177 everything_else,
173178 already_has_extern_crate,
174179 test_id,
175- invalid_ast : false ,
180+ failed_to_parse : false ,
176181 can_be_merged,
177182 }
178183 }
@@ -192,7 +197,7 @@ pub(crate) struct DocTestBuilder {
192197 pub ( crate ) crates : String ,
193198 pub ( crate ) everything_else : String ,
194199 pub ( crate ) test_id : Option < String > ,
195- pub ( crate ) invalid_ast : bool ,
200+ pub ( crate ) failed_to_parse : bool ,
196201 pub ( crate ) can_be_merged : bool ,
197202}
198203
@@ -271,29 +276,6 @@ impl std::string::ToString for DocTestWrapResult {
271276}
272277
273278impl DocTestBuilder {
274- fn invalid (
275- global_crate_attrs : Vec < String > ,
276- crate_attrs : String ,
277- maybe_crate_attrs : String ,
278- crates : String ,
279- everything_else : String ,
280- test_id : Option < String > ,
281- ) -> Self {
282- Self {
283- supports_color : false ,
284- has_main_fn : false ,
285- global_crate_attrs,
286- crate_attrs,
287- maybe_crate_attrs,
288- crates,
289- everything_else,
290- already_has_extern_crate : false ,
291- test_id,
292- invalid_ast : true ,
293- can_be_merged : false ,
294- }
295- }
296-
297279 /// Transforms a test into code that can be compiled into a Rust binary, and returns the number of
298280 /// lines before the test code begins.
299281 pub ( crate ) fn generate_unique_doctest (
@@ -303,10 +285,10 @@ impl DocTestBuilder {
303285 opts : & GlobalTestOptions ,
304286 crate_name : Option < & str > ,
305287 ) -> ( DocTestWrapResult , usize ) {
306- if self . invalid_ast {
307- // If the AST failed to compile, no need to go generate a complete doctest, the error
308- // will be better this way.
309- debug ! ( "invalid AST :\n {test_code}" ) ;
288+ if self . failed_to_parse {
289+ // If we failed to parse the doctest, there's no need to go generate a complete doctest,
290+ // the diagnostics will be better this way.
291+ debug ! ( "failed to parse doctest :\n {test_code}" ) ;
310292 return ( DocTestWrapResult :: SyntaxError ( test_code. to_string ( ) ) , 0 ) ;
311293 }
312294 let mut line_offset = 0 ;
@@ -428,16 +410,6 @@ impl DocTestBuilder {
428410 }
429411}
430412
431- fn reset_error_count ( psess : & ParseSess ) {
432- // Reset errors so that they won't be reported as compiler bugs when dropping the
433- // dcx. Any errors in the tests will be reported when the test file is compiled,
434- // Note that we still need to cancel the errors above otherwise `Diag` will panic on
435- // drop.
436- psess. dcx ( ) . reset_err_count ( ) ;
437- }
438-
439- const DOCTEST_CODE_WRAPPER : & str = "fn f(){" ;
440-
441413fn parse_source (
442414 source : & str ,
443415 crate_name : & Option < & str > ,
@@ -447,15 +419,17 @@ fn parse_source(
447419 let mut info =
448420 ParseSourceInfo { already_has_extern_crate : crate_name. is_none ( ) , ..Default :: default ( ) } ;
449421
422+ const DOCTEST_CODE_WRAPPER : & str = "fn f(){" ;
450423 let wrapped_source = format ! ( "{DOCTEST_CODE_WRAPPER}{source}\n }}" ) ;
451424
452425 // Any parse errors should also appear when the doctest is compiled for real,
453426 // so just suppress them here.
454427 let emitter = SilentEmitter { translator : rustc_driver:: default_translator ( ) } ;
455428
456- // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
457429 let dcx = DiagCtxt :: new ( Box :: new ( emitter) ) . disable_warnings ( ) ;
458430 let psess = ParseSess :: with_dcx ( dcx, Arc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ) ;
431+ // Reset errors at the end so that we don't panic on delayed bugs.
432+ let _defer = rustc_data_structures:: defer ( || psess. dcx ( ) . reset_err_count ( ) ) ;
459433
460434 // Don't strip any tokens; it wouldn't matter anyway because the source is wrapped in a function.
461435 let mut parser = match new_parser_from_source_str (
@@ -467,7 +441,6 @@ fn parse_source(
467441 Ok ( p) => p,
468442 Err ( errs) => {
469443 errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
470- reset_error_count ( & psess) ;
471444 return Err ( ( ) ) ;
472445 }
473446 } ;
@@ -516,124 +489,120 @@ fn parse_source(
516489 is_extern_crate
517490 }
518491
519- let mut prev_span_hi = 0 ;
520492 let not_crate_attrs = & [ sym:: forbid, sym:: allow, sym:: warn, sym:: deny, sym:: expect] ;
521- let parsed = parser. parse_item ( rustc_parse:: parser:: ForceCollect :: No ) ;
522-
523- let result = match parsed {
524- Ok ( Some ( ref item) )
525- if let ast:: ItemKind :: Fn ( ref fn_item) = item. kind
526- && let Some ( ref body) = fn_item. body
527- // The parser might've recovered from some syntax errors and thus returned `Ok(_)`.
528- // However, since we've suppressed diagnostic emission above, we must ensure that
529- // we mark the doctest as syntactically invalid. Otherwise, we can end up generating
530- // a runnable doctest that's free of any errors even though the source was malformed.
531- && psess. dcx ( ) . has_errors ( ) . is_none ( ) =>
532- {
533- for attr in & item. attrs {
534- if attr. style == AttrStyle :: Outer || attr. has_any_name ( not_crate_attrs) {
535- // There is one exception to these attributes:
536- // `#![allow(internal_features)]`. If this attribute is used, we need to
537- // consider it only as a crate-level attribute.
538- if attr. has_name ( sym:: allow)
539- && let Some ( list) = attr. meta_item_list ( )
540- && list. iter ( ) . any ( |sub_attr| sub_attr. has_name ( sym:: internal_features) )
541- {
542- push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
543- } else {
544- push_to_s (
545- & mut info. maybe_crate_attrs ,
546- source,
547- attr. span ,
548- & mut prev_span_hi,
549- ) ;
550- }
551- } else {
552- push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
553- }
493+ let mut prev_span_hi = 0 ;
494+
495+ let item = parser. parse_item ( rustc_parse:: parser:: ForceCollect :: No ) . map_err ( Diag :: cancel) ?;
496+
497+ // The parser might've recovered from some syntax errors and thus returned `Ok(_)`.
498+ // However, since we've suppressed diagnostic emission above, we must ensure that
499+ // we mark the doctest as syntactically invalid. Otherwise, we can end up generating
500+ // a runnable doctest that's free of any errors even though the source was malformed.
501+ if psess. dcx ( ) . has_errors ( ) . is_some ( ) {
502+ return Err ( ( ) ) ;
503+ }
504+
505+ let Some ( box ast:: Item {
506+ attrs,
507+ kind : ast:: ItemKind :: Fn ( box ast:: Fn { body : Some ( body) , .. } ) ,
508+ ..
509+ } ) = item
510+ else {
511+ // We know that this *has* to be a function with a body
512+ // because we've wrapped the whole source in one above.
513+ unreachable ! ( )
514+ } ;
515+
516+ for attr in attrs {
517+ if attr. style == AttrStyle :: Outer || attr. has_any_name ( not_crate_attrs) {
518+ // There is one exception to these attributes:
519+ // `#![allow(internal_features)]`. If this attribute is used, we need to
520+ // consider it only as a crate-level attribute.
521+ if attr. has_name ( sym:: allow)
522+ && let Some ( list) = attr. meta_item_list ( )
523+ && list. iter ( ) . any ( |sub_attr| sub_attr. has_name ( sym:: internal_features) )
524+ {
525+ push_to_s ( & mut info. crate_attrs , source, attr. span , & mut prev_span_hi) ;
526+ } else {
527+ push_to_s ( & mut info. maybe_crate_attrs , source, attr. span , & mut prev_span_hi) ;
554528 }
555- let mut has_non_items = false ;
556- for stmt in & body . stmts {
557- let mut is_extern_crate = false ;
558- match stmt . kind {
559- StmtKind :: Item ( ref item ) => {
560- is_extern_crate = check_item ( item , & mut info , crate_name ) ;
561- }
562- // We assume that the macro calls will expand to item(s) even though they could
563- // expand to statements and expressions.
564- StmtKind :: MacCall ( ref mac_call ) => {
565- if ! info. has_main_fn {
566- // For backward compatibility, we look for the token sequence `fn main(…)`
567- // in the macro input (!) to crudely detect main functions "masked by a
568- // wrapper macro". For the record, this is a horrible heuristic!
569- // See <https://github.com/rust-lang/rust/issues/56898>.
570- let mut iter = mac_call . mac . args . tokens . iter ( ) ;
571- while let Some ( token) = iter . next ( ) {
572- if let TokenTree :: Token ( token , _ ) = token
573- && let TokenKind :: Ident ( kw :: Fn , _ ) = token . kind
574- && let Some ( TokenTree :: Token ( ident , _ ) ) = iter . peek ( )
575- && let TokenKind :: Ident ( sym :: main , _ ) = ident . kind
576- && let Some ( TokenTree :: Delimited ( .. , Delimiter :: Parenthesis , _ ) ) = {
577- iter . next ( ) ;
578- iter . peek ( )
579- }
580- {
581- info . has_main_fn = true ;
582- break ;
583- }
529+ } else {
530+ push_to_s ( & mut info . crate_attrs , source , attr . span , & mut prev_span_hi ) ;
531+ }
532+ }
533+
534+ let mut has_non_items = false ;
535+ for stmt in & body . stmts {
536+ let mut is_extern_crate = false ;
537+ match stmt . kind {
538+ StmtKind :: Item ( ref item ) => {
539+ is_extern_crate = check_item ( item , & mut info, crate_name ) ;
540+ }
541+ // We assume that the macro calls will expand to item(s) even though they could
542+ // expand to statements and expressions.
543+ StmtKind :: MacCall ( ref mac_call ) => {
544+ if !info . has_main_fn {
545+ // For backward compatibility, we look for the token sequence `fn main(…)`
546+ // in the macro input (!) to crudely detect main functions "masked by a
547+ // wrapper macro". For the record, this is a horrible heuristic!
548+ // See <https://github.com/rust-lang/rust/issues/56898>.
549+ let mut iter = mac_call . mac . args . tokens . iter ( ) ;
550+ while let Some ( token ) = iter . next ( ) {
551+ if let TokenTree :: Token ( token , _ ) = token
552+ && let TokenKind :: Ident ( kw :: Fn , _ ) = token . kind
553+ && let Some ( TokenTree :: Token ( ident , _ ) ) = iter . peek ( )
554+ && let TokenKind :: Ident ( sym :: main , _ ) = ident . kind
555+ && let Some ( TokenTree :: Delimited ( .. , Delimiter :: Parenthesis , _ ) ) = {
556+ iter . next ( ) ;
557+ iter . peek ( )
584558 }
559+ {
560+ info. has_main_fn = true ;
561+ break ;
585562 }
586563 }
587- StmtKind :: Let ( _) | StmtKind :: Expr ( _) | StmtKind :: Semi ( _) | StmtKind :: Empty => {
588- has_non_items = true
589- }
590- }
591-
592- // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
593- // tweak the span to include the attributes as well.
594- let mut span = stmt. span ;
595- if let Some ( attr) =
596- stmt. kind . attrs ( ) . iter ( ) . find ( |attr| attr. style == AttrStyle :: Outer )
597- {
598- span = span. with_lo ( attr. span . lo ( ) ) ;
599- }
600- if info. everything_else . is_empty ( )
601- && ( !info. maybe_crate_attrs . is_empty ( ) || !info. crate_attrs . is_empty ( ) )
602- {
603- // To keep the doctest code "as close as possible" to the original, we insert
604- // all the code located between this new span and the previous span which
605- // might contain code comments and backlines.
606- push_to_s ( & mut info. crates , source, span. shrink_to_lo ( ) , & mut prev_span_hi) ;
607- }
608- if !is_extern_crate {
609- push_to_s ( & mut info. everything_else , source, span, & mut prev_span_hi) ;
610- } else {
611- push_to_s ( & mut info. crates , source, span, & mut prev_span_hi) ;
612564 }
613565 }
614- if has_non_items {
615- if info. has_main_fn
616- && let Some ( dcx) = parent_dcx
617- && !span. is_dummy ( )
618- {
619- dcx. span_warn (
620- span,
621- "the `main` function of this doctest won't be run as it contains \
622- expressions at the top level, meaning that the whole doctest code will be \
623- wrapped in a function",
624- ) ;
625- }
626- info. has_main_fn = false ;
566+ StmtKind :: Let ( _) | StmtKind :: Expr ( _) | StmtKind :: Semi ( _) | StmtKind :: Empty => {
567+ has_non_items = true
627568 }
628- Ok ( info)
629569 }
630- Err ( e) => {
631- e. cancel ( ) ;
632- Err ( ( ) )
570+
571+ // Weirdly enough, the `Stmt` span doesn't include its attributes, so we need to
572+ // tweak the span to include the attributes as well.
573+ let mut span = stmt. span ;
574+ if let Some ( attr) = stmt. kind . attrs ( ) . iter ( ) . find ( |attr| attr. style == AttrStyle :: Outer ) {
575+ span = span. with_lo ( attr. span . lo ( ) ) ;
633576 }
634- _ => Err ( ( ) ) ,
635- } ;
577+ if info. everything_else . is_empty ( )
578+ && ( !info. maybe_crate_attrs . is_empty ( ) || !info. crate_attrs . is_empty ( ) )
579+ {
580+ // To keep the doctest code "as close as possible" to the original, we insert
581+ // all the code located between this new span and the previous span which
582+ // might contain code comments and backlines.
583+ push_to_s ( & mut info. crates , source, span. shrink_to_lo ( ) , & mut prev_span_hi) ;
584+ }
585+ if !is_extern_crate {
586+ push_to_s ( & mut info. everything_else , source, span, & mut prev_span_hi) ;
587+ } else {
588+ push_to_s ( & mut info. crates , source, span, & mut prev_span_hi) ;
589+ }
590+ }
591+
592+ if has_non_items {
593+ if info. has_main_fn
594+ && let Some ( dcx) = parent_dcx
595+ && !span. is_dummy ( )
596+ {
597+ dcx. span_warn (
598+ span,
599+ "the `main` function of this doctest won't be run as it contains \
600+ expressions at the top level, meaning that the whole doctest code will be \
601+ wrapped in a function",
602+ ) ;
603+ }
604+ info. has_main_fn = false ;
605+ }
636606
637- reset_error_count ( & psess) ;
638- result
607+ Ok ( info)
639608}
0 commit comments