diff --git a/build.rs b/build.rs index 51ad927673..1b715931cf 100644 --- a/build.rs +++ b/build.rs @@ -11,6 +11,10 @@ fn main() { None => return, }; + if minor >= 19 { + println!("cargo:rustc-cfg=syn_can_use_thread_id"); + } + // Macro modularization allows re-exporting the `quote!` macro in 1.30+. if minor >= 30 { println!("cargo:rustc-cfg=syn_can_call_macro_by_path"); diff --git a/src/error.rs b/src/error.rs index 712b9b7d82..32adb4455c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -15,6 +15,7 @@ use proc_macro2::{ }; use buffer::Cursor; +use thread::ThreadBound; /// The result of a Syn parser. pub type Result = std::result::Result; @@ -26,12 +27,20 @@ pub type Result = std::result::Result; /// [module documentation]: index.html /// /// *This type is available if Syn is built with the `"parsing"` feature.* -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Error { - span: Span, + // Span is implemented as an index into a thread-local interner to keep the + // size small. It is not safe to access from a different thread. We want + // errors to be Send and Sync to play nicely with the Failure crate, so pin + // the span we're given to its original thread and assume it is + // Span::call_site if accessed from any other thread. + span: ThreadBound, message: String, } +#[cfg(test)] +struct _Test where Error: Send + Sync; + impl Error { /// Usually the [`ParseStream::error`] method will be used instead, which /// automatically uses the correct span from the current position of the @@ -70,13 +79,21 @@ impl Error { /// ``` pub fn new(span: Span, message: T) -> Self { Error { - span: span, + span: ThreadBound::new(span), message: message.to_string(), } } + /// The source location of the error. + /// + /// Spans are not thread-safe so this function returns `Span::call_site()` + /// if called from a different thread than the one on which the `Error` was + /// originally created. pub fn span(&self) -> Span { - self.span + match self.span.get() { + Some(span) => *span, + None => Span::call_site(), + } } /// Render the error as an invocation of [`compile_error!`]. @@ -87,23 +104,25 @@ impl Error { /// [`compile_error!`]: https://doc.rust-lang.org/std/macro.compile_error.html /// [`parse_macro_input!`]: ../macro.parse_macro_input.html pub fn to_compile_error(&self) -> TokenStream { + let span = self.span(); + // compile_error!($message) TokenStream::from_iter(vec![ - TokenTree::Ident(Ident::new("compile_error", self.span)), + TokenTree::Ident(Ident::new("compile_error", span)), TokenTree::Punct({ let mut punct = Punct::new('!', Spacing::Alone); - punct.set_span(self.span); + punct.set_span(span); punct }), TokenTree::Group({ let mut group = Group::new(Delimiter::Brace, { TokenStream::from_iter(vec![TokenTree::Literal({ let mut string = Literal::string(&self.message); - string.set_span(self.span); + string.set_span(span); string })]) }); - group.set_span(self.span); + group.set_span(span); group }), ]) @@ -124,6 +143,15 @@ impl Display for Error { } } +impl Clone for Error { + fn clone(&self) -> Self { + Error { + span: ThreadBound::new(self.span()), + message: self.message.clone(), + } + } +} + impl std::error::Error for Error { fn description(&self) -> &str { "parse error" diff --git a/src/lib.rs b/src/lib.rs index a5348543a2..b61a2ad6b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -569,6 +569,9 @@ mod span; #[cfg(all(any(feature = "full", feature = "derive"), feature = "printing"))] mod print; +#[cfg(feature = "parsing")] +mod thread; + //////////////////////////////////////////////////////////////////////////////// #[cfg(any(feature = "parsing", feature = "full", feature = "derive"))] diff --git a/src/thread.rs b/src/thread.rs new file mode 100644 index 0000000000..8a79e951b0 --- /dev/null +++ b/src/thread.rs @@ -0,0 +1,81 @@ +use std::fmt::{self, Debug}; + +use self::thread_id::ThreadId; + +/// ThreadBound is a Sync-maker and Send-maker that allows accessing a value +/// of type T only from the original thread on which the ThreadBound was +/// constructed. +pub struct ThreadBound { + value: T, + thread_id: ThreadId, +} + +unsafe impl Sync for ThreadBound {} + +// Send bound requires Copy, as otherwise Drop could run in the wrong place. +unsafe impl Send for ThreadBound {} + +impl ThreadBound { + pub fn new(value: T) -> Self { + ThreadBound { + value: value, + thread_id: thread_id::current(), + } + } + + pub fn get(&self) -> Option<&T> { + if thread_id::current() == self.thread_id { + Some(&self.value) + } else { + None + } + } +} + +impl Debug for ThreadBound { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + match self.get() { + Some(value) => Debug::fmt(value, formatter), + None => formatter.write_str("unknown"), + } + } +} + +#[cfg(syn_can_use_thread_id)] +mod thread_id { + use std::thread; + + pub use std::thread::ThreadId; + + pub fn current() -> ThreadId { + thread::current().id() + } +} + +#[cfg(not(syn_can_use_thread_id))] +mod thread_id { + use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; + + thread_local! { + static THREAD_ID: usize = { + static NEXT_THREAD_ID: AtomicUsize = ATOMIC_USIZE_INIT; + + // Ordering::Relaxed because our only requirement for the ids is + // that they are unique. It is okay for the compiler to rearrange + // other memory reads around this fetch. It's still an atomic + // fetch_add, so no two threads will be able to read the same value + // from it. + // + // The main thing which these orderings affect is other memory reads + // around the atomic read, which for our case are irrelevant as this + // atomic guards nothing. + NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed) + }; + } + + pub type ThreadId = usize; + + pub fn current() -> ThreadId { + THREAD_ID.with(|id| *id) + } +}