Skip to content

Commit 8be132e

Browse files
committed
Initial diagnostic API for proc-macros.
This commit introduces the ability to create and emit `Diagnostic` structures from proc-macros, allowing for proc-macro authors to emit warning, error, note, and help messages just like the compiler does.
1 parent a0c3bd2 commit 8be132e

File tree

9 files changed

+314
-1
lines changed

9 files changed

+314
-1
lines changed

src/Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/libproc_macro/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ crate-type = ["dylib"]
1010
[dependencies]
1111
syntax = { path = "../libsyntax" }
1212
syntax_pos = { path = "../libsyntax_pos" }
13+
rustc_errors = { path = "../librustc_errors" }

src/libproc_macro/diagnostic.rs

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright 2017 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+
use Span;
12+
13+
use rustc_errors as rustc;
14+
15+
/// An enum representing a diagnostic level.
16+
#[unstable(feature = "proc_macro", issue = "38356")]
17+
#[derive(Copy, Clone, Debug)]
18+
pub enum Level {
19+
/// An error.
20+
Error,
21+
/// A warning.
22+
Warning,
23+
/// A note.
24+
Note,
25+
/// A help message.
26+
Help,
27+
#[doc(hidden)]
28+
__Nonexhaustive,
29+
}
30+
31+
/// A structure representing a diagnostic message and associated children
32+
/// messages.
33+
#[unstable(feature = "proc_macro", issue = "38356")]
34+
#[derive(Clone, Debug)]
35+
pub struct Diagnostic {
36+
level: Level,
37+
message: String,
38+
span: Option<Span>,
39+
children: Vec<Diagnostic>
40+
}
41+
42+
macro_rules! diagnostic_child_methods {
43+
($spanned:ident, $regular:ident, $level:expr) => (
44+
/// Add a new child diagnostic message to `self` with the level
45+
/// identified by this methods name with the given `span` and `message`.
46+
#[unstable(feature = "proc_macro", issue = "38356")]
47+
pub fn $spanned<T: Into<String>>(mut self, span: Span, message: T) -> Diagnostic {
48+
self.children.push(Diagnostic::spanned(span, $level, message));
49+
self
50+
}
51+
52+
/// Add a new child diagnostic message to `self` with the level
53+
/// identified by this method's name with the given `message`.
54+
#[unstable(feature = "proc_macro", issue = "38356")]
55+
pub fn $regular<T: Into<String>>(mut self, message: T) -> Diagnostic {
56+
self.children.push(Diagnostic::new($level, message));
57+
self
58+
}
59+
)
60+
}
61+
62+
impl Diagnostic {
63+
/// Create a new diagnostic with the given `level` and `message`.
64+
#[unstable(feature = "proc_macro", issue = "38356")]
65+
pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
66+
Diagnostic {
67+
level: level,
68+
message: message.into(),
69+
span: None,
70+
children: vec![]
71+
}
72+
}
73+
74+
/// Create a new diagnostic with the given `level` and `message` pointing to
75+
/// the given `span`.
76+
#[unstable(feature = "proc_macro", issue = "38356")]
77+
pub fn spanned<T: Into<String>>(span: Span, level: Level, message: T) -> Diagnostic {
78+
Diagnostic {
79+
level: level,
80+
message: message.into(),
81+
span: Some(span),
82+
children: vec![]
83+
}
84+
}
85+
86+
diagnostic_child_methods!(span_error, error, Level::Error);
87+
diagnostic_child_methods!(span_warning, warning, Level::Warning);
88+
diagnostic_child_methods!(span_note, note, Level::Note);
89+
diagnostic_child_methods!(span_help, help, Level::Help);
90+
91+
/// Returns the diagnostic `level` for `self`.
92+
#[unstable(feature = "proc_macro", issue = "38356")]
93+
pub fn level(&self) -> Level {
94+
self.level
95+
}
96+
97+
/// Emit the diagnostic.
98+
#[unstable(feature = "proc_macro", issue = "38356")]
99+
pub fn emit(self) {
100+
::__internal::with_sess(move |(sess, _)| {
101+
let handler = &sess.span_diagnostic;
102+
let level = __internal::level_to_internal_level(self.level);
103+
let mut diag = rustc::DiagnosticBuilder::new(handler, level, &*self.message);
104+
105+
if let Some(span) = self.span {
106+
diag.set_span(span.0);
107+
}
108+
109+
for child in self.children {
110+
let span = child.span.map(|s| s.0);
111+
let level = __internal::level_to_internal_level(child.level);
112+
diag.sub(level, &*child.message, span);
113+
}
114+
115+
diag.emit();
116+
});
117+
}
118+
}
119+
120+
#[unstable(feature = "proc_macro_internals", issue = "27812")]
121+
#[doc(hidden)]
122+
pub mod __internal {
123+
use super::{Level, rustc};
124+
125+
pub fn level_to_internal_level(level: Level) -> rustc::Level {
126+
match level {
127+
Level::Error => rustc::Level::Error,
128+
Level::Warning => rustc::Level::Warning,
129+
Level::Note => rustc::Level::Note,
130+
Level::Help => rustc::Level::Help,
131+
Level::__Nonexhaustive => unreachable!("Level::__Nonexhaustive")
132+
}
133+
}
134+
}

src/libproc_macro/lib.rs

+22
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
#[macro_use]
4343
extern crate syntax;
4444
extern crate syntax_pos;
45+
extern crate rustc_errors;
46+
47+
mod diagnostic;
48+
49+
#[unstable(feature = "proc_macro", issue = "38356")]
50+
pub use diagnostic::{Diagnostic, Level};
4551

4652
use std::{ascii, fmt, iter};
4753
use std::str::FromStr;
@@ -191,12 +197,28 @@ pub fn quote_span(span: Span) -> TokenStream {
191197
TokenStream(quote::Quote::quote(&span.0))
192198
}
193199

200+
macro_rules! diagnostic_method {
201+
($name:ident, $level:expr) => (
202+
/// Create a new `Diagnostic` with the given `message` at the span
203+
/// `self`.
204+
#[unstable(feature = "proc_macro", issue = "38356")]
205+
pub fn $name<T: Into<String>>(self, message: T) -> Diagnostic {
206+
Diagnostic::spanned(self, $level, message)
207+
}
208+
)
209+
}
210+
194211
impl Span {
195212
/// The span of the invocation of the current procedural macro.
196213
#[unstable(feature = "proc_macro", issue = "38356")]
197214
pub fn call_site() -> Span {
198215
::__internal::with_sess(|(_, mark)| Span(mark.expn_info().unwrap().call_site))
199216
}
217+
218+
diagnostic_method!(error, Level::Error);
219+
diagnostic_method!(warning, Level::Warning);
220+
diagnostic_method!(note, Level::Note);
221+
diagnostic_method!(help, Level::Help);
200222
}
201223

202224
/// A single token or a delimited sequence of token trees (e.g. `[1, (), ..]`).

src/librustc_errors/diagnostic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ impl Diagnostic {
288288

289289
/// Convenience function for internal use, clients should use one of the
290290
/// public methods above.
291-
fn sub(&mut self,
291+
pub(crate) fn sub(&mut self,
292292
level: Level,
293293
message: &str,
294294
span: MultiSpan,

src/librustc_errors/diagnostic_builder.rs

+13
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ impl<'a> DiagnosticBuilder<'a> {
110110
// }
111111
}
112112

113+
/// Convenience function for internal use, clients should use one of the
114+
/// span_* methods instead.
115+
pub fn sub<S: Into<MultiSpan>>(
116+
&mut self,
117+
level: Level,
118+
message: &str,
119+
span: Option<S>,
120+
) -> &mut Self {
121+
let span = span.map(|s| s.into()).unwrap_or(MultiSpan::new());
122+
self.diagnostic.sub(level, message, span, None);
123+
self
124+
}
125+
113126
/// Delay emission of this diagnostic as a bug.
114127
///
115128
/// This can be useful in contexts where an error indicates a bug but
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright 2017 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+
// no-prefer-dynamic
12+
#![feature(proc_macro)]
13+
#![crate_type = "proc-macro"]
14+
15+
extern crate proc_macro;
16+
17+
use proc_macro::{TokenStream, TokenNode, Span, Diagnostic};
18+
19+
fn parse(input: TokenStream) -> Result<(), Diagnostic> {
20+
let mut count = 0;
21+
let mut last_span = Span::default();
22+
for tree in input {
23+
let span = tree.span;
24+
if count >= 3 {
25+
return Err(span.error(format!("expected EOF, found `{}`.", tree))
26+
.span_note(last_span, "last good input was here")
27+
.help("input must be: `===`"))
28+
}
29+
30+
if let TokenNode::Op('=', _) = tree.kind {
31+
count += 1;
32+
} else {
33+
return Err(span.error(format!("expected `=`, found `{}`.", tree)));
34+
}
35+
36+
last_span = span;
37+
}
38+
39+
if count < 3 {
40+
return Err(Span::default()
41+
.error(format!("found {} equal signs, need exactly 3", count))
42+
.help("input must be: `===`"))
43+
}
44+
45+
Ok(())
46+
}
47+
48+
#[proc_macro]
49+
pub fn three_equals(input: TokenStream) -> TokenStream {
50+
if let Err(diag) = parse(input) {
51+
diag.emit();
52+
return TokenStream::empty();
53+
}
54+
55+
"3".parse().unwrap()
56+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
// aux-build:three-equals.rs
12+
// ignore-stage1
13+
14+
#![feature(proc_macro)]
15+
16+
extern crate three_equals;
17+
18+
use three_equals::three_equals;
19+
20+
fn main() {
21+
// This one is okay.
22+
three_equals!(===);
23+
24+
// Need exactly three equals.
25+
three_equals!(==);
26+
27+
// Need exactly three equals.
28+
three_equals!(=====);
29+
30+
// Only equals accepted.
31+
three_equals!(abc);
32+
33+
// Only equals accepted.
34+
three_equals!(!!);
35+
36+
// Only three characters expected.
37+
three_equals!(===a);
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
error: found 2 equal signs, need exactly 3
2+
--> $DIR/three-equals.rs:25:5
3+
|
4+
25 | three_equals!(==);
5+
| ^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: input must be: `===`
8+
9+
error: expected EOF, found `=`.
10+
--> $DIR/three-equals.rs:28:21
11+
|
12+
28 | three_equals!(=====);
13+
| ^^
14+
|
15+
note: last good input was here
16+
--> $DIR/three-equals.rs:28:21
17+
|
18+
28 | three_equals!(=====);
19+
| ^^
20+
= help: input must be: `===`
21+
22+
error: expected `=`, found `abc`.
23+
--> $DIR/three-equals.rs:31:19
24+
|
25+
31 | three_equals!(abc);
26+
| ^^^
27+
28+
error: expected `=`, found `!`.
29+
--> $DIR/three-equals.rs:34:19
30+
|
31+
34 | three_equals!(!!);
32+
| ^
33+
34+
error: expected EOF, found `a`.
35+
--> $DIR/three-equals.rs:37:22
36+
|
37+
37 | three_equals!(===a);
38+
| ^
39+
|
40+
note: last good input was here
41+
--> $DIR/three-equals.rs:37:21
42+
|
43+
37 | three_equals!(===a);
44+
| ^
45+
= help: input must be: `===`
46+
47+
error: aborting due to 5 previous errors
48+

0 commit comments

Comments
 (0)