diff --git a/.cargo-ok b/.cargo-ok new file mode 100644 index 0000000..b5754e2 --- /dev/null +++ b/.cargo-ok @@ -0,0 +1 @@ +ok \ No newline at end of file diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..4cedf6c --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "b01743f24cb5b19f96a3eac6bce0e7aee10f6199" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 3d30690..0000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.31.0" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5e67515..ed04303 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,7 @@ name: CI on: push: pull_request: + workflow_dispatch: schedule: [cron: "40 1 * * *"] permissions: @@ -12,45 +13,58 @@ env: RUSTFLAGS: -Dwarnings jobs: + pre_ci: + uses: dtolnay/.github/.github/workflows/pre_ci.yml@master + test: name: Rust ${{matrix.rust}} + needs: pre_ci + if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest strategy: fail-fast: false matrix: - rust: [stable, beta, 1.56.0] + rust: [nightly, stable, beta, 1.56.0] timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.rust}} components: rust-src + - name: Enable type layout randomization + run: echo RUSTFLAGS=${RUSTFLAGS}\ -Zrandomize-layout >> $GITHUB_ENV + if: matrix.rust == 'nightly' - run: cargo test + - run: cargo run --manifest-path benches/Cargo.toml - nightly: - name: Rust nightly + minimal: + name: Minimal versions + needs: pre_ci + if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@nightly + - run: cargo generate-lockfile -Z minimal-versions + - run: cargo check --locked + + doc: + name: Documentation + needs: pre_ci + if: needs.pre_ci.outputs.continue + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + RUSTDOCFLAGS: -Dwarnings + steps: + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: rust-src - - run: cargo test - - run: cargo update -Z minimal-versions - - run: cargo build - - msrv: - name: Rust 1.31.0 - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@1.31.0 - with: - components: rust-src - - run: cargo check + - uses: dtolnay/install@cargo-docs-rs + - run: cargo docs-rs clippy: name: Clippy @@ -58,19 +72,22 @@ jobs: if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@nightly with: components: clippy, rust-src - - run: cargo clippy --tests -- -Dclippy::all -Dclippy::pedantic + - run: cargo clippy --tests --workspace -- -Dclippy::all -Dclippy::pedantic miri: name: Miri + needs: pre_ci + if: needs.pre_ci.outputs.continue runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@miri + - run: cargo miri setup - run: cargo miri test env: MIRIFLAGS: -Zmiri-strict-provenance @@ -81,6 +98,6 @@ jobs: if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/install@cargo-outdated - run: cargo outdated --workspace --exit-code 1 diff --git a/BUILD.gn b/BUILD.gn index 2faa46b..944eac3 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -14,18 +14,16 @@ import("//build/ohos.gni") ohos_cargo_crate("lib") { - crate_name = "quote" - crate_type = "rlib" - crate_root = "src/lib.rs" + crate_name = "quote" + crate_type = "rlib" + crate_root = "src/lib.rs" - sources = ["src/lib.rs"] - edition = "2018" - cargo_pkg_version = "1.0.23" - cargo_pkg_authors = "David Tolnay " - cargo_pkg_name = "quote" - cargo_pkg_description = "Quasi-quoting macro quote!(...)" - deps = ["//third_party/rust/crates/proc-macro2:lib"] - features = ["proc-macro"] - build_root = "build.rs" - build_sources = ["build.rs"] + sources = [ "src/lib.rs" ] + edition = "2018" + cargo_pkg_version = "1.0.35" + cargo_pkg_authors = "David Tolnay " + cargo_pkg_name = "quote" + cargo_pkg_description = "Quasi-quoting macro quote!(...)" + deps = [ "//third_party/rust/crates/proc-macro2:lib" ] + features = [ "proc-macro" ] } diff --git a/Cargo.toml b/Cargo.toml index f99b48f..f3222c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,35 +1,50 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + [package] +edition = "2018" +rust-version = "1.56" name = "quote" -version = "1.0.23" # don't forget to update html_root_url, version in readme for breaking changes +version = "1.0.35" authors = ["David Tolnay "] autobenches = false -categories = ["development-tools::procedural-macro-helpers"] description = "Quasi-quoting macro quote!(...)" documentation = "https://docs.rs/quote/" -edition = "2018" -keywords = ["macros", "syn"] +readme = "README.md" +keywords = [ + "macros", + "syn", +] +categories = ["development-tools::procedural-macro-helpers"] license = "MIT OR Apache-2.0" repository = "https://github.com/dtolnay/quote" -rust-version = "1.31" -[dependencies] -proc-macro2 = { version = "1.0.40", default-features = false } - -[dev-dependencies] -rustversion = "1.0" -trybuild = { version = "1.0.66", features = ["diff"] } - -[features] -default = ["proc-macro"] -# Disabling the proc-macro feature removes the dynamic library dependency on -# libproc_macro in the rustc compiler. -proc-macro = ["proc-macro2/proc-macro"] +[package.metadata.docs.rs] +rustdoc-args = ["--generate-link-to-definition"] +targets = ["x86_64-unknown-linux-gnu"] [lib] doc-scrape-examples = false -[workspace] -members = ["benches"] +[dependencies.proc-macro2] +version = "1.0.74" +default-features = false -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] +[dev-dependencies.rustversion] +version = "1.0" + +[dev-dependencies.trybuild] +version = "1.0.66" +features = ["diff"] + +[features] +default = ["proc-macro"] +proc-macro = ["proc-macro2/proc-macro"] diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..33d9865 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,36 @@ +[package] +name = "quote" +version = "1.0.35" # don't forget to update html_root_url, version in readme for breaking changes +authors = ["David Tolnay "] +autobenches = false +categories = ["development-tools::procedural-macro-helpers"] +description = "Quasi-quoting macro quote!(...)" +documentation = "https://docs.rs/quote/" +edition = "2018" +keywords = ["macros", "syn"] +license = "MIT OR Apache-2.0" +repository = "https://github.com/dtolnay/quote" +rust-version = "1.56" + +[dependencies] +proc-macro2 = { version = "1.0.74", default-features = false } + +[dev-dependencies] +rustversion = "1.0" +trybuild = { version = "1.0.66", features = ["diff"] } + +[features] +default = ["proc-macro"] +# Disabling the proc-macro feature removes the dynamic library dependency on +# libproc_macro in the rustc compiler. +proc-macro = ["proc-macro2/proc-macro"] + +[lib] +doc-scrape-examples = false + +[workspace] +members = ["benches"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +rustdoc-args = ["--generate-link-to-definition"] diff --git a/LICENSE-APACHE b/LICENSE-APACHE index 16fe87b..1b5ec8b 100644 --- a/LICENSE-APACHE +++ b/LICENSE-APACHE @@ -174,28 +174,3 @@ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. diff --git a/README.OpenSource b/README.OpenSource index ce1063e..b40ea85 100644 --- a/README.OpenSource +++ b/README.OpenSource @@ -3,7 +3,7 @@ "Name": "quote", "License": "Apache License V2.0, MIT", "License File": "LICENSE-APACHE, LICENSE-MIT", - "Version Number": "1.0.23", + "Version Number": "1.0.35", "Owner": "fangting12@huawei.com", "Upstream URL": "https://github.com/dtolnay/quote", "Description": "A Rust library that provides support for generating Rust code." diff --git a/README.md b/README.md index 74e99ce..bfc91a9 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ macros. quote = "1.0" ``` -*Version requirement: Quote supports rustc 1.31 and up.*
+*Version requirement: Quote supports rustc 1.56 and up.*
[*Release notes*](https://github.com/dtolnay/quote/releases)
@@ -233,15 +233,26 @@ macro. ## Non-macro code generators When using `quote` in a build.rs or main.rs and writing the output out to a -file, consider having the code generator pass the tokens through [rustfmt] -before writing (either by shelling out to the `rustfmt` binary or by pulling in -the `rustfmt` library as a dependency). This way if an error occurs in the -generated code it is convenient for a human to read and debug. +file, consider having the code generator pass the tokens through [prettyplease] +before writing. This way if an error occurs in the generated code it is +convenient for a human to read and debug. Be aware that no kind of hygiene or span information is retained when tokens are written to a file; the conversion from tokens to source code is lossy. -[rustfmt]: https://github.com/rust-lang/rustfmt +Example usage in build.rs: + +```rust +let output = quote! { ... }; +let syntax_tree = syn::parse2(output).unwrap(); +let formatted = prettyplease::unparse(&syntax_tree); + +let out_dir = env::var_os("OUT_DIR").unwrap(); +let dest_path = Path::new(&out_dir).join("out.rs"); +fs::write(dest_path, formatted).unwrap(); +``` + +[prettyplease]: https://github.com/dtolnay/prettyplease
diff --git a/benches/Cargo.toml b/benches/Cargo.toml deleted file mode 100644 index 366f9df..0000000 --- a/benches/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "quote-benchmark" -version = "0.0.0" -authors = ["David Tolnay "] -edition = "2018" -license = "MIT OR Apache-2.0" -publish = false - -[lib] -proc-macro = true -path = "lib.rs" - -[[bin]] -name = "quote-benchmark" -path = "main.rs" - -[dependencies] -proc-macro2 = "1.0" -quote = { path = ".." } -termcolor = "1.1" diff --git a/benches/README.md b/benches/README.md deleted file mode 100644 index 2393ed1..0000000 --- a/benches/README.md +++ /dev/null @@ -1,18 +0,0 @@ -Example output: - -
-$ cargo run && cargo run --release
-
-   Compiling quote v1.0.10
-   Compiling quote-benchmark v0.0.0
-macro in debug mode: 1655 micros
-    Finished dev [unoptimized + debuginfo] target(s) in 4.39s
-     Running `/git/quote/target/debug/quote-benchmark`
-non-macro in debug mode: 1205 micros
-   Compiling quote v1.0.10
-   Compiling quote-benchmark v0.0.0
-macro in release mode: 1635 micros
-    Finished release [optimized] target(s) in 4.00s
-     Running `/git/quote/target/release/quote-benchmark`
-non-macro in release mode: 105 micros
-
diff --git a/benches/lib.rs b/benches/lib.rs deleted file mode 100644 index f194165..0000000 --- a/benches/lib.rs +++ /dev/null @@ -1,206 +0,0 @@ -use quote::quote; - -#[allow(unused_macros)] -macro_rules! benchmark { - (|$ident:ident| $quote:expr) => { - mod timer; - - use proc_macro::TokenStream; - use proc_macro2::Ident; - - #[proc_macro] - pub fn run_quote_benchmark(input: TokenStream) -> TokenStream { - let input = proc_macro2::TokenStream::from(input); - let span = input.into_iter().next().unwrap().span(); - let $ident = Ident::new("Response", span); - timer::time("macro", || proc_macro::TokenStream::from($quote)); - TokenStream::new() - } - }; -} - -#[allow(unused_imports)] -use benchmark; - -crate::benchmark! { - |ident| quote! { - impl<'de> _serde::Deserialize<'de> for #ident { - fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result - where - __D: _serde::Deserializer<'de>, - { - #[allow(non_camel_case_types)] - enum __Field { - __field0, - __field1, - __ignore, - } - struct __FieldVisitor; - impl<'de> _serde::de::Visitor<'de> for __FieldVisitor { - type Value = __Field; - fn expecting( - &self, - __formatter: &mut _serde::export::Formatter, - ) -> _serde::export::fmt::Result { - _serde::export::Formatter::write_str(__formatter, "field identifier") - } - fn visit_u64<__E>(self, __value: u64) -> _serde::export::Result - where - __E: _serde::de::Error, - { - match __value { - 0u64 => _serde::export::Ok(__Field::__field0), - 1u64 => _serde::export::Ok(__Field::__field1), - _ => _serde::export::Err(_serde::de::Error::invalid_value( - _serde::de::Unexpected::Unsigned(__value), - &"field index 0 <= i < 2", - )), - } - } - fn visit_str<__E>(self, __value: &str) -> _serde::export::Result - where - __E: _serde::de::Error, - { - match __value { - "id" => _serde::export::Ok(__Field::__field0), - "s" => _serde::export::Ok(__Field::__field1), - _ => _serde::export::Ok(__Field::__ignore), - } - } - fn visit_bytes<__E>( - self, - __value: &[u8], - ) -> _serde::export::Result - where - __E: _serde::de::Error, - { - match __value { - b"id" => _serde::export::Ok(__Field::__field0), - b"s" => _serde::export::Ok(__Field::__field1), - _ => _serde::export::Ok(__Field::__ignore), - } - } - } - impl<'de> _serde::Deserialize<'de> for __Field { - #[inline] - fn deserialize<__D>(__deserializer: __D) -> _serde::export::Result - where - __D: _serde::Deserializer<'de>, - { - _serde::Deserializer::deserialize_identifier(__deserializer, __FieldVisitor) - } - } - struct __Visitor<'de> { - marker: _serde::export::PhantomData<#ident>, - lifetime: _serde::export::PhantomData<&'de ()>, - } - impl<'de> _serde::de::Visitor<'de> for __Visitor<'de> { - type Value = #ident; - fn expecting( - &self, - __formatter: &mut _serde::export::Formatter, - ) -> _serde::export::fmt::Result { - _serde::export::Formatter::write_str(__formatter, "struct") - } - #[inline] - fn visit_seq<__A>( - self, - mut __seq: __A, - ) -> _serde::export::Result - where - __A: _serde::de::SeqAccess<'de>, - { - let __field0 = - match try!(_serde::de::SeqAccess::next_element::(&mut __seq)) { - _serde::export::Some(__value) => __value, - _serde::export::None => { - return _serde::export::Err(_serde::de::Error::invalid_length( - 0usize, - &"struct with 2 elements", - )); - } - }; - let __field1 = - match try!(_serde::de::SeqAccess::next_element::(&mut __seq)) { - _serde::export::Some(__value) => __value, - _serde::export::None => { - return _serde::export::Err(_serde::de::Error::invalid_length( - 1usize, - &"struct with 2 elements", - )); - } - }; - _serde::export::Ok(#ident { - id: __field0, - s: __field1, - }) - } - #[inline] - fn visit_map<__A>( - self, - mut __map: __A, - ) -> _serde::export::Result - where - __A: _serde::de::MapAccess<'de>, - { - let mut __field0: _serde::export::Option = _serde::export::None; - let mut __field1: _serde::export::Option = _serde::export::None; - while let _serde::export::Some(__key) = - try!(_serde::de::MapAccess::next_key::<__Field>(&mut __map)) - { - match __key { - __Field::__field0 => { - if _serde::export::Option::is_some(&__field0) { - return _serde::export::Err( - <__A::Error as _serde::de::Error>::duplicate_field("id"), - ); - } - __field0 = _serde::export::Some( - try!(_serde::de::MapAccess::next_value::(&mut __map)), - ); - } - __Field::__field1 => { - if _serde::export::Option::is_some(&__field1) { - return _serde::export::Err( - <__A::Error as _serde::de::Error>::duplicate_field("s"), - ); - } - __field1 = _serde::export::Some( - try!(_serde::de::MapAccess::next_value::(&mut __map)), - ); - } - _ => { - let _ = try!(_serde::de::MapAccess::next_value::< - _serde::de::IgnoredAny, - >(&mut __map)); - } - } - } - let __field0 = match __field0 { - _serde::export::Some(__field0) => __field0, - _serde::export::None => try!(_serde::private::de::missing_field("id")), - }; - let __field1 = match __field1 { - _serde::export::Some(__field1) => __field1, - _serde::export::None => try!(_serde::private::de::missing_field("s")), - }; - _serde::export::Ok(#ident { - id: __field0, - s: __field1, - }) - } - } - const FIELDS: &'static [&'static str] = &["id", "s"]; - _serde::Deserializer::deserialize_struct( - __deserializer, - stringify!(#ident), - FIELDS, - __Visitor { - marker: _serde::export::PhantomData::<#ident>, - lifetime: _serde::export::PhantomData, - }, - ) - } - } - } -} diff --git a/benches/main.rs b/benches/main.rs deleted file mode 100644 index 8cfbcb6..0000000 --- a/benches/main.rs +++ /dev/null @@ -1,25 +0,0 @@ -quote_benchmark::run_quote_benchmark!(_); - -mod benchmark { - macro_rules! benchmark { - (|$ident:ident| $quote:expr) => { - use proc_macro2::{Ident, Span}; - - pub fn quote() -> proc_macro2::TokenStream { - let $ident = Ident::new("Response", Span::call_site()); - $quote - } - }; - } - - pub(crate) use benchmark; -} - -use benchmark::benchmark; - -mod lib; -mod timer; - -fn main() { - timer::time("non-macro", lib::quote); -} diff --git a/benches/timer.rs b/benches/timer.rs deleted file mode 100644 index cc1e426..0000000 --- a/benches/timer.rs +++ /dev/null @@ -1,17 +0,0 @@ -use std::io::Write; -use std::time::Instant; -use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; - -const ITERATIONS: u32 = 1000; - -pub fn time(name: &'static str, function: impl Fn() -> T) { - let begin = Instant::now(); - for _ in 0..ITERATIONS { - _ = function(); - } - let micros = (begin.elapsed() / ITERATIONS).as_micros(); - let mode = ["release", "debug"][cfg!(debug_assertions) as usize]; - let mut writer = StandardStream::stderr(ColorChoice::Auto); - _ = writer.set_color(ColorSpec::new().set_fg(Some(Color::Magenta))); - _ = writeln!(&mut writer, "{} in {} mode: {} micros", name, mode, micros); -} diff --git a/build.rs b/build.rs deleted file mode 100644 index a7e6b2c..0000000 --- a/build.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::env; -use std::process::{self, Command}; -use std::str; - -fn main() { - println!("cargo:rerun-if-changed=build.rs"); - - let version = match rustc_version() { - Some(version) => version, - None => return, - }; - - if version.minor < 31 { - eprintln!("Minimum supported rustc version is 1.31"); - process::exit(1); - } - - if version.minor < 53 { - // https://github.com/rust-lang/rust/issues/43081 - println!("cargo:rustc-cfg=needs_invalid_span_workaround"); - } -} - -struct RustcVersion { - minor: u32, -} - -fn rustc_version() -> Option { - let rustc = env::var_os("RUSTC")?; - let output = Command::new(rustc).arg("--version").output().ok()?; - let version = str::from_utf8(&output.stdout).ok()?; - let mut pieces = version.split('.'); - if pieces.next() != Some("rustc 1") { - return None; - } - let minor = pieces.next()?.parse().ok()?; - Some(RustcVersion { minor }) -} diff --git a/src/ident_fragment.rs b/src/ident_fragment.rs index cf74024..6c2a9a8 100644 --- a/src/ident_fragment.rs +++ b/src/ident_fragment.rs @@ -1,6 +1,6 @@ +use alloc::borrow::Cow; use core::fmt; use proc_macro2::{Ident, Span}; -use std::borrow::Cow; /// Specialized formatting trait used by `format_ident!`. /// @@ -8,6 +8,8 @@ use std::borrow::Cow; /// stripped, if present. /// /// See [`format_ident!`] for more information. +/// +/// [`format_ident!`]: crate::format_ident pub trait IdentFragment { /// Format this value as an identifier fragment. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result; @@ -47,8 +49,8 @@ impl IdentFragment for Ident { 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) + if let Some(id) = id.strip_prefix("r#") { + fmt::Display::fmt(id, f) } else { fmt::Display::fmt(&id[..], f) } diff --git a/src/lib.rs b/src/lib.rs index adc14c5..8b97abd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,9 +79,20 @@ //! } //! }; //! ``` +//! +//!
+//! +//! # Non-macro code generators +//! +//! When using `quote` in a build.rs or main.rs and writing the output out to a +//! file, consider having the code generator pass the tokens through +//! [prettyplease] before writing. This way if an error occurs in the generated +//! code it is convenient for a human to read and debug. +//! +//! [prettyplease]: https://github.com/dtolnay/prettyplease // Quote types in rustdoc of other crates get linked to here. -#![doc(html_root_url = "https://docs.rs/quote/1.0.23")] +#![doc(html_root_url = "https://docs.rs/quote/1.0.35")] #![allow( clippy::doc_markdown, clippy::missing_errors_doc, @@ -91,10 +102,9 @@ clippy::wrong_self_convention, )] -#[cfg(all( - not(all(target_arch = "wasm32", target_os = "unknown")), - feature = "proc-macro" -))] +extern crate alloc; + +#[cfg(feature = "proc-macro")] extern crate proc_macro; mod ext; @@ -418,7 +428,7 @@ pub mod spanned; /// appears suffixed as integer literals by interpolating them as [`syn::Index`] /// instead. /// -/// [`syn::Index`]: https://docs.rs/syn/1.0/syn/struct.Index.html +/// [`syn::Index`]: https://docs.rs/syn/2.0/syn/struct.Index.html /// /// ```compile_fail /// let i = 0usize..self.fields.len(); @@ -619,14 +629,14 @@ macro_rules! quote_spanned { #[macro_export] macro_rules! quote_spanned { ($span:expr=>) => {{ - let _: $crate::__private::Span = $span; + let _: $crate::__private::Span = $crate::__private::get_span($span).__into_span(); $crate::__private::TokenStream::new() }}; // Special case rule for a single tt, for performance. ($span:expr=> $tt:tt) => {{ let mut _s = $crate::__private::TokenStream::new(); - let _span: $crate::__private::Span = $span; + let _span: $crate::__private::Span = $crate::__private::get_span($span).__into_span(); $crate::quote_token_spanned!{$tt _s _span} _s }}; @@ -634,13 +644,13 @@ macro_rules! quote_spanned { // Special case rules for two tts, for performance. ($span:expr=> # $var:ident) => {{ let mut _s = $crate::__private::TokenStream::new(); - let _: $crate::__private::Span = $span; + let _: $crate::__private::Span = $crate::__private::get_span($span).__into_span(); $crate::ToTokens::to_tokens(&$var, &mut _s); _s }}; ($span:expr=> $tt1:tt $tt2:tt) => {{ let mut _s = $crate::__private::TokenStream::new(); - let _span: $crate::__private::Span = $span; + let _span: $crate::__private::Span = $crate::__private::get_span($span).__into_span(); $crate::quote_token_spanned!{$tt1 _s _span} $crate::quote_token_spanned!{$tt2 _s _span} _s @@ -649,7 +659,7 @@ macro_rules! quote_spanned { // Rule for any other number of tokens. ($span:expr=> $($tt:tt)*) => {{ let mut _s = $crate::__private::TokenStream::new(); - let _span: $crate::__private::Span = $span; + let _span: $crate::__private::Span = $crate::__private::get_span($span).__into_span(); $crate::quote_each_token_spanned!{_s _span $($tt)*} _s }}; diff --git a/src/runtime.rs b/src/runtime.rs index f3cdded..eff044a 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1,13 +1,25 @@ +use self::get_span::{GetSpan, GetSpanBase, GetSpanInner}; use crate::{IdentFragment, ToTokens, TokenStreamExt}; use core::fmt; use core::iter; use core::ops::BitOr; +use proc_macro2::{Group, Ident, Punct, Spacing, TokenTree}; +#[doc(hidden)] +pub use alloc::format; +#[doc(hidden)] pub use core::option::Option; -pub use proc_macro2::*; -pub use std::format; +#[doc(hidden)] +pub type Delimiter = proc_macro2::Delimiter; +#[doc(hidden)] +pub type Span = proc_macro2::Span; +#[doc(hidden)] +pub type TokenStream = proc_macro2::TokenStream; + +#[doc(hidden)] pub struct HasIterator; // True +#[doc(hidden)] pub struct ThereIsNoIteratorInRepetition; // False impl BitOr for ThereIsNoIteratorInRepetition { @@ -44,14 +56,16 @@ impl BitOr for HasIterator { /// 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. +#[doc(hidden)] pub mod ext { use super::RepInterp; use super::{HasIterator as HasIter, ThereIsNoIteratorInRepetition as DoesNotHaveIter}; use crate::ToTokens; + use alloc::collections::btree_set::{self, BTreeSet}; use core::slice; - use std::collections::btree_set::{self, BTreeSet}; /// Extension trait providing the `quote_into_iter` method on iterators. + #[doc(hidden)] pub trait RepIteratorExt: Iterator + Sized { fn quote_into_iter(self) -> (Self, HasIter) { (self, HasIter) @@ -63,6 +77,7 @@ pub mod ext { /// Extension trait providing the `quote_into_iter` method for /// non-iterable types. These types interpolate the same value in each /// iteration of the repetition. + #[doc(hidden)] 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 @@ -80,6 +95,7 @@ pub mod ext { /// Extension trait providing the `quote_into_iter` method for types that /// can be referenced as an iterator. + #[doc(hidden)] pub trait RepAsIteratorExt<'q> { type Iter: Iterator; @@ -138,6 +154,7 @@ pub mod ext { // Helper type used within interpolations to allow for repeated binding names. // Implements the relevant traits, and exports a dummy `next()` method. #[derive(Copy, Clone)] +#[doc(hidden)] pub struct RepInterp(pub T); impl RepInterp { @@ -164,10 +181,69 @@ impl ToTokens for RepInterp { } } +#[doc(hidden)] +#[inline] +pub fn get_span(span: T) -> GetSpan { + GetSpan(GetSpanInner(GetSpanBase(span))) +} + +mod get_span { + use core::ops::Deref; + use proc_macro2::extra::DelimSpan; + use proc_macro2::Span; + + pub struct GetSpan(pub(crate) GetSpanInner); + + pub struct GetSpanInner(pub(crate) GetSpanBase); + + pub struct GetSpanBase(pub(crate) T); + + impl GetSpan { + #[inline] + pub fn __into_span(self) -> Span { + ((self.0).0).0 + } + } + + impl GetSpanInner { + #[inline] + pub fn __into_span(&self) -> Span { + (self.0).0.join() + } + } + + impl GetSpanBase { + #[allow(clippy::unused_self)] + pub fn __into_span(&self) -> T { + unreachable!() + } + } + + impl Deref for GetSpan { + type Target = GetSpanInner; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl Deref for GetSpanInner { + type Target = GetSpanBase; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.0 + } + } +} + +#[doc(hidden)] pub fn push_group(tokens: &mut TokenStream, delimiter: Delimiter, inner: TokenStream) { tokens.append(Group::new(delimiter, inner)); } +#[doc(hidden)] pub fn push_group_spanned( tokens: &mut TokenStream, span: Span, @@ -179,11 +255,13 @@ pub fn push_group_spanned( tokens.append(g); } +#[doc(hidden)] pub fn parse(tokens: &mut TokenStream, s: &str) { let s: TokenStream = s.parse().expect("invalid token stream"); tokens.extend(iter::once(s)); } +#[doc(hidden)] pub fn parse_spanned(tokens: &mut TokenStream, span: Span, s: &str) { let s: TokenStream = s.parse().expect("invalid token stream"); tokens.extend(s.into_iter().map(|t| respan_token_tree(t, span))); @@ -206,15 +284,18 @@ fn respan_token_tree(mut token: TokenTree, span: Span) -> TokenTree { token } +#[doc(hidden)] pub fn push_ident(tokens: &mut TokenStream, s: &str) { let span = Span::call_site(); push_ident_spanned(tokens, span, s); } +#[doc(hidden)] pub fn push_ident_spanned(tokens: &mut TokenStream, span: Span, s: &str) { tokens.append(ident_maybe_raw(s, span)); } +#[doc(hidden)] pub fn push_lifetime(tokens: &mut TokenStream, lifetime: &str) { struct Lifetime<'a> { name: &'a str, @@ -245,6 +326,7 @@ pub fn push_lifetime(tokens: &mut TokenStream, lifetime: &str) { }); } +#[doc(hidden)] pub fn push_lifetime_spanned(tokens: &mut TokenStream, span: Span, lifetime: &str) { struct Lifetime<'a> { name: &'a str, @@ -281,9 +363,11 @@ pub fn push_lifetime_spanned(tokens: &mut TokenStream, span: Span, lifetime: &st macro_rules! push_punct { ($name:ident $spanned:ident $char1:tt) => { + #[doc(hidden)] pub fn $name(tokens: &mut TokenStream) { tokens.append(Punct::new($char1, Spacing::Alone)); } + #[doc(hidden)] pub fn $spanned(tokens: &mut TokenStream, span: Span) { let mut punct = Punct::new($char1, Spacing::Alone); punct.set_span(span); @@ -291,10 +375,12 @@ macro_rules! push_punct { } }; ($name:ident $spanned:ident $char1:tt $char2:tt) => { + #[doc(hidden)] pub fn $name(tokens: &mut TokenStream) { tokens.append(Punct::new($char1, Spacing::Joint)); tokens.append(Punct::new($char2, Spacing::Alone)); } + #[doc(hidden)] pub fn $spanned(tokens: &mut TokenStream, span: Span) { let mut punct = Punct::new($char1, Spacing::Joint); punct.set_span(span); @@ -305,11 +391,13 @@ macro_rules! push_punct { } }; ($name:ident $spanned:ident $char1:tt $char2:tt $char3:tt) => { + #[doc(hidden)] pub fn $name(tokens: &mut TokenStream) { tokens.append(Punct::new($char1, Spacing::Joint)); tokens.append(Punct::new($char2, Spacing::Joint)); tokens.append(Punct::new($char3, Spacing::Alone)); } + #[doc(hidden)] pub fn $spanned(tokens: &mut TokenStream, span: Span) { let mut punct = Punct::new($char1, Spacing::Joint); punct.set_span(span); @@ -369,24 +457,27 @@ push_punct!(push_star push_star_spanned '*'); push_punct!(push_sub push_sub_spanned '-'); push_punct!(push_sub_eq push_sub_eq_spanned '-' '='); +#[doc(hidden)] pub fn push_underscore(tokens: &mut TokenStream) { push_underscore_spanned(tokens, Span::call_site()); } +#[doc(hidden)] pub fn push_underscore_spanned(tokens: &mut TokenStream, span: Span) { tokens.append(Ident::new("_", span)); } // Helper method for constructing identifiers from the `format_ident!` macro, // handling `r#` prefixes. +#[doc(hidden)] pub fn mk_ident(id: &str, span: Option) -> Ident { let span = span.unwrap_or_else(Span::call_site); ident_maybe_raw(id, span) } fn ident_maybe_raw(id: &str, span: Span) -> Ident { - if id.starts_with("r#") { - Ident::new_raw(&id[2..], span) + if let Some(id) = id.strip_prefix("r#") { + Ident::new_raw(id, span) } else { Ident::new(id, span) } @@ -399,6 +490,7 @@ fn ident_maybe_raw(id: &str, span: Span) -> Ident { // `Octal`, `LowerHex`, `UpperHex`, and `Binary` to allow for their use within // `format_ident!`. #[derive(Copy, Clone)] +#[doc(hidden)] pub struct IdentFragmentAdapter(pub T); impl IdentFragmentAdapter { diff --git a/src/spanned.rs b/src/spanned.rs index f64c8f5..6eba644 100644 --- a/src/spanned.rs +++ b/src/spanned.rs @@ -1,7 +1,9 @@ use crate::ToTokens; +use proc_macro2::extra::DelimSpan; use proc_macro2::{Span, TokenStream}; -pub trait Spanned { +// Not public API other than via the syn crate. Use syn::spanned::Spanned. +pub trait Spanned: private::Sealed { fn __span(&self) -> Span; } @@ -11,6 +13,12 @@ impl Spanned for Span { } } +impl Spanned for DelimSpan { + fn __span(&self) -> Span { + self.join() + } +} + impl Spanned for T { fn __span(&self) -> Span { join_spans(self.into_token_stream()) @@ -18,20 +26,8 @@ impl Spanned for T { } fn join_spans(tokens: TokenStream) -> Span { - #[cfg(not(needs_invalid_span_workaround))] let mut iter = tokens.into_iter().map(|tt| tt.span()); - #[cfg(needs_invalid_span_workaround)] - let mut iter = tokens.into_iter().filter_map(|tt| { - 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(), @@ -41,3 +37,14 @@ fn join_spans(tokens: TokenStream) -> Span { .and_then(|last| first.join(last)) .unwrap_or(first) } + +mod private { + use crate::ToTokens; + use proc_macro2::extra::DelimSpan; + use proc_macro2::Span; + + pub trait Sealed {} + impl Sealed for Span {} + impl Sealed for DelimSpan {} + impl Sealed for T {} +} diff --git a/src/to_tokens.rs b/src/to_tokens.rs index 5748721..23b6ec2 100644 --- a/src/to_tokens.rs +++ b/src/to_tokens.rs @@ -1,8 +1,8 @@ use super::TokenStreamExt; +use alloc::borrow::Cow; +use alloc::rc::Rc; use core::iter; use proc_macro2::{Group, Ident, Literal, Punct, Span, TokenStream, TokenTree}; -use std::borrow::Cow; -use std::rc::Rc; /// Types that can be interpolated inside a `quote!` invocation. /// diff --git a/tests/test.rs b/tests/test.rs index 52ec7bc..eab4f55 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -1,14 +1,17 @@ #![allow( clippy::disallowed_names, + clippy::let_underscore_untyped, clippy::shadow_unrelated, clippy::unseparated_literal_suffix, clippy::used_underscore_binding )] +extern crate proc_macro; + use std::borrow::Cow; use std::collections::BTreeSet; -use proc_macro2::{Ident, Span, TokenStream}; +use proc_macro2::{Delimiter, Group, Ident, Span, TokenStream}; use quote::{format_ident, quote, quote_spanned, TokenStreamExt}; struct X; @@ -517,3 +520,30 @@ fn test_quote_raw_id() { let id = quote!(r#raw_id); assert_eq!(id.to_string(), "r#raw_id"); } + +#[test] +fn test_type_inference_for_span() { + trait CallSite { + fn get() -> Self; + } + + impl CallSite for Span { + fn get() -> Self { + Span::call_site() + } + } + + let span = Span::call_site(); + let _ = quote_spanned!(span=> ...); + + let delim_span = Group::new(Delimiter::Parenthesis, TokenStream::new()).delim_span(); + let _ = quote_spanned!(delim_span=> ...); + + let inferred = CallSite::get(); + let _ = quote_spanned!(inferred=> ...); + + if false { + let proc_macro_span = proc_macro::Span::call_site(); + let _ = quote_spanned!(proc_macro_span.into()=> ...); + } +} diff --git a/tests/ui/does-not-have-iter-interpolated-dup.stderr b/tests/ui/does-not-have-iter-interpolated-dup.stderr index 8087879..99c20a5 100644 --- a/tests/ui/does-not-have-iter-interpolated-dup.stderr +++ b/tests/ui/does-not-have-iter-interpolated-dup.stderr @@ -4,7 +4,8 @@ error[E0308]: mismatched types 8 | quote!(#(#nonrep #nonrep)*); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | - | expected struct `HasIterator`, found struct `ThereIsNoIteratorInRepetition` + | expected `HasIterator`, found `ThereIsNoIteratorInRepetition` | expected due to this + | here the type of `has_iter` is inferred to be `ThereIsNoIteratorInRepetition` | = note: this error originates in the macro `$crate::quote_token_with_context` which comes from the expansion of the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/does-not-have-iter-interpolated.stderr b/tests/ui/does-not-have-iter-interpolated.stderr index 2dcf206..ef90813 100644 --- a/tests/ui/does-not-have-iter-interpolated.stderr +++ b/tests/ui/does-not-have-iter-interpolated.stderr @@ -4,7 +4,8 @@ error[E0308]: mismatched types 8 | quote!(#(#nonrep)*); | ^^^^^^^^^^^^^^^^^^^ | | - | expected struct `HasIterator`, found struct `ThereIsNoIteratorInRepetition` + | expected `HasIterator`, found `ThereIsNoIteratorInRepetition` | expected due to this + | here the type of `has_iter` is inferred to be `ThereIsNoIteratorInRepetition` | = note: this error originates in the macro `$crate::quote_token_with_context` which comes from the expansion of the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/does-not-have-iter-separated.stderr b/tests/ui/does-not-have-iter-separated.stderr index 87a19fe..7c6e30f 100644 --- a/tests/ui/does-not-have-iter-separated.stderr +++ b/tests/ui/does-not-have-iter-separated.stderr @@ -4,7 +4,7 @@ error[E0308]: mismatched types 4 | quote!(#(a b),*); | ^^^^^^^^^^^^^^^^ | | - | expected struct `HasIterator`, found struct `ThereIsNoIteratorInRepetition` + | expected `HasIterator`, found `ThereIsNoIteratorInRepetition` | expected due to this | = note: this error originates in the macro `$crate::quote_token_with_context` which comes from the expansion of the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/does-not-have-iter.stderr b/tests/ui/does-not-have-iter.stderr index 32aa62d..0b13e5c 100644 --- a/tests/ui/does-not-have-iter.stderr +++ b/tests/ui/does-not-have-iter.stderr @@ -4,7 +4,7 @@ error[E0308]: mismatched types 4 | quote!(#(a b)*); | ^^^^^^^^^^^^^^^ | | - | expected struct `HasIterator`, found struct `ThereIsNoIteratorInRepetition` + | expected `HasIterator`, found `ThereIsNoIteratorInRepetition` | expected due to this | = note: this error originates in the macro `$crate::quote_token_with_context` which comes from the expansion of the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/not-quotable.rs b/tests/ui/not-quotable.rs index 220542d..f991c18 100644 --- a/tests/ui/not-quotable.rs +++ b/tests/ui/not-quotable.rs @@ -3,5 +3,5 @@ use std::net::Ipv4Addr; fn main() { let ip = Ipv4Addr::LOCALHOST; - _ = quote! { #ip }; + let _ = quote! { #ip }; } diff --git a/tests/ui/not-quotable.stderr b/tests/ui/not-quotable.stderr index c323d99..35cb6f2 100644 --- a/tests/ui/not-quotable.stderr +++ b/tests/ui/not-quotable.stderr @@ -1,20 +1,20 @@ error[E0277]: the trait bound `Ipv4Addr: ToTokens` is not satisfied - --> tests/ui/not-quotable.rs:6:9 + --> tests/ui/not-quotable.rs:6:13 | -6 | _ = quote! { #ip }; - | ^^^^^^^^^^^^^^ - | | - | the trait `ToTokens` is not implemented for `Ipv4Addr` - | required by a bound introduced by this call +6 | let _ = quote! { #ip }; + | ^^^^^^^^^^^^^^ + | | + | the trait `ToTokens` is not implemented for `Ipv4Addr` + | required by a bound introduced by this call | = help: the following other types implement trait `ToTokens`: - &'a T - &'a mut T - Box - Cow<'a, T> - Option - Rc - RepInterp - String - and 23 others + bool + char + isize + i8 + i16 + i32 + i64 + i128 + and $N others = note: this error originates in the macro `quote` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/ui/not-repeatable.rs b/tests/ui/not-repeatable.rs index c1debf5..a8f0fe7 100644 --- a/tests/ui/not-repeatable.rs +++ b/tests/ui/not-repeatable.rs @@ -4,5 +4,5 @@ struct Ipv4Addr; fn main() { let ip = Ipv4Addr; - _ = quote! { #(#ip)* }; + let _ = quote! { #(#ip)* }; } diff --git a/tests/ui/not-repeatable.stderr b/tests/ui/not-repeatable.stderr index 264a89f..2ed1da0 100644 --- a/tests/ui/not-repeatable.stderr +++ b/tests/ui/not-repeatable.stderr @@ -1,5 +1,5 @@ error[E0599]: the method `quote_into_iter` exists for struct `Ipv4Addr`, but its trait bounds were not satisfied - --> tests/ui/not-repeatable.rs:7:9 + --> tests/ui/not-repeatable.rs:7:13 | 3 | struct Ipv4Addr; | --------------- @@ -10,8 +10,8 @@ error[E0599]: the method `quote_into_iter` exists for struct `Ipv4Addr`, but its | doesn't satisfy `Ipv4Addr: ext::RepIteratorExt` | doesn't satisfy `Ipv4Addr: ext::RepToTokensExt` ... -7 | _ = quote! { #(#ip)* }; - | ^^^^^^^^^^^^^^^^^^ method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds +7 | let _ = quote! { #(#ip)* }; + | ^^^^^^^^^^^^^^^^^^ method cannot be called on `Ipv4Addr` due to unsatisfied trait bounds | = note: the following trait bounds were not satisfied: `Ipv4Addr: Iterator` diff --git a/tests/ui/wrong-type-span.stderr b/tests/ui/wrong-type-span.stderr index c774a4c..12ad307 100644 --- a/tests/ui/wrong-type-span.stderr +++ b/tests/ui/wrong-type-span.stderr @@ -1,8 +1,10 @@ error[E0308]: mismatched types - --> tests/ui/wrong-type-span.rs:6:20 + --> tests/ui/wrong-type-span.rs:6:5 | 6 | quote_spanned!(span=> #x); - | ---------------^^^^------ - | | | - | | expected struct `Span`, found `&str` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | expected `Span`, found `&str` | expected due to this + | + = note: this error originates in the macro `quote_spanned` (in Nightly builds, run with -Z macro-backtrace for more info)