Skip to content

Commit 6d69fe7

Browse files
committed
Auto merge of #54861 - rep-nop:find_main_in_doctest, r=estebank
rustdoc: Replaces fn main search and extern crate search with proper parsing during doctests. Fixes #21299. Fixes #33731. Let me know if there's any additional changes you'd like made!
2 parents 4c5c05d + 014c8c4 commit 6d69fe7

File tree

5 files changed

+201
-26
lines changed

5 files changed

+201
-26
lines changed

src/librustdoc/test.rs

+118-17
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ pub fn make_test(s: &str,
378378
dont_insert_main: bool,
379379
opts: &TestOptions)
380380
-> (String, usize) {
381-
let (crate_attrs, everything_else) = partition_source(s);
381+
let (crate_attrs, everything_else, crates) = partition_source(s);
382382
let everything_else = everything_else.trim();
383383
let mut line_offset = 0;
384384
let mut prog = String::new();
@@ -402,30 +402,91 @@ pub fn make_test(s: &str,
402402
// are intended to be crate attributes.
403403
prog.push_str(&crate_attrs);
404404

405+
// Uses libsyntax to parse the doctest and find if there's a main fn and the extern
406+
// crate already is included.
407+
let (already_has_main, already_has_extern_crate) = crate::syntax::with_globals(|| {
408+
use crate::syntax::{ast, parse::{self, ParseSess}, source_map::FilePathMapping};
409+
use crate::syntax_pos::FileName;
410+
use errors::emitter::EmitterWriter;
411+
use errors::Handler;
412+
413+
let filename = FileName::Anon;
414+
let source = crates + &everything_else;
415+
416+
// any errors in parsing should also appear when the doctest is compiled for real, so just
417+
// send all the errors that libsyntax emits directly into a Sink instead of stderr
418+
let cm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
419+
let emitter = EmitterWriter::new(box io::sink(), None, false, false);
420+
let handler = Handler::with_emitter(false, false, box emitter);
421+
let sess = ParseSess::with_span_handler(handler, cm);
422+
423+
debug!("about to parse: \n{}", source);
424+
425+
let mut found_main = false;
426+
let mut found_extern_crate = cratename.is_none();
427+
428+
let mut parser = match parse::maybe_new_parser_from_source_str(&sess, filename, source) {
429+
Ok(p) => p,
430+
Err(errs) => {
431+
for mut err in errs {
432+
err.cancel();
433+
}
434+
435+
return (found_main, found_extern_crate);
436+
}
437+
};
438+
439+
loop {
440+
match parser.parse_item() {
441+
Ok(Some(item)) => {
442+
if !found_main {
443+
if let ast::ItemKind::Fn(..) = item.node {
444+
if item.ident.as_str() == "main" {
445+
found_main = true;
446+
}
447+
}
448+
}
449+
450+
if !found_extern_crate {
451+
if let ast::ItemKind::ExternCrate(original) = item.node {
452+
// This code will never be reached if `cratename` is none because
453+
// `found_extern_crate` is initialized to `true` if it is none.
454+
let cratename = cratename.unwrap();
455+
456+
match original {
457+
Some(name) => found_extern_crate = name.as_str() == cratename,
458+
None => found_extern_crate = item.ident.as_str() == cratename,
459+
}
460+
}
461+
}
462+
463+
if found_main && found_extern_crate {
464+
break;
465+
}
466+
}
467+
Ok(None) => break,
468+
Err(mut e) => {
469+
e.cancel();
470+
break;
471+
}
472+
}
473+
}
474+
475+
(found_main, found_extern_crate)
476+
});
477+
405478
// Don't inject `extern crate std` because it's already injected by the
406479
// compiler.
407-
if !s.contains("extern crate") && !opts.no_crate_inject && cratename != Some("std") {
480+
if !already_has_extern_crate && !opts.no_crate_inject && cratename != Some("std") {
408481
if let Some(cratename) = cratename {
482+
// Make sure its actually used if not included.
409483
if s.contains(cratename) {
410484
prog.push_str(&format!("extern crate {};\n", cratename));
411485
line_offset += 1;
412486
}
413487
}
414488
}
415489

416-
// FIXME (#21299): prefer libsyntax or some other actual parser over this
417-
// best-effort ad hoc approach
418-
let already_has_main = s.lines()
419-
.map(|line| {
420-
let comment = line.find("//");
421-
if let Some(comment_begins) = comment {
422-
&line[0..comment_begins]
423-
} else {
424-
line
425-
}
426-
})
427-
.any(|code| code.contains("fn main"));
428-
429490
if dont_insert_main || already_has_main {
430491
prog.push_str(everything_else);
431492
} else {
@@ -441,9 +502,10 @@ pub fn make_test(s: &str,
441502
}
442503

443504
// FIXME(aburka): use a real parser to deal with multiline attributes
444-
fn partition_source(s: &str) -> (String, String) {
505+
fn partition_source(s: &str) -> (String, String, String) {
445506
let mut after_header = false;
446507
let mut before = String::new();
508+
let mut crates = String::new();
447509
let mut after = String::new();
448510

449511
for line in s.lines() {
@@ -457,12 +519,17 @@ fn partition_source(s: &str) -> (String, String) {
457519
after.push_str(line);
458520
after.push_str("\n");
459521
} else {
522+
if trimline.starts_with("#[macro_use] extern crate")
523+
|| trimline.starts_with("extern crate") {
524+
crates.push_str(line);
525+
crates.push_str("\n");
526+
}
460527
before.push_str(line);
461528
before.push_str("\n");
462529
}
463530
}
464531

465-
(before, after)
532+
(before, after, crates)
466533
}
467534

468535
pub trait Tester {
@@ -1014,4 +1081,38 @@ assert_eq!(2+2, 4);
10141081
let output = make_test(input, None, false, &opts);
10151082
assert_eq!(output, (expected, 1));
10161083
}
1084+
1085+
#[test]
1086+
fn make_test_issues_21299_33731() {
1087+
let opts = TestOptions::default();
1088+
1089+
let input =
1090+
"// fn main
1091+
assert_eq!(2+2, 4);";
1092+
1093+
let expected =
1094+
"#![allow(unused)]
1095+
fn main() {
1096+
// fn main
1097+
assert_eq!(2+2, 4);
1098+
}".to_string();
1099+
1100+
let output = make_test(input, None, false, &opts);
1101+
assert_eq!(output, (expected, 2));
1102+
1103+
let input =
1104+
"extern crate hella_qwop;
1105+
assert_eq!(asdf::foo, 4);";
1106+
1107+
let expected =
1108+
"#![allow(unused)]
1109+
extern crate hella_qwop;
1110+
extern crate asdf;
1111+
fn main() {
1112+
assert_eq!(asdf::foo, 4);
1113+
}".to_string();
1114+
1115+
let output = make_test(input, Some("asdf"), false, &opts);
1116+
assert_eq!(output, (expected, 3));
1117+
}
10171118
}

src/libsyntax/lib.rs

+17
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,23 @@ macro_rules! panictry {
7070
})
7171
}
7272

73+
// A variant of 'panictry!' that works on a Vec<Diagnostic> instead of a single DiagnosticBuilder.
74+
macro_rules! panictry_buffer {
75+
($handler:expr, $e:expr) => ({
76+
use std::result::Result::{Ok, Err};
77+
use errors::{FatalError, DiagnosticBuilder};
78+
match $e {
79+
Ok(e) => e,
80+
Err(errs) => {
81+
for e in errs {
82+
DiagnosticBuilder::new_diagnostic($handler, e).emit();
83+
}
84+
FatalError.raise()
85+
}
86+
}
87+
})
88+
}
89+
7390
#[macro_export]
7491
macro_rules! unwrap_or {
7592
($opt:expr, $default:expr) => {

src/libsyntax/parse/lexer/mod.rs

+22-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
use ast::{self, Ident};
1212
use syntax_pos::{self, BytePos, CharPos, Pos, Span, NO_EXPANSION};
1313
use source_map::{SourceMap, FilePathMapping};
14-
use errors::{Applicability, FatalError, DiagnosticBuilder};
14+
use errors::{Applicability, FatalError, Diagnostic, DiagnosticBuilder};
1515
use parse::{token, ParseSess};
1616
use str::char_at;
1717
use symbol::{Symbol, keywords};
@@ -175,6 +175,16 @@ impl<'a> StringReader<'a> {
175175
self.fatal_errs.clear();
176176
}
177177

178+
pub fn buffer_fatal_errors(&mut self) -> Vec<Diagnostic> {
179+
let mut buffer = Vec::new();
180+
181+
for err in self.fatal_errs.drain(..) {
182+
err.buffer(&mut buffer);
183+
}
184+
185+
buffer
186+
}
187+
178188
pub fn peek(&self) -> TokenAndSpan {
179189
// FIXME(pcwalton): Bad copy!
180190
TokenAndSpan {
@@ -251,6 +261,17 @@ impl<'a> StringReader<'a> {
251261
Ok(sr)
252262
}
253263

264+
pub fn new_or_buffered_errs(sess: &'a ParseSess,
265+
source_file: Lrc<syntax_pos::SourceFile>,
266+
override_span: Option<Span>) -> Result<Self, Vec<Diagnostic>> {
267+
let mut sr = StringReader::new_raw(sess, source_file, override_span);
268+
if sr.advance_token().is_err() {
269+
Err(sr.buffer_fatal_errors())
270+
} else {
271+
Ok(sr)
272+
}
273+
}
274+
254275
pub fn retokenize(sess: &'a ParseSess, mut span: Span) -> Self {
255276
let begin = sess.source_map().lookup_byte_offset(span.lo());
256277
let end = sess.source_map().lookup_byte_offset(span.hi());

src/libsyntax/parse/mod.rs

+42-8
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use ast::{self, CrateConfig, NodeId};
1515
use early_buffered_lints::{BufferedEarlyLint, BufferedEarlyLintId};
1616
use source_map::{SourceMap, FilePathMapping};
1717
use syntax_pos::{Span, SourceFile, FileName, MultiSpan};
18-
use errors::{Handler, ColorConfig, DiagnosticBuilder};
18+
use errors::{Handler, ColorConfig, Diagnostic, DiagnosticBuilder};
1919
use feature_gate::UnstableFeatures;
2020
use parse::parser::Parser;
2121
use ptr::P;
@@ -174,12 +174,21 @@ pub fn parse_stream_from_source_str(name: FileName, source: String, sess: &Parse
174174
source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span)
175175
}
176176

177-
// Create a new parser from a source string
177+
/// Create a new parser from a source string
178178
pub fn new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String)
179179
-> Parser {
180-
let mut parser = source_file_to_parser(sess, sess.source_map().new_source_file(name, source));
180+
panictry_buffer!(&sess.span_diagnostic, maybe_new_parser_from_source_str(sess, name, source))
181+
}
182+
183+
/// Create a new parser from a source string. Returns any buffered errors from lexing the initial
184+
/// token stream.
185+
pub fn maybe_new_parser_from_source_str(sess: &ParseSess, name: FileName, source: String)
186+
-> Result<Parser, Vec<Diagnostic>>
187+
{
188+
let mut parser = maybe_source_file_to_parser(sess,
189+
sess.source_map().new_source_file(name, source))?;
181190
parser.recurse_into_file_modules = false;
182-
parser
191+
Ok(parser)
183192
}
184193

185194
/// Create a new parser, handling errors as appropriate
@@ -204,14 +213,23 @@ crate fn new_sub_parser_from_file<'a>(sess: &'a ParseSess,
204213

205214
/// Given a source_file and config, return a parser
206215
fn source_file_to_parser(sess: & ParseSess, source_file: Lrc<SourceFile>) -> Parser {
216+
panictry_buffer!(&sess.span_diagnostic,
217+
maybe_source_file_to_parser(sess, source_file))
218+
}
219+
220+
/// Given a source_file and config, return a parser. Returns any buffered errors from lexing the
221+
/// initial token stream.
222+
fn maybe_source_file_to_parser(sess: &ParseSess, source_file: Lrc<SourceFile>)
223+
-> Result<Parser, Vec<Diagnostic>>
224+
{
207225
let end_pos = source_file.end_pos;
208-
let mut parser = stream_to_parser(sess, source_file_to_stream(sess, source_file, None));
226+
let mut parser = stream_to_parser(sess, maybe_file_to_stream(sess, source_file, None)?);
209227

210228
if parser.token == token::Eof && parser.span.is_dummy() {
211229
parser.span = Span::new(end_pos, end_pos, parser.span.ctxt());
212230
}
213231

214-
parser
232+
Ok(parser)
215233
}
216234

217235
// must preserve old name for now, because quote! from the *existing*
@@ -243,9 +261,25 @@ fn file_to_source_file(sess: &ParseSess, path: &Path, spanopt: Option<Span>)
243261
pub fn source_file_to_stream(sess: &ParseSess,
244262
source_file: Lrc<SourceFile>,
245263
override_span: Option<Span>) -> TokenStream {
246-
let mut srdr = lexer::StringReader::new(sess, source_file, override_span);
264+
panictry_buffer!(&sess.span_diagnostic, maybe_file_to_stream(sess, source_file, override_span))
265+
}
266+
267+
/// Given a source file, produce a sequence of token-trees. Returns any buffered errors from
268+
/// parsing the token tream.
269+
pub fn maybe_file_to_stream(sess: &ParseSess,
270+
source_file: Lrc<SourceFile>,
271+
override_span: Option<Span>) -> Result<TokenStream, Vec<Diagnostic>> {
272+
let mut srdr = lexer::StringReader::new_or_buffered_errs(sess, source_file, override_span)?;
247273
srdr.real_token();
248-
panictry!(srdr.parse_all_token_trees())
274+
275+
match srdr.parse_all_token_trees() {
276+
Ok(stream) => Ok(stream),
277+
Err(err) => {
278+
let mut buffer = Vec::with_capacity(1);
279+
err.buffer(&mut buffer);
280+
Err(buffer)
281+
}
282+
}
249283
}
250284

251285
/// Given stream and the `ParseSess`, produce a parser

src/tools/error_index_generator/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
#![feature(rustc_private)]
1212

13+
extern crate env_logger;
1314
extern crate syntax;
1415
extern crate rustdoc;
1516
extern crate serialize as rustc_serialize;
@@ -264,6 +265,7 @@ fn parse_args() -> (OutputFormat, PathBuf) {
264265
}
265266

266267
fn main() {
268+
env_logger::init();
267269
PLAYGROUND.with(|slot| {
268270
*slot.borrow_mut() = Some((None, String::from("https://play.rust-lang.org/")));
269271
});

0 commit comments

Comments
 (0)