diff --git a/src/libproc_macro/bridge/mod.rs b/src/libproc_macro/bridge/mod.rs index f03c63fc04c..cf9229909d9 100644 --- a/src/libproc_macro/bridge/mod.rs +++ b/src/libproc_macro/bridge/mod.rs @@ -165,6 +165,7 @@ fn sub( fn end($self: $S::Span) -> LineColumn; fn join($self: $S::Span, other: $S::Span) -> Option<$S::Span>; fn resolved_at($self: $S::Span, at: $S::Span) -> $S::Span; + fn source_text($self: $S::Span) -> Option; }, } }; diff --git a/src/libproc_macro/lib.rs b/src/libproc_macro/lib.rs index 32c81302931..b39e139de54 100644 --- a/src/libproc_macro/lib.rs +++ b/src/libproc_macro/lib.rs @@ -341,6 +341,18 @@ pub fn eq(&self, other: &Span) -> bool { self.0 == other.0 } + /// Returns the source text behind a span. This preserves the original source + /// code, including spaces and comments. It only returns a result if the span + /// corresponds to real source code. + /// + /// Note: The observable result of a macro should only rely on the tokens and + /// not on this source text. The result of this function is a best effort to + /// be used for diagnostics only. + #[unstable(feature = "proc_macro_span", issue = "54725")] + pub fn source_text(&self) -> Option { + self.0.source_text() + } + diagnostic_method!(error, Level::Error); diagnostic_method!(warning, Level::Warning); diagnostic_method!(note, Level::Note); diff --git a/src/libsyntax_ext/proc_macro_server.rs b/src/libsyntax_ext/proc_macro_server.rs index 56bd58b28a6..a5a35662ec5 100644 --- a/src/libsyntax_ext/proc_macro_server.rs +++ b/src/libsyntax_ext/proc_macro_server.rs @@ -748,4 +748,7 @@ fn join(&mut self, first: Self::Span, second: Self::Span) -> Option fn resolved_at(&mut self, span: Self::Span, at: Self::Span) -> Self::Span { span.with_ctxt(at.ctxt()) } + fn source_text(&mut self, span: Self::Span) -> Option { + self.sess.source_map().span_to_snippet(span).ok() + } } diff --git a/src/test/run-pass/proc-macro/auxiliary/span-api-tests.rs b/src/test/run-pass/proc-macro/auxiliary/span-api-tests.rs index 8e2c5c0a088..7afc341d416 100644 --- a/src/test/run-pass/proc-macro/auxiliary/span-api-tests.rs +++ b/src/test/run-pass/proc-macro/auxiliary/span-api-tests.rs @@ -43,3 +43,14 @@ pub fn assert_source_file(input: TokenStream) -> TokenStream { "".parse().unwrap() } + +#[proc_macro] +pub fn macro_stringify(input: TokenStream) -> TokenStream { + let mut tokens = input.into_iter(); + let first_span = tokens.next().expect("first token").span(); + let last_span = tokens.last().map(|x| x.span()).unwrap_or(first_span); + let span = first_span.join(last_span).expect("joined span"); + let src = span.source_text().expect("source_text"); + TokenTree::Literal(Literal::string(&src)).into() +} + diff --git a/src/test/run-pass/proc-macro/span-api-tests.rs b/src/test/run-pass/proc-macro/span-api-tests.rs index 415cada265e..51cd8cfa208 100644 --- a/src/test/run-pass/proc-macro/span-api-tests.rs +++ b/src/test/run-pass/proc-macro/span-api-tests.rs @@ -13,12 +13,14 @@ // ignore-pretty +#![feature(proc_macro_hygiene)] + #[macro_use] extern crate span_test_macros; extern crate span_api_tests; -use span_api_tests::{reemit, assert_fake_source_file, assert_source_file}; +use span_api_tests::{reemit, assert_fake_source_file, assert_source_file, macro_stringify}; macro_rules! say_hello { ($macname:ident) => ( $macname! { "Hello, world!" }) @@ -38,4 +40,32 @@ macro_rules! say_hello { assert_source_file! { "Hello, world!" } } -fn main() {} +fn main() { + let s = macro_stringify!(Hello, world!); + assert_eq!(s, "Hello, world!"); + assert_eq!(macro_stringify!(Hello, world!), "Hello, world!"); + assert_eq!(reemit_legacy!(macro_stringify!(Hello, world!)), "Hello, world!"); + reemit_legacy!(assert_eq!(macro_stringify!(Hello, world!), "Hello, world!")); + // reemit change the span to be that of the call site + assert_eq!( + reemit!(macro_stringify!(Hello, world!)), + "reemit!(macro_stringify!(Hello, world!))" + ); + let r = "reemit!(assert_eq!(macro_stringify!(Hello, world!), r));"; + reemit!(assert_eq!(macro_stringify!(Hello, world!), r)); + + assert_eq!(macro_stringify!( + Hello, + world! + ), "Hello,\n world!"); + + assert_eq!(macro_stringify!(Hello, /*world */ !), "Hello, /*world */ !"); + assert_eq!(macro_stringify!( + Hello, + // comment + world! + ), "Hello,\n // comment\n world!"); + + assert_eq!(say_hello! { macro_stringify }, "\"Hello, world!\""); + assert_eq!(say_hello_extern! { macro_stringify }, "\"Hello, world!\""); +}