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:
Alex Crichton 2017-08-30 11:11:12 -05:00 committed by GitHub
commit fd4f362b30
9 changed files with 314 additions and 1 deletions

1
src/Cargo.lock generated
View File

@ -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",
]

View File

@ -10,3 +10,4 @@ crate-type = ["dylib"]
[dependencies]
syntax = { path = "../libsyntax" }
syntax_pos = { path = "../libsyntax_pos" }
rustc_errors = { path = "../librustc_errors" }

View 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")
}
}
}

View File

@ -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, (), ..]`).

View File

@ -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,

View File

@ -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

View 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()
}

View 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);
}

View 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