David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 1 | //! A trait that can provide the `Span` of the complete contents of a syntax |
| 2 | //! tree node. |
| 3 | //! |
David Tolnay | 461d98e | 2018-01-07 11:07:19 -0800 | [diff] [blame] | 4 | //! *This module is available if Syn is built with both the `"parsing"` and |
| 5 | //! `"printing"` features.* |
| 6 | //! |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 7 | //! # Example |
| 8 | //! |
| 9 | //! Suppose in a procedural macro we have a [`Type`] that we want to assert |
| 10 | //! implements the [`Sync`] trait. Maybe this is the type of one of the fields |
| 11 | //! of a struct for which we are deriving a trait implementation, and we need to |
| 12 | //! be able to pass a reference to one of those fields across threads. |
| 13 | //! |
| 14 | //! [`Type`]: ../enum.Type.html |
| 15 | //! [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html |
| 16 | //! |
| 17 | //! If the field type does *not* implement `Sync` as required, we want the |
| 18 | //! compiler to report an error pointing out exactly which type it was. |
| 19 | //! |
| 20 | //! The following macro code takes a variable `ty` of type `Type` and produces a |
| 21 | //! static assertion that `Sync` is implemented for that type. |
| 22 | //! |
David Tolnay | 95989db | 2019-01-01 15:05:57 -0500 | [diff] [blame] | 23 | //! ```edition2018 |
David Tolnay | 9b00f65 | 2018-09-01 10:31:02 -0700 | [diff] [blame] | 24 | //! # extern crate proc_macro; |
David Tolnay | 9b00f65 | 2018-09-01 10:31:02 -0700 | [diff] [blame] | 25 | //! # |
David Tolnay | 0a6deb2 | 2018-01-06 18:26:05 -0800 | [diff] [blame] | 26 | //! use proc_macro::TokenStream; |
| 27 | //! use proc_macro2::Span; |
David Tolnay | fd5b117 | 2018-12-31 17:54:36 -0500 | [diff] [blame] | 28 | //! use quote::quote_spanned; |
David Tolnay | 9b00f65 | 2018-09-01 10:31:02 -0700 | [diff] [blame] | 29 | //! use syn::Type; |
| 30 | //! use syn::spanned::Spanned; |
David Tolnay | 0a6deb2 | 2018-01-06 18:26:05 -0800 | [diff] [blame] | 31 | //! |
| 32 | //! # const IGNORE_TOKENS: &str = stringify! { |
| 33 | //! #[proc_macro_derive(MyMacro)] |
| 34 | //! # }; |
| 35 | //! pub fn my_macro(input: TokenStream) -> TokenStream { |
| 36 | //! # let ty = get_a_type(); |
| 37 | //! /* ... */ |
| 38 | //! |
Alex Crichton | 9a4dca2 | 2018-03-28 06:32:19 -0700 | [diff] [blame] | 39 | //! let assert_sync = quote_spanned! {ty.span()=> |
David Tolnay | 0a6deb2 | 2018-01-06 18:26:05 -0800 | [diff] [blame] | 40 | //! struct _AssertSync where #ty: Sync; |
| 41 | //! }; |
| 42 | //! |
| 43 | //! /* ... */ |
| 44 | //! # input |
| 45 | //! } |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 46 | //! # |
David Tolnay | 0a6deb2 | 2018-01-06 18:26:05 -0800 | [diff] [blame] | 47 | //! # fn get_a_type() -> Type { |
| 48 | //! # unimplemented!() |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 49 | //! # } |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 50 | //! ``` |
| 51 | //! |
| 52 | //! By inserting this `assert_sync` fragment into the output code generated by |
| 53 | //! our macro, the user's code will fail to compile if `ty` does not implement |
| 54 | //! `Sync`. The errors they would see look like the following. |
| 55 | //! |
| 56 | //! ```text |
| 57 | //! error[E0277]: the trait bound `*const i32: std::marker::Sync` is not satisfied |
| 58 | //! --> src/main.rs:10:21 |
| 59 | //! | |
| 60 | //! 10 | bad_field: *const i32, |
| 61 | //! | ^^^^^^^^^^ `*const i32` cannot be shared between threads safely |
| 62 | //! ``` |
| 63 | //! |
| 64 | //! In this technique, using the `Type`'s span for the error message makes the |
Alex Crichton | 9a4dca2 | 2018-03-28 06:32:19 -0700 | [diff] [blame] | 65 | //! error appear in the correct place underlining the right type. |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 66 | |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 67 | use proc_macro2::{Span, TokenStream}; |
Alex Crichton | a74a1c8 | 2018-05-16 10:20:44 -0700 | [diff] [blame] | 68 | use quote::ToTokens; |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 69 | |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 70 | /// A trait that can provide the `Span` of the complete contents of a syntax |
| 71 | /// tree node. |
| 72 | /// |
| 73 | /// This trait is automatically implemented for all types that implement |
David Tolnay | 6315c65 | 2018-08-24 16:05:45 -0400 | [diff] [blame] | 74 | /// [`ToTokens`] from the `quote` crate. It is sealed and cannot be implemented |
| 75 | /// outside of the Syn crate other than by implementing `ToTokens`. |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 76 | /// |
David Tolnay | 442cf76 | 2018-08-31 11:08:24 -0700 | [diff] [blame] | 77 | /// [`ToTokens`]: https://docs.rs/quote/0.6/quote/trait.ToTokens.html |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 78 | /// |
| 79 | /// See the [module documentation] for an example. |
| 80 | /// |
| 81 | /// [module documentation]: index.html |
David Tolnay | 461d98e | 2018-01-07 11:07:19 -0800 | [diff] [blame] | 82 | /// |
| 83 | /// *This trait is available if Syn is built with both the `"parsing"` and |
| 84 | /// `"printing"` features.* |
David Tolnay | 6315c65 | 2018-08-24 16:05:45 -0400 | [diff] [blame] | 85 | pub trait Spanned: private::Sealed { |
David Tolnay | a922130 | 2018-01-06 18:20:54 -0800 | [diff] [blame] | 86 | /// Returns a `Span` covering the complete contents of this syntax tree |
| 87 | /// node, or [`Span::call_site()`] if this node is empty. |
| 88 | /// |
David Tolnay | 442cf76 | 2018-08-31 11:08:24 -0700 | [diff] [blame] | 89 | /// [`Span::call_site()`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html#method.call_site |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 90 | fn span(&self) -> Span; |
| 91 | } |
| 92 | |
David Tolnay | 6315c65 | 2018-08-24 16:05:45 -0400 | [diff] [blame] | 93 | mod private { |
| 94 | use quote::ToTokens; |
| 95 | pub trait Sealed {} |
| 96 | impl<T: ToTokens> Sealed for T {} |
| 97 | } |
| 98 | |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 99 | impl<T> Spanned for T |
| 100 | where |
| 101 | T: ToTokens, |
| 102 | { |
| 103 | fn span(&self) -> Span { |
David Tolnay | 785a79a | 2018-10-02 21:28:24 -0700 | [diff] [blame] | 104 | join_spans(self.into_token_stream()) |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | fn join_spans(tokens: TokenStream) -> Span { |
David Tolnay | f1a1026 | 2018-10-13 15:33:50 -0700 | [diff] [blame] | 109 | let mut iter = tokens.into_iter().filter_map(|tt| { |
| 110 | // FIXME: This shouldn't be required, since optimally spans should |
| 111 | // never be invalid. This filter_map can probably be removed when |
| 112 | // https://github.com/rust-lang/rust/issues/43081 is resolved. |
| 113 | let span = tt.span(); |
| 114 | let debug = format!("{:?}", span); |
| 115 | if debug.ends_with("bytes(0..0)") { |
| 116 | None |
| 117 | } else { |
| 118 | Some(span) |
| 119 | } |
| 120 | }); |
David Tolnay | 785a79a | 2018-10-02 21:28:24 -0700 | [diff] [blame] | 121 | |
| 122 | let mut joined = match iter.next() { |
| 123 | Some(span) => span, |
| 124 | None => return Span::call_site(), |
| 125 | }; |
| 126 | |
| 127 | #[cfg(procmacro2_semver_exempt)] |
| 128 | { |
| 129 | for next in iter { |
| 130 | if let Some(span) = joined.join(next) { |
| 131 | joined = span; |
| 132 | } |
| 133 | } |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 134 | } |
David Tolnay | 4d942b4 | 2018-01-02 22:14:04 -0800 | [diff] [blame] | 135 | |
| 136 | #[cfg(not(procmacro2_semver_exempt))] |
David Tolnay | 785a79a | 2018-10-02 21:28:24 -0700 | [diff] [blame] | 137 | { |
David Tolnay | 4d942b4 | 2018-01-02 22:14:04 -0800 | [diff] [blame] | 138 | // We can't join spans without procmacro2_semver_exempt so just grab the |
| 139 | // first one. |
David Tolnay | 785a79a | 2018-10-02 21:28:24 -0700 | [diff] [blame] | 140 | joined = joined; |
David Tolnay | 4d942b4 | 2018-01-02 22:14:04 -0800 | [diff] [blame] | 141 | } |
David Tolnay | 785a79a | 2018-10-02 21:28:24 -0700 | [diff] [blame] | 142 | |
| 143 | joined |
David Tolnay | f790b61 | 2017-12-31 18:46:57 -0500 | [diff] [blame] | 144 | } |