Skip to content

Commit c5fb4d0

Browse files
committed
Auto merge of #55780 - ogoffart:span_source_text, r=petrochenkov
Introduce proc_macro::Span::source_text A function to extract the actual source behind a Span. Background: I would like to use `syn` in a `build.rs` script to parse the rust code, and extract part of the source code. However, `syn` only gives access to proc_macro2::Span, and i would like to get the source code behind that. I opened an issue on proc_macro2 bug tracker for this feature dtolnay/proc-macro2#110 and @alexcrichton said the feature should first go upstream in proc_macro. So there it is! Since most of the Span API is unstable anyway, this is guarded by the same `proc_macro_span` feature as everything else.
2 parents 267fb90 + e88b0d9 commit c5fb4d0

File tree

5 files changed

+59
-2
lines changed

5 files changed

+59
-2
lines changed

src/libproc_macro/bridge/mod.rs

+1
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ macro_rules! with_api {
155155
fn end($self: $S::Span) -> LineColumn;
156156
fn join($self: $S::Span, other: $S::Span) -> Option<$S::Span>;
157157
fn resolved_at($self: $S::Span, at: $S::Span) -> $S::Span;
158+
fn source_text($self: $S::Span) -> Option<String>;
158159
},
159160
}
160161
};

src/libproc_macro/lib.rs

+12
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,18 @@ impl Span {
333333
self.0 == other.0
334334
}
335335

336+
/// Returns the source text behind a span. This preserves the original source
337+
/// code, including spaces and comments. It only returns a result if the span
338+
/// corresponds to real source code.
339+
///
340+
/// Note: The observable result of a macro should only rely on the tokens and
341+
/// not on this source text. The result of this function is a best effort to
342+
/// be used for diagnostics only.
343+
#[unstable(feature = "proc_macro_span", issue = "54725")]
344+
pub fn source_text(&self) -> Option<String> {
345+
self.0.source_text()
346+
}
347+
336348
diagnostic_method!(error, Level::Error);
337349
diagnostic_method!(warning, Level::Warning);
338350
diagnostic_method!(note, Level::Note);

src/libsyntax_ext/proc_macro_server.rs

+3
Original file line numberDiff line numberDiff line change
@@ -740,4 +740,7 @@ impl server::Span for Rustc<'_> {
740740
fn resolved_at(&mut self, span: Self::Span, at: Self::Span) -> Self::Span {
741741
span.with_ctxt(at.ctxt())
742742
}
743+
fn source_text(&mut self, span: Self::Span) -> Option<String> {
744+
self.sess.source_map().span_to_snippet(span).ok()
745+
}
743746
}

src/test/run-pass/proc-macro/auxiliary/span-api-tests.rs

+11
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ pub fn assert_source_file(input: TokenStream) -> TokenStream {
3333

3434
"".parse().unwrap()
3535
}
36+
37+
#[proc_macro]
38+
pub fn macro_stringify(input: TokenStream) -> TokenStream {
39+
let mut tokens = input.into_iter();
40+
let first_span = tokens.next().expect("first token").span();
41+
let last_span = tokens.last().map(|x| x.span()).unwrap_or(first_span);
42+
let span = first_span.join(last_span).expect("joined span");
43+
let src = span.source_text().expect("source_text");
44+
TokenTree::Literal(Literal::string(&src)).into()
45+
}
46+

src/test/run-pass/proc-macro/span-api-tests.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33

44
// ignore-pretty
55

6+
#![feature(proc_macro_hygiene)]
7+
68
#[macro_use]
79
extern crate span_test_macros;
810

911
extern crate span_api_tests;
1012

11-
use span_api_tests::{reemit, assert_fake_source_file, assert_source_file};
13+
use span_api_tests::{reemit, assert_fake_source_file, assert_source_file, macro_stringify};
1214

1315
macro_rules! say_hello {
1416
($macname:ident) => ( $macname! { "Hello, world!" })
@@ -28,4 +30,32 @@ reemit! {
2830
assert_source_file! { "Hello, world!" }
2931
}
3032

31-
fn main() {}
33+
fn main() {
34+
let s = macro_stringify!(Hello, world!);
35+
assert_eq!(s, "Hello, world!");
36+
assert_eq!(macro_stringify!(Hello, world!), "Hello, world!");
37+
assert_eq!(reemit_legacy!(macro_stringify!(Hello, world!)), "Hello, world!");
38+
reemit_legacy!(assert_eq!(macro_stringify!(Hello, world!), "Hello, world!"));
39+
// reemit change the span to be that of the call site
40+
assert_eq!(
41+
reemit!(macro_stringify!(Hello, world!)),
42+
"reemit!(macro_stringify!(Hello, world!))"
43+
);
44+
let r = "reemit!(assert_eq!(macro_stringify!(Hello, world!), r));";
45+
reemit!(assert_eq!(macro_stringify!(Hello, world!), r));
46+
47+
assert_eq!(macro_stringify!(
48+
Hello,
49+
world!
50+
), "Hello,\n world!");
51+
52+
assert_eq!(macro_stringify!(Hello, /*world */ !), "Hello, /*world */ !");
53+
assert_eq!(macro_stringify!(
54+
Hello,
55+
// comment
56+
world!
57+
), "Hello,\n // comment\n world!");
58+
59+
assert_eq!(say_hello! { macro_stringify }, "\"Hello, world!\"");
60+
assert_eq!(say_hello_extern! { macro_stringify }, "\"Hello, world!\"");
61+
}

0 commit comments

Comments
 (0)