]> git.lizzy.rs Git - rust.git/commitdiff
Initial diagnostic API for proc-macros.
authorSergio Benitez <sb@sergio.bz>
Mon, 28 Aug 2017 09:56:43 +0000 (02:56 -0700)
committerSergio Benitez <sb@sergio.bz>
Mon, 28 Aug 2017 09:58:22 +0000 (02:58 -0700)
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
src/libproc_macro/Cargo.toml
src/libproc_macro/diagnostic.rs [new file with mode: 0644]
src/libproc_macro/lib.rs
src/librustc_errors/diagnostic.rs
src/librustc_errors/diagnostic_builder.rs
src/test/ui-fulldeps/proc-macro/auxiliary/three-equals.rs [new file with mode: 0644]
src/test/ui-fulldeps/proc-macro/three-equals.rs [new file with mode: 0644]
src/test/ui-fulldeps/proc-macro/three-equals.stderr [new file with mode: 0644]

index 123c884585c19f276c7acc2d31968900eb55f6e4..1dd45de759ed13b2a0aac95ade9beaac9743d918 100644 (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",
 ]
index 1b5141773a96719549892f3b2edba0fedbe7f298..cfd83e348a8e2e62e2a229f3616578f29f186b31 100644 (file)
@@ -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 (file)
index 0000000..c39aec8
--- /dev/null
@@ -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")
+        }
+    }
+}
index 3f425c24a9143479b72c3ed62ec4adb80f917a75..4e7783da67194b6b50d45e38fa1e6728d6f1bf73 100644 (file)
 #[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<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, (), ..]`).
index 0f063542383dce0eeaf9f9ed152b8e9b6b51b131..9aae188f9ecdfdf4b7644ed5c0f3a78ca01d241b 100644 (file)
@@ -288,7 +288,7 @@ pub fn copy_details_not_message(&mut self, from: &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,
index 2c8d8b4691f0a10356be35771859713d382eb267..2cd433bfe3aeef5c2cbd45766d790718de4c56c3 100644 (file)
@@ -110,6 +110,19 @@ pub fn emit(&mut self) {
         // }
     }
 
+    /// 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
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 (file)
index 0000000..6fca32f
--- /dev/null
@@ -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()
+}
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 (file)
index 0000000..016e05c
--- /dev/null
@@ -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);
+}
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 (file)
index 0000000..1afe0be
--- /dev/null
@@ -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
+