@@ -6,7 +6,7 @@ use std::io;
6
6
use rustc_ast as ast;
7
7
use rustc_data_structures:: sync:: Lrc ;
8
8
use rustc_errors:: emitter:: stderr_destination;
9
- use rustc_errors:: ColorConfig ;
9
+ use rustc_errors:: { ColorConfig , FatalError } ;
10
10
use rustc_parse:: new_parser_from_source_str;
11
11
use rustc_parse:: parser:: attr:: InnerAttrPolicy ;
12
12
use rustc_session:: parse:: ParseSess ;
@@ -55,15 +55,103 @@ pub(crate) fn make_test(
55
55
56
56
// Uses librustc_ast to parse the doctest and find if there's a main fn and the extern
57
57
// 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 > {
58
147
let result = rustc_driver:: catch_fatal_errors ( || {
59
148
rustc_span:: create_session_if_not_set_then ( edition, |_| {
60
149
use rustc_errors:: emitter:: { Emitter , HumanEmitter } ;
61
150
use rustc_errors:: DiagCtxt ;
62
151
use rustc_parse:: parser:: ForceCollect ;
63
152
use rustc_span:: source_map:: FilePathMapping ;
64
153
65
- let filename = FileName :: anon_source_code ( s) ;
66
- let source = crates + everything_else;
154
+ let filename = FileName :: anon_source_code ( & source) ;
67
155
68
156
// Any errors in parsing should also appear when the doctest is compiled for real, so just
69
157
// send all the errors that librustc_ast emits directly into a `Sink` instead of stderr.
@@ -72,7 +160,7 @@ pub(crate) fn make_test(
72
160
rustc_driver:: DEFAULT_LOCALE_RESOURCES . to_vec ( ) ,
73
161
false ,
74
162
) ;
75
- supports_color =
163
+ * supports_color =
76
164
HumanEmitter :: new ( stderr_destination ( ColorConfig :: Auto ) , fallback_bundle. clone ( ) )
77
165
. supports_color ( ) ;
78
166
@@ -86,13 +174,14 @@ pub(crate) fn make_test(
86
174
let mut found_extern_crate = crate_name. is_none ( ) ;
87
175
let mut found_macro = false ;
88
176
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
+ } ;
96
185
97
186
loop {
98
187
match parser. parse_item ( ForceCollect :: No ) {
@@ -146,18 +235,15 @@ pub(crate) fn make_test(
146
235
( found_main, found_extern_crate, found_macro)
147
236
} )
148
237
} ) ;
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?;
154
239
155
240
// If a doctest's `fn main` is being masked by a wrapper macro, the parsing loop above won't
156
241
// see it. In that case, run the old text-based scan to see if they at least have a main
157
242
// function written inside a macro invocation. See
158
243
// https://github.com/rust-lang/rust/issues/56898
159
244
let already_has_main = if found_macro && !already_has_main {
160
- s. lines ( )
245
+ source
246
+ . lines ( )
161
247
. map ( |line| {
162
248
let comment = line. find ( "//" ) ;
163
249
if let Some ( comment_begins) = comment { & line[ 0 ..comment_begins] } else { line }
@@ -167,79 +253,7 @@ pub(crate) fn make_test(
167
253
already_has_main
168
254
} ;
169
255
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) )
243
257
}
244
258
245
259
fn check_if_attr_is_complete ( source : & str , edition : Edition ) -> bool {
0 commit comments