Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
44 changes: 36 additions & 8 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use proc_macro2::{
};

use buffer::Cursor;
use thread::ThreadBound;

/// The result of a Syn parser.
pub type Result<T> = std::result::Result<T, Error>;
Expand All @@ -26,12 +27,20 @@ pub type Result<T> = std::result::Result<T, Error>;
/// [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<Span>,
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
Expand Down Expand Up @@ -70,13 +79,21 @@ impl Error {
/// ```
pub fn new<T: Display>(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!`].
Expand All @@ -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
}),
])
Expand All @@ -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"
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))]
Expand Down
81 changes: 81 additions & 0 deletions src/thread.rs
Original file line number Diff line number Diff line change
@@ -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<T> {
value: T,
thread_id: ThreadId,
}

unsafe impl<T> Sync for ThreadBound<T> {}

// Send bound requires Copy, as otherwise Drop could run in the wrong place.
unsafe impl<T: Copy> Send for ThreadBound<T> {}

impl<T> ThreadBound<T> {
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<T: Debug> Debug for ThreadBound<T> {
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)
}
}