Rollup merge of #44125 - SergioBenitez:master, r=nrc
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. The API is somewhat based on the diagnostic API already present in `rustc` with several changes that improve usability. The entry point into the diagnostic API is a new `Diagnostic` type which is primarily created through new `error`, `warning`, `help`, and `note` methods on `Span`. The `Diagnostic` type records the diagnostic level, message, and optional `Span` for the top-level diagnostic and contains a `Vec` of all of the child diagnostics. Child diagnostics can be added through builder methods on `Diagnostic`. A typical use of the API may look like: ```rust let token = parse_token(); let val = parse_val(); val.span .error(format!("expected A but found {}", val)) .span_note(token.span, "because of this token") .help("consider using a different token") .emit(); ``` cc @jseyfried @nrc @dtolnay @alexcrichton
This commit is contained in:
commit
fd4f362b30
1
src/Cargo.lock
generated
1
src/Cargo.lock
generated
@ -1038,6 +1038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
name = "proc_macro"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"rustc_errors 0.0.0",
|
||||
"syntax 0.0.0",
|
||||
"syntax_pos 0.0.0",
|
||||
]
|
||||
|
@ -10,3 +10,4 @@ crate-type = ["dylib"]
|
||||
[dependencies]
|
||||
syntax = { path = "../libsyntax" }
|
||||
syntax_pos = { path = "../libsyntax_pos" }
|
||||
rustc_errors = { path = "../librustc_errors" }
|
||||
|
134
src/libproc_macro/diagnostic.rs
Normal file
134
src/libproc_macro/diagnostic.rs
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
use Span;
|
||||
|
||||
use rustc_errors as rustc;
|
||||
|
||||
/// An enum representing a diagnostic level.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Level {
|
||||
/// An error.
|
||||
Error,
|
||||
/// A warning.
|
||||
Warning,
|
||||
/// A note.
|
||||
Note,
|
||||
/// A help message.
|
||||
Help,
|
||||
#[doc(hidden)]
|
||||
__Nonexhaustive,
|
||||
}
|
||||
|
||||
/// A structure representing a diagnostic message and associated children
|
||||
/// messages.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Diagnostic {
|
||||
level: Level,
|
||||
message: String,
|
||||
span: Option<Span>,
|
||||
children: Vec<Diagnostic>
|
||||
}
|
||||
|
||||
macro_rules! diagnostic_child_methods {
|
||||
($spanned:ident, $regular:ident, $level:expr) => (
|
||||
/// Add a new child diagnostic message to `self` with the level
|
||||
/// identified by this methods name with the given `span` and `message`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn $spanned<T: Into<String>>(mut self, span: Span, message: T) -> Diagnostic {
|
||||
self.children.push(Diagnostic::spanned(span, $level, message));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a new child diagnostic message to `self` with the level
|
||||
/// identified by this method's name with the given `message`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn $regular<T: Into<String>>(mut self, message: T) -> Diagnostic {
|
||||
self.children.push(Diagnostic::new($level, message));
|
||||
self
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl Diagnostic {
|
||||
/// Create a new diagnostic with the given `level` and `message`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn new<T: Into<String>>(level: Level, message: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
level: level,
|
||||
message: message.into(),
|
||||
span: None,
|
||||
children: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new diagnostic with the given `level` and `message` pointing to
|
||||
/// the given `span`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn spanned<T: Into<String>>(span: Span, level: Level, message: T) -> Diagnostic {
|
||||
Diagnostic {
|
||||
level: level,
|
||||
message: message.into(),
|
||||
span: Some(span),
|
||||
children: vec![]
|
||||
}
|
||||
}
|
||||
|
||||
diagnostic_child_methods!(span_error, error, Level::Error);
|
||||
diagnostic_child_methods!(span_warning, warning, Level::Warning);
|
||||
diagnostic_child_methods!(span_note, note, Level::Note);
|
||||
diagnostic_child_methods!(span_help, help, Level::Help);
|
||||
|
||||
/// Returns the diagnostic `level` for `self`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn level(&self) -> Level {
|
||||
self.level
|
||||
}
|
||||
|
||||
/// Emit the diagnostic.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn emit(self) {
|
||||
::__internal::with_sess(move |(sess, _)| {
|
||||
let handler = &sess.span_diagnostic;
|
||||
let level = __internal::level_to_internal_level(self.level);
|
||||
let mut diag = rustc::DiagnosticBuilder::new(handler, level, &*self.message);
|
||||
|
||||
if let Some(span) = self.span {
|
||||
diag.set_span(span.0);
|
||||
}
|
||||
|
||||
for child in self.children {
|
||||
let span = child.span.map(|s| s.0);
|
||||
let level = __internal::level_to_internal_level(child.level);
|
||||
diag.sub(level, &*child.message, span);
|
||||
}
|
||||
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "proc_macro_internals", issue = "27812")]
|
||||
#[doc(hidden)]
|
||||
pub mod __internal {
|
||||
use super::{Level, rustc};
|
||||
|
||||
pub fn level_to_internal_level(level: Level) -> rustc::Level {
|
||||
match level {
|
||||
Level::Error => rustc::Level::Error,
|
||||
Level::Warning => rustc::Level::Warning,
|
||||
Level::Note => rustc::Level::Note,
|
||||
Level::Help => rustc::Level::Help,
|
||||
Level::__Nonexhaustive => unreachable!("Level::__Nonexhaustive")
|
||||
}
|
||||
}
|
||||
}
|
@ -42,6 +42,12 @@
|
||||
#[macro_use]
|
||||
extern crate syntax;
|
||||
extern crate syntax_pos;
|
||||
extern crate rustc_errors;
|
||||
|
||||
mod diagnostic;
|
||||
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub use diagnostic::{Diagnostic, Level};
|
||||
|
||||
use std::{ascii, fmt, iter};
|
||||
use std::str::FromStr;
|
||||
@ -188,12 +194,28 @@ pub fn quote_span(span: Span) -> TokenStream {
|
||||
TokenStream(quote::Quote::quote(&span.0))
|
||||
}
|
||||
|
||||
macro_rules! diagnostic_method {
|
||||
($name:ident, $level:expr) => (
|
||||
/// Create a new `Diagnostic` with the given `message` at the span
|
||||
/// `self`.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn $name<T: Into<String>>(self, message: T) -> Diagnostic {
|
||||
Diagnostic::spanned(self, $level, message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// The span of the invocation of the current procedural macro.
|
||||
#[unstable(feature = "proc_macro", issue = "38356")]
|
||||
pub fn call_site() -> Span {
|
||||
::__internal::with_sess(|(_, mark)| Span(mark.expn_info().unwrap().call_site))
|
||||
}
|
||||
|
||||
diagnostic_method!(error, Level::Error);
|
||||
diagnostic_method!(warning, Level::Warning);
|
||||
diagnostic_method!(note, Level::Note);
|
||||
diagnostic_method!(help, Level::Help);
|
||||
}
|
||||
|
||||
/// A single token or a delimited sequence of token trees (e.g. `[1, (), ..]`).
|
||||
|
@ -288,7 +288,7 @@ impl Diagnostic {
|
||||
|
||||
/// Convenience function for internal use, clients should use one of the
|
||||
/// public methods above.
|
||||
fn sub(&mut self,
|
||||
pub(crate) fn sub(&mut self,
|
||||
level: Level,
|
||||
message: &str,
|
||||
span: MultiSpan,
|
||||
|
@ -110,6 +110,19 @@ impl<'a> DiagnosticBuilder<'a> {
|
||||
// }
|
||||
}
|
||||
|
||||
/// Convenience function for internal use, clients should use one of the
|
||||
/// span_* methods instead.
|
||||
pub fn sub<S: Into<MultiSpan>>(
|
||||
&mut self,
|
||||
level: Level,
|
||||
message: &str,
|
||||
span: Option<S>,
|
||||
) -> &mut Self {
|
||||
let span = span.map(|s| s.into()).unwrap_or(MultiSpan::new());
|
||||
self.diagnostic.sub(level, message, span, None);
|
||||
self
|
||||
}
|
||||
|
||||
/// Delay emission of this diagnostic as a bug.
|
||||
///
|
||||
/// This can be useful in contexts where an error indicates a bug but
|
||||
|
56
src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs
Normal file
56
src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2017 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.
|
||||
|
||||
// no-prefer-dynamic
|
||||
#![feature(proc_macro)]
|
||||
#![crate_type = "proc-macro"]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use proc_macro::{TokenStream, TokenNode, Span, Diagnostic};
|
||||
|
||||
fn parse(input: TokenStream) -> Result<(), Diagnostic> {
|
||||
let mut count = 0;
|
||||
let mut last_span = Span::default();
|
||||
for tree in input {
|
||||
let span = tree.span;
|
||||
if count >= 3 {
|
||||
return Err(span.error(format!("expected EOF, found `{}`.", tree))
|
||||
.span_note(last_span, "last good input was here")
|
||||
.help("input must be: `===`"))
|
||||
}
|
||||
|
||||
if let TokenNode::Op('=', _) = tree.kind {
|
||||
count += 1;
|
||||
} else {
|
||||
return Err(span.error(format!("expected `=`, found `{}`.", tree)));
|
||||
}
|
||||
|
||||
last_span = span;
|
||||
}
|
||||
|
||||
if count < 3 {
|
||||
return Err(Span::default()
|
||||
.error(format!("found {} equal signs, need exactly 3", count))
|
||||
.help("input must be: `===`"))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn three_equals(input: TokenStream) -> TokenStream {
|
||||
if let Err(diag) = parse(input) {
|
||||
diag.emit();
|
||||
return TokenStream::empty();
|
||||
}
|
||||
|
||||
"3".parse().unwrap()
|
||||
}
|
38
src/test/ui-fulldeps/proc-macro/three-equals.rs
Normal file
38
src/test/ui-fulldeps/proc-macro/three-equals.rs
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2016 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.
|
||||
|
||||
// aux-build:three-equals.rs
|
||||
// ignore-stage1
|
||||
|
||||
#![feature(proc_macro)]
|
||||
|
||||
extern crate three_equals;
|
||||
|
||||
use three_equals::three_equals;
|
||||
|
||||
fn main() {
|
||||
// This one is okay.
|
||||
three_equals!(===);
|
||||
|
||||
// Need exactly three equals.
|
||||
three_equals!(==);
|
||||
|
||||
// Need exactly three equals.
|
||||
three_equals!(=====);
|
||||
|
||||
// Only equals accepted.
|
||||
three_equals!(abc);
|
||||
|
||||
// Only equals accepted.
|
||||
three_equals!(!!);
|
||||
|
||||
// Only three characters expected.
|
||||
three_equals!(===a);
|
||||
}
|
48
src/test/ui-fulldeps/proc-macro/three-equals.stderr
Normal file
48
src/test/ui-fulldeps/proc-macro/three-equals.stderr
Normal file
@ -0,0 +1,48 @@
|
||||
error: found 2 equal signs, need exactly 3
|
||||
--> $DIR/three-equals.rs:25:5
|
||||
|
|
||||
25 | three_equals!(==);
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= help: input must be: `===`
|
||||
|
||||
error: expected EOF, found `=`.
|
||||
--> $DIR/three-equals.rs:28:21
|
||||
|
|
||||
28 | three_equals!(=====);
|
||||
| ^^
|
||||
|
|
||||
note: last good input was here
|
||||
--> $DIR/three-equals.rs:28:21
|
||||
|
|
||||
28 | three_equals!(=====);
|
||||
| ^^
|
||||
= help: input must be: `===`
|
||||
|
||||
error: expected `=`, found `abc`.
|
||||
--> $DIR/three-equals.rs:31:19
|
||||
|
|
||||
31 | three_equals!(abc);
|
||||
| ^^^
|
||||
|
||||
error: expected `=`, found `!`.
|
||||
--> $DIR/three-equals.rs:34:19
|
||||
|
|
||||
34 | three_equals!(!!);
|
||||
| ^
|
||||
|
||||
error: expected EOF, found `a`.
|
||||
--> $DIR/three-equals.rs:37:22
|
||||
|
|
||||
37 | three_equals!(===a);
|
||||
| ^
|
||||
|
|
||||
note: last good input was here
|
||||
--> $DIR/three-equals.rs:37:21
|
||||
|
|
||||
37 | three_equals!(===a);
|
||||
| ^
|
||||
= help: input must be: `===`
|
||||
|
||||
error: aborting due to 5 previous errors
|
||||
|
Loading…
Reference in New Issue
Block a user