Auto merge of #47274 - Manishearth:rustdoc-span, r=QuietMisdreavus
Use correct line offsets for doctests Not yet tested. This doesn't handle char positions. It could if I collected a map of char offsets and lines, but this is a bit more work and requires hooking into the parser much more (unsure if it's possible). r? @QuietMisdreavus (fixes #45868)
This commit is contained in:
commit
5d6f6e65ff
7 changed files with 84 additions and 11 deletions
|
@ -990,7 +990,7 @@ impl EmitterWriter {
|
||||||
buffer.append(buffer_msg_line_offset,
|
buffer.append(buffer_msg_line_offset,
|
||||||
&format!("{}:{}:{}",
|
&format!("{}:{}:{}",
|
||||||
loc.file.name,
|
loc.file.name,
|
||||||
loc.line,
|
cm.doctest_offset_line(loc.line),
|
||||||
loc.col.0 + 1),
|
loc.col.0 + 1),
|
||||||
Style::LineAndColumn);
|
Style::LineAndColumn);
|
||||||
for _ in 0..max_line_num_len {
|
for _ in 0..max_line_num_len {
|
||||||
|
@ -1000,7 +1000,7 @@ impl EmitterWriter {
|
||||||
buffer.prepend(0,
|
buffer.prepend(0,
|
||||||
&format!("{}:{}:{} - ",
|
&format!("{}:{}:{} - ",
|
||||||
loc.file.name,
|
loc.file.name,
|
||||||
loc.line,
|
cm.doctest_offset_line(loc.line),
|
||||||
loc.col.0 + 1),
|
loc.col.0 + 1),
|
||||||
Style::LineAndColumn);
|
Style::LineAndColumn);
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ pub trait CodeMapper {
|
||||||
fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span>;
|
fn merge_spans(&self, sp_lhs: Span, sp_rhs: Span) -> Option<Span>;
|
||||||
fn call_span_if_macro(&self, sp: Span) -> Span;
|
fn call_span_if_macro(&self, sp: Span) -> Span;
|
||||||
fn ensure_filemap_source_present(&self, file_map: Rc<FileMap>) -> bool;
|
fn ensure_filemap_source_present(&self, file_map: Rc<FileMap>) -> bool;
|
||||||
|
fn doctest_offset_line(&self, line: usize) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeSuggestion {
|
impl CodeSuggestion {
|
||||||
|
|
|
@ -196,7 +196,7 @@ impl<'a, I: Iterator<Item = Event<'a>>> Iterator for CodeBlocks<'a, I> {
|
||||||
.map(|l| map_line(l).for_code())
|
.map(|l| map_line(l).for_code())
|
||||||
.collect::<Vec<&str>>().join("\n");
|
.collect::<Vec<&str>>().join("\n");
|
||||||
let krate = krate.as_ref().map(|s| &**s);
|
let krate = krate.as_ref().map(|s| &**s);
|
||||||
let test = test::make_test(&test, krate, false,
|
let (test, _) = test::make_test(&test, krate, false,
|
||||||
&Default::default());
|
&Default::default());
|
||||||
let channel = if test.contains("#![feature(") {
|
let channel = if test.contains("#![feature(") {
|
||||||
"&version=nightly"
|
"&version=nightly"
|
||||||
|
@ -607,7 +607,7 @@ pub fn render(w: &mut fmt::Formatter,
|
||||||
.map(|l| map_line(l).for_code())
|
.map(|l| map_line(l).for_code())
|
||||||
.collect::<Vec<&str>>().join("\n");
|
.collect::<Vec<&str>>().join("\n");
|
||||||
let krate = krate.as_ref().map(|s| &**s);
|
let krate = krate.as_ref().map(|s| &**s);
|
||||||
let test = test::make_test(&test, krate, false,
|
let (test, _) = test::make_test(&test, krate, false,
|
||||||
&Default::default());
|
&Default::default());
|
||||||
let channel = if test.contains("#![feature(") {
|
let channel = if test.contains("#![feature(") {
|
||||||
"&version=nightly"
|
"&version=nightly"
|
||||||
|
|
|
@ -176,7 +176,8 @@ fn scrape_test_config(krate: &::rustc::hir::Crate) -> TestOptions {
|
||||||
opts
|
opts
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_test(test: &str, cratename: &str, filename: &FileName, cfgs: Vec<String>, libs: SearchPaths,
|
fn run_test(test: &str, cratename: &str, filename: &FileName, line: usize,
|
||||||
|
cfgs: Vec<String>, libs: SearchPaths,
|
||||||
externs: Externs,
|
externs: Externs,
|
||||||
should_panic: bool, no_run: bool, as_test_harness: bool,
|
should_panic: bool, no_run: bool, as_test_harness: bool,
|
||||||
compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
|
compile_fail: bool, mut error_codes: Vec<String>, opts: &TestOptions,
|
||||||
|
@ -184,7 +185,7 @@ fn run_test(test: &str, cratename: &str, filename: &FileName, cfgs: Vec<String>,
|
||||||
linker: Option<PathBuf>) {
|
linker: Option<PathBuf>) {
|
||||||
// the test harness wants its own `main` & top level functions, so
|
// the test harness wants its own `main` & top level functions, so
|
||||||
// never wrap the test in `fn main() { ... }`
|
// never wrap the test in `fn main() { ... }`
|
||||||
let test = make_test(test, Some(cratename), as_test_harness, opts);
|
let (test, line_offset) = make_test(test, Some(cratename), as_test_harness, opts);
|
||||||
// FIXME(#44940): if doctests ever support path remapping, then this filename
|
// FIXME(#44940): if doctests ever support path remapping, then this filename
|
||||||
// needs to be the result of CodeMap::span_to_unmapped_path
|
// needs to be the result of CodeMap::span_to_unmapped_path
|
||||||
let input = config::Input::Str {
|
let input = config::Input::Str {
|
||||||
|
@ -234,7 +235,9 @@ fn run_test(test: &str, cratename: &str, filename: &FileName, cfgs: Vec<String>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let data = Arc::new(Mutex::new(Vec::new()));
|
let data = Arc::new(Mutex::new(Vec::new()));
|
||||||
let codemap = Rc::new(CodeMap::new(sessopts.file_path_mapping()));
|
let codemap = Rc::new(CodeMap::new_doctest(
|
||||||
|
sessopts.file_path_mapping(), filename.clone(), line as isize - line_offset as isize
|
||||||
|
));
|
||||||
let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
|
let emitter = errors::emitter::EmitterWriter::new(box Sink(data.clone()),
|
||||||
Some(codemap.clone()),
|
Some(codemap.clone()),
|
||||||
false);
|
false);
|
||||||
|
@ -326,13 +329,14 @@ fn run_test(test: &str, cratename: &str, filename: &FileName, cfgs: Vec<String>,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Makes the test file. Also returns the number of lines before the code begins
|
||||||
pub fn make_test(s: &str,
|
pub fn make_test(s: &str,
|
||||||
cratename: Option<&str>,
|
cratename: Option<&str>,
|
||||||
dont_insert_main: bool,
|
dont_insert_main: bool,
|
||||||
opts: &TestOptions)
|
opts: &TestOptions)
|
||||||
-> String {
|
-> (String, usize) {
|
||||||
let (crate_attrs, everything_else) = partition_source(s);
|
let (crate_attrs, everything_else) = partition_source(s);
|
||||||
|
let mut line_offset = 0;
|
||||||
let mut prog = String::new();
|
let mut prog = String::new();
|
||||||
|
|
||||||
if opts.attrs.is_empty() {
|
if opts.attrs.is_empty() {
|
||||||
|
@ -341,11 +345,13 @@ pub fn make_test(s: &str,
|
||||||
// commonly used to make tests fail in case they trigger warnings, so having this there in
|
// commonly used to make tests fail in case they trigger warnings, so having this there in
|
||||||
// that case may cause some tests to pass when they shouldn't have.
|
// that case may cause some tests to pass when they shouldn't have.
|
||||||
prog.push_str("#![allow(unused)]\n");
|
prog.push_str("#![allow(unused)]\n");
|
||||||
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
|
// Next, any attributes that came from the crate root via #![doc(test(attr(...)))].
|
||||||
for attr in &opts.attrs {
|
for attr in &opts.attrs {
|
||||||
prog.push_str(&format!("#![{}]\n", attr));
|
prog.push_str(&format!("#![{}]\n", attr));
|
||||||
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now push any outer attributes from the example, assuming they
|
// Now push any outer attributes from the example, assuming they
|
||||||
|
@ -358,6 +364,7 @@ pub fn make_test(s: &str,
|
||||||
if let Some(cratename) = cratename {
|
if let Some(cratename) = cratename {
|
||||||
if s.contains(cratename) {
|
if s.contains(cratename) {
|
||||||
prog.push_str(&format!("extern crate {};\n", cratename));
|
prog.push_str(&format!("extern crate {};\n", cratename));
|
||||||
|
line_offset += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -379,6 +386,7 @@ pub fn make_test(s: &str,
|
||||||
prog.push_str(&everything_else);
|
prog.push_str(&everything_else);
|
||||||
} else {
|
} else {
|
||||||
prog.push_str("fn main() {\n");
|
prog.push_str("fn main() {\n");
|
||||||
|
line_offset += 1;
|
||||||
prog.push_str(&everything_else);
|
prog.push_str(&everything_else);
|
||||||
prog = prog.trim().into();
|
prog = prog.trim().into();
|
||||||
prog.push_str("\n}");
|
prog.push_str("\n}");
|
||||||
|
@ -386,7 +394,7 @@ pub fn make_test(s: &str,
|
||||||
|
|
||||||
info!("final test program: {}", prog);
|
info!("final test program: {}", prog);
|
||||||
|
|
||||||
prog
|
(prog, line_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME(aburka): use a real parser to deal with multiline attributes
|
// FIXME(aburka): use a real parser to deal with multiline attributes
|
||||||
|
@ -543,6 +551,7 @@ impl Collector {
|
||||||
run_test(&test,
|
run_test(&test,
|
||||||
&cratename,
|
&cratename,
|
||||||
&filename,
|
&filename,
|
||||||
|
line,
|
||||||
cfgs,
|
cfgs,
|
||||||
libs,
|
libs,
|
||||||
externs,
|
externs,
|
||||||
|
|
|
@ -131,6 +131,9 @@ pub struct CodeMap {
|
||||||
// -Zremap-path-prefix to all FileMaps allocated within this CodeMap.
|
// -Zremap-path-prefix to all FileMaps allocated within this CodeMap.
|
||||||
path_mapping: FilePathMapping,
|
path_mapping: FilePathMapping,
|
||||||
stable_id_to_filemap: RefCell<FxHashMap<StableFilemapId, Rc<FileMap>>>,
|
stable_id_to_filemap: RefCell<FxHashMap<StableFilemapId, Rc<FileMap>>>,
|
||||||
|
/// In case we are in a doctest, replace all file names with the PathBuf,
|
||||||
|
/// and add the given offsets to the line info
|
||||||
|
doctest_offset: Option<(FileName, isize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CodeMap {
|
impl CodeMap {
|
||||||
|
@ -140,9 +143,19 @@ impl CodeMap {
|
||||||
file_loader: Box::new(RealFileLoader),
|
file_loader: Box::new(RealFileLoader),
|
||||||
path_mapping,
|
path_mapping,
|
||||||
stable_id_to_filemap: RefCell::new(FxHashMap()),
|
stable_id_to_filemap: RefCell::new(FxHashMap()),
|
||||||
|
doctest_offset: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_doctest(path_mapping: FilePathMapping,
|
||||||
|
file: FileName, line: isize) -> CodeMap {
|
||||||
|
CodeMap {
|
||||||
|
doctest_offset: Some((file, line)),
|
||||||
|
..CodeMap::new(path_mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
pub fn with_file_loader(file_loader: Box<FileLoader>,
|
pub fn with_file_loader(file_loader: Box<FileLoader>,
|
||||||
path_mapping: FilePathMapping)
|
path_mapping: FilePathMapping)
|
||||||
-> CodeMap {
|
-> CodeMap {
|
||||||
|
@ -151,6 +164,7 @@ impl CodeMap {
|
||||||
file_loader,
|
file_loader,
|
||||||
path_mapping,
|
path_mapping,
|
||||||
stable_id_to_filemap: RefCell::new(FxHashMap()),
|
stable_id_to_filemap: RefCell::new(FxHashMap()),
|
||||||
|
doctest_offset: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -164,7 +178,12 @@ impl CodeMap {
|
||||||
|
|
||||||
pub fn load_file(&self, path: &Path) -> io::Result<Rc<FileMap>> {
|
pub fn load_file(&self, path: &Path) -> io::Result<Rc<FileMap>> {
|
||||||
let src = self.file_loader.read_file(path)?;
|
let src = self.file_loader.read_file(path)?;
|
||||||
Ok(self.new_filemap(path.to_owned().into(), src))
|
let filename = if let Some((ref name, _)) = self.doctest_offset {
|
||||||
|
name.clone()
|
||||||
|
} else {
|
||||||
|
path.to_owned().into()
|
||||||
|
};
|
||||||
|
Ok(self.new_filemap(filename, src))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn files(&self) -> Ref<Vec<Rc<FileMap>>> {
|
pub fn files(&self) -> Ref<Vec<Rc<FileMap>>> {
|
||||||
|
@ -303,6 +322,18 @@ impl CodeMap {
|
||||||
pos.col.to_usize() + 1)).to_string()
|
pos.col.to_usize() + 1)).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If there is a doctest_offset, apply it to the line
|
||||||
|
pub fn doctest_offset_line(&self, mut orig: usize) -> usize {
|
||||||
|
if let Some((_, line)) = self.doctest_offset {
|
||||||
|
if line >= 0 {
|
||||||
|
orig = orig + line as usize;
|
||||||
|
} else {
|
||||||
|
orig = orig - (-line) as usize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
orig
|
||||||
|
}
|
||||||
|
|
||||||
/// Lookup source information about a BytePos
|
/// Lookup source information about a BytePos
|
||||||
pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
|
pub fn lookup_char_pos(&self, pos: BytePos) -> Loc {
|
||||||
let chpos = self.bytepos_to_file_charpos(pos);
|
let chpos = self.bytepos_to_file_charpos(pos);
|
||||||
|
@ -681,6 +712,9 @@ impl CodeMapper for CodeMap {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn doctest_offset_line(&self, line: usize) -> usize {
|
||||||
|
self.doctest_offset_line(line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|
8
src/test/run-make/rustdoc-error-lines/Makefile
Normal file
8
src/test/run-make/rustdoc-error-lines/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
-include ../tools.mk
|
||||||
|
|
||||||
|
# Test that hir-tree output doens't crash and includes
|
||||||
|
# the string constant we would expect to see.
|
||||||
|
|
||||||
|
all:
|
||||||
|
$(RUSTDOC) --test input.rs > $(TMPDIR)/output || true
|
||||||
|
$(CGREP) 'input.rs:17:15' < $(TMPDIR)/output
|
21
src/test/run-make/rustdoc-error-lines/input.rs
Normal file
21
src/test/run-make/rustdoc-error-lines/input.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
// Test for #45868
|
||||||
|
|
||||||
|
// random #![feature] to ensure that crate attrs
|
||||||
|
// do not offset things
|
||||||
|
/// ```rust
|
||||||
|
/// #![feature(nll)]
|
||||||
|
/// let x: char = 1;
|
||||||
|
/// ```
|
||||||
|
pub fn foo() {
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue