Skip to content

Commit 169b612

Browse files
authored
Auto merge of #35702 - jonathandturner:add_backtrace_labels, r=nikomatsakis
Replace macro backtraces with labeled local uses This PR (which builds on #35688) follows from the conversations on how best to [handle the macro backtraces](https://internals.rust-lang.org/t/improving-macro-errors/3809). The feeling there was that there were two different "groups" of users. The first group, the macro users, rarely (and likely never) want to see the macro backtrace. This is often more confusing to users as it will be talking about code they didn't write. The second group, the macro writers, are trying to debug a macro. They'll want to see something of the backtrace so that they can see where it's going wrong and what the steps were to get there. For the first group, it seems clear that we don't want to show *any* macro backtrace. For the second group, we want to show enough to help the macro writer. This PR uses a heuristic. It will only show any backtrace steps if they are in the same crate that is being compiled. This keeps errors in foreign crates from showing to users that didn't need them. Additionally, in asking around I repeated heard that the middle steps of the backtrace are rarely, if ever, used in practice. This PR takes and applies this knowledge. Now, instead of a full backtrace, the user is given the error underline inside of a local macro as well as the use site as a secondary label. This effectively means seeing the root of the error and the top of the backtrace, eliding the middle steps. Rather than being the "perfect solution", this PR opts to take an incremental step towards a better experience. Likely it would help to have additional macro debugging tools, as they could be much more verbose than we'd likely want to use in the error messages themselves. Some examples follow. **Example 1** Before: <img width="1275" alt="screen shot 2016-08-15 at 4 13 18 pm" src="https://cloud.githubusercontent.com/assets/547158/17682828/3948cea2-6303-11e6-93b4-b567e9d62848.png"> After: <img width="596" alt="screen shot 2016-08-15 at 4 13 03 pm" src="https://cloud.githubusercontent.com/assets/547158/17682832/3d670d8c-6303-11e6-9bdc-f30a30bf11ac.png"> **Example 2** Before: <img width="918" alt="screen shot 2016-08-15 at 4 14 35 pm" src="https://cloud.githubusercontent.com/assets/547158/17682870/722225de-6303-11e6-9175-336a3f7ce308.png"> After: <img width="483" alt="screen shot 2016-08-15 at 4 15 01 pm" src="https://cloud.githubusercontent.com/assets/547158/17682872/7582cf6c-6303-11e6-9235-f67960f6bd4c.png">
2 parents aef6971 + 54d42cc commit 169b612

21 files changed

+368
-77
lines changed

src/librustc_errors/emitter.rs

+103-41
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use self::Destination::*;
1212

1313
use syntax_pos::{COMMAND_LINE_SP, DUMMY_SP, FileMap, Span, MultiSpan, CharPos};
1414

15-
use {Level, CodeSuggestion, DiagnosticBuilder, CodeMapper};
15+
use {Level, CodeSuggestion, DiagnosticBuilder, SubDiagnostic, CodeMapper};
1616
use RenderSpan::*;
1717
use snippet::{StyledString, Style, Annotation, Line};
1818
use styled_buffer::StyledBuffer;
@@ -30,7 +30,10 @@ pub trait Emitter {
3030

3131
impl Emitter for EmitterWriter {
3232
fn emit(&mut self, db: &DiagnosticBuilder) {
33-
self.emit_messages_default(db);
33+
let mut primary_span = db.span.clone();
34+
let mut children = db.children.clone();
35+
self.fix_multispans_in_std_macros(&mut primary_span, &mut children);
36+
self.emit_messages_default(&db.level, &db.message, &db.code, &primary_span, &children);
3437
}
3538
}
3639

@@ -381,19 +384,100 @@ impl EmitterWriter {
381384
max
382385
}
383386

384-
fn get_max_line_num(&mut self, db: &DiagnosticBuilder) -> usize {
387+
fn get_max_line_num(&mut self, span: &MultiSpan, children: &Vec<SubDiagnostic>) -> usize {
385388
let mut max = 0;
386389

387-
let primary = self.get_multispan_max_line_num(&db.span);
390+
let primary = self.get_multispan_max_line_num(span);
388391
max = if primary > max { primary } else { max };
389392

390-
for sub in &db.children {
393+
for sub in children {
391394
let sub_result = self.get_multispan_max_line_num(&sub.span);
392395
max = if sub_result > max { primary } else { max };
393396
}
394397
max
395398
}
396399

400+
// This "fixes" MultiSpans that contain Spans that are pointing to locations inside of
401+
// <*macros>. Since these locations are often difficult to read, we move these Spans from
402+
// <*macros> to their corresponding use site.
403+
fn fix_multispan_in_std_macros(&mut self, span: &mut MultiSpan) -> bool {
404+
let mut spans_updated = false;
405+
406+
if let Some(ref cm) = self.cm {
407+
let mut before_after: Vec<(Span, Span)> = vec![];
408+
let mut new_labels: Vec<(Span, String)> = vec![];
409+
410+
// First, find all the spans in <*macros> and point instead at their use site
411+
for sp in span.primary_spans() {
412+
if (*sp == COMMAND_LINE_SP) || (*sp == DUMMY_SP) {
413+
continue;
414+
}
415+
if cm.span_to_filename(sp.clone()).contains("macros>") {
416+
let v = cm.macro_backtrace(sp.clone());
417+
if let Some(use_site) = v.last() {
418+
before_after.push((sp.clone(), use_site.call_site.clone()));
419+
}
420+
}
421+
for trace in cm.macro_backtrace(sp.clone()).iter().rev() {
422+
// Only show macro locations that are local
423+
// and display them like a span_note
424+
if let Some(def_site) = trace.def_site_span {
425+
if (def_site == COMMAND_LINE_SP) || (def_site == DUMMY_SP) {
426+
continue;
427+
}
428+
// Check to make sure we're not in any <*macros>
429+
if !cm.span_to_filename(def_site).contains("macros>") {
430+
new_labels.push((trace.call_site,
431+
"in this macro invocation".to_string()));
432+
break;
433+
}
434+
}
435+
}
436+
}
437+
for (label_span, label_text) in new_labels {
438+
span.push_span_label(label_span, label_text);
439+
}
440+
for sp_label in span.span_labels() {
441+
if (sp_label.span == COMMAND_LINE_SP) || (sp_label.span == DUMMY_SP) {
442+
continue;
443+
}
444+
if cm.span_to_filename(sp_label.span.clone()).contains("macros>") {
445+
let v = cm.macro_backtrace(sp_label.span.clone());
446+
if let Some(use_site) = v.last() {
447+
before_after.push((sp_label.span.clone(), use_site.call_site.clone()));
448+
}
449+
}
450+
}
451+
// After we have them, make sure we replace these 'bad' def sites with their use sites
452+
for (before, after) in before_after {
453+
span.replace(before, after);
454+
spans_updated = true;
455+
}
456+
}
457+
458+
spans_updated
459+
}
460+
461+
// This does a small "fix" for multispans by looking to see if it can find any that
462+
// point directly at <*macros>. Since these are often difficult to read, this
463+
// will change the span to point at the use site.
464+
fn fix_multispans_in_std_macros(&mut self,
465+
span: &mut MultiSpan,
466+
children: &mut Vec<SubDiagnostic>) {
467+
let mut spans_updated = self.fix_multispan_in_std_macros(span);
468+
for child in children.iter_mut() {
469+
spans_updated |= self.fix_multispan_in_std_macros(&mut child.span);
470+
}
471+
if spans_updated {
472+
children.push(SubDiagnostic {
473+
level: Level::Note,
474+
message: "this error originates in a macro from the standard library".to_string(),
475+
span: MultiSpan::new(),
476+
render_span: None
477+
});
478+
}
479+
}
480+
397481
fn emit_message_default(&mut self,
398482
msp: &MultiSpan,
399483
msg: &str,
@@ -528,10 +612,6 @@ impl EmitterWriter {
528612
}
529613
}
530614

531-
if let Some(ref primary_span) = msp.primary_span().as_ref() {
532-
self.render_macro_backtrace_old_school(primary_span, &mut buffer)?;
533-
}
534-
535615
// final step: take our styled buffer, render it, then output it
536616
emit_to_destination(&buffer.render(), level, &mut self.dst)?;
537617

@@ -578,26 +658,31 @@ impl EmitterWriter {
578658
}
579659
Ok(())
580660
}
581-
fn emit_messages_default(&mut self, db: &DiagnosticBuilder) {
582-
let max_line_num = self.get_max_line_num(db);
661+
fn emit_messages_default(&mut self,
662+
level: &Level,
663+
message: &String,
664+
code: &Option<String>,
665+
span: &MultiSpan,
666+
children: &Vec<SubDiagnostic>) {
667+
let max_line_num = self.get_max_line_num(span, children);
583668
let max_line_num_len = max_line_num.to_string().len();
584669

585-
match self.emit_message_default(&db.span,
586-
&db.message,
587-
&db.code,
588-
&db.level,
670+
match self.emit_message_default(span,
671+
message,
672+
code,
673+
level,
589674
max_line_num_len,
590675
false) {
591676
Ok(()) => {
592-
if !db.children.is_empty() {
677+
if !children.is_empty() {
593678
let mut buffer = StyledBuffer::new();
594679
draw_col_separator_no_space(&mut buffer, 0, max_line_num_len + 1);
595-
match emit_to_destination(&buffer.render(), &db.level, &mut self.dst) {
680+
match emit_to_destination(&buffer.render(), level, &mut self.dst) {
596681
Ok(()) => (),
597682
Err(e) => panic!("failed to emit error: {}", e)
598683
}
599684
}
600-
for child in &db.children {
685+
for child in children {
601686
match child.render_span {
602687
Some(FullSpan(ref msp)) => {
603688
match self.emit_message_default(msp,
@@ -640,29 +725,6 @@ impl EmitterWriter {
640725
_ => ()
641726
}
642727
}
643-
fn render_macro_backtrace_old_school(&mut self,
644-
sp: &Span,
645-
buffer: &mut StyledBuffer) -> io::Result<()> {
646-
if let Some(ref cm) = self.cm {
647-
for trace in cm.macro_backtrace(sp.clone()) {
648-
let line_offset = buffer.num_lines();
649-
650-
let mut diag_string =
651-
format!("in this expansion of {}", trace.macro_decl_name);
652-
if let Some(def_site_span) = trace.def_site_span {
653-
diag_string.push_str(
654-
&format!(" (defined in {})",
655-
cm.span_to_filename(def_site_span)));
656-
}
657-
let snippet = cm.span_to_string(trace.call_site);
658-
buffer.append(line_offset, &format!("{} ", snippet), Style::NoStyle);
659-
buffer.append(line_offset, "note", Style::Level(Level::Note));
660-
buffer.append(line_offset, ": ", Style::NoStyle);
661-
buffer.append(line_offset, &diag_string, Style::OldSchoolNoteText);
662-
}
663-
}
664-
Ok(())
665-
}
666728
}
667729

668730
fn draw_col_separator(buffer: &mut StyledBuffer, line: usize, col: usize) {

src/libsyntax_pos/lib.rs

+19
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,25 @@ impl MultiSpan {
221221
&self.primary_spans
222222
}
223223

224+
/// Replaces all occurances of one Span with another. Used to move Spans in areas that don't
225+
/// display well (like std macros). Returns true if replacements occurred.
226+
pub fn replace(&mut self, before: Span, after: Span) -> bool {
227+
let mut replacements_occurred = false;
228+
for primary_span in &mut self.primary_spans {
229+
if *primary_span == before {
230+
*primary_span = after;
231+
replacements_occurred = true;
232+
}
233+
}
234+
for span_label in &mut self.span_labels {
235+
if span_label.0 == before {
236+
span_label.0 = after;
237+
replacements_occurred = true;
238+
}
239+
}
240+
replacements_occurred
241+
}
242+
224243
/// Returns the strings to highlight. We always ensure that there
225244
/// is an entry for each of the primary spans -- for each primary
226245
/// span P, if there is at least one label with span P, we return

src/test/run-fail-fulldeps/qquote.rs

+2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,10 @@ fn main() {
4040
});
4141
let cx = &mut cx;
4242

43+
println!("{}", pprust::expr_to_string(&*quote_expr!(&cx, 23)));
4344
assert_eq!(pprust::expr_to_string(&*quote_expr!(&cx, 23)), "23");
4445

4546
let expr = quote_expr!(&cx, let x isize = 20;);
47+
println!("{}", pprust::expr_to_string(&*expr));
4648
assert_eq!(pprust::expr_to_string(&*expr), "let x isize = 20;");
4749
}

src/test/compile-fail/bad-format-args.rs renamed to src/test/ui/codemap_tests/bad-format-args.rs

-7
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,6 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// error-pattern: requires at least a format string argument
12-
// error-pattern: in this expansion
13-
14-
// error-pattern: expected token: `,`
15-
// error-pattern: in this expansion
16-
// error-pattern: in this expansion
17-
1811
fn main() {
1912
format!();
2013
format!("" 1);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
error: requires at least a format string argument
2+
--> $DIR/bad-format-args.rs:12:5
3+
|
4+
12 | format!();
5+
| ^^^^^^^^^^
6+
|
7+
= note: this error originates in a macro from the standard library
8+
9+
error: expected token: `,`
10+
--> $DIR/bad-format-args.rs:13:5
11+
|
12+
13 | format!("" 1);
13+
| ^^^^^^^^^^^^^^
14+
|
15+
= note: this error originates in a macro from the standard library
16+
17+
error: expected token: `,`
18+
--> $DIR/bad-format-args.rs:14:5
19+
|
20+
14 | format!("", 1 1);
21+
| ^^^^^^^^^^^^^^^^^
22+
|
23+
= note: this error originates in a macro from the standard library
24+
25+
error: aborting due to 3 previous errors
26+

src/test/compile-fail/issue-28308.rs renamed to src/test/ui/codemap_tests/issue-28308.rs

-4
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,6 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11-
// this error is dispayed in `<std macros>`
12-
// error-pattern:cannot apply unary operator `!` to type `&'static str`
13-
// error-pattern:in this expansion of assert!
14-
1511
fn main() {
1612
assert!("foo");
1713
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: cannot apply unary operator `!` to type `&'static str`
2+
--> $DIR/issue-28308.rs:12:5
3+
|
4+
12 | assert!("foo");
5+
| ^^^^^^^^^^^^^^^
6+
|
7+
= note: this error originates in a macro from the standard library
8+
9+
error: aborting due to previous error
10+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
let x = vec![];
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0282]: unable to infer enough type information about `_`
2+
--> $DIR/repair_span_std_macros.rs:12:13
3+
|
4+
12 | let x = vec![];
5+
| ^^^^^^ cannot infer type for `_`
6+
|
7+
= note: type annotations or generic parameter binding required
8+
= note: this error originates in a macro from the standard library
9+
10+
error: aborting due to previous error
11+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![crate_type = "dylib"]
12+
13+
pub fn print(_args: std::fmt::Arguments) {}
14+
15+
#[macro_export]
16+
macro_rules! myprint {
17+
($($arg:tt)*) => (print(format_args!($($arg)*)));
18+
}
19+
20+
#[macro_export]
21+
macro_rules! myprintln {
22+
($fmt:expr) => (myprint!(concat!($fmt, "\n")));
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2012 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// aux-build:extern_macro_crate.rs
12+
#[macro_use(myprintln, myprint)]
13+
extern crate extern_macro_crate;
14+
15+
fn main() {
16+
myprintln!("{}"); //~ ERROR in this macro
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: invalid reference to argument `0` (no arguments given)
2+
--> $DIR/main.rs:16:5
3+
|
4+
16 | myprintln!("{}"); //~ ERROR in this macro
5+
| ^^^^^^^^^^^^^^^^^
6+
|
7+
= note: this error originates in a macro from the standard library
8+
9+
error: aborting due to previous error
10+

src/test/ui/macros/bad_hello.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
fn main() {
12+
println!(3 + 4);
13+
}

0 commit comments

Comments
 (0)