@@ -6,7 +6,7 @@ use std::io;
66use rustc_ast as ast;
77use rustc_data_structures:: sync:: Lrc ;
88use rustc_errors:: emitter:: stderr_destination;
9- use rustc_errors:: ColorConfig ;
9+ use rustc_errors:: { ColorConfig , FatalError } ;
1010use rustc_parse:: new_parser_from_source_str;
1111use rustc_parse:: parser:: attr:: InnerAttrPolicy ;
1212use rustc_session:: parse:: ParseSess ;
@@ -55,15 +55,103 @@ pub(crate) fn make_test(
5555
5656 // Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
5757 // crate already is included.
58+ let Ok ( ( already_has_main, already_has_extern_crate) ) =
59+ check_for_main_and_extern_crate ( crate_name, s. to_owned ( ) , edition, & mut supports_color)
60+ else {
61+ // If the parser panicked due to a fatal error, pass the test code through unchanged.
62+ // The error will be reported during compilation.
63+ return ( s. to_owned ( ) , 0 , false ) ;
64+ } ;
65+
66+ // Don't inject `extern crate std` because it's already injected by the
67+ // compiler.
68+ if !already_has_extern_crate && !opts. no_crate_inject && crate_name != Some ( "std" ) {
69+ if let Some ( crate_name) = crate_name {
70+ // Don't inject `extern crate` if the crate is never used.
71+ // NOTE: this is terribly inaccurate because it doesn't actually
72+ // parse the source, but only has false positives, not false
73+ // negatives.
74+ if s. contains ( crate_name) {
75+ // rustdoc implicitly inserts an `extern crate` item for the own crate
76+ // which may be unused, so we need to allow the lint.
77+ prog. push_str ( "#[allow(unused_extern_crates)]\n " ) ;
78+
79+ prog. push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
80+ line_offset += 1 ;
81+ }
82+ }
83+ }
84+
85+ // FIXME: This code cannot yet handle no_std test cases yet
86+ if dont_insert_main || already_has_main || prog. contains ( "![no_std]" ) {
87+ prog. push_str ( everything_else) ;
88+ } else {
89+ let returns_result = everything_else. trim_end ( ) . ends_with ( "(())" ) ;
90+ // Give each doctest main function a unique name.
91+ // This is for example needed for the tooling around `-C instrument-coverage`.
92+ let inner_fn_name = if let Some ( test_id) = test_id {
93+ format ! ( "_doctest_main_{test_id}" )
94+ } else {
95+ "_inner" . into ( )
96+ } ;
97+ let inner_attr = if test_id. is_some ( ) { "#[allow(non_snake_case)] " } else { "" } ;
98+ let ( main_pre, main_post) = if returns_result {
99+ (
100+ format ! (
101+ "fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n " ,
102+ ) ,
103+ format ! ( "\n }} {inner_fn_name}().unwrap() }}" ) ,
104+ )
105+ } else if test_id. is_some ( ) {
106+ (
107+ format ! ( "fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n " , ) ,
108+ format ! ( "\n }} {inner_fn_name}() }}" ) ,
109+ )
110+ } else {
111+ ( "fn main() {\n " . into ( ) , "\n }" . into ( ) )
112+ } ;
113+ // Note on newlines: We insert a line/newline *before*, and *after*
114+ // the doctest and adjust the `line_offset` accordingly.
115+ // In the case of `-C instrument-coverage`, this means that the generated
116+ // inner `main` function spans from the doctest opening codeblock to the
117+ // closing one. For example
118+ // /// ``` <- start of the inner main
119+ // /// <- code under doctest
120+ // /// ``` <- end of the inner main
121+ line_offset += 1 ;
122+
123+ // add extra 4 spaces for each line to offset the code block
124+ let content = if opts. insert_indent_space {
125+ everything_else
126+ . lines ( )
127+ . map ( |line| format ! ( " {}" , line) )
128+ . collect :: < Vec < String > > ( )
129+ . join ( "\n " )
130+ } else {
131+ everything_else. to_string ( )
132+ } ;
133+ prog. extend ( [ & main_pre, content. as_str ( ) , & main_post] . iter ( ) . cloned ( ) ) ;
134+ }
135+
136+ debug ! ( "final doctest:\n {prog}" ) ;
137+
138+ ( prog, line_offset, supports_color)
139+ }
140+
141+ fn check_for_main_and_extern_crate (
142+ crate_name : Option < & str > ,
143+ source : String ,
144+ edition : Edition ,
145+ supports_color : & mut bool ,
146+ ) -> Result < ( bool , bool ) , FatalError > {
58147 let result = rustc_driver:: catch_fatal_errors ( || {
59148 rustc_span:: create_session_if_not_set_then ( edition, |_| {
60149 use rustc_errors:: emitter:: { Emitter , HumanEmitter } ;
61150 use rustc_errors:: DiagCtxt ;
62151 use rustc_parse:: parser:: ForceCollect ;
63152 use rustc_span:: source_map:: FilePathMapping ;
64153
65- let filename = FileName :: anon_source_code ( s) ;
66- let source = crates + everything_else;
154+ let filename = FileName :: anon_source_code ( & source) ;
67155
68156 // Any errors in parsing should also appear when the doctest is compiled for real, so just
69157 // send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
@@ -72,7 +160,7 @@ pub(crate) fn make_test(
72160 rustc_driver:: DEFAULT_LOCALE_RESOURCES . to_vec ( ) ,
73161 false ,
74162 ) ;
75- supports_color =
163+ * supports_color =
76164 HumanEmitter :: new ( stderr_destination ( ColorConfig :: Auto ) , fallback_bundle. clone ( ) )
77165 . supports_color ( ) ;
78166
@@ -86,13 +174,14 @@ pub(crate) fn make_test(
86174 let mut found_extern_crate = crate_name. is_none ( ) ;
87175 let mut found_macro = false ;
88176
89- let mut parser = match new_parser_from_source_str ( & psess, filename, source) {
90- Ok ( p) => p,
91- Err ( errs) => {
92- errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
93- return ( found_main, found_extern_crate, found_macro) ;
94- }
95- } ;
177+ let mut parser =
178+ match new_parser_from_source_str ( & psess, filename, source. clone ( ) ) {
179+ Ok ( p) => p,
180+ Err ( errs) => {
181+ errs. into_iter ( ) . for_each ( |err| err. cancel ( ) ) ;
182+ return ( found_main, found_extern_crate, found_macro) ;
183+ }
184+ } ;
96185
97186 loop {
98187 match parser. parse_item ( ForceCollect :: No ) {
@@ -146,18 +235,15 @@ pub(crate) fn make_test(
146235 ( found_main, found_extern_crate, found_macro)
147236 } )
148237 } ) ;
149- let Ok ( ( already_has_main, already_has_extern_crate, found_macro) ) = result else {
150- // If the parser panicked due to a fatal error, pass the test code through unchanged.
151- // The error will be reported during compilation.
152- return ( s. to_owned ( ) , 0 , false ) ;
153- } ;
238+ let ( already_has_main, already_has_extern_crate, found_macro) = result?;
154239
155240 // If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
156241 // see it. In that case, run the old text-based scan to see if they at least have a main
157242 // function written inside a macro invocation. See
158243 // https://github.com/rust-lang/rust/issues/56898
159244 let already_has_main = if found_macro && !already_has_main {
160- s. lines ( )
245+ source
246+ . lines ( )
161247 . map ( |line| {
162248 let comment = line. find ( "//" ) ;
163249 if let Some ( comment_begins) = comment { & line[ 0 ..comment_begins] } else { line }
@@ -167,79 +253,7 @@ pub(crate) fn make_test(
167253 already_has_main
168254 } ;
169255
170- // Don't inject `extern crate std` because it's already injected by the
171- // compiler.
172- if !already_has_extern_crate && !opts. no_crate_inject && crate_name != Some ( "std" ) {
173- if let Some ( crate_name) = crate_name {
174- // Don't inject `extern crate` if the crate is never used.
175- // NOTE: this is terribly inaccurate because it doesn't actually
176- // parse the source, but only has false positives, not false
177- // negatives.
178- if s. contains ( crate_name) {
179- // rustdoc implicitly inserts an `extern crate` item for the own crate
180- // which may be unused, so we need to allow the lint.
181- prog. push_str ( "#[allow(unused_extern_crates)]\n " ) ;
182-
183- prog. push_str ( & format ! ( "extern crate r#{crate_name};\n " ) ) ;
184- line_offset += 1 ;
185- }
186- }
187- }
188-
189- // FIXME: This code cannot yet handle no_std test cases yet
190- if dont_insert_main || already_has_main || prog. contains ( "![no_std]" ) {
191- prog. push_str ( everything_else) ;
192- } else {
193- let returns_result = everything_else. trim_end ( ) . ends_with ( "(())" ) ;
194- // Give each doctest main function a unique name.
195- // This is for example needed for the tooling around `-C instrument-coverage`.
196- let inner_fn_name = if let Some ( test_id) = test_id {
197- format ! ( "_doctest_main_{test_id}" )
198- } else {
199- "_inner" . into ( )
200- } ;
201- let inner_attr = if test_id. is_some ( ) { "#[allow(non_snake_case)] " } else { "" } ;
202- let ( main_pre, main_post) = if returns_result {
203- (
204- format ! (
205- "fn main() {{ {inner_attr}fn {inner_fn_name}() -> Result<(), impl core::fmt::Debug> {{\n " ,
206- ) ,
207- format ! ( "\n }} {inner_fn_name}().unwrap() }}" ) ,
208- )
209- } else if test_id. is_some ( ) {
210- (
211- format ! ( "fn main() {{ {inner_attr}fn {inner_fn_name}() {{\n " , ) ,
212- format ! ( "\n }} {inner_fn_name}() }}" ) ,
213- )
214- } else {
215- ( "fn main() {\n " . into ( ) , "\n }" . into ( ) )
216- } ;
217- // Note on newlines: We insert a line/newline *before*, and *after*
218- // the doctest and adjust the `line_offset` accordingly.
219- // In the case of `-C instrument-coverage`, this means that the generated
220- // inner `main` function spans from the doctest opening codeblock to the
221- // closing one. For example
222- // /// ``` <- start of the inner main
223- // /// <- code under doctest
224- // /// ``` <- end of the inner main
225- line_offset += 1 ;
226-
227- // add extra 4 spaces for each line to offset the code block
228- let content = if opts. insert_indent_space {
229- everything_else
230- . lines ( )
231- . map ( |line| format ! ( " {}" , line) )
232- . collect :: < Vec < String > > ( )
233- . join ( "\n " )
234- } else {
235- everything_else. to_string ( )
236- } ;
237- prog. extend ( [ & main_pre, content. as_str ( ) , & main_post] . iter ( ) . cloned ( ) ) ;
238- }
239-
240- debug ! ( "final doctest:\n {prog}" ) ;
241-
242- ( prog, line_offset, supports_color)
256+ Ok ( ( already_has_main, already_has_extern_crate) )
243257}
244258
245259fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> bool {
0 commit comments