Skip to content

Commit 88323e8

Browse files
committed
Auto merge of #6458 - ebroto:6022_parse_doctest, r=Manishearth
needless_doctest_main: handle correctly parse errors Before this change, finding an error when parsing a doctest would make Clippy exit without emitting an error. Now we properly catch a fatal error and ignore it. Also, if a doctest specifies an edition in the info line, it will be used when parsing it. changelog: needless_doctest_main: handle correctly parse errors Fixes #6022
2 parents 5c00931 + bb68ec6 commit 88323e8

File tree

3 files changed

+88
-62
lines changed

3 files changed

+88
-62
lines changed

clippy_lints/src/doc.rs

+79-60
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use rustc_middle::ty;
1414
use rustc_parse::maybe_new_parser_from_source_str;
1515
use rustc_session::parse::ParseSess;
1616
use rustc_session::{declare_tool_lint, impl_lint_pass};
17+
use rustc_span::edition::Edition;
1718
use rustc_span::source_map::{BytePos, FilePathMapping, MultiSpan, SourceMap, Span};
1819
use rustc_span::{sym, FileName, Pos};
1920
use std::io;
@@ -377,7 +378,7 @@ fn check_attrs<'a>(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, attrs
377378
check_doc(cx, valid_idents, events, &spans)
378379
}
379380

380-
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail", "edition2018"];
381+
const RUST_CODE: &[&str] = &["rust", "no_run", "should_panic", "compile_fail"];
381382

382383
fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize>)>>(
383384
cx: &LateContext<'_>,
@@ -400,13 +401,24 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
400401
let mut in_link = None;
401402
let mut in_heading = false;
402403
let mut is_rust = false;
404+
let mut edition = None;
403405
for (event, range) in events {
404406
match event {
405407
Start(CodeBlock(ref kind)) => {
406408
in_code = true;
407409
if let CodeBlockKind::Fenced(lang) = kind {
408-
is_rust =
409-
lang.is_empty() || !lang.contains("ignore") && lang.split(',').any(|i| RUST_CODE.contains(&i));
410+
for item in lang.split(',') {
411+
if item == "ignore" {
412+
is_rust = false;
413+
break;
414+
}
415+
if let Some(stripped) = item.strip_prefix("edition") {
416+
is_rust = true;
417+
edition = stripped.parse::<Edition>().ok();
418+
} else if item.is_empty() || RUST_CODE.contains(&item) {
419+
is_rust = true;
420+
}
421+
}
410422
}
411423
},
412424
End(CodeBlock(_)) => {
@@ -436,7 +448,8 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
436448
let (begin, span) = spans[index];
437449
if in_code {
438450
if is_rust {
439-
check_code(cx, &text, span);
451+
let edition = edition.unwrap_or_else(|| cx.tcx.sess.edition());
452+
check_code(cx, &text, edition, span);
440453
}
441454
} else {
442455
// Adjust for the beginning of the current `Event`
@@ -450,67 +463,73 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
450463
headers
451464
}
452465

453-
fn check_code(cx: &LateContext<'_>, text: &str, span: Span) {
454-
fn has_needless_main(code: &str) -> bool {
455-
let filename = FileName::anon_source_code(code);
456-
457-
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
458-
let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
459-
let handler = Handler::with_emitter(false, None, box emitter);
460-
let sess = ParseSess::with_span_handler(handler, sm);
461-
462-
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code.into()) {
463-
Ok(p) => p,
464-
Err(errs) => {
465-
for mut err in errs {
466-
err.cancel();
467-
}
468-
return false;
469-
},
470-
};
471-
472-
let mut relevant_main_found = false;
473-
loop {
474-
match parser.parse_item() {
475-
Ok(Some(item)) => match &item.kind {
476-
// Tests with one of these items are ignored
477-
ItemKind::Static(..)
478-
| ItemKind::Const(..)
479-
| ItemKind::ExternCrate(..)
480-
| ItemKind::ForeignMod(..) => return false,
481-
// We found a main function ...
482-
ItemKind::Fn(_, sig, _, Some(block)) if item.ident.name == sym::main => {
483-
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
484-
let returns_nothing = match &sig.decl.output {
485-
FnRetTy::Default(..) => true,
486-
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
487-
_ => false,
488-
};
489-
490-
if returns_nothing && !is_async && !block.stmts.is_empty() {
491-
// This main function should be linted, but only if there are no other functions
492-
relevant_main_found = true;
493-
} else {
494-
// This main function should not be linted, we're done
495-
return false;
466+
fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, span: Span) {
467+
fn has_needless_main(code: &str, edition: Edition) -> bool {
468+
rustc_driver::catch_fatal_errors(|| {
469+
rustc_span::with_session_globals(edition, || {
470+
let filename = FileName::anon_source_code(code);
471+
472+
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
473+
let emitter = EmitterWriter::new(box io::sink(), None, false, false, false, None, false);
474+
let handler = Handler::with_emitter(false, None, box emitter);
475+
let sess = ParseSess::with_span_handler(handler, sm);
476+
477+
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code.into()) {
478+
Ok(p) => p,
479+
Err(errs) => {
480+
for mut err in errs {
481+
err.cancel();
496482
}
483+
return false;
497484
},
498-
// Another function was found; this case is ignored too
499-
ItemKind::Fn(..) => return false,
500-
_ => {},
501-
},
502-
Ok(None) => break,
503-
Err(mut e) => {
504-
e.cancel();
505-
return false;
506-
},
507-
}
508-
}
485+
};
486+
487+
let mut relevant_main_found = false;
488+
loop {
489+
match parser.parse_item() {
490+
Ok(Some(item)) => match &item.kind {
491+
// Tests with one of these items are ignored
492+
ItemKind::Static(..)
493+
| ItemKind::Const(..)
494+
| ItemKind::ExternCrate(..)
495+
| ItemKind::ForeignMod(..) => return false,
496+
// We found a main function ...
497+
ItemKind::Fn(_, sig, _, Some(block)) if item.ident.name == sym::main => {
498+
let is_async = matches!(sig.header.asyncness, Async::Yes { .. });
499+
let returns_nothing = match &sig.decl.output {
500+
FnRetTy::Default(..) => true,
501+
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
502+
_ => false,
503+
};
504+
505+
if returns_nothing && !is_async && !block.stmts.is_empty() {
506+
// This main function should be linted, but only if there are no other functions
507+
relevant_main_found = true;
508+
} else {
509+
// This main function should not be linted, we're done
510+
return false;
511+
}
512+
},
513+
// Another function was found; this case is ignored too
514+
ItemKind::Fn(..) => return false,
515+
_ => {},
516+
},
517+
Ok(None) => break,
518+
Err(mut e) => {
519+
e.cancel();
520+
return false;
521+
},
522+
}
523+
}
509524

510-
relevant_main_found
525+
relevant_main_found
526+
})
527+
})
528+
.ok()
529+
.unwrap_or_default()
511530
}
512531

513-
if has_needless_main(text) {
532+
if has_needless_main(text, edition) {
514533
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
515534
}
516535
}

clippy_lints/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ extern crate rustc_ast;
2727
extern crate rustc_ast_pretty;
2828
extern crate rustc_attr;
2929
extern crate rustc_data_structures;
30+
extern crate rustc_driver;
3031
extern crate rustc_errors;
3132
extern crate rustc_hir;
3233
extern crate rustc_hir_pretty;

tests/ui/needless_doc_main.rs

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
/// ```
1111
///
1212
/// With an explicit return type it should lint too
13-
/// ```
13+
/// ```edition2015
1414
/// fn main() -> () {
1515
/// unimplemented!();
1616
/// }
@@ -39,7 +39,7 @@ fn bad_doctests() {}
3939
/// ```
4040
///
4141
/// This shouldn't lint either, because main is async:
42-
/// ```
42+
/// ```edition2018
4343
/// async fn main() {
4444
/// assert_eq!(42, ANSWER);
4545
/// }
@@ -128,6 +128,12 @@ fn bad_doctests() {}
128128
/// ```
129129
fn no_false_positives() {}
130130

131+
/// Yields a parse error when interpreted as rust code:
132+
/// ```
133+
/// r#"hi"
134+
/// ```
135+
fn issue_6022() {}
136+
131137
fn main() {
132138
bad_doctests();
133139
no_false_positives();

0 commit comments

Comments
 (0)