diff options
author | Chih-Hung Hsieh <chh@google.com> | 2019-09-30 13:26:56 -0700 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2019-09-30 13:26:56 -0700 |
commit | efc43820c93a5e71a9d18bf1ced705d158f75dea (patch) | |
tree | a93d7bc976ca5ff533c1e3d083435781ada3f28e | |
parent | af66a02f4aebb40530b2913160588aae25246bf4 (diff) | |
parent | f079f81b2288ba10892ec94bf4bab59e66553b54 (diff) | |
download | platform_external_rust_crates_quote-efc43820c93a5e71a9d18bf1ced705d158f75dea.tar.gz platform_external_rust_crates_quote-efc43820c93a5e71a9d18bf1ced705d158f75dea.tar.bz2 platform_external_rust_crates_quote-efc43820c93a5e71a9d18bf1ced705d158f75dea.zip |
Merge aosp/upstream-master up to Release 1.0.2 am: cb0f96530f am: 6b1a2b83fb
am: f079f81b22
Change-Id: I0b49cbc4fd1119312d317a3aec0e7b229338d486
30 files changed, 1269 insertions, 427 deletions
diff --git a/.travis.yml b/.travis.yml index 46d8006..7182d9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ language: rust rust: - stable - - 1.15.1 + - 1.31.0 - beta script: @@ -1,10 +1,9 @@ rust_library_rlib { name: "libquote", - deny_warnings: false, host_supported: true, crate_name: "quote", srcs: ["src/lib.rs"], rlibs: ["libproc_macro2"], features: ["proc-macro"], - edition: "2015", + edition: "2018", } @@ -1,6 +1,6 @@ [package] name = "quote" -version = "0.6.13" # don't forget to update html_root_url, version in readme for breaking changes +version = "1.0.2" # don't forget to update html_root_url, version in readme for breaking changes authors = ["David Tolnay <dtolnay@gmail.com>"] license = "MIT OR Apache-2.0" description = "Quasi-quoting macro quote!(...)" @@ -10,9 +10,17 @@ keywords = ["syn"] categories = ["development-tools::procedural-macro-helpers"] readme = "README.md" include = ["Cargo.toml", "src/**/*.rs", "tests/**/*.rs", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] +edition = "2018" + +[lib] +name = "quote" [dependencies] -proc-macro2 = { version = "0.4.21", default-features = false } +proc-macro2 = { version = "1.0", default-features = false } + +[dev-dependencies] +rustversion = "0.1" +trybuild = "1.0" [features] default = ["proc-macro"] @@ -8,13 +8,13 @@ Rust Quasi-Quoting This crate provides the [`quote!`] macro for turning Rust syntax tree data structures into tokens of source code. -[`quote!`]: https://docs.rs/quote/0.6/quote/macro.quote.html +[`quote!`]: https://docs.rs/quote/1.0/quote/macro.quote.html Procedural macros in Rust receive a stream of tokens as input, execute arbitrary Rust code to determine how to manipulate those tokens, and produce a stream of tokens to hand back to the compiler to compile into the caller's crate. -Quasi-quoting is a solution to one piece of that -- producing tokens to return -to the compiler. +Quasi-quoting is a solution to one piece of that — producing tokens to +return to the compiler. The idea of quasi-quoting is that we write *code* that we treat as *data*. Within the `quote!` macro, we can write what looks like code to our text editor @@ -35,7 +35,7 @@ first support for procedural macros in Rust 1.15.0.* ```toml [dependencies] -quote = "0.6" +quote = "1.0" ``` ## Syntax @@ -44,13 +44,13 @@ The quote crate provides a [`quote!`] macro within which you can write Rust code that gets packaged into a [`TokenStream`] and can be treated as data. You should think of `TokenStream` as representing a fragment of Rust source code. -[`TokenStream`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.TokenStream.html +[`TokenStream`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.TokenStream.html Within the `quote!` macro, interpolation is done with `#var`. Any type implementing the [`quote::ToTokens`] trait can be interpolated. This includes most Rust primitive types as well as most of the syntax tree types from [`syn`]. -[`quote::ToTokens`]: https://docs.rs/quote/0.6/quote/trait.ToTokens.html +[`quote::ToTokens`]: https://docs.rs/quote/1.0/quote/trait.ToTokens.html [`syn`]: https://github.com/dtolnay/syn ```rust @@ -148,8 +148,20 @@ quote! { } ``` -The solution is to perform token-level manipulations using the APIs provided by -Syn and proc-macro2. +The solution is to build a new identifier token with the correct value. As this +is such a common case, the `format_ident!` macro provides a convenient utility +for doing so correctly. + +```rust +let varname = format_ident!("_{}", ident); +quote! { + let mut #varname = 0; +} +``` + +Alternatively, the APIs provided by Syn and proc-macro2 can be used to directly +build the identifier. This is roughly equivalent to the above, but will not +handle `ident` being a raw identifier. ```rust let concatenated = format!("_{}", ident); @@ -200,30 +212,12 @@ Any interpolated tokens preserve the `Span` information provided by their `ToTokens` implementation. Tokens that originate within a `quote!` invocation are spanned with [`Span::call_site()`]. -[`Span::call_site()`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html#method.call_site +[`Span::call_site()`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html#method.call_site A different span can be provided explicitly through the [`quote_spanned!`] macro. -[`quote_spanned!`]: https://docs.rs/quote/0.6/quote/macro.quote_spanned.html - -### Limitations - -- A non-repeating variable may not be interpolated inside of a repeating block - ([#7]). -- The same variable may not be interpolated more than once inside of a repeating - block ([#8]). - -[#7]: https://github.com/dtolnay/quote/issues/7 -[#8]: https://github.com/dtolnay/quote/issues/8 - -### Recursion limit - -The `quote!` macro relies on deep recursion so some large invocations may fail -with "recursion limit reached" when you compile. If it fails, bump up the -recursion limit by adding `#![recursion_limit = "128"]` to your crate. An even -higher limit may be necessary for especially large invocations. You don't need -this unless the compiler tells you that you need it. +[`quote_spanned!`]: https://docs.rs/quote/1.0/quote/macro.quote_spanned.html <br> diff --git a/benches/bench.rs b/benches/bench.rs index cdbdb45..c76a638 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,7 +1,6 @@ #![feature(test)] #![recursion_limit = "512"] -extern crate quote; extern crate test; use quote::quote; diff --git a/build.rs b/build.rs deleted file mode 100644 index a0474a4..0000000 --- a/build.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::env; -use std::process::Command; -use std::str::{self, FromStr}; - -// The rustc-cfg strings below are *not* public API. Please let us know by -// opening a GitHub issue if your build environment requires some way to enable -// these cfgs other than by executing our build script. -fn main() { - let minor = match rustc_minor_version() { - Some(minor) => minor, - None => return, - }; - - // 128-bit integers stabilized in Rust 1.26: - // https://blog.rust-lang.org/2018/05/10/Rust-1.26.html - if minor >= 26 { - println!("cargo:rustc-cfg=integer128"); - } -} - -fn rustc_minor_version() -> Option<u32> { - let rustc = match env::var_os("RUSTC") { - Some(rustc) => rustc, - None => return None, - }; - - let output = match Command::new(rustc).arg("--version").output() { - Ok(output) => output, - Err(_) => return None, - }; - - let version = match str::from_utf8(&output.stdout) { - Ok(version) => version, - Err(_) => return None, - }; - - let mut pieces = version.split('.'); - if pieces.next() != Some("rustc 1") { - return None; - } - - let next = match pieces.next() { - Some(next) => next, - None => return None, - }; - - u32::from_str(next).ok() -} @@ -17,7 +17,7 @@ pub trait TokenStreamExt: private::Sealed { /// For use by `ToTokens` implementations. /// - /// ```edition2018 + /// ``` /// # use quote::{quote, TokenStreamExt, ToTokens}; /// # use proc_macro2::TokenStream; /// # @@ -32,29 +32,29 @@ pub trait TokenStreamExt: private::Sealed { /// let tokens = quote!(#X); /// assert_eq!(tokens.to_string(), "true false"); /// ``` - fn append_all<T, I>(&mut self, iter: I) + fn append_all<I>(&mut self, iter: I) where - T: ToTokens, - I: IntoIterator<Item = T>; + I: IntoIterator, + I::Item: ToTokens; /// For use by `ToTokens` implementations. /// /// Appends all of the items in the iterator `I`, separated by the tokens /// `U`. - fn append_separated<T, I, U>(&mut self, iter: I, op: U) + fn append_separated<I, U>(&mut self, iter: I, op: U) where - T: ToTokens, - I: IntoIterator<Item = T>, + I: IntoIterator, + I::Item: ToTokens, U: ToTokens; /// For use by `ToTokens` implementations. /// /// Appends all tokens in the iterator `I`, appending `U` after each /// element, including after the last element of the iterator. - fn append_terminated<T, I, U>(&mut self, iter: I, term: U) + fn append_terminated<I, U>(&mut self, iter: I, term: U) where - T: ToTokens, - I: IntoIterator<Item = T>, + I: IntoIterator, + I::Item: ToTokens, U: ToTokens; } @@ -66,20 +66,20 @@ impl TokenStreamExt for TokenStream { self.extend(iter::once(token.into())); } - fn append_all<T, I>(&mut self, iter: I) + fn append_all<I>(&mut self, iter: I) where - T: ToTokens, - I: IntoIterator<Item = T>, + I: IntoIterator, + I::Item: ToTokens, { for token in iter { token.to_tokens(self); } } - fn append_separated<T, I, U>(&mut self, iter: I, op: U) + fn append_separated<I, U>(&mut self, iter: I, op: U) where - T: ToTokens, - I: IntoIterator<Item = T>, + I: IntoIterator, + I::Item: ToTokens, U: ToTokens, { for (i, token) in iter.into_iter().enumerate() { @@ -90,10 +90,10 @@ impl TokenStreamExt for TokenStream { } } - fn append_terminated<T, I, U>(&mut self, iter: I, term: U) + fn append_terminated<I, U>(&mut self, iter: I, term: U) where - T: ToTokens, - I: IntoIterator<Item = T>, + I: IntoIterator, + I::Item: ToTokens, U: ToTokens, { for token in iter { diff --git a/src/format.rs b/src/format.rs new file mode 100644 index 0000000..13c8811 --- /dev/null +++ b/src/format.rs @@ -0,0 +1,164 @@ +/// Formatting macro for constructing `Ident`s. +/// +/// <br> +/// +/// # Syntax +/// +/// Syntax is copied from the [`format!`] macro, supporting both positional and +/// named arguments. +/// +/// Only a limited set of formatting traits are supported. The current mapping +/// of format types to traits is: +/// +/// * `{}` ⇒ [`IdentFragment`] +/// * `{:o}` ⇒ [`Octal`](`std::fmt::Octal`) +/// * `{:x}` ⇒ [`LowerHex`](`std::fmt::LowerHex`) +/// * `{:X}` ⇒ [`UpperHex`](`std::fmt::UpperHex`) +/// * `{:b}` ⇒ [`Binary`](`std::fmt::Binary`) +/// +/// See [`std::fmt`] for more information. +/// +/// <br> +/// +/// # IdentFragment +/// +/// Unlike `format!`, this macro uses the [`IdentFragment`] formatting trait by +/// default. This trait is like `Display`, with a few differences: +/// +/// * `IdentFragment` is only implemented for a limited set of types, such as +/// unsigned integers and strings. +/// * [`Ident`] arguments will have their `r#` prefixes stripped, if present. +/// +/// [`Ident`]: `proc_macro2::Ident` +/// +/// <br> +/// +/// # Hygiene +/// +/// The [`Span`] of the first `Ident` argument is used as the span of the final +/// identifier, falling back to [`Span::call_site`] when no identifiers are +/// provided. +/// +/// ``` +/// # use quote::format_ident; +/// # let ident = format_ident!("Ident"); +/// // If `ident` is an Ident, the span of `my_ident` will be inherited from it. +/// let my_ident = format_ident!("My{}{}", ident, "IsCool"); +/// assert_eq!(my_ident, "MyIdentIsCool"); +/// ``` +/// +/// Alternatively, the span can be overridden by passing the `span` named +/// argument. +/// +/// ``` +/// # use quote::format_ident; +/// # const IGNORE_TOKENS: &'static str = stringify! { +/// let my_span = /* ... */; +/// # }; +/// # let my_span = proc_macro2::Span::call_site(); +/// format_ident!("MyIdent", span = my_span); +/// ``` +/// +/// [`Span`]: `proc_macro2::Span` +/// [`Span::call_site`]: `proc_macro2::Span::call_site` +/// +/// <p><br></p> +/// +/// # Panics +/// +/// This method will panic if the resulting formatted string is not a valid +/// identifier. +/// +/// <br> +/// +/// # Examples +/// +/// Composing raw and non-raw identifiers: +/// ``` +/// # use quote::format_ident; +/// let my_ident = format_ident!("My{}", "Ident"); +/// assert_eq!(my_ident, "MyIdent"); +/// +/// let raw = format_ident!("r#Raw"); +/// assert_eq!(raw, "r#Raw"); +/// +/// let my_ident_raw = format_ident!("{}Is{}", my_ident, raw); +/// assert_eq!(my_ident_raw, "MyIdentIsRaw"); +/// ``` +/// +/// Integer formatting options: +/// ``` +/// # use quote::format_ident; +/// let num: u32 = 10; +/// +/// let decimal = format_ident!("Id_{}", num); +/// assert_eq!(decimal, "Id_10"); +/// +/// let octal = format_ident!("Id_{:o}", num); +/// assert_eq!(octal, "Id_12"); +/// +/// let binary = format_ident!("Id_{:b}", num); +/// assert_eq!(binary, "Id_1010"); +/// +/// let lower_hex = format_ident!("Id_{:x}", num); +/// assert_eq!(lower_hex, "Id_a"); +/// +/// let upper_hex = format_ident!("Id_{:X}", num); +/// assert_eq!(upper_hex, "Id_A"); +/// ``` +#[macro_export] +macro_rules! format_ident { + ($fmt:expr) => { + $crate::format_ident_impl!([ + ::std::option::Option::None, + $fmt + ]) + }; + + ($fmt:expr, $($rest:tt)*) => { + $crate::format_ident_impl!([ + ::std::option::Option::None, + $fmt + ] $($rest)*) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! format_ident_impl { + // Final state + ([$span:expr, $($fmt:tt)*]) => { + $crate::__rt::mk_ident(&format!($($fmt)*), $span) + }; + + // Span argument + ([$old:expr, $($fmt:tt)*] span = $span:expr) => { + $crate::format_ident_impl!([$old, $($fmt)*] span = $span,) + }; + ([$old:expr, $($fmt:tt)*] span = $span:expr, $($rest:tt)*) => { + $crate::format_ident_impl!([ + ::std::option::Option::Some::<$crate::__rt::Span>($span), + $($fmt)* + ] $($rest)*) + }; + + // Named argument + ([$span:expr, $($fmt:tt)*] $name:ident = $arg:expr) => { + $crate::format_ident_impl!([$span, $($fmt)*] $name = $arg,) + }; + ([$span:expr, $($fmt:tt)*] $name:ident = $arg:expr, $($rest:tt)*) => { + match $crate::__rt::IdentFragmentAdapter(&$arg) { + arg => $crate::format_ident_impl!([$span.or(arg.span()), $($fmt)*, $name = arg] $($rest)*), + } + }; + + // Positional argument + ([$span:expr, $($fmt:tt)*] $arg:expr) => { + $crate::format_ident_impl!([$span, $($fmt)*] $arg,) + }; + ([$span:expr, $($fmt:tt)*] $arg:expr, $($rest:tt)*) => { + match $crate::__rt::IdentFragmentAdapter(&$arg) { + arg => $crate::format_ident_impl!([$span.or(arg.span()), $($fmt)*, arg] $($rest)*), + } + }; +} diff --git a/src/ident_fragment.rs b/src/ident_fragment.rs new file mode 100644 index 0000000..09ead65 --- /dev/null +++ b/src/ident_fragment.rs @@ -0,0 +1,72 @@ +use proc_macro2::{Ident, Span}; +use std::fmt; + +/// Specialized formatting trait used by `format_ident!`. +/// +/// [`Ident`] arguments formatted using this trait will have their `r#` prefix +/// stripped, if present. +/// +/// See [`format_ident!`] for more information. +pub trait IdentFragment { + /// Format this value as an identifier fragment. + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result; + + /// Span associated with this `IdentFragment`. + /// + /// If non-`None`, may be inherited by formatted identifiers. + fn span(&self) -> Option<Span> { + None + } +} + +impl<'a, T: IdentFragment + ?Sized> IdentFragment for &'a T { + fn span(&self) -> Option<Span> { + <T as IdentFragment>::span(*self) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(*self, f) + } +} + +impl<'a, T: IdentFragment + ?Sized> IdentFragment for &'a mut T { + fn span(&self) -> Option<Span> { + <T as IdentFragment>::span(*self) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(*self, f) + } +} + +impl IdentFragment for Ident { + fn span(&self) -> Option<Span> { + Some(self.span()) + } + + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let id = self.to_string(); + if id.starts_with("r#") { + fmt::Display::fmt(&id[2..], f) + } else { + fmt::Display::fmt(&id[..], f) + } + } +} + +// Limited set of types which this is implemented for, as we want to avoid types +// which will often include non-identifier characters in their `Display` impl. +macro_rules! ident_fragment_display { + ($($T:ty),*) => { + $( + impl IdentFragment for $T { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } + } + )* + } +} + +ident_fragment_display!(bool, str, String); +ident_fragment_display!(u8, u16, u32, u64, u128, usize); @@ -6,8 +6,8 @@ //! Procedural macros in Rust receive a stream of tokens as input, execute //! arbitrary Rust code to determine how to manipulate those tokens, and produce //! a stream of tokens to hand back to the compiler to compile into the caller's -//! crate. Quasi-quoting is a solution to one piece of that -- producing tokens -//! to return to the compiler. +//! crate. Quasi-quoting is a solution to one piece of that — producing +//! tokens to return to the compiler. //! //! The idea of quasi-quoting is that we write *code* that we treat as *data*. //! Within the `quote!` macro, we can write what looks like code to our text @@ -21,14 +21,13 @@ //! general-purpose Rust quasi-quoting library and is not specific to procedural //! macros. //! -//! *Version requirement: Quote supports any compiler version back to Rust's -//! very first support for procedural macros in Rust 1.15.0.* -//! //! ```toml //! [dependencies] -//! quote = "0.6" +//! quote = "1.0" //! ``` //! +//! <br> +//! //! # Example //! //! The following quasi-quoted block of code is something you might find in [a] @@ -41,7 +40,7 @@ //! [a]: https://serde.rs/ //! [`quote_spanned!`]: macro.quote_spanned.html //! -//! ```edition2018 +//! ``` //! # use quote::quote; //! # //! # let generics = ""; @@ -72,42 +71,45 @@ //! } //! }; //! ``` -//! -//! # Recursion limit -//! -//! The `quote!` macro relies on deep recursion so some large invocations may -//! fail with "recursion limit reached" when you compile. If it fails, bump up -//! the recursion limit by adding `#![recursion_limit = "128"]` to your crate. -//! An even higher limit may be necessary for especially large invocations. // Quote types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/quote/0.6.13")] +#![doc(html_root_url = "https://docs.rs/quote/1.0.2")] #[cfg(all( not(all(target_arch = "wasm32", target_os = "unknown")), feature = "proc-macro" ))] extern crate proc_macro; -extern crate proc_macro2; mod ext; -pub use ext::TokenStreamExt; - +mod format; +mod ident_fragment; mod to_tokens; -pub use to_tokens::ToTokens; // Not public API. #[doc(hidden)] #[path = "runtime.rs"] pub mod __rt; +pub use crate::ext::TokenStreamExt; +pub use crate::ident_fragment::IdentFragment; +pub use crate::to_tokens::ToTokens; + +// Not public API. +#[doc(hidden)] +pub mod spanned; + /// The whole point. /// /// Performs variable interpolation against the input and produces it as -/// [`TokenStream`]. For returning tokens to the compiler in a procedural macro, use -/// `into()` to build a `TokenStream`. +/// [`proc_macro2::TokenStream`]. /// -/// [`TokenStream`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.TokenStream.html +/// Note: for returning tokens to the compiler in a procedural macro, use +/// `.into()` on the result to convert to [`proc_macro::TokenStream`]. +/// +/// [`TokenStream`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.TokenStream.html +/// +/// <br> /// /// # Interpolation /// @@ -124,27 +126,15 @@ pub mod __rt; /// Repetition is done using `#(...)*` or `#(...),*` again similar to /// `macro_rules!`. This iterates through the elements of any variable /// interpolated within the repetition and inserts a copy of the repetition body -/// for each one. The variables in an interpolation may be anything that -/// implements `IntoIterator`, including `Vec` or a pre-existing iterator. +/// for each one. The variables in an interpolation may be a `Vec`, slice, +/// `BTreeSet`, or any `Iterator`. /// /// - `#(#var)*` — no separators /// - `#(#var),*` — the character before the asterisk is used as a separator /// - `#( struct #var; )*` — the repetition can contain other tokens /// - `#( #k => println!("{}", #v), )*` — even multiple interpolations /// -/// There are two limitations around interpolations in a repetition: -/// -/// - Every interpolation inside of a repetition must be a distinct variable. -/// That is, `#(#a #a)*` is not allowed. Work around this by collecting `a` -/// into a vector and taking references `a1 = &a` and `a2 = &a` which you use -/// inside the repetition: `#(#a1 #a2)*`. Where possible, use meaningful names -/// that indicate the distinct role of each copy. -/// -/// - Every interpolation inside of a repetition must be iterable. If we have -/// `vec` which is a vector and `ident` which is a single identifier, -/// `#(#ident #vec)*` is not allowed. Work around this by using -/// `std::iter::repeat(ident)` to produce an iterable that can be used from -/// within the repetition. +/// <br> /// /// # Hygiene /// @@ -152,12 +142,14 @@ pub mod __rt; /// `ToTokens` implementation. Tokens that originate within the `quote!` /// invocation are spanned with [`Span::call_site()`]. /// -/// [`Span::call_site()`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html#method.call_site +/// [`Span::call_site()`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html#method.call_site /// /// A different span can be provided through the [`quote_spanned!`] macro. /// /// [`quote_spanned!`]: macro.quote_spanned.html /// +/// <br> +/// /// # Return type /// /// The macro evaluates to an expression of type `proc_macro2::TokenStream`. @@ -178,9 +170,11 @@ pub mod __rt; /// /// [`From`]: https://doc.rust-lang.org/std/convert/trait.From.html /// +/// <br> +/// /// # Examples /// -/// ## Procedural macro +/// ### Procedural macro /// /// The structure of a basic procedural macro is as follows. Refer to the [Syn] /// crate for further useful guidance on using `quote!` as part of a procedural @@ -188,12 +182,14 @@ pub mod __rt; /// /// [Syn]: https://github.com/dtolnay/syn /// -/// ```edition2018 +/// ``` /// # #[cfg(any())] /// extern crate proc_macro; -/// # use proc_macro2 as proc_macro; +/// # extern crate proc_macro2; /// +/// # #[cfg(any())] /// use proc_macro::TokenStream; +/// # use proc_macro2::TokenStream; /// use quote::quote; /// /// # const IGNORE_TOKENS: &'static str = stringify! { @@ -223,14 +219,16 @@ pub mod __rt; /// } /// ``` /// -/// ## Combining quoted fragments +/// <p><br></p> +/// +/// ### Combining quoted fragments /// /// Usually you don't end up constructing an entire final `TokenStream` in one /// piece. Different parts may come from different helper functions. The tokens /// produced by `quote!` themselves implement `ToTokens` and so can be /// interpolated into later `quote!` invocations to build up a final result. /// -/// ```edition2018 +/// ``` /// # use quote::quote; /// # /// let type_definition = quote! {...}; @@ -242,7 +240,9 @@ pub mod __rt; /// }; /// ``` /// -/// ## Constructing identifiers +/// <p><br></p> +/// +/// ### Constructing identifiers /// /// Suppose we have an identifier `ident` which came from somewhere in a macro /// input and we need to modify it in some way for the macro output. Let's @@ -252,7 +252,7 @@ pub mod __rt; /// behavior of concatenating them. The underscore and the identifier will /// continue to be two separate tokens as if you had written `_ x`. /// -/// ```edition2018 +/// ``` /// # use proc_macro2::{self as syn, Span}; /// # use quote::quote; /// # @@ -265,10 +265,28 @@ pub mod __rt; /// # ; /// ``` /// -/// The solution is to perform token-level manipulations using the APIs provided -/// by Syn and proc-macro2. +/// The solution is to build a new identifier token with the correct value. As +/// this is such a common case, the [`format_ident!`] macro provides a +/// convenient utility for doing so correctly. /// -/// ```edition2018 +/// ``` +/// # use proc_macro2::{Ident, Span}; +/// # use quote::{format_ident, quote}; +/// # +/// # let ident = Ident::new("i", Span::call_site()); +/// # +/// let varname = format_ident!("_{}", ident); +/// quote! { +/// let mut #varname = 0; +/// } +/// # ; +/// ``` +/// +/// Alternatively, the APIs provided by Syn and proc-macro2 can be used to +/// directly build the identifier. This is roughly equivalent to the above, but +/// will not handle `ident` being a raw identifier. +/// +/// ``` /// # use proc_macro2::{self as syn, Span}; /// # use quote::quote; /// # @@ -282,13 +300,15 @@ pub mod __rt; /// # ; /// ``` /// -/// ## Making method calls +/// <p><br></p> +/// +/// ### Making method calls /// /// Let's say our macro requires some type specified in the macro input to have /// a constructor called `new`. We have the type in a variable called /// `field_type` of type `syn::Type` and want to invoke the constructor. /// -/// ```edition2018 +/// ``` /// # use quote::quote; /// # /// # let field_type = quote!(...); @@ -306,7 +326,7 @@ pub mod __rt; /// syntax. Ordinarily in handwritten Rust we would write `Vec::<i32>::new()` /// but for macros often the following is more convenient. /// -/// ```edition2018 +/// ``` /// # use quote::quote; /// # /// # let field_type = quote!(...); @@ -321,7 +341,7 @@ pub mod __rt; /// /// A similar pattern is appropriate for trait methods. /// -/// ```edition2018 +/// ``` /// # use quote::quote; /// # /// # let field_type = quote!(...); @@ -331,25 +351,137 @@ pub mod __rt; /// } /// # ; /// ``` -#[macro_export(local_inner_macros)] +/// +/// <p><br></p> +/// +/// ### Interpolating text inside of doc comments +/// +/// Neither doc comments nor string literals get interpolation behavior in +/// quote: +/// +/// ```compile_fail +/// quote! { +/// /// try to interpolate: #ident +/// /// +/// /// ... +/// } +/// ``` +/// +/// ```compile_fail +/// quote! { +/// #[doc = "try to interpolate: #ident"] +/// } +/// ``` +/// +/// Macro calls in a doc attribute are not valid syntax: +/// +/// ```compile_fail +/// quote! { +/// #[doc = concat!("try to interpolate: ", stringify!(#ident))] +/// } +/// ``` +/// +/// Instead the best way to build doc comments that involve variables is by +/// formatting the doc string literal outside of quote. +/// +/// ```rust +/// # use proc_macro2::{Ident, Span}; +/// # use quote::quote; +/// # +/// # const IGNORE: &str = stringify! { +/// let msg = format!(...); +/// # }; +/// # +/// # let ident = Ident::new("var", Span::call_site()); +/// # let msg = format!("try to interpolate: {}", ident); +/// quote! { +/// #[doc = #msg] +/// /// +/// /// ... +/// } +/// # ; +/// ``` +/// +/// <p><br></p> +/// +/// ### Indexing into a tuple struct +/// +/// When interpolating indices of a tuple or tuple struct, we need them not to +/// appears suffixed as integer literals by interpolating them as [`syn::Index`] +/// instead. +/// +/// [`syn::Index`]: https://docs.rs/syn/1.0/syn/struct.Index.html +/// +/// ```compile_fail +/// let i = 0usize..self.fields.len(); +/// +/// // expands to 0 + self.0usize.heap_size() + self.1usize.heap_size() + ... +/// // which is not valid syntax +/// quote! { +/// 0 #( + self.#i.heap_size() )* +/// } +/// ``` +/// +/// ``` +/// # use proc_macro2::{Ident, TokenStream}; +/// # use quote::quote; +/// # +/// # mod syn { +/// # use proc_macro2::{Literal, TokenStream}; +/// # use quote::{ToTokens, TokenStreamExt}; +/// # +/// # pub struct Index(usize); +/// # +/// # impl From<usize> for Index { +/// # fn from(i: usize) -> Self { +/// # Index(i) +/// # } +/// # } +/// # +/// # impl ToTokens for Index { +/// # fn to_tokens(&self, tokens: &mut TokenStream) { +/// # tokens.append(Literal::usize_unsuffixed(self.0)); +/// # } +/// # } +/// # } +/// # +/// # struct Struct { +/// # fields: Vec<Ident>, +/// # } +/// # +/// # impl Struct { +/// # fn example(&self) -> TokenStream { +/// let i = (0..self.fields.len()).map(syn::Index::from); +/// +/// // expands to 0 + self.0.heap_size() + self.1.heap_size() + ... +/// quote! { +/// 0 #( + self.#i.heap_size() )* +/// } +/// # } +/// # } +/// ``` +#[macro_export] macro_rules! quote { ($($tt:tt)*) => { - quote_spanned!($crate::__rt::Span::call_site()=> $($tt)*) + $crate::quote_spanned!($crate::__rt::Span::call_site()=> $($tt)*) }; } /// Same as `quote!`, but applies a given span to all tokens originating within /// the macro invocation. /// +/// <br> +/// /// # Syntax /// /// A span expression of type [`Span`], followed by `=>`, followed by the tokens -/// to quote. The span expression should be brief -- use a variable for anything -/// more than a few characters. There should be no space before the `=>` token. +/// to quote. The span expression should be brief — use a variable for +/// anything more than a few characters. There should be no space before the +/// `=>` token. /// -/// [`Span`]: https://docs.rs/proc-macro2/0.4/proc_macro2/struct.Span.html +/// [`Span`]: https://docs.rs/proc-macro2/1.0/proc_macro2/struct.Span.html /// -/// ```edition2018 +/// ``` /// # use proc_macro2::Span; /// # use quote::quote_spanned; /// # @@ -374,12 +506,16 @@ macro_rules! quote { /// being evaluated in the context of the procedural macro and the remaining /// tokens being evaluated in the generated code. /// +/// <br> +/// /// # Hygiene /// /// Any interpolated tokens preserve the `Span` information provided by their /// `ToTokens` implementation. Tokens that originate within the `quote_spanned!` /// invocation are spanned with the given span argument. /// +/// <br> +/// /// # Example /// /// The following procedural macro code uses `quote_spanned!` to assert that a @@ -388,7 +524,7 @@ macro_rules! quote { /// /// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html /// -/// ```edition2018 +/// ``` /// # use quote::{quote_spanned, TokenStreamExt, ToTokens}; /// # use proc_macro2::{Span, TokenStream}; /// # @@ -426,436 +562,387 @@ macro_rules! quote { /// /// In this example it is important for the where-clause to be spanned with the /// line/column information of the user's input type so that error messages are -/// placed appropriately by the compiler. But it is also incredibly important -/// that `Sync` resolves at the macro definition site and not the macro call -/// site. If we resolve `Sync` at the same span that the user's type is going to -/// be resolved, then they could bypass our check by defining their own trait -/// named `Sync` that is implemented for their type. -#[macro_export(local_inner_macros)] +/// placed appropriately by the compiler. +#[macro_export] macro_rules! quote_spanned { ($span:expr=> $($tt:tt)*) => {{ let mut _s = $crate::__rt::TokenStream::new(); - let _span = $span; - quote_each_token!(_s _span $($tt)*); + let _span: $crate::__rt::Span = $span; + $crate::quote_each_token!(_s _span $($tt)*); _s }}; } -// Extract the names of all #metavariables and pass them to the $finish macro. +// Extract the names of all #metavariables and pass them to the $call macro. // -// in: pounded_var_names!(then () a #b c #( #d )* #e) -// out: then!(() b d e) -#[macro_export(local_inner_macros)] +// in: pounded_var_names!(then!(...) a #b c #( #d )* #e) +// out: then!(... b); +// then!(... d); +// then!(... e); +#[macro_export] #[doc(hidden)] macro_rules! pounded_var_names { - ($finish:ident ($($found:ident)*) # ( $($inner:tt)* ) $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) - }; - - ($finish:ident ($($found:ident)*) # [ $($inner:tt)* ] $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) - }; - - ($finish:ident ($($found:ident)*) # { $($inner:tt)* } $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) + ($call:ident! $extra:tt $($tts:tt)*) => { + $crate::pounded_var_names_with_context!($call! $extra + (@ $($tts)*) + ($($tts)* @) + ) }; +} - ($finish:ident ($($found:ident)*) # $first:ident $($rest:tt)*) => { - pounded_var_names!($finish ($($found)* $first) $($rest)*) +#[macro_export] +#[doc(hidden)] +macro_rules! pounded_var_names_with_context { + ($call:ident! $extra:tt ($($b1:tt)*) ($($curr:tt)*)) => { + $( + $crate::pounded_var_with_context!($call! $extra $b1 $curr); + )* }; +} - ($finish:ident ($($found:ident)*) ( $($inner:tt)* ) $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) +#[macro_export] +#[doc(hidden)] +macro_rules! pounded_var_with_context { + ($call:ident! $extra:tt $b1:tt ( $($inner:tt)* )) => { + $crate::pounded_var_names!($call! $extra $($inner)*); }; - ($finish:ident ($($found:ident)*) [ $($inner:tt)* ] $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) + ($call:ident! $extra:tt $b1:tt [ $($inner:tt)* ]) => { + $crate::pounded_var_names!($call! $extra $($inner)*); }; - ($finish:ident ($($found:ident)*) { $($inner:tt)* } $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($inner)* $($rest)*) + ($call:ident! $extra:tt $b1:tt { $($inner:tt)* }) => { + $crate::pounded_var_names!($call! $extra $($inner)*); }; - ($finish:ident ($($found:ident)*) $ignore:tt $($rest:tt)*) => { - pounded_var_names!($finish ($($found)*) $($rest)*) + ($call:ident!($($extra:tt)*) # $var:ident) => { + $crate::$call!($($extra)* $var); }; - ($finish:ident ($($found:ident)*)) => { - $finish!(() $($found)*) - }; + ($call:ident! $extra:tt $b1:tt $curr:tt) => {}; } -// in: nested_tuples_pat!(() a b c d e) -// out: ((((a b) c) d) e) -// -// in: nested_tuples_pat!(() a) -// out: a -#[macro_export(local_inner_macros)] +#[macro_export] #[doc(hidden)] -macro_rules! nested_tuples_pat { - (()) => { - &() - }; - - (() $first:ident $($rest:ident)*) => { - nested_tuples_pat!(($first) $($rest)*) - }; - - (($pat:pat) $first:ident $($rest:ident)*) => { - nested_tuples_pat!((($pat, $first)) $($rest)*) - }; - - (($done:pat)) => { - $done +macro_rules! quote_bind_into_iter { + ($has_iter:ident $var:ident) => { + // `mut` may be unused if $var occurs multiple times in the list. + #[allow(unused_mut)] + let (mut $var, i) = $var.quote_into_iter(); + let $has_iter = $has_iter | i; }; } -// in: multi_zip_expr!(() a b c d e) -// out: a.into_iter().zip(b).zip(c).zip(d).zip(e) -// -// in: multi_zip_iter!(() a) -// out: a -#[macro_export(local_inner_macros)] +#[macro_export] #[doc(hidden)] -macro_rules! multi_zip_expr { - (()) => { - &[] - }; - - (() $single:ident) => { - $single - }; - - (() $first:ident $($rest:ident)*) => { - multi_zip_expr!(($first.into_iter()) $($rest)*) - }; - - (($zips:expr) $first:ident $($rest:ident)*) => { - multi_zip_expr!(($zips.zip($first)) $($rest)*) - }; - - (($done:expr)) => { - $done +macro_rules! quote_bind_next_or_break { + ($var:ident) => { + let $var = match $var.next() { + Some(_x) => $crate::__rt::RepInterp(_x), + None => break, + }; }; } -#[macro_export(local_inner_macros)] +#[macro_export] #[doc(hidden)] macro_rules! quote_each_token { - ($tokens:ident $span:ident) => {}; - - ($tokens:ident $span:ident # ! $($rest:tt)*) => { - quote_each_token!($tokens $span #); - quote_each_token!($tokens $span !); - quote_each_token!($tokens $span $($rest)*); + ($tokens:ident $span:ident $($tts:tt)*) => { + $crate::quote_tokens_with_context!($tokens $span + (@ @ @ @ @ @ $($tts)*) + (@ @ @ @ @ $($tts)* @) + (@ @ @ @ $($tts)* @ @) + (@ @ @ $(($tts))* @ @ @) + (@ @ $($tts)* @ @ @ @) + (@ $($tts)* @ @ @ @ @) + ($($tts)* @ @ @ @ @ @) + ); }; +} - ($tokens:ident $span:ident # ( $($inner:tt)* ) * $($rest:tt)*) => { - for pounded_var_names!(nested_tuples_pat () $($inner)*) - in pounded_var_names!(multi_zip_expr () $($inner)*) { - quote_each_token!($tokens $span $($inner)*); - } - quote_each_token!($tokens $span $($rest)*); +#[macro_export] +#[doc(hidden)] +macro_rules! quote_tokens_with_context { + ($tokens:ident $span:ident + ($($b3:tt)*) ($($b2:tt)*) ($($b1:tt)*) + ($($curr:tt)*) + ($($a1:tt)*) ($($a2:tt)*) ($($a3:tt)*) + ) => { + $( + $crate::quote_token_with_context!($tokens $span $b3 $b2 $b1 $curr $a1 $a2 $a3); + )* }; +} - ($tokens:ident $span:ident # ( $($inner:tt)* ) $sep:tt * $($rest:tt)*) => { - for (_i, pounded_var_names!(nested_tuples_pat () $($inner)*)) - in pounded_var_names!(multi_zip_expr () $($inner)*).into_iter().enumerate() { +#[macro_export] +#[doc(hidden)] +macro_rules! quote_token_with_context { + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt @ $a1:tt $a2:tt $a3:tt) => {}; + + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) ( $($inner:tt)* ) * $a3:tt) => {{ + use $crate::__rt::ext::*; + let has_iter = $crate::__rt::ThereIsNoIteratorInRepetition; + $crate::pounded_var_names!(quote_bind_into_iter!(has_iter) () $($inner)*); + let _: $crate::__rt::HasIterator = has_iter; + // This is `while true` instead of `loop` because if there are no + // iterators used inside of this repetition then the body would not + // contain any `break`, so the compiler would emit unreachable code + // warnings on anything below the loop. We use has_iter to detect and + // fail to compile when there are no iterators, so here we just work + // around the unneeded extra warning. + while true { + $crate::pounded_var_names!(quote_bind_next_or_break!() () $($inner)*); + $crate::quote_each_token!($tokens $span $($inner)*); + } + }}; + ($tokens:ident $span:ident $b3:tt $b2:tt # (( $($inner:tt)* )) * $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt # ( $($inner:tt)* ) (*) $a1:tt $a2:tt $a3:tt) => {}; + + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) ( $($inner:tt)* ) $sep:tt *) => {{ + use $crate::__rt::ext::*; + let mut _i = 0usize; + let has_iter = $crate::__rt::ThereIsNoIteratorInRepetition; + $crate::pounded_var_names!(quote_bind_into_iter!(has_iter) () $($inner)*); + let _: $crate::__rt::HasIterator = has_iter; + while true { + $crate::pounded_var_names!(quote_bind_next_or_break!() () $($inner)*); if _i > 0 { - quote_each_token!($tokens $span $sep); + $crate::quote_token!($tokens $span $sep); } - quote_each_token!($tokens $span $($inner)*); + _i += 1; + $crate::quote_each_token!($tokens $span $($inner)*); } - quote_each_token!($tokens $span $($rest)*); + }}; + ($tokens:ident $span:ident $b3:tt $b2:tt # (( $($inner:tt)* )) $sep:tt * $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt # ( $($inner:tt)* ) ($sep:tt) * $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident # ( $($inner:tt)* ) * (*) $a1:tt $a2:tt $a3:tt) => { + // https://github.com/dtolnay/quote/issues/130 + $crate::quote_token!($tokens $span *); }; + ($tokens:ident $span:ident # ( $($inner:tt)* ) $sep:tt (*) $a1:tt $a2:tt $a3:tt) => {}; - ($tokens:ident $span:ident # [ $($inner:tt)* ] $($rest:tt)*) => { - quote_each_token!($tokens $span #); - $tokens.extend({ - let mut g = $crate::__rt::Group::new( - $crate::__rt::Delimiter::Bracket, - quote_spanned!($span=> $($inner)*), - ); - g.set_span($span); - Some($crate::__rt::TokenTree::from(g)) - }); - quote_each_token!($tokens $span $($rest)*); + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt (#) $var:ident $a2:tt $a3:tt) => { + $crate::ToTokens::to_tokens(&$var, &mut $tokens); }; - - ($tokens:ident $span:ident # $first:ident $($rest:tt)*) => { - $crate::ToTokens::to_tokens(&$first, &mut $tokens); - quote_each_token!($tokens $span $($rest)*); + ($tokens:ident $span:ident $b3:tt $b2:tt # ($var:ident) $a1:tt $a2:tt $a3:tt) => {}; + ($tokens:ident $span:ident $b3:tt $b2:tt $b1:tt ($curr:tt) $a1:tt $a2:tt $a3:tt) => { + $crate::quote_token!($tokens $span $curr); }; +} - ($tokens:ident $span:ident ( $($first:tt)* ) $($rest:tt)*) => { +#[macro_export] +#[doc(hidden)] +macro_rules! quote_token { + ($tokens:ident $span:ident ( $($inner:tt)* )) => { $tokens.extend({ let mut g = $crate::__rt::Group::new( $crate::__rt::Delimiter::Parenthesis, - quote_spanned!($span=> $($first)*), + $crate::quote_spanned!($span=> $($inner)*), ); g.set_span($span); Some($crate::__rt::TokenTree::from(g)) }); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident [ $($first:tt)* ] $($rest:tt)*) => { + ($tokens:ident $span:ident [ $($inner:tt)* ]) => { $tokens.extend({ let mut g = $crate::__rt::Group::new( $crate::__rt::Delimiter::Bracket, - quote_spanned!($span=> $($first)*), + $crate::quote_spanned!($span=> $($inner)*), ); g.set_span($span); Some($crate::__rt::TokenTree::from(g)) }); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident { $($first:tt)* } $($rest:tt)*) => { + ($tokens:ident $span:ident { $($inner:tt)* }) => { $tokens.extend({ let mut g = $crate::__rt::Group::new( $crate::__rt::Delimiter::Brace, - quote_spanned!($span=> $($first)*), + $crate::quote_spanned!($span=> $($inner)*), ); g.set_span($span); Some($crate::__rt::TokenTree::from(g)) }); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident + $($rest:tt)*) => { + ($tokens:ident $span:ident +) => { $crate::__rt::push_add(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident += $($rest:tt)*) => { + ($tokens:ident $span:ident +=) => { $crate::__rt::push_add_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident & $($rest:tt)*) => { + ($tokens:ident $span:ident &) => { $crate::__rt::push_and(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident && $($rest:tt)*) => { + ($tokens:ident $span:ident &&) => { $crate::__rt::push_and_and(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident &= $($rest:tt)*) => { + ($tokens:ident $span:ident &=) => { $crate::__rt::push_and_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident @ $($rest:tt)*) => { + ($tokens:ident $span:ident @) => { $crate::__rt::push_at(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ! $($rest:tt)*) => { + ($tokens:ident $span:ident !) => { $crate::__rt::push_bang(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ^ $($rest:tt)*) => { + ($tokens:ident $span:ident ^) => { $crate::__rt::push_caret(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ^= $($rest:tt)*) => { + ($tokens:ident $span:ident ^=) => { $crate::__rt::push_caret_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident : $($rest:tt)*) => { + ($tokens:ident $span:ident :) => { $crate::__rt::push_colon(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident :: $($rest:tt)*) => { + ($tokens:ident $span:ident ::) => { $crate::__rt::push_colon2(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident , $($rest:tt)*) => { + ($tokens:ident $span:ident ,) => { $crate::__rt::push_comma(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident / $($rest:tt)*) => { + ($tokens:ident $span:ident /) => { $crate::__rt::push_div(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident /= $($rest:tt)*) => { + ($tokens:ident $span:ident /=) => { $crate::__rt::push_div_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident . $($rest:tt)*) => { + ($tokens:ident $span:ident .) => { $crate::__rt::push_dot(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident .. $($rest:tt)*) => { + ($tokens:ident $span:ident ..) => { $crate::__rt::push_dot2(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ... $($rest:tt)*) => { + ($tokens:ident $span:ident ...) => { $crate::__rt::push_dot3(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ..= $($rest:tt)*) => { + ($tokens:ident $span:ident ..=) => { $crate::__rt::push_dot_dot_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident = $($rest:tt)*) => { + ($tokens:ident $span:ident =) => { $crate::__rt::push_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident == $($rest:tt)*) => { + ($tokens:ident $span:ident ==) => { $crate::__rt::push_eq_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident >= $($rest:tt)*) => { + ($tokens:ident $span:ident >=) => { $crate::__rt::push_ge(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident > $($rest:tt)*) => { + ($tokens:ident $span:ident >) => { $crate::__rt::push_gt(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident <= $($rest:tt)*) => { + ($tokens:ident $span:ident <=) => { $crate::__rt::push_le(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident < $($rest:tt)*) => { + ($tokens:ident $span:ident <) => { $crate::__rt::push_lt(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident *= $($rest:tt)*) => { + ($tokens:ident $span:ident *=) => { $crate::__rt::push_mul_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident != $($rest:tt)*) => { + ($tokens:ident $span:ident !=) => { $crate::__rt::push_ne(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident | $($rest:tt)*) => { + ($tokens:ident $span:ident |) => { $crate::__rt::push_or(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident |= $($rest:tt)*) => { + ($tokens:ident $span:ident |=) => { $crate::__rt::push_or_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident || $($rest:tt)*) => { + ($tokens:ident $span:ident ||) => { $crate::__rt::push_or_or(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident # $($rest:tt)*) => { + ($tokens:ident $span:ident #) => { $crate::__rt::push_pound(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ? $($rest:tt)*) => { + ($tokens:ident $span:ident ?) => { $crate::__rt::push_question(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident -> $($rest:tt)*) => { + ($tokens:ident $span:ident ->) => { $crate::__rt::push_rarrow(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident <- $($rest:tt)*) => { + ($tokens:ident $span:ident <-) => { $crate::__rt::push_larrow(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident % $($rest:tt)*) => { + ($tokens:ident $span:ident %) => { $crate::__rt::push_rem(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident %= $($rest:tt)*) => { + ($tokens:ident $span:ident %=) => { $crate::__rt::push_rem_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident => $($rest:tt)*) => { + ($tokens:ident $span:ident =>) => { $crate::__rt::push_fat_arrow(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident ; $($rest:tt)*) => { + ($tokens:ident $span:ident ;) => { $crate::__rt::push_semi(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident << $($rest:tt)*) => { + ($tokens:ident $span:ident <<) => { $crate::__rt::push_shl(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident <<= $($rest:tt)*) => { + ($tokens:ident $span:ident <<=) => { $crate::__rt::push_shl_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident >> $($rest:tt)*) => { + ($tokens:ident $span:ident >>) => { $crate::__rt::push_shr(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident >>= $($rest:tt)*) => { + ($tokens:ident $span:ident >>=) => { $crate::__rt::push_shr_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident * $($rest:tt)*) => { + ($tokens:ident $span:ident *) => { $crate::__rt::push_star(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident - $($rest:tt)*) => { + ($tokens:ident $span:ident -) => { $crate::__rt::push_sub(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident -= $($rest:tt)*) => { + ($tokens:ident $span:ident -=) => { $crate::__rt::push_sub_eq(&mut $tokens, $span); - quote_each_token!($tokens $span $($rest)*); }; - ($tokens:ident $span:ident $first:tt $($rest:tt)*) => { - $crate::__rt::parse(&mut $tokens, $span, quote_stringify!($first)); - quote_each_token!($tokens $span $($rest)*); - }; -} - -// Unhygienically invoke whatever `stringify` the caller has in scope i.e. not a -// local macro. The macros marked `local_inner_macros` above cannot invoke -// `stringify` directly. -#[macro_export] -#[doc(hidden)] -macro_rules! quote_stringify { - ($tt:tt) => { - stringify!($tt) + ($tokens:ident $span:ident $other:tt) => { + $crate::__rt::parse(&mut $tokens, $span, stringify!($other)); }; } diff --git a/src/runtime.rs b/src/runtime.rs index 75337c9..4a1c14c 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,6 +1,185 @@ -use ext::TokenStreamExt; +use crate::{IdentFragment, ToTokens, TokenStreamExt}; +use std::fmt; +use std::ops::BitOr; + pub use proc_macro2::*; +pub struct HasIterator; // True +pub struct ThereIsNoIteratorInRepetition; // False + +impl BitOr<ThereIsNoIteratorInRepetition> for ThereIsNoIteratorInRepetition { + type Output = ThereIsNoIteratorInRepetition; + fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> ThereIsNoIteratorInRepetition { + ThereIsNoIteratorInRepetition + } +} + +impl BitOr<ThereIsNoIteratorInRepetition> for HasIterator { + type Output = HasIterator; + fn bitor(self, _rhs: ThereIsNoIteratorInRepetition) -> HasIterator { + HasIterator + } +} + +impl BitOr<HasIterator> for ThereIsNoIteratorInRepetition { + type Output = HasIterator; + fn bitor(self, _rhs: HasIterator) -> HasIterator { + HasIterator + } +} + +impl BitOr<HasIterator> for HasIterator { + type Output = HasIterator; + fn bitor(self, _rhs: HasIterator) -> HasIterator { + HasIterator + } +} + +/// Extension traits used by the implementation of `quote!`. These are defined +/// in separate traits, rather than as a single trait due to ambiguity issues. +/// +/// These traits expose a `quote_into_iter` method which should allow calling +/// whichever impl happens to be applicable. Calling that method repeatedly on +/// the returned value should be idempotent. +pub mod ext { + use super::RepInterp; + use super::{HasIterator as HasIter, ThereIsNoIteratorInRepetition as DoesNotHaveIter}; + use crate::ToTokens; + use std::collections::btree_set::{self, BTreeSet}; + use std::slice; + + /// Extension trait providing the `quote_into_iter` method on iterators. + pub trait RepIteratorExt: Iterator + Sized { + fn quote_into_iter(self) -> (Self, HasIter) { + (self, HasIter) + } + } + + impl<T: Iterator> RepIteratorExt for T {} + + /// Extension trait providing the `quote_into_iter` method for + /// non-iterable types. These types interpolate the same value in each + /// iteration of the repetition. + pub trait RepToTokensExt { + /// Pretend to be an iterator for the purposes of `quote_into_iter`. + /// This allows repeated calls to `quote_into_iter` to continue + /// correctly returning DoesNotHaveIter. + fn next(&self) -> Option<&Self> { + Some(self) + } + + fn quote_into_iter(&self) -> (&Self, DoesNotHaveIter) { + (self, DoesNotHaveIter) + } + } + + impl<T: ToTokens + ?Sized> RepToTokensExt for T {} + + /// Extension trait providing the `quote_into_iter` method for types that + /// can be referenced as an iterator. + pub trait RepAsIteratorExt<'q> { + type Iter: Iterator; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter); + } + + impl<'q, 'a, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &'a T { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + <T as RepAsIteratorExt>::quote_into_iter(*self) + } + } + + impl<'q, 'a, T: RepAsIteratorExt<'q> + ?Sized> RepAsIteratorExt<'q> for &'a mut T { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + <T as RepAsIteratorExt>::quote_into_iter(*self) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for [T] { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for Vec<T> { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + impl<'q, T: 'q> RepAsIteratorExt<'q> for BTreeSet<T> { + type Iter = btree_set::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + + macro_rules! array_rep_slice { + ($($l:tt)*) => { + $( + impl<'q, T: 'q> RepAsIteratorExt<'q> for [T; $l] { + type Iter = slice::Iter<'q, T>; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + (self.iter(), HasIter) + } + } + )* + } + } + + array_rep_slice!( + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 + 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 + ); + + impl<'q, T: RepAsIteratorExt<'q>> RepAsIteratorExt<'q> for RepInterp<T> { + type Iter = T::Iter; + + fn quote_into_iter(&'q self) -> (Self::Iter, HasIter) { + self.0.quote_into_iter() + } + } +} + +// Helper type used within interpolations to allow for repeated binding names. +// Implements the relevant traits, and exports a dummy `next()` method. +#[derive(Copy, Clone)] +pub struct RepInterp<T>(pub T); + +impl<T> RepInterp<T> { + // This method is intended to look like `Iterator::next`, and is called when + // a name is bound multiple times, as the previous binding will shadow the + // original `Iterator` object. This allows us to avoid advancing the + // iterator multiple times per iteration. + pub fn next(self) -> Option<T> { + Some(self.0) + } +} + +impl<T: Iterator> Iterator for RepInterp<T> { + type Item = T::Item; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next() + } +} + +impl<T: ToTokens> ToTokens for RepInterp<T> { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.0.to_tokens(tokens); + } +} + fn is_ident_start(c: u8) -> bool { (b'a' <= c && c <= b'z') || (b'A' <= c && c <= b'Z') || c == b'_' } @@ -12,7 +191,7 @@ fn is_ident_continue(c: u8) -> bool { fn is_ident(token: &str) -> bool { let mut iter = token.bytes(); let first_ok = iter.next().map(is_ident_start).unwrap_or(false); - + first_ok && iter.all(is_ident_continue) } @@ -106,3 +285,89 @@ push_punct!(push_shr_eq '>' '>' '='); push_punct!(push_star '*'); push_punct!(push_sub '-'); push_punct!(push_sub_eq '-' '='); + +// Helper method for constructing identifiers from the `format_ident!` macro, +// handling `r#` prefixes. +// +// Directly parsing the input string may produce a valid identifier, +// although the input string was invalid, due to ignored characters such as +// whitespace and comments. Instead, we always create a non-raw identifier +// to validate that the string is OK, and only parse again if needed. +// +// The `is_ident` method defined above is insufficient for validation, as it +// will reject non-ASCII identifiers. +pub fn mk_ident(id: &str, span: Option<Span>) -> Ident { + let span = span.unwrap_or_else(Span::call_site); + + let is_raw = id.starts_with("r#"); + let unraw = Ident::new(if is_raw { &id[2..] } else { id }, span); + if !is_raw { + return unraw; + } + + // At this point, the identifier is raw, and the unraw-ed version of it was + // successfully converted into an identifier. Try to produce a valid raw + // identifier by running the `TokenStream` parser, and unwrapping the first + // token as an `Ident`. + // + // FIXME: When `Ident::new_raw` becomes stable, this method should be + // updated to call it when available. + match id.parse::<TokenStream>() { + Ok(ts) => { + let mut iter = ts.into_iter(); + match (iter.next(), iter.next()) { + (Some(TokenTree::Ident(mut id)), None) => { + id.set_span(span); + id + } + _ => unreachable!("valid raw ident fails to parse"), + } + } + Err(_) => unreachable!("valid raw ident fails to parse"), + } +} + +// Adapts from `IdentFragment` to `fmt::Display` for use by the `format_ident!` +// macro, and exposes span information from these fragments. +// +// This struct also has forwarding implementations of the formatting traits +// `Octal`, `LowerHex`, `UpperHex`, and `Binary` to allow for their use within +// `format_ident!`. +#[derive(Copy, Clone)] +pub struct IdentFragmentAdapter<T: IdentFragment>(pub T); + +impl<T: IdentFragment> IdentFragmentAdapter<T> { + pub fn span(&self) -> Option<Span> { + self.0.span() + } +} + +impl<T: IdentFragment> fmt::Display for IdentFragmentAdapter<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + IdentFragment::fmt(&self.0, f) + } +} + +impl<T: IdentFragment + fmt::Octal> fmt::Octal for IdentFragmentAdapter<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Octal::fmt(&self.0, f) + } +} + +impl<T: IdentFragment + fmt::LowerHex> fmt::LowerHex for IdentFragmentAdapter<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} + +impl<T: IdentFragment + fmt::UpperHex> fmt::UpperHex for IdentFragmentAdapter<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::UpperHex::fmt(&self.0, f) + } +} + +impl<T: IdentFragment + fmt::Binary> fmt::Binary for IdentFragmentAdapter<T> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Binary::fmt(&self.0, f) + } +} diff --git a/src/spanned.rs b/src/spanned.rs new file mode 100644 index 0000000..55168bd --- /dev/null +++ b/src/spanned.rs @@ -0,0 +1,42 @@ +use crate::ToTokens; +use proc_macro2::{Span, TokenStream}; + +pub trait Spanned { + fn __span(&self) -> Span; +} + +impl Spanned for Span { + fn __span(&self) -> Span { + *self + } +} + +impl<T: ?Sized + ToTokens> Spanned for T { + fn __span(&self) -> Span { + join_spans(self.into_token_stream()) + } +} + +fn join_spans(tokens: TokenStream) -> Span { + let mut iter = tokens.into_iter().filter_map(|tt| { + // FIXME: This shouldn't be required, since optimally spans should + // never be invalid. This filter_map can probably be removed when + // https://github.com/rust-lang/rust/issues/43081 is resolved. + let span = tt.span(); + let debug = format!("{:?}", span); + if debug.ends_with("bytes(0..0)") { + None + } else { + Some(span) + } + }); + + let first = match iter.next() { + Some(span) => span, + None => return Span::call_site(), + }; + + iter.fold(None, |_prev, next| Some(next)) + .and_then(|last| first.join(last)) + .unwrap_or(first) +} diff --git a/src/to_tokens.rs b/src/to_tokens.rs index 9d221b2..7f98083 100644 --- a/src/to_tokens.rs +++ b/src/to_tokens.rs @@ -6,7 +6,7 @@ use std::rc::Rc; use proc_macro2::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; -/// Types that can be interpolated inside a [`quote!`] invocation. +/// Types that can be interpolated inside a `quote!` invocation. /// /// [`quote!`]: macro.quote.html pub trait ToTokens { @@ -22,7 +22,7 @@ pub trait ToTokens { /// Example implementation for a struct representing Rust paths like /// `std::cmp::PartialEq`: /// - /// ```edition2018 + /// ``` /// use proc_macro2::{TokenTree, Spacing, Span, Punct, TokenStream}; /// use quote::{TokenStreamExt, ToTokens}; /// @@ -58,13 +58,21 @@ pub trait ToTokens { /// /// This method is implicitly implemented using `to_tokens`, and acts as a /// convenience method for consumers of the `ToTokens` trait. + fn to_token_stream(&self) -> TokenStream { + let mut tokens = TokenStream::new(); + self.to_tokens(&mut tokens); + tokens + } + + /// Convert `self` directly into a `TokenStream` object. + /// + /// This method is implicitly implemented using `to_tokens`, and acts as a + /// convenience method for consumers of the `ToTokens` trait. fn into_token_stream(self) -> TokenStream where Self: Sized, { - let mut tokens = TokenStream::new(); - self.to_tokens(&mut tokens); - tokens + self.to_token_stream() } } @@ -133,24 +141,20 @@ primitive! { i16 => i16_suffixed i32 => i32_suffixed i64 => i64_suffixed + i128 => i128_suffixed isize => isize_suffixed u8 => u8_suffixed u16 => u16_suffixed u32 => u32_suffixed u64 => u64_suffixed + u128 => u128_suffixed usize => usize_suffixed f32 => f32_suffixed f64 => f64_suffixed } -#[cfg(integer128)] -primitive! { - i128 => i128_suffixed - u128 => u128_suffixed -} - impl ToTokens for char { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append(Literal::character(*self)); diff --git a/tests/compiletest.rs b/tests/compiletest.rs new file mode 100644 index 0000000..f9aea23 --- /dev/null +++ b/tests/compiletest.rs @@ -0,0 +1,6 @@ +#[rustversion::attr(not(nightly), ignore)] +#[test] +fn ui() { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/ui/*.rs"); +} diff --git a/tests/conditional/integer128.rs b/tests/conditional/integer128.rs deleted file mode 100644 index 61e2274..0000000 --- a/tests/conditional/integer128.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[test] -fn test_integer128() { - let ii128 = -1i128; - let uu128 = 1u128; - - let tokens = quote! { - #ii128 #uu128 - }; - let expected = "-1i128 1u128"; - assert_eq!(expected, tokens.to_string()); -} diff --git a/tests/test.rs b/tests/test.rs index 85562bf..957d470 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,18 +1,10 @@ #![cfg_attr(feature = "cargo-clippy", allow(blacklisted_name))] use std::borrow::Cow; - -extern crate proc_macro2; -#[macro_use] -extern crate quote; +use std::collections::BTreeSet; use proc_macro2::{Ident, Span, TokenStream}; -use quote::TokenStreamExt; - -mod conditional { - #[cfg(integer128)] - mod integer128; -} +use quote::{format_ident, quote, TokenStreamExt}; struct X; @@ -125,18 +117,20 @@ fn test_integer() { let ii16 = -1i16; let ii32 = -1i32; let ii64 = -1i64; + let ii128 = -1i128; let iisize = -1isize; let uu8 = 1u8; let uu16 = 1u16; let uu32 = 1u32; let uu64 = 1u64; + let uu128 = 1u128; let uusize = 1usize; let tokens = quote! { - #ii8 #ii16 #ii32 #ii64 #iisize - #uu8 #uu16 #uu32 #uu64 #uusize + #ii8 #ii16 #ii32 #ii64 #ii128 #iisize + #uu8 #uu16 #uu32 #uu64 #uu128 #uusize }; - let expected = "-1i8 -1i16 -1i32 -1i64 -1isize 1u8 1u16 1u32 1u64 1usize"; + let expected = "-1i8 -1i16 -1i32 -1i64 -1i128 -1isize 1u8 1u16 1u32 1u64 1u128 1usize"; assert_eq!(expected, tokens.to_string()); } @@ -233,9 +227,42 @@ fn test_nested_fancy_repetition() { } #[test] -fn test_empty_repetition() { - let tokens = quote!(#(a b)* #(c d),*); - assert_eq!("", tokens.to_string()); +fn test_duplicate_name_repetition() { + let foo = &["a", "b"]; + + let tokens = quote! { + #(#foo: #foo),* + #(#foo: #foo),* + }; + + let expected = r#""a" : "a" , "b" : "b" "a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_duplicate_name_repetition_no_copy() { + let foo = vec!["a".to_owned(), "b".to_owned()]; + + let tokens = quote! { + #(#foo: #foo),* + }; + + let expected = r#""a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_btreeset_repetition() { + let mut set = BTreeSet::new(); + set.insert("a".to_owned()); + set.insert("b".to_owned()); + + let tokens = quote! { + #(#set: #set),* + }; + + let expected = r#""a" : "a" , "b" : "b""#; + assert_eq!(expected, tokens.to_string()); } #[test] @@ -249,6 +276,19 @@ fn test_variable_name_conflict() { } #[test] +fn test_nonrep_in_repetition() { + let rep = vec!["a", "b"]; + let nonrep = "c"; + + let tokens = quote! { + #(#rep #rep : #nonrep #nonrep),* + }; + + let expected = r#""a" "a" : "c" "c" , "b" "b" : "c" "c""#; + assert_eq!(expected, tokens.to_string()); +} + +#[test] fn test_empty_quote() { let tokens = quote!(); assert_eq!("", tokens.to_string()); @@ -275,7 +315,7 @@ fn test_cow() { #[test] fn test_closure() { fn field_i(i: usize) -> Ident { - Ident::new(&format!("__field{}", i), Span::call_site()) + format_ident!("__field{}", i) } let fields = (0usize..3) @@ -293,3 +333,97 @@ fn test_append_tokens() { a.append_all(b); assert_eq!("a b", a.to_string()); } + +#[test] +fn test_format_ident() { + let id0 = format_ident!("Aa"); + let id1 = format_ident!("Hello{x}", x = id0); + let id2 = format_ident!("Hello{x}", x = 5usize); + let id3 = format_ident!("Hello{}_{x}", id0, x = 10usize); + let id4 = format_ident!("Aa", span = Span::call_site()); + + assert_eq!(id0, "Aa"); + assert_eq!(id1, "HelloAa"); + assert_eq!(id2, "Hello5"); + assert_eq!(id3, "HelloAa_10"); + assert_eq!(id4, "Aa"); +} + +#[test] +fn test_format_ident_strip_raw() { + let id = format_ident!("r#struct"); + let my_id = format_ident!("MyId{}", id); + let raw_my_id = format_ident!("r#MyId{}", id); + + assert_eq!(id, "r#struct"); + assert_eq!(my_id, "MyIdstruct"); + assert_eq!(raw_my_id, "r#MyIdstruct"); +} + +#[test] +fn test_outer_line_comment() { + let tokens = quote! { + /// doc + }; + let expected = "# [ doc = r\" doc\" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_line_comment() { + let tokens = quote! { + //! doc + }; + let expected = "# ! [ doc = r\" doc\" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_outer_block_comment() { + let tokens = quote! { + /** doc */ + }; + let expected = "# [ doc = r\" doc \" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_block_comment() { + let tokens = quote! { + /*! doc */ + }; + let expected = "# ! [ doc = r\" doc \" ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_outer_attr() { + let tokens = quote! { + #[inline] + }; + let expected = "# [ inline ]"; + assert_eq!(expected, tokens.to_string()); +} + +#[test] +fn test_inner_attr() { + let tokens = quote! { + #![no_std] + }; + let expected = "# ! [ no_std ]"; + assert_eq!(expected, tokens.to_string()); +} + +// https://github.com/dtolnay/quote/issues/130 +#[test] +fn test_star_after_repetition() { + let c = vec!['0', '1']; + let tokens = quote! { + #( + f(#c); + )* + *out = None; + }; + let expected = "f ( '0' ) ; f ( '1' ) ; * out = None ;"; + assert_eq!(expected, tokens.to_string()); +} diff --git a/tests/ui/does-not-have-iter-interpolated-dup.rs b/tests/ui/does-not-have-iter-interpolated-dup.rs new file mode 100644 index 0000000..0a39f41 --- /dev/null +++ b/tests/ui/does-not-have-iter-interpolated-dup.rs @@ -0,0 +1,9 @@ +use quote::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!(#(#nonrep #nonrep)*); +} diff --git a/tests/ui/does-not-have-iter-interpolated-dup.stderr b/tests/ui/does-not-have-iter-interpolated-dup.stderr new file mode 100644 index 0000000..6ee6fdf --- /dev/null +++ b/tests/ui/does-not-have-iter-interpolated-dup.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-interpolated-dup.rs:8:5 + | +8 | quote!(#(#nonrep #nonrep)*); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/does-not-have-iter-interpolated.rs b/tests/ui/does-not-have-iter-interpolated.rs new file mode 100644 index 0000000..2c740cc --- /dev/null +++ b/tests/ui/does-not-have-iter-interpolated.rs @@ -0,0 +1,9 @@ +use quote::quote; + +fn main() { + let nonrep = ""; + + // Without some protection against repetitions with no iterator somewhere + // inside, this would loop infinitely. + quote!(#(#nonrep)*); +} diff --git a/tests/ui/does-not-have-iter-interpolated.stderr b/tests/ui/does-not-have-iter-interpolated.stderr new file mode 100644 index 0000000..8d6c990 --- /dev/null +++ b/tests/ui/does-not-have-iter-interpolated.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-interpolated.rs:8:5 + | +8 | quote!(#(#nonrep)*); + | ^^^^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/does-not-have-iter-separated.rs b/tests/ui/does-not-have-iter-separated.rs new file mode 100644 index 0000000..c027243 --- /dev/null +++ b/tests/ui/does-not-have-iter-separated.rs @@ -0,0 +1,5 @@ +use quote::quote; + +fn main() { + quote!(#(a b),*); +} diff --git a/tests/ui/does-not-have-iter-separated.stderr b/tests/ui/does-not-have-iter-separated.stderr new file mode 100644 index 0000000..c1fd0ad --- /dev/null +++ b/tests/ui/does-not-have-iter-separated.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter-separated.rs:4:5 + | +4 | quote!(#(a b),*); + | ^^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/does-not-have-iter.rs b/tests/ui/does-not-have-iter.rs new file mode 100644 index 0000000..8908353 --- /dev/null +++ b/tests/ui/does-not-have-iter.rs @@ -0,0 +1,5 @@ +use quote::quote; + +fn main() { + quote!(#(a b)*); +} diff --git a/tests/ui/does-not-have-iter.stderr b/tests/ui/does-not-have-iter.stderr new file mode 100644 index 0000000..3b87705 --- /dev/null +++ b/tests/ui/does-not-have-iter.stderr @@ -0,0 +1,11 @@ +error[E0308]: mismatched types + --> $DIR/does-not-have-iter.rs:4:5 + | +4 | quote!(#(a b)*); + | ^^^^^^^^^^^^^^^^ expected struct `quote::__rt::HasIterator`, found struct `quote::__rt::ThereIsNoIteratorInRepetition` + | + = note: expected type `quote::__rt::HasIterator` + found type `quote::__rt::ThereIsNoIteratorInRepetition` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/not-quotable.rs b/tests/ui/not-quotable.rs new file mode 100644 index 0000000..f991c18 --- /dev/null +++ b/tests/ui/not-quotable.rs @@ -0,0 +1,7 @@ +use quote::quote; +use std::net::Ipv4Addr; + +fn main() { + let ip = Ipv4Addr::LOCALHOST; + let _ = quote! { #ip }; +} diff --git a/tests/ui/not-quotable.stderr b/tests/ui/not-quotable.stderr new file mode 100644 index 0000000..f51f85f --- /dev/null +++ b/tests/ui/not-quotable.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `std::net::Ipv4Addr: quote::to_tokens::ToTokens` is not satisfied + --> $DIR/not-quotable.rs:6:13 + | +6 | let _ = quote! { #ip }; + | ^^^^^^^^^^^^^^ the trait `quote::to_tokens::ToTokens` is not implemented for `std::net::Ipv4Addr` + | + = note: required by `quote::to_tokens::ToTokens::to_tokens` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/not-repeatable.rs b/tests/ui/not-repeatable.rs new file mode 100644 index 0000000..ff18060 --- /dev/null +++ b/tests/ui/not-repeatable.rs @@ -0,0 +1,7 @@ +use quote::quote; +use std::net::Ipv4Addr; + +fn main() { + let ip = Ipv4Addr::LOCALHOST; + let _ = quote! { #(#ip)* }; +} diff --git a/tests/ui/not-repeatable.stderr b/tests/ui/not-repeatable.stderr new file mode 100644 index 0000000..ddcac05 --- /dev/null +++ b/tests/ui/not-repeatable.stderr @@ -0,0 +1,14 @@ +error[E0599]: no method named `quote_into_iter` found for type `std::net::Ipv4Addr` in the current scope + --> $DIR/not-repeatable.rs:6:13 + | +6 | let _ = quote! { #(#ip)* }; + | ^^^^^^^^^^^^^^^^^^ + | + = note: the method `quote_into_iter` exists but the following trait bounds were not satisfied: + `&mut std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `&std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `std::net::Ipv4Addr : quote::__rt::ext::RepIteratorExt` + `std::net::Ipv4Addr : quote::__rt::ext::RepToTokensExt` + = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info) + +For more information about this error, try `rustc --explain E0599`. diff --git a/tests/ui/wrong-type-span.rs b/tests/ui/wrong-type-span.rs new file mode 100644 index 0000000..1ce391c --- /dev/null +++ b/tests/ui/wrong-type-span.rs @@ -0,0 +1,7 @@ +use quote::quote_spanned; + +fn main() { + let span = ""; + let x = 0; + quote_spanned!(span=> #x); +} diff --git a/tests/ui/wrong-type-span.stderr b/tests/ui/wrong-type-span.stderr new file mode 100644 index 0000000..a6ae8ef --- /dev/null +++ b/tests/ui/wrong-type-span.stderr @@ -0,0 +1,10 @@ +error[E0308]: mismatched types + --> $DIR/wrong-type-span.rs:6:20 + | +6 | quote_spanned!(span=> #x); + | ^^^^ expected struct `proc_macro2::Span`, found &str + | + = note: expected type `proc_macro2::Span` + found type `&str` + +For more information about this error, try `rustc --explain E0308`. |