@@ -196,6 +196,73 @@ pub(crate) struct DocTestBuilder {
196196 pub ( crate ) can_be_merged : bool ,
197197}
198198
199+ /// Contains needed information for doctest to be correctly generated with expected "wrapping".
200+ pub ( crate ) struct WrapperInfo {
201+ pub ( crate ) before : String ,
202+ pub ( crate ) after : String ,
203+ pub ( crate ) returns_result : bool ,
204+ insert_indent_space : bool ,
205+ }
206+
207+ impl WrapperInfo {
208+ fn len ( & self ) -> usize {
209+ self . before . len ( ) + self . after . len ( )
210+ }
211+ }
212+
213+ /// Contains a doctest information. Can be converted into code with the `to_string()` method.
214+ pub ( crate ) enum DocTestWrapResult {
215+ Valid {
216+ crate_level_code : String ,
217+ wrapper : Option < WrapperInfo > ,
218+ code : String ,
219+ } ,
220+ /// Contains the original source code.
221+ SyntaxError ( String ) ,
222+ }
223+
224+ impl std:: string:: ToString for DocTestWrapResult {
225+ fn to_string ( & self ) -> String {
226+ match self {
227+ Self :: SyntaxError ( s) => s. clone ( ) ,
228+ Self :: Valid { crate_level_code, wrapper, code } => {
229+ let mut prog_len = code. len ( ) + crate_level_code. len ( ) ;
230+ if let Some ( wrapper) = wrapper {
231+ prog_len += wrapper. len ( ) ;
232+ if wrapper. insert_indent_space {
233+ prog_len += code. lines ( ) . count ( ) * 4 ;
234+ }
235+ }
236+ let mut prog = String :: with_capacity ( prog_len) ;
237+
238+ prog. push_str ( crate_level_code) ;
239+ if let Some ( wrapper) = wrapper {
240+ prog. push_str ( & wrapper. before ) ;
241+
242+ // add extra 4 spaces for each line to offset the code block
243+ if wrapper. insert_indent_space {
244+ write ! (
245+ prog,
246+ "{}" ,
247+ fmt:: from_fn( |f| code
248+ . lines( )
249+ . map( |line| fmt:: from_fn( move |f| write!( f, " {line}" ) ) )
250+ . joined( "\n " , f) )
251+ )
252+ . unwrap ( ) ;
253+ } else {
254+ prog. push_str ( code) ;
255+ }
256+ prog. push_str ( & wrapper. after ) ;
257+ } else {
258+ prog. push_str ( code) ;
259+ }
260+ prog
261+ }
262+ }
263+ }
264+ }
265+
199266impl DocTestBuilder {
200267 fn invalid (
201268 global_crate_attrs : Vec < String > ,
@@ -228,50 +295,49 @@ impl DocTestBuilder {
228295 dont_insert_main : bool ,
229296 opts : & GlobalTestOptions ,
230297 crate_name : Option < & str > ,
231- ) -> ( String , usize ) {
298+ ) -> ( DocTestWrapResult , usize ) {
232299 if self . invalid_ast {
233300 // If the AST failed to compile, no need to go generate a complete doctest, the error
234301 // will be better this way.
235302 debug ! ( "invalid AST:\n {test_code}" ) ;
236- return ( test_code. to_string ( ) , 0 ) ;
303+ return ( DocTestWrapResult :: SyntaxError ( test_code. to_string ( ) ) , 0 ) ;
237304 }
238305 let mut line_offset = 0 ;
239- let mut prog = String :: new ( ) ;
240- let everything_else = self . everything_else . trim ( ) ;
241-
306+ let mut crate_level_code = String :: new ( ) ;
307+ let code = self . everything_else . trim ( ) ;
242308 if self . global_crate_attrs . is_empty ( ) {
243309 // If there aren't any attributes supplied by #![doc(test(attr(...)))], then allow some
244310 // lints that are commonly triggered in doctests. The crate-level test attributes are
245311 // commonly used to make tests fail in case they trigger warnings, so having this there in
246312 // that case may cause some tests to pass when they shouldn't have.
247- prog . push_str ( "#![allow(unused)]\n " ) ;
313+ crate_level_code . push_str ( "#![allow(unused)]\n " ) ;
248314 line_offset += 1 ;
249315 }
250316
251317 // Next, any attributes that came from #![doc(test(attr(...)))].
252318 for attr in & self . global_crate_attrs {
253- prog . push_str ( & format ! ( "#![{attr}]\n " ) ) ;
319+ crate_level_code . push_str ( & format ! ( "#![{attr}]\n " ) ) ;
254320 line_offset += 1 ;
255321 }
256322
257323 // Now push any outer attributes from the example, assuming they
258324 // are intended to be crate attributes.
259325 if !self . crate_attrs . is_empty ( ) {
260- prog . push_str ( & self . crate_attrs ) ;
326+ crate_level_code . push_str ( & self . crate_attrs ) ;
261327 if !self . crate_attrs . ends_with ( '\n' ) {
262- prog . push ( '\n' ) ;
328+ crate_level_code . push ( '\n' ) ;
263329 }
264330 }
265331 if !self . maybe_crate_attrs . is_empty ( ) {
266- prog . push_str ( & self . maybe_crate_attrs ) ;
332+ crate_level_code . push_str ( & self . maybe_crate_attrs ) ;
267333 if !self . maybe_crate_attrs . ends_with ( '\n' ) {
268- prog . push ( '\n' ) ;
334+ crate_level_code . push ( '\n' ) ;
269335 }
270336 }
271337 if !self . crates . is_empty ( ) {
272- prog . push_str ( & self . crates ) ;
338+ crate_level_code . push_str ( & self . crates ) ;
273339 if !self . crates . ends_with ( '\n' ) {
274- prog . push ( '\n' ) ;
340+ crate_level_code . push ( '\n' ) ;
275341 }
276342 }
277343
@@ -289,17 +355,20 @@ impl DocTestBuilder {
289355 {
290356 // rustdoc implicitly inserts an `extern crate` item for the own crate
291357 // which may be unused, so we need to allow the lint.
292- prog . push_str ( "#[allow(unused_extern_crates)]\n " ) ;
358+ crate_level_code . push_str ( "#[allow(unused_extern_crates)]\n " ) ;
293359
294- prog . push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
360+ crate_level_code . push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
295361 line_offset += 1 ;
296362 }
297363
298364 // FIXME: This code cannot yet handle no_std test cases yet
299- if dont_insert_main || self . has_main_fn || prog. contains ( "![no_std]" ) {
300- prog. push_str ( everything_else) ;
365+ let wrapper = if dont_insert_main
366+ || self . has_main_fn
367+ || crate_level_code. contains ( "![no_std]" )
368+ {
369+ None
301370 } else {
302- let returns_result = everything_else . ends_with ( "(())" ) ;
371+ let returns_result = code . ends_with ( "(())" ) ;
303372 // Give each doctest main function a unique name.
304373 // This is for example needed for the tooling around `-C instrument-coverage`.
305374 let inner_fn_name = if let Some ( ref test_id) = self . test_id {
@@ -333,28 +402,18 @@ impl DocTestBuilder {
333402 // /// ``` <- end of the inner main
334403 line_offset += 1 ;
335404
336- prog. push_str ( & main_pre) ;
337-
338- // add extra 4 spaces for each line to offset the code block
339- if opts. insert_indent_space {
340- write ! (
341- prog,
342- "{}" ,
343- fmt:: from_fn( |f| everything_else
344- . lines( )
345- . map( |line| fmt:: from_fn( move |f| write!( f, " {line}" ) ) )
346- . joined( "\n " , f) )
347- )
348- . unwrap ( ) ;
349- } else {
350- prog. push_str ( everything_else) ;
351- } ;
352- prog. push_str ( & main_post) ;
353- }
354-
355- debug ! ( "final doctest:\n {prog}" ) ;
405+ Some ( WrapperInfo {
406+ before : main_pre,
407+ after : main_post,
408+ returns_result,
409+ insert_indent_space : opts. insert_indent_space ,
410+ } )
411+ } ;
356412
357- ( prog, line_offset)
413+ (
414+ DocTestWrapResult :: Valid { code : code. to_string ( ) , wrapper, crate_level_code } ,
415+ line_offset,
416+ )
358417 }
359418}
360419
0 commit comments