Repository: dtolnay/proc-macro2 (Rust)

Issue:
proc_macro2::TokenStream doesn't implement UnwindSafe
While writing tests for a procedural macro I learned that `proc_macro2::TokenStream` isn't `UnwindSafe`. Since I suspect it is not intentional, I am reporting it here.

In my tests I have a function that executes a thunk and asserts that it panicked, something like this:

```rust
fn assert_panic(f: impl FnOnce() + std::panic::UnwindSafe) {
    if std::panic::catch_unwind(f).is_ok() {
        panic!("panic didn't happen");
    }
    // in real code this also asserts that the panic that occurred matches an
    // expected message substring
}
```

I'd like to test my proc macro with a range of inputs that should all fail, as in this test:

```rust
fn macro_impl(input: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
    todo!()
}

fn main() {
    for unsupported_type in &[quote!(i8), quote!(u128), quote!(())] {
        assert_panic(|| {
            macro_impl(quote! {
                struct X {
                    field: #unsupported_type
                }
            });
        });
    }
}
```

But the above code fails to compile with the following error ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=42a8b1b8c76936999713cc0baed7610a)):

```
error[E0277]: the type `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
  --> src/main.rs:18:9
   |
4  | fn assert_panic(f: impl FnOnce() + std::panic::UnwindSafe) {
   |                                    ---------------------- required by this bound in `assert_panic`
...
18 |         assert_panic(|| {
   |         ^^^^^^^^^^^^ `std::cell::UnsafeCell<usize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   |
   = help: within `&proc_macro2::TokenStream`, the trait `std::panic::RefUnwindSafe` is not implemented for `std::cell::UnsafeCell<usize>`
   = note: required because it appears within the type `std::cell::Cell<usize>`
   = note: required because it appears within the type `std::rc::RcBox<()>`
   = note: required because it appears within the type `std::marker::PhantomData<std::rc::RcBox<()>>`
   = note: required because it appears within the type `std::rc::Rc<()>`
   = note: required because it appears within the type `std::marker::PhantomData<std::rc::Rc<()>>`
   = note: required because it appears within the type `proc_macro2::TokenStream`
   = note: required because it appears within the type `&proc_macro2::TokenStream`
   = note: required because of the requirements on the impl of `std::panic::UnwindSafe` for `&&proc_macro2::TokenStream`
   = note: required because it appears within the type `[closure@src/main.rs:18:22: 24:10 unsupported_type:&&proc_macro2::TokenStream]`
```

I don't know if this is intentional, but I suspect it might not be. Perhaps `UnsafeCell` is used for efficiency and the type is actually `UnwindSafe`, it just needs the appropriate explicit marker.

I worked around the issue by avoiding the capture of `TokenStream`s produced by `quote!` and capturing strings instead ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=51d46dea9a596bd9863a603afb90fb47)):

```rust
fn main() {
    for unsupported_type in &["i8", "u128", "()"] {
        assert_panic(|| {
            let unsupported_type: proc_macro2::TokenStream = syn::parse_str(unsupported_type).unwrap();
            macro_impl(quote! {
                struct X {
                    field: #unsupported_type
                }
            });
        });
    }
}
```

That works, but the explicit parsing is less readable than `quote!`. If `TokenStream` can be `UnwindSafe`, it would be nice for it to implement the trait.


Discussion:
Fixed in 1.0.21.



Patch:
diff --git a/build.rs b/build.rs
index 153e13f5..edc0db7a 100644
--- a/build.rs
+++ b/build.rs
@@ -61,6 +61,10 @@ fn main() {
         println!("cargo:rustc-cfg=span_locations");
     }
 
+    if version.minor < 32 {
+        println!("cargo:rustc-cfg=no_libprocmacro_unwind_safe");
+    }
+
     if version.minor < 39 {
         println!("cargo:rustc-cfg=no_bind_by_move_pattern_guard");
     }
diff --git a/src/lib.rs b/src/lib.rs
index 5eecdcf1..9abf515f 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -86,6 +86,7 @@
 #[cfg(use_proc_macro)]
 extern crate proc_macro;
 
+mod marker;
 mod parse;
 
 #[cfg(wrap_proc_macro)]
@@ -102,15 +103,14 @@ use crate::fallback as imp;
 #[cfg(wrap_proc_macro)]
 mod imp;
 
+use crate::marker::Marker;
 use std::cmp::Ordering;
 use std::fmt::{self, Debug, Display};
 use std::hash::{Hash, Hasher};
 use std::iter::FromIterator;
-use std::marker::PhantomData;
 use std::ops::RangeBounds;
 #[cfg(procmacro2_semver_exempt)]
 use std::path::PathBuf;
-use std::rc::Rc;
 use std::str::FromStr;
 
 /// An abstract stream of tokens, or more concretely a sequence of token trees.
@@ -123,27 +123,27 @@ use std::str::FromStr;
 #[derive(Clone)]
 pub struct TokenStream {
     inner: imp::TokenStream,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 /// Error returned from `TokenStream::from_str`.
 pub struct LexError {
     inner: imp::LexError,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 impl TokenStream {
     fn _new(inner: imp::TokenStream) -> TokenStream {
         TokenStream {
             inner,
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
     fn _new_stable(inner: fallback::TokenStream) -> TokenStream {
         TokenStream {
             inner: inner.into(),
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
@@ -180,7 +180,7 @@ impl FromStr for TokenStream {
     fn from_str(src: &str) -> Result<TokenStream, LexError> {
         let e = src.parse().map_err(|e| LexError {
             inner: e,
-            _marker: PhantomData,
+            _marker: Marker,
         })?;
         Ok(TokenStream::_new(e))
     }
@@ -261,7 +261,7 @@ impl Debug for LexError {
 #[derive(Clone, PartialEq, Eq)]
 pub struct SourceFile {
     inner: imp::SourceFile,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 #[cfg(procmacro2_semver_exempt)]
@@ -269,7 +269,7 @@ impl SourceFile {
     fn _new(inner: imp::SourceFile) -> Self {
         SourceFile {
             inner,
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
@@ -338,21 +338,21 @@ impl PartialOrd for LineColumn {
 #[derive(Copy, Clone)]
 pub struct Span {
     inner: imp::Span,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 impl Span {
     fn _new(inner: imp::Span) -> Span {
         Span {
             inner,
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
     fn _new_stable(inner: fallback::Span) -> Span {
         Span {
             inner: inner.into(),
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
@@ -840,14 +840,14 @@ impl Debug for Punct {
 #[derive(Clone)]
 pub struct Ident {
     inner: imp::Ident,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 impl Ident {
     fn _new(inner: imp::Ident) -> Ident {
         Ident {
             inner,
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
@@ -968,7 +968,7 @@ impl Debug for Ident {
 #[derive(Clone)]
 pub struct Literal {
     inner: imp::Literal,
-    _marker: PhantomData<Rc<()>>,
+    _marker: Marker,
 }
 
 macro_rules! suffixed_int_literals {
@@ -1015,14 +1015,14 @@ impl Literal {
     fn _new(inner: imp::Literal) -> Literal {
         Literal {
             inner,
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
     fn _new_stable(inner: fallback::Literal) -> Literal {
         Literal {
             inner: inner.into(),
-            _marker: PhantomData,
+            _marker: Marker,
         }
     }
 
@@ -1181,10 +1181,9 @@ impl Display for Literal {
 
 /// Public implementation details for the `TokenStream` type, such as iterators.
 pub mod token_stream {
+    use crate::marker::Marker;
     use crate::{imp, TokenTree};
     use std::fmt::{self, Debug};
-    use std::marker::PhantomData;
-    use std::rc::Rc;
 
     pub use crate::TokenStream;
 
@@ -1195,7 +1194,7 @@ pub mod token_stream {
     #[derive(Clone)]
     pub struct IntoIter {
         inner: imp::TokenTreeIter,
-        _marker: PhantomData<Rc<()>>,
+        _marker: Marker,
     }
 
     impl Iterator for IntoIter {
@@ -1219,7 +1218,7 @@ pub mod token_stream {
         fn into_iter(self) -> IntoIter {
             IntoIter {
                 inner: self.inner.into_iter(),
-                _marker: PhantomData,
+                _marker: Marker,
             }
         }
     }
diff --git a/src/marker.rs b/src/marker.rs
new file mode 100644
index 00000000..58729baf
--- /dev/null
+++ b/src/marker.rs
@@ -0,0 +1,18 @@
+use std::marker::PhantomData;
+use std::panic::{RefUnwindSafe, UnwindSafe};
+use std::rc::Rc;
+
+// Zero sized marker with the correct set of autotrait impls we want all proc
+// macro types to have.
+pub(crate) type Marker = PhantomData<ProcMacroAutoTraits>;
+
+pub(crate) use self::value::*;
+
+mod value {
+    pub(crate) use std::marker::PhantomData as Marker;
+}
+
+pub(crate) struct ProcMacroAutoTraits(Rc<()>);
+
+impl UnwindSafe for ProcMacroAutoTraits {}
+impl RefUnwindSafe for ProcMacroAutoTraits {}


Test patch:
diff --git a/tests/marker.rs b/tests/marker.rs
index 7af2539c..70e57677 100644
--- a/tests/marker.rs
+++ b/tests/marker.rs
@@ -57,3 +57,36 @@ mod semver_exempt {
 
     assert_impl!(SourceFile is not Send or Sync);
 }
+
+#[cfg(not(no_libprocmacro_unwind_safe))]
+mod unwind_safe {
+    use super::*;
+    use std::panic::{RefUnwindSafe, UnwindSafe};
+
+    macro_rules! assert_unwind_safe {
+        ($($types:ident)*) => {
+            $(
+                assert_impl!($types is UnwindSafe and RefUnwindSafe);
+            )*
+        };
+    }
+
+    assert_unwind_safe! {
+        Delimiter
+        Group
+        Ident
+        LexError
+        Literal
+        Punct
+        Spacing
+        Span
+        TokenStream
+        TokenTree
+    }
+
+    #[cfg(procmacro2_semver_exempt)]
+    assert_unwind_safe! {
+        LineColumn
+        SourceFile
+    }
+}
diff --git a/tests/ui/test-not-send.stderr b/tests/ui/test-not-send.stderr
index 9253c1bf..f1487dd9 100644
--- a/tests/ui/test-not-send.stderr
+++ b/tests/ui/test-not-send.stderr
@@ -19,5 +19,6 @@ error[E0277]: `Rc<()>` cannot be sent between threads safely
   |     ^^^^^^^^^^^^^^^^^^^^^ `Rc<()>` cannot be sent between threads safely
   |
   = help: within `Span`, the trait `Send` is not implemented for `Rc<()>`
-  = note: required because it appears within the type `PhantomData<Rc<()>>`
+  = note: required because it appears within the type `proc_macro2::marker::ProcMacroAutoTraits`
+  = note: required because it appears within the type `PhantomData<proc_macro2::marker::ProcMacroAutoTraits>`
   = note: required because it appears within the type `Span`