From 8be132e9d76232feb2376de9edcbb34fe3ac99ac Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Mon, 28 Aug 2017 02:56:43 -0700 Subject: [PATCH] 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. --- src/Cargo.lock | 1 + src/libproc_macro/Cargo.toml | 1 + src/libproc_macro/diagnostic.rs | 134 ++++++++++++++++++ src/libproc_macro/lib.rs | 22 +++ src/librustc_errors/diagnostic.rs | 2 +- src/librustc_errors/diagnostic_builder.rs | 13 ++ .../proc-macro/auxiliary/three-equals.rs | 56 ++++++++ .../ui-fulldeps/proc-macro/three-equals.rs | 38 +++++ .../proc-macro/three-equals.stderr | 48 +++++++ 9 files changed, 314 insertions(+), 1 deletion(-) create mode 100644 src/libproc_macro/diagnostic.rs create mode 100644 src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs create mode 100644 src/test/ui-fulldeps/proc-macro/three-equals.rs create mode 100644 src/test/ui-fulldeps/proc-macro/three-equals.stderr diff --git a/src/Cargo.lock b/src/Cargo.lock index 123c884585c..1dd45de759e 100644 --- a/src/Cargo.lock +++ b/src/Cargo.lock @@ -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", ] diff --git a/src/libproc_macro/Cargo.toml b/src/libproc_macro/Cargo.toml index 1b5141773a9..cfd83e348a8 100644 --- a/src/libproc_macro/Cargo.toml +++ b/src/libproc_macro/Cargo.toml @@ -10,3 +10,4 @@ crate-type = ["dylib"] [dependencies] syntax = { path = "../libsyntax" } syntax_pos = { path = "../libsyntax_pos" } +rustc_errors = { path = "../librustc_errors" } diff --git a/src/libproc_macro/diagnostic.rs b/src/libproc_macro/diagnostic.rs new file mode 100644 index 00000000000..c39aec896e6 --- /dev/null +++ b/src/libproc_macro/diagnostic.rs @@ -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 or the MIT license +// , 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, + children: Vec +} + +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>(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>(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>(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>(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") + } + } +} diff --git a/src/libproc_macro/lib.rs b/src/libproc_macro/lib.rs index 3f425c24a91..4e7783da671 100644 --- a/src/libproc_macro/lib.rs +++ b/src/libproc_macro/lib.rs @@ -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; @@ -191,12 +197,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>(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, (), ..]`). diff --git a/src/librustc_errors/diagnostic.rs b/src/librustc_errors/diagnostic.rs index 0f063542383..9aae188f9ec 100644 --- a/src/librustc_errors/diagnostic.rs +++ b/src/librustc_errors/diagnostic.rs @@ -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, diff --git a/src/librustc_errors/diagnostic_builder.rs b/src/librustc_errors/diagnostic_builder.rs index 2c8d8b4691f..2cd433bfe3a 100644 --- a/src/librustc_errors/diagnostic_builder.rs +++ b/src/librustc_errors/diagnostic_builder.rs @@ -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>( + &mut self, + level: Level, + message: &str, + span: Option, + ) -> &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 diff --git a/src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs b/src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs new file mode 100644 index 00000000000..6fca32fece1 --- /dev/null +++ b/src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs @@ -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 or the MIT license +// , 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() +} diff --git a/src/test/ui-fulldeps/proc-macro/three-equals.rs b/src/test/ui-fulldeps/proc-macro/three-equals.rs new file mode 100644 index 00000000000..016e05c51f5 --- /dev/null +++ b/src/test/ui-fulldeps/proc-macro/three-equals.rs @@ -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 or the MIT license +// , 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); +} diff --git a/src/test/ui-fulldeps/proc-macro/three-equals.stderr b/src/test/ui-fulldeps/proc-macro/three-equals.stderr new file mode 100644 index 00000000000..1afe0be2800 --- /dev/null +++ b/src/test/ui-fulldeps/proc-macro/three-equals.stderr @@ -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 +