@@ -25,6 +25,7 @@ pub(crate) struct DocTest {
25
25
pub ( crate ) crates : String ,
26
26
pub ( crate ) everything_else : String ,
27
27
pub ( crate ) test_id : Option < String > ,
28
+ pub ( crate ) failed_ast : bool ,
28
29
}
29
30
30
31
impl DocTest {
@@ -40,8 +41,15 @@ impl DocTest {
40
41
41
42
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
42
43
// crate already is included.
43
- let Ok ( ( main_fn_span, already_has_extern_crate) ) =
44
- check_for_main_and_extern_crate ( crate_name, source, edition, & mut supports_color)
44
+ let Ok ( ( main_fn_span, already_has_extern_crate, failed_ast) ) =
45
+ check_for_main_and_extern_crate (
46
+ crate_name,
47
+ source,
48
+ & everything_else,
49
+ & crates,
50
+ edition,
51
+ & mut supports_color,
52
+ )
45
53
else {
46
54
// If the parser panicked due to a fatal error, pass the test code through unchanged.
47
55
// The error will be reported during compilation.
@@ -53,6 +61,7 @@ impl DocTest {
53
61
everything_else,
54
62
already_has_extern_crate : false ,
55
63
test_id,
64
+ failed_ast : true ,
56
65
} ;
57
66
} ;
58
67
Self {
@@ -63,6 +72,7 @@ impl DocTest {
63
72
everything_else,
64
73
already_has_extern_crate,
65
74
test_id,
75
+ failed_ast,
66
76
}
67
77
}
68
78
@@ -179,111 +189,195 @@ impl DocTest {
179
189
}
180
190
}
181
191
182
- fn check_for_main_and_extern_crate (
183
- crate_name : Option < & str > ,
184
- source : & str ,
185
- edition : Edition ,
186
- supports_color : & mut bool ,
187
- ) -> Result < ( Option < Span > , bool ) , FatalError > {
188
- let result = rustc_driver:: catch_fatal_errors ( || {
189
- rustc_span:: create_session_if_not_set_then ( edition, |_| {
190
- use rustc_errors:: emitter:: { Emitter , HumanEmitter } ;
191
- use rustc_errors:: DiagCtxt ;
192
- use rustc_parse:: parser:: ForceCollect ;
193
- use rustc_span:: source_map:: FilePathMapping ;
194
-
195
- let filename = FileName :: anon_source_code ( source) ;
192
+ #[ derive( PartialEq , Eq , Debug ) ]
193
+ enum ParsingResult {
194
+ Failed ,
195
+ AstError ,
196
+ Ok ,
197
+ }
196
198
197
- // Any errors in parsing should also appear when the doctest is compiled for real, so just
198
- // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
199
- let sm = Lrc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ;
200
- let fallback_bundle = rustc_errors:: fallback_fluent_bundle (
201
- rustc_driver:: DEFAULT_LOCALE_RESOURCES . to_vec ( ) ,
202
- false ,
203
- ) ;
204
- * supports_color =
205
- HumanEmitter :: new ( stderr_destination ( ColorConfig :: Auto ) , fallback_bundle. clone ( ) )
206
- . supports_color ( ) ;
199
+ fn cancel_error_count ( psess : & ParseSess ) {
200
+ // Reset errors so that they won't be reported as compiler bugs when dropping the
201
+ // dcx. Any errors in the tests will be reported when the test file is compiled,
202
+ // Note that we still need to cancel the errors above otherwise `Diag` will panic on
203
+ // drop.
204
+ psess. dcx ( ) . reset_err_count ( ) ;
205
+ }
207
206
208
- let emitter = HumanEmitter :: new ( Box :: new ( io:: sink ( ) ) , fallback_bundle) ;
207
+ fn parse_source (
208
+ source : String ,
209
+ found_main_span : & mut Option < Span > ,
210
+ found_extern_crate : & mut bool ,
211
+ found_macro : & mut bool ,
212
+ crate_name : & Option < & str > ,
213
+ supports_color : & mut bool ,
214
+ ) -> ParsingResult {
215
+ use rustc_errors:: emitter:: { Emitter , HumanEmitter } ;
216
+ use rustc_errors:: DiagCtxt ;
217
+ use rustc_parse:: parser:: ForceCollect ;
218
+ use rustc_span:: source_map:: FilePathMapping ;
219
+
220
+ let filename = FileName :: anon_source_code ( & source) ;
221
+
222
+ // Any errors in parsing should also appear when the doctest is compiled for real, so just
223
+ // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
224
+ let sm = Lrc :: new ( SourceMap :: new ( FilePathMapping :: empty ( ) ) ) ;
225
+ let fallback_bundle = rustc_errors:: fallback_fluent_bundle (
226
+ rustc_driver:: DEFAULT_LOCALE_RESOURCES . to_vec ( ) ,
227
+ false ,
228
+ ) ;
229
+ * supports_color =
230
+ HumanEmitter :: new ( stderr_destination ( ColorConfig :: Auto ) , fallback_bundle. clone ( ) )
231
+ . supports_color ( ) ;
232
+
233
+ let emitter = HumanEmitter :: new ( Box :: new ( io:: sink ( ) ) , fallback_bundle) ;
234
+
235
+ // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
236
+ let dcx = DiagCtxt :: new ( Box :: new ( emitter) ) . disable_warnings ( ) ;
237
+ let psess = ParseSess :: with_dcx ( dcx, sm) ;
238
+
239
+ let mut parser = match new_parser_from_source_str ( & psess, filename, source) {
240
+ Ok ( p) => p,
241
+ Err ( errs) => {
242
+ errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
243
+ cancel_error_count ( & psess) ;
244
+ return ParsingResult :: Failed ;
245
+ }
246
+ } ;
247
+ let mut parsing_result = ParsingResult :: Ok ;
248
+
249
+ // Recurse through functions body. It is necessary because the doctest source code is
250
+ // wrapped in a function to limit the number of AST errors. If we don't recurse into
251
+ // functions, we would thing all top-level items (so basically nothing).
252
+ fn check_item (
253
+ item : & ast:: Item ,
254
+ found_main_span : & mut Option < Span > ,
255
+ found_extern_crate : & mut bool ,
256
+ found_macro : & mut bool ,
257
+ crate_name : & Option < & str > ,
258
+ ) {
259
+ match item. kind {
260
+ ast:: ItemKind :: Fn ( ref fn_item) if found_main_span. is_none ( ) => {
261
+ if item. ident . name == sym:: main {
262
+ * found_main_span = Some ( item. span ) ;
263
+ }
264
+ if let Some ( ref body) = fn_item. body {
265
+ for stmt in & body. stmts {
266
+ match stmt. kind {
267
+ ast:: StmtKind :: Item ( ref item) => check_item (
268
+ item,
269
+ found_main_span,
270
+ found_extern_crate,
271
+ found_macro,
272
+ crate_name,
273
+ ) ,
274
+ ast:: StmtKind :: MacCall ( ..) => * found_macro = true ,
275
+ _ => { }
276
+ }
277
+ }
278
+ }
279
+ }
280
+ ast:: ItemKind :: ExternCrate ( original) => {
281
+ if !* found_extern_crate && let Some ( ref crate_name) = crate_name {
282
+ * found_extern_crate = match original {
283
+ Some ( name) => name. as_str ( ) == * crate_name,
284
+ None => item. ident . as_str ( ) == * crate_name,
285
+ } ;
286
+ }
287
+ }
288
+ ast:: ItemKind :: MacCall ( ..) => * found_macro = true ,
289
+ _ => { }
290
+ }
291
+ }
209
292
210
- // FIXME(misdreavus): pass `-Z treat-err-as-bug` to the doctest parser
211
- let dcx = DiagCtxt :: new ( Box :: new ( emitter) ) . disable_warnings ( ) ;
212
- let psess = ParseSess :: with_dcx ( dcx, sm) ;
293
+ loop {
294
+ match parser. parse_item ( ForceCollect :: No ) {
295
+ Ok ( Some ( item) ) => {
296
+ check_item ( & item, found_main_span, found_extern_crate, found_macro, crate_name) ;
213
297
214
- let mut found_main = None ;
215
- let mut found_extern_crate = crate_name. is_none ( ) ;
216
- let mut found_macro = false ;
217
-
218
- let mut parser = match new_parser_from_source_str ( & psess, filename, source. to_owned ( ) ) {
219
- Ok ( p) => p,
220
- Err ( errs) => {
221
- errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
222
- return ( found_main, found_extern_crate, found_macro) ;
298
+ if found_main_span. is_some ( ) && * found_extern_crate {
299
+ break ;
223
300
}
224
- } ;
225
-
226
- loop {
227
- match parser. parse_item ( ForceCollect :: No ) {
228
- Ok ( Some ( item) ) => {
229
- if found_main. is_none ( )
230
- && let ast:: ItemKind :: Fn ( ..) = item. kind
231
- && item. ident . name == sym:: main
232
- {
233
- found_main = Some ( item. span ) ;
234
- }
301
+ }
302
+ Ok ( None ) => break ,
303
+ Err ( e) => {
304
+ parsing_result = ParsingResult :: AstError ;
305
+ e. cancel ( ) ;
306
+ break ;
307
+ }
308
+ }
235
309
236
- if !found_extern_crate
237
- && let ast:: ItemKind :: ExternCrate ( original) = item. kind
238
- {
239
- // This code will never be reached if `crate_name` is none because
240
- // `found_extern_crate` is initialized to `true` if it is none.
241
- let crate_name = crate_name. unwrap ( ) ;
242
-
243
- match original {
244
- Some ( name) => found_extern_crate = name. as_str ( ) == crate_name,
245
- None => found_extern_crate = item. ident . as_str ( ) == crate_name,
246
- }
247
- }
310
+ // The supplied slice is only used for diagnostics,
311
+ // which are swallowed here anyway.
312
+ parser. maybe_consume_incorrect_semicolon ( None ) ;
313
+ }
248
314
249
- if !found_macro && let ast :: ItemKind :: MacCall ( .. ) = item . kind {
250
- found_macro = true ;
251
- }
315
+ cancel_error_count ( & psess ) ;
316
+ parsing_result
317
+ }
252
318
253
- if found_main. is_some ( ) && found_extern_crate {
254
- break ;
255
- }
256
- }
257
- Ok ( None ) => break ,
258
- Err ( e) => {
259
- e. cancel ( ) ;
260
- break ;
261
- }
262
- }
319
+ fn check_for_main_and_extern_crate (
320
+ crate_name : Option < & str > ,
321
+ original_source_code : & str ,
322
+ everything_else : & str ,
323
+ crates : & str ,
324
+ edition : Edition ,
325
+ supports_color : & mut bool ,
326
+ ) -> Result < ( Option < Span > , bool , bool ) , FatalError > {
327
+ let result = rustc_driver:: catch_fatal_errors ( || {
328
+ rustc_span:: create_session_if_not_set_then ( edition, |_| {
329
+ let mut found_main_span = None ;
330
+ let mut found_extern_crate = crate_name. is_none ( ) ;
331
+ let mut found_macro = false ;
263
332
264
- // The supplied item is only used for diagnostics,
265
- // which are swallowed here anyway.
266
- parser. maybe_consume_incorrect_semicolon ( None ) ;
333
+ let mut parsing_result = parse_source (
334
+ format ! ( "{crates}{everything_else}" ) ,
335
+ & mut found_main_span,
336
+ & mut found_extern_crate,
337
+ & mut found_macro,
338
+ & crate_name,
339
+ supports_color,
340
+ ) ;
341
+ // No need to double-check this if the "merged doctests" feature isn't enabled (so
342
+ // before the 2024 edition).
343
+ if edition >= Edition :: Edition2024 && parsing_result != ParsingResult :: Ok {
344
+ // If we found an AST error, we want to ensure it's because of an expression being
345
+ // used outside of a function.
346
+ //
347
+ // To do so, we wrap in a function in order to make sure that the doctest AST is
348
+ // correct. For example, if your doctest is `foo::bar()`, if we don't wrap it in a
349
+ // block, it would emit an AST error, which would be problematic for us since we
350
+ // want to filter out such errors which aren't "real" errors.
351
+ //
352
+ // The end goal is to be able to merge as many doctests as possible as one for much
353
+ // faster doctests run time.
354
+ parsing_result = parse_source (
355
+ format ! ( "{crates}\n fn __doctest_wrap(){{{everything_else}\n }}" ) ,
356
+ & mut found_main_span,
357
+ & mut found_extern_crate,
358
+ & mut found_macro,
359
+ & crate_name,
360
+ supports_color,
361
+ ) ;
267
362
}
268
363
269
- // Reset errors so that they won't be reported as compiler bugs when dropping the
270
- // dcx. Any errors in the tests will be reported when the test file is compiled,
271
- // Note that we still need to cancel the errors above otherwise `Diag` will panic on
272
- // drop.
273
- psess. dcx ( ) . reset_err_count ( ) ;
274
-
275
- ( found_main, found_extern_crate, found_macro)
364
+ ( found_main_span, found_extern_crate, found_macro, parsing_result)
276
365
} )
277
366
} ) ;
278
- let ( mut main_fn_span, already_has_extern_crate, found_macro) = result?;
367
+ let ( mut main_fn_span, already_has_extern_crate, found_macro, parsing_result) = match result {
368
+ Err ( ..) | Ok ( ( _, _, _, ParsingResult :: Failed ) ) => return Err ( FatalError ) ,
369
+ Ok ( ( main_fn_span, already_has_extern_crate, found_macro, parsing_result) ) => {
370
+ ( main_fn_span, already_has_extern_crate, found_macro, parsing_result)
371
+ }
372
+ } ;
279
373
280
374
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
281
375
// see it. In that case, run the old text-based scan to see if they at least have a main
282
376
// function written inside a macro invocation. See
283
377
// https://github.com/rust-lang/rust/issues/56898
284
378
if found_macro
285
379
&& main_fn_span. is_none ( )
286
- && source
380
+ && original_source_code
287
381
. lines ( )
288
382
. map ( |line| {
289
383
let comment = line. find ( "//" ) ;
@@ -294,7 +388,7 @@ fn check_for_main_and_extern_crate(
294
388
main_fn_span = Some ( DUMMY_SP ) ;
295
389
}
296
390
297
- Ok ( ( main_fn_span, already_has_extern_crate) )
391
+ Ok ( ( main_fn_span, already_has_extern_crate, parsing_result != ParsingResult :: Ok ) )
298
392
}
299
393
300
394
fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> bool {
0 commit comments