diff --git a/src/libproc_macro/lib.rs b/src/libproc_macro/lib.rs index 2c540c8de8f..e6307f10c13 100644 --- a/src/libproc_macro/lib.rs +++ b/src/libproc_macro/lib.rs @@ -50,6 +50,7 @@ mod diagnostic; pub use diagnostic::{Diagnostic, Level}; use std::{ascii, fmt, iter}; +use std::rc::Rc; use std::str::FromStr; use syntax::ast; @@ -58,7 +59,7 @@ use syntax::parse::{self, token}; use syntax::symbol::Symbol; use syntax::tokenstream; use syntax_pos::DUMMY_SP; -use syntax_pos::SyntaxContext; +use syntax_pos::{FileMap, Pos, SyntaxContext}; use syntax_pos::hygiene::Mark; /// The main type provided by this crate, representing an abstract stream of @@ -173,7 +174,7 @@ impl TokenStream { /// A region of source code, along with macro expansion information. #[unstable(feature = "proc_macro", issue = "38356")] -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Span(syntax_pos::Span); #[unstable(feature = "proc_macro", issue = "38356")] @@ -211,12 +212,132 @@ impl Span { ::__internal::with_sess(|(_, mark)| Span(mark.expn_info().unwrap().call_site)) } + /// The original source file into which this span points. + #[unstable(feature = "proc_macro", issue = "38356")] + pub fn source_file(&self) -> SourceFile { + SourceFile { + filemap: __internal::lookup_char_pos(self.0.lo()).file, + } + } + + /// Get the starting line/column in the source file for this span. + #[unstable(feature = "proc_macro", issue = "38356")] + pub fn start(&self) -> LineColumn { + let loc = __internal::lookup_char_pos(self.0.lo()); + LineColumn { + line: loc.line, + column: loc.col.to_usize() + } + } + + /// Get the ending line/column in the source file for this span. + #[unstable(feature = "proc_macro", issue = "38356")] + pub fn end(&self) -> LineColumn { + let loc = __internal::lookup_char_pos(self.0.hi()); + LineColumn { + line: loc.line, + column: loc.col.to_usize() + } + } + + /// Create a new span encompassing `self` and `other`. + /// + /// Returns `None` if `self` and `other` are from different files. + #[unstable(feature = "proc_macro", issue = "38356")] + pub fn join(&self, other: Span) -> Option { + let self_loc = __internal::lookup_char_pos(self.0.lo()); + let other_loc = __internal::lookup_char_pos(self.0.lo()); + + if self_loc.file.name != other_loc.file.name { return None } + + Some(Span(self.0.to(other.0))) + } + diagnostic_method!(error, Level::Error); diagnostic_method!(warning, Level::Warning); diagnostic_method!(note, Level::Note); diagnostic_method!(help, Level::Help); } +/// A line-column pair representing the start or end of a `Span`. +#[unstable(feature = "proc_macro", issue = "38356")] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub struct LineColumn { + /// The 1-indexed line in the source file on which the span starts or ends (inclusive). + line: usize, + /// The 0-indexed column (in UTF-8 characters) in the source file on which + /// the span starts or ends (inclusive). + column: usize +} + +/// The source file of a given `Span`. +#[unstable(feature = "proc_macro", issue = "38356")] +#[derive(Clone)] +pub struct SourceFile { + filemap: Rc, +} + +impl SourceFile { + /// Get the path to this source file as a string. + /// + /// ### Note + /// If the code span associated with this `SourceFile` was generated by an external macro, this + /// may not be an actual path on the filesystem. Use [`is_real`] to check. + /// + /// Also note that even if `is_real` returns `true`, if `-Z remap-path-prefix-*` was passed on + /// the command line, the path as given may not actually be valid. + /// + /// [`is_real`]: #method.is_real + # [unstable(feature = "proc_macro", issue = "38356")] + pub fn as_str(&self) -> &str { + &self.filemap.name + } + + /// Returns `true` if this source file is a real source file, and not generated by an external + /// macro's expansion. + # [unstable(feature = "proc_macro", issue = "38356")] + pub fn is_real(&self) -> bool { + // This is a hack until intercrate spans are implemented and we can have real source files + // for spans generated in external macros. + // https://github.com/rust-lang/rust/pull/43604#issuecomment-333334368 + self.filemap.is_real_file() + } +} + +#[unstable(feature = "proc_macro", issue = "38356")] +impl AsRef for SourceFile { + fn as_ref(&self) -> &str { + self.as_str() + } +} + +#[unstable(feature = "proc_macro", issue = "38356")] +impl fmt::Debug for SourceFile { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SourceFile") + .field("path", &self.as_str()) + .field("is_real", &self.is_real()) + .finish() + } +} + +#[unstable(feature = "proc_macro", issue = "38356")] +impl PartialEq for SourceFile { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.filemap, &other.filemap) + } +} + +#[unstable(feature = "proc_macro", issue = "38356")] +impl Eq for SourceFile {} + +#[unstable(feature = "proc_macro", issue = "38356")] +impl PartialEq for SourceFile { + fn eq(&self, other: &str) -> bool { + self.as_ref() == other + } +} + /// A single token or a delimited sequence of token trees (e.g. `[1, (), ..]`). #[unstable(feature = "proc_macro", issue = "38356")] #[derive(Clone, Debug)] @@ -618,10 +739,14 @@ pub mod __internal { use syntax::parse::{self, ParseSess}; use syntax::parse::token::{self, Token}; use syntax::tokenstream; - use syntax_pos::DUMMY_SP; + use syntax_pos::{BytePos, Loc, DUMMY_SP}; use super::{TokenStream, LexError}; + pub fn lookup_char_pos(pos: BytePos) -> Loc { + with_sess(|(sess, _)| sess.codemap().lookup_char_pos(pos)) + } + pub fn new_token_stream(item: P) -> TokenStream { let token = Token::interpolated(token::NtItem(item)); TokenStream(tokenstream::TokenTree::Token(DUMMY_SP, token).into()) diff --git a/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-api-tests.rs b/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-api-tests.rs new file mode 100644 index 00000000000..ce6ffcc3cb0 --- /dev/null +++ b/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-api-tests.rs @@ -0,0 +1,45 @@ +// 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. + +// force-host +// no-prefer-dynamic + +#![crate_type = "proc-macro"] +#![feature(proc_macro)] + +extern crate proc_macro; + +use proc_macro::*; + +// Re-emits the input tokens by parsing them from strings +#[proc_macro] +pub fn reemit(input: TokenStream) -> TokenStream { + input.to_string().parse().unwrap() +} + +#[proc_macro] +pub fn assert_fake_source_file(input: TokenStream) -> TokenStream { + for tk in input { + let source_file = tk.span.source_file(); + assert!(!source_file.is_real(), "Source file is real: {:?}", source_file); + } + + "".parse().unwrap() +} + +#[proc_macro] +pub fn assert_source_file(input: TokenStream) -> TokenStream { + for tk in input { + let source_file = tk.span.source_file(); + assert!(source_file.is_real(), "Source file is not real: {:?}", source_file); + } + + "".parse().unwrap() +} diff --git a/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-test-macros.rs b/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-test-macros.rs new file mode 100644 index 00000000000..b4666e2cb61 --- /dev/null +++ b/src/test/run-pass-fulldeps/proc-macro/auxiliary/span-test-macros.rs @@ -0,0 +1,19 @@ +// 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. + +#[macro_export] +macro_rules! reemit_legacy { + ($($tok:tt)*) => ($($tok)*) +} + +#[macro_export] +macro_rules! say_hello_extern { + ($macname:ident) => ( $macname! { "Hello, world!" }) +} diff --git a/src/test/run-pass-fulldeps/proc-macro/span-api-tests.rs b/src/test/run-pass-fulldeps/proc-macro/span-api-tests.rs new file mode 100644 index 00000000000..c2df561b43a --- /dev/null +++ b/src/test/run-pass-fulldeps/proc-macro/span-api-tests.rs @@ -0,0 +1,43 @@ +// 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. + +// aux-build:span-api-tests.rs +// aux-build:span-test-macros.rs + +// ignore-pretty + +#![feature(proc_macro)] + +#[macro_use] +extern crate span_test_macros; + +extern crate span_api_tests; + +use span_api_tests::{reemit, assert_fake_source_file, assert_source_file}; + +macro_rules! say_hello { + ($macname:ident) => ( $macname! { "Hello, world!" }) +} + +assert_source_file! { "Hello, world!" } + +say_hello! { assert_source_file } + +reemit_legacy! { + assert_source_file! { "Hello, world!" } +} + +say_hello_extern! { assert_fake_source_file } + +reemit! { + assert_source_file! { "Hello, world!" } +} + +fn main() {}