diff --git a/.gitattributes b/.gitattributes index d14b9d48..d6338305 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,3 @@ -src/gen/** linguist-generated -syn.json linguist-generated -tests/debug/gen.rs linguist-generated +/src/gen/** linguist-generated +/syn.json linguist-generated +/tests/debug/gen.rs linguist-generated diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d9f630a..20c45adf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,11 +23,17 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly with: components: llvm-tools, rustc-dev - run: cargo test --all-features --release --tests + - uses: actions/upload-artifact@v6 + if: always() + with: + name: Cargo.lock + path: Cargo.lock + continue-on-error: true build: name: ${{matrix.name || format('Rust {0}', matrix.rust)}} @@ -37,7 +43,7 @@ jobs: strategy: fail-fast: false matrix: - rust: [stable, beta, 1.56.0] + rust: [stable, beta, 1.82.0, 1.68.0] include: - rust: nightly components: rustc-dev @@ -45,8 +51,11 @@ jobs: name: WebAssembly target: wasm32-unknown-unknown - rust: nightly - name: WASI - target: wasm32-wasi + name: WASI preview1 + target: wasm32-wasip1 + - rust: nightly + name: WASI preview2 + target: wasm32-wasip2 - rust: nightly name: Windows os: windows @@ -54,7 +63,7 @@ jobs: target: ${{matrix.target && format('--target={0}', matrix.target)}} timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{matrix.rust}} @@ -77,6 +86,8 @@ jobs: - run: cargo check ${{env.target}} --no-default-features --features 'full fold visit visit-mut parsing printing' - if: matrix.components == 'rustc-dev' run: cargo check --benches --all-features --release + - if: matrix.rust != '1.68.0' + run: cargo check ${{env.target}} --manifest-path json/Cargo.toml --no-default-features examples: name: Examples @@ -85,7 +96,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly - run: cargo check --manifest-path examples/dump-syntax/Cargo.toml - run: cargo check --manifest-path examples/heapsize/example/Cargo.toml @@ -101,7 +112,7 @@ jobs: env: RUSTDOCFLAGS: -Dwarnings steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/install@cargo-docs-rs - run: cargo docs-rs @@ -115,7 +126,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable - run: cargo run --manifest-path codegen/Cargo.toml - run: git diff --exit-code @@ -127,7 +138,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly - run: cargo generate-lockfile -Z minimal-versions - run: cargo check --all-features --locked @@ -139,7 +150,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly - uses: dtolnay/install@cargo-fuzz - run: cargo fuzz check @@ -151,10 +162,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@miri - run: cargo miri setup - - run: cargo miri test --all-features + - run: cargo miri test --all-features ${{github.event_name == 'pull_request' && '--tests' || ''}} env: MIRIFLAGS: -Zmiri-strict-provenance @@ -164,7 +175,7 @@ jobs: if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly with: components: clippy,rustc-dev @@ -177,7 +188,9 @@ jobs: if: github.event_name != 'pull_request' timeout-minutes: 45 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + - uses: dtolnay/rust-toolchain@stable - uses: dtolnay/install@cargo-outdated - run: cargo outdated --workspace --exit-code 1 - run: cargo outdated --manifest-path fuzz/Cargo.toml --exit-code 1 + - run: cargo outdated --manifest-path codegen/Cargo.toml --exit-code 1 diff --git a/BUILD.gn b/BUILD.gn index ff0261a5..b431c219 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -20,7 +20,7 @@ ohos_cargo_crate("lib") { sources = [ "src/lib.rs" ] edition = "2021" - cargo_pkg_version = "2.0.48" + cargo_pkg_version = "2.0.114" cargo_pkg_authors = "David Tolnay " cargo_pkg_name = "syn" cargo_pkg_description = "Parser for Rust source code" diff --git a/Cargo.toml b/Cargo.toml index c17ef658..62d7cdc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syn" -version = "2.0.48" # don't forget to update html_root_url and syn.json +version = "2.0.114" authors = ["David Tolnay "] categories = ["development-tools::procedural-macro-helpers", "parser-implementations"] description = "Parser for Rust source code" @@ -18,43 +18,42 @@ include = [ keywords = ["macros", "syn"] license = "MIT OR Apache-2.0" repository = "https://github.com/dtolnay/syn" -rust-version = "1.56" +rust-version = "1.68" [features] default = ["derive", "parsing", "printing", "clone-impls", "proc-macro"] derive = [] full = [] parsing = [] -printing = ["quote"] +printing = ["dep:quote"] visit = [] visit-mut = [] fold = [] clone-impls = [] extra-traits = [] -proc-macro = ["proc-macro2/proc-macro", "quote/proc-macro"] +proc-macro = ["proc-macro2/proc-macro", "quote?/proc-macro"] test = ["syn-test-suite/all-features"] [dependencies] -proc-macro2 = { version = "1.0.75", default-features = false } +proc-macro2 = { version = "1.0.91", default-features = false } quote = { version = "1.0.35", optional = true, default-features = false } unicode-ident = "1" [dev-dependencies] anyhow = "1" automod = "1" -flate2 = "1" insta = "1" -rayon = "1" ref-cast = "1" -reqwest = { version = "0.11", features = ["blocking"] } rustversion = "1" syn-test-suite = { version = "0", path = "tests/features" } -tar = "0.4.16" termcolor = "1" -walkdir = "2.3.2" -[lib] -doc-scrape-examples = false +[target.'cfg(not(miri))'.dev-dependencies] +flate2 = "1" +rayon = "1" +reqwest = { version = "0.13", features = ["blocking"] } +tar = "0.4.16" +walkdir = "2.3.2" [[bench]] name = "rust" @@ -68,7 +67,15 @@ required-features = ["full", "parsing"] [package.metadata.docs.rs] all-features = true targets = ["x86_64-unknown-linux-gnu"] -rustdoc-args = ["--cfg", "doc_cfg", "--generate-link-to-definition"] +rustdoc-args = [ + "--generate-link-to-definition", + "--generate-macro-expansion", + "--extend-css=src/gen/token.css", + "--extern-html-root-url=core=https://doc.rust-lang.org", + "--extern-html-root-url=alloc=https://doc.rust-lang.org", + "--extern-html-root-url=std=https://doc.rust-lang.org", + "--extern-html-root-url=proc_macro=https://doc.rust-lang.org", +] [package.metadata.playground] features = ["full", "visit", "visit-mut", "fold", "extra-traits"] diff --git a/README.OpenSource b/README.OpenSource index aaf91898..31a4dcea 100644 --- a/README.OpenSource +++ b/README.OpenSource @@ -3,9 +3,14 @@ "Name": "syn", "License": "Apache License V2.0, MIT", "License File": "LICENSE-APACHE, LICENSE-MIT", - "Version Number": "2.0.48", + "Version Number": "2.0.114", "Owner": "fangting12@huawei.com", "Upstream URL": "https://github.com/dtolnay/syn", - "Description": "A Rust library that provides support for parsing Rust code." + "Description": "A Rust library that provides support for parsing Rust code.", + "Dependencies": [ + "proc-macro2", + "quote", + "unicode-ident" + ] } ] \ No newline at end of file diff --git a/README.md b/README.md index e8d99abc..9ae472ea 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,6 @@ contains some APIs that may be useful more generally. [`syn::DeriveInput`]: https://docs.rs/syn/2.0/syn/struct.DeriveInput.html [parser functions]: https://docs.rs/syn/2.0/syn/parse/index.html -*Version requirement: Syn supports rustc 1.56 and up.* - [*Release notes*](https://github.com/dtolnay/syn/releases)
diff --git a/benches/file.rs b/benches/file.rs index b4247239..6167488c 100644 --- a/benches/file.rs +++ b/benches/file.rs @@ -3,11 +3,13 @@ #![feature(rustc_private, test)] #![recursion_limit = "1024"] #![allow( + clippy::elidable_lifetime_names, clippy::items_after_statements, clippy::manual_let_else, clippy::match_like_matches_macro, clippy::missing_panics_doc, clippy::must_use_candidate, + clippy::needless_lifetimes, clippy::uninlined_format_args )] diff --git a/benches/rust.rs b/benches/rust.rs index 15536db4..ecb9c56f 100644 --- a/benches/rust.rs +++ b/benches/rust.rs @@ -8,9 +8,11 @@ #![allow( clippy::arc_with_non_send_sync, clippy::cast_lossless, + clippy::elidable_lifetime_names, clippy::let_underscore_untyped, clippy::manual_let_else, clippy::match_like_matches_macro, + clippy::needless_lifetimes, clippy::uninlined_format_args, clippy::unnecessary_wraps )] @@ -24,20 +26,24 @@ mod macros; mod repo; use std::fs; +use std::path::Path; use std::time::{Duration, Instant}; #[cfg(not(syn_only))] mod tokenstream_parse { use proc_macro2::TokenStream; + use std::path::Path; use std::str::FromStr; - pub fn bench(content: &str) -> Result<(), ()> { + pub fn bench(_path: &Path, content: &str) -> Result<(), ()> { TokenStream::from_str(content).map(drop).map_err(drop) } } mod syn_parse { - pub fn bench(content: &str) -> Result<(), ()> { + use std::path::Path; + + pub fn bench(_path: &Path, content: &str) -> Result<(), ()> { syn::parse_file(content).map(drop).map_err(drop) } } @@ -52,45 +58,49 @@ mod librustc_parse { extern crate rustc_session; extern crate rustc_span; - use rustc_data_structures::sync::Lrc; - use rustc_error_messages::FluentBundle; - use rustc_errors::{emitter::Emitter, translation::Translate, DiagCtxt, Diagnostic}; + use crate::repo; + use rustc_errors::emitter::Emitter; + use rustc_errors::registry::Registry; + use rustc_errors::translation::Translator; + use rustc_errors::{DiagCtxt, DiagInner}; + use rustc_parse::lexer::StripTokens; use rustc_session::parse::ParseSess; use rustc_span::source_map::{FilePathMapping, SourceMap}; - use rustc_span::{edition::Edition, FileName}; + use rustc_span::FileName; + use std::path::Path; + use std::sync::Arc; - pub fn bench(content: &str) -> Result<(), ()> { + pub fn bench(path: &Path, content: &str) -> Result<(), ()> { struct SilentEmitter; impl Emitter for SilentEmitter { - fn emit_diagnostic(&mut self, _diag: &Diagnostic) {} - fn source_map(&self) -> Option<&Lrc> { + fn emit_diagnostic(&mut self, _diag: DiagInner, _registry: &Registry) {} + fn source_map(&self) -> Option<&SourceMap> { None } - } - - impl Translate for SilentEmitter { - fn fluent_bundle(&self) -> Option<&Lrc> { - None - } - fn fallback_fluent_bundle(&self) -> &FluentBundle { + fn translator(&self) -> &Translator { panic!("silent emitter attempted to translate a diagnostic"); } } - rustc_span::create_session_if_not_set_then(Edition::Edition2018, |_| { - let source_map = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let edition = repo::edition(path).parse().unwrap(); + rustc_span::create_session_if_not_set_then(edition, |_| { + let source_map = Arc::new(SourceMap::new(FilePathMapping::empty())); let emitter = Box::new(SilentEmitter); - let handler = DiagCtxt::with_emitter(emitter); + let handler = DiagCtxt::new(emitter); let sess = ParseSess::with_dcx(handler, source_map); - if let Err(diagnostic) = rustc_parse::parse_crate_from_source_str( - FileName::Custom("bench".to_owned()), - content.to_owned(), + let name = FileName::Custom("bench".to_owned()); + let mut parser = rustc_parse::new_parser_from_source_str( &sess, - ) { + name, + content.to_owned(), + StripTokens::ShebangAndFrontmatter, + ) + .unwrap(); + if let Err(diagnostic) = parser.parse_crate_mod() { diagnostic.cancel(); return Err(()); - }; + } Ok(()) }) } @@ -98,13 +108,15 @@ mod librustc_parse { #[cfg(not(syn_only))] mod read_from_disk { - pub fn bench(content: &str) -> Result<(), ()> { + use std::path::Path; + + pub fn bench(_path: &Path, content: &str) -> Result<(), ()> { let _ = content; Ok(()) } } -fn exec(mut codepath: impl FnMut(&str) -> Result<(), ()>) -> Duration { +fn exec(mut codepath: impl FnMut(&Path, &str) -> Result<(), ()>) -> Duration { let begin = Instant::now(); let mut success = 0; let mut total = 0; @@ -123,7 +135,7 @@ fn exec(mut codepath: impl FnMut(&str) -> Result<(), ()>) -> Duration { return; } let content = fs::read_to_string(path).unwrap(); - let ok = codepath(&content).is_ok(); + let ok = codepath(path, &content).is_ok(); success += ok as usize; total += 1; if !ok { @@ -143,7 +155,7 @@ fn main() { [ $( $(#[$cfg])* - (stringify!($name), $name::bench as fn(&str) -> Result<(), ()>), + (stringify!($name), $name::bench as fn(&Path, &str) -> Result<(), ()>), )* ] }; @@ -153,7 +165,7 @@ fn main() { { let mut lines = 0; let mut files = 0; - exec(|content| { + exec(|_path, content| { lines += content.lines().count(); files += 1; Ok(()) diff --git a/build.rs b/build.rs index f14ce219..6ef45aa9 100644 --- a/build.rs +++ b/build.rs @@ -1,16 +1,22 @@ +#![allow(clippy::uninlined_format_args)] + use std::env; use std::ffi::OsString; +use std::iter; use std::process::{self, Command, Stdio}; -// 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() { println!("cargo:rerun-if-changed=build.rs"); // Note: add "/build.rs" to package.include in Cargo.toml if adding any // conditional compilation within the library. + println!("cargo:rustc-cfg=check_cfg"); + println!("cargo:rustc-check-cfg=cfg(check_cfg)"); + println!("cargo:rustc-check-cfg=cfg(fuzzing)"); + println!("cargo:rustc-check-cfg=cfg(syn_disable_nightly_tests)"); + println!("cargo:rustc-check-cfg=cfg(syn_only)"); + if !unstable() { println!("cargo:rustc-cfg=syn_disable_nightly_tests"); } @@ -18,16 +24,15 @@ fn main() { fn unstable() -> bool { let rustc = cargo_env_var("RUSTC"); - - // Pick up Cargo rustc configuration. - let mut cmd = if let Some(wrapper) = env::var_os("RUSTC_WRAPPER") { - let mut cmd = Command::new(wrapper); - // The wrapper's first argument is supposed to be the path to rustc. - cmd.arg(rustc); - cmd - } else { - Command::new(rustc) - }; + let rustc_wrapper = env::var_os("RUSTC_WRAPPER").filter(|wrapper| !wrapper.is_empty()); + let rustc_workspace_wrapper = + env::var_os("RUSTC_WORKSPACE_WRAPPER").filter(|wrapper| !wrapper.is_empty()); + let mut rustc = rustc_wrapper + .into_iter() + .chain(rustc_workspace_wrapper) + .chain(iter::once(rustc)); + let mut cmd = Command::new(rustc.next().unwrap()); + cmd.args(rustc); cmd.stdin(Stdio::null()); cmd.stdout(Stdio::null()); diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 0cfb10af..9d95997a 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -6,23 +6,24 @@ edition = "2021" publish = false # this is an internal crate which should never be published +[features] +default = ["json"] +json = ["syn-codegen/serde"] + [dependencies] anyhow = "1" -color-backtrace = "0.4" +color-backtrace = "0.7" indexmap = { version = "2", features = ["serde"] } +indoc = "2" inflections = "1.1" prettyplease = "0.2.3" proc-macro2 = { version = "1.0.20", features = ["span-locations"] } quote = "1" semver = { version = "1", features = ["serde"] } serde = "1.0.88" -serde_derive = "1.0.88" serde_json = "1.0.38" syn = { version = "2", features = ["derive", "full", "parsing", "printing"], default-features = false } -syn-codegen = { path = "../json" } -thiserror = "1" -toml = "0.5" +syn-codegen = { path = "../json", default-features = false } +toml = { version = "0.9", default-features = false, features = ["parse", "serde"] } [workspace] -[patch.crates-io] -syn = { path = ".." } diff --git a/codegen/src/cfg.rs b/codegen/src/cfg.rs index f11dc869..847552ad 100644 --- a/codegen/src/cfg.rs +++ b/codegen/src/cfg.rs @@ -2,28 +2,40 @@ use proc_macro2::TokenStream; use quote::quote; use syn_codegen::Features; -pub fn features<'a>( - features: &Features, - overriding_cfg: impl Into>, -) -> TokenStream { +pub enum DocCfg { + Ordinary, + Override(&'static str), + None, +} + +impl From<&'static str> for DocCfg { + fn from(overriding_cfg: &'static str) -> Self { + DocCfg::Override(overriding_cfg) + } +} + +pub fn features(features: &Features, doc_cfg: impl Into) -> TokenStream { let features = &features.any; let cfg = match features.len() { 0 => None, 1 => Some(quote! { cfg(feature = #(#features)*) }), _ => Some(quote! { cfg(any(#(feature = #features),*)) }), }; - match (cfg, overriding_cfg.into()) { - (Some(cfg), Some(overriding_cfg)) => quote! { + match (cfg, doc_cfg.into()) { + (Some(cfg), DocCfg::Ordinary) => quote! { #[#cfg] - #[cfg_attr(doc_cfg, doc(cfg(feature = #overriding_cfg)))] + #[cfg_attr(docsrs, doc(#cfg))] }, - (Some(cfg), None) => quote! { + (Some(cfg), DocCfg::Override(overriding_cfg)) => quote! { #[#cfg] - #[cfg_attr(doc_cfg, doc(#cfg))] + #[cfg_attr(docsrs, doc(cfg(feature = #overriding_cfg)))] }, - (None, Some(overriding_cfg)) => quote! { - #[cfg_attr(doc_cfg, doc(cfg(feature = #overriding_cfg)))] + (Some(cfg), DocCfg::None) => quote! { + #[#cfg] }, - (None, None) => TokenStream::new(), + (None, DocCfg::Override(overriding_cfg)) => quote! { + #[cfg_attr(docsrs, doc(cfg(feature = #overriding_cfg)))] + }, + (None, DocCfg::Ordinary | DocCfg::None) => TokenStream::new(), } } diff --git a/codegen/src/clone.rs b/codegen/src/clone.rs index 9db771e7..92dbc966 100644 --- a/codegen/src/clone.rs +++ b/codegen/src/clone.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -13,11 +13,12 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { quote! { - #ident::#variant => #ident::#variant, + crate::#ident::#variant => crate::#ident::#variant, } } else { let mut pats = Vec::new(); @@ -28,20 +29,21 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { pats.push(pat); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } } quote! { #cfg - #ident::#variant(#(#pats),*) => #ident::#variant(#(#clones),*), + crate::#ident::#variant(#(#pats),*) => crate::#ident::#variant(#(#clones),*), } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(), @@ -63,7 +65,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { #ident: self.#ident.clone(), } }); - quote!(#ident { #(#fields)* }) + quote!(crate::#ident { #(#fields)* }) } Data::Private => unreachable!(), } @@ -86,9 +88,9 @@ fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { if copy { return quote! { #cfg_features - impl Copy for #ident {} + impl Copy for crate::#ident {} #cfg_features - impl Clone for #ident { + impl Clone for crate::#ident { fn clone(&self) -> Self { *self } @@ -100,7 +102,7 @@ fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { quote! { #cfg_features - impl Clone for #ident { + impl Clone for crate::#ident { fn clone(&self) -> Self { #body } @@ -119,8 +121,6 @@ pub fn generate(defs: &Definitions) -> Result<()> { quote! { #![allow(clippy::clone_on_copy, clippy::expl_impl_clone_on_copy)] - use crate::*; - #impls }, )?; diff --git a/codegen/src/css.rs b/codegen/src/css.rs new file mode 100644 index 00000000..66e4dbee --- /dev/null +++ b/codegen/src/css.rs @@ -0,0 +1,92 @@ +use crate::workspace_path; +use anyhow::Result; +use indoc::{formatdoc, indoc}; +use std::cmp::Ordering; +use std::collections::BTreeMap; +use std::fs; +use syn_codegen::Definitions; + +pub fn generate(defs: &Definitions) -> Result<()> { + let mut styles = String::new(); + for ty in defs.tokens.keys() { + styles += &format!("a.struct[title=\"struct syn::token::{ty}\"],\n"); + } + styles.truncate(styles.len() - 2); + styles += indoc! {" + { + \tdisplay: inline-block; + \tcolor: transparent; + \twhite-space: nowrap; + } + "}; + styles.push('\n'); + for ty in defs.tokens.keys() { + styles += &format!("a.struct[title=\"struct syn::token::{ty}\"]::before,\n"); + } + styles.truncate(styles.len() - 2); + styles += indoc! {" + { + \tdisplay: inline-block; + \tcolor: var(--type-link-color); + \twidth: 0; + } + "}; + let mut shrink = BTreeMap::new(); + let mut grow = BTreeMap::new(); + for (ty, repr) in &defs.tokens { + let macro_len = "Token![]".len() + repr.len(); + let ty_len = ty.len(); + styles.push('\n'); + styles += &match Ord::cmp(¯o_len, &ty_len) { + Ordering::Less => { + shrink + .entry((macro_len, ty_len)) + .or_insert_with(Vec::new) + .push(ty); + formatdoc! {" + a.struct[title=\"struct syn::token::{ty}\"]::before {{ + \tcontent: \"Token![{repr}]\"; + \tfont-size: calc(100% * {ty_len} / {macro_len}); + }} + "} + } + Ordering::Equal => unreachable!(), + Ordering::Greater => { + let padding = macro_len.saturating_sub(ty.len()); + grow.entry(padding).or_insert_with(Vec::new).push(ty); + formatdoc! {" + a.struct[title=\"struct syn::token::{ty}\"]::before {{ + \tcontent: \"Token![{repr}]\"; + }} + "} + } + }; + } + for ((macro_len, ty_len), types) in shrink { + for ty in types { + styles += &format!("\na.struct[title=\"struct syn::token::{ty}\"],"); + } + styles.truncate(styles.len() - 1); + styles += &formatdoc! {" + {{ + \tfont-size: calc(100% * {macro_len} / {ty_len}); + }} + "}; + } + for (padding, types) in grow { + for ty in types { + styles += &format!("\na.struct[title=\"struct syn::token::{ty}\"]::after,"); + } + styles.truncate(styles.len() - 1); + let padding = ".".repeat(padding); + styles += &formatdoc! {" + {{ + \tcontent: \"{padding}\"; + }} + "}; + } + + let css_path = workspace_path::get("src/gen/token.css"); + fs::write(css_path, styles)?; + Ok(()) +} diff --git a/codegen/src/debug.rs b/codegen/src/debug.rs index b0f51752..6ba25af3 100644 --- a/codegen/src/debug.rs +++ b/codegen/src/debug.rs @@ -1,4 +1,5 @@ -use crate::{cfg, file, lookup}; +use crate::cfg::{self, DocCfg}; +use crate::{file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -49,21 +50,23 @@ fn expand_impl_body( let ident = Ident::new(type_name, Span::call_site()); let is_syntax_tree_variant = syntax_tree_variants.contains(type_name.as_str()); - let body = match &node.data { + match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { assert!(!is_syntax_tree_variant); + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { quote! { - #ident::#variant => formatter.write_str(#variant_name), + crate::#ident::#variant => formatter.write_str(#variant_name), } } else { let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } @@ -71,7 +74,7 @@ fn expand_impl_body( if syntax_tree_enum(type_name, variant_name, fields).is_some() { quote! { #cfg - #ident::#variant(v0) => v0.debug(formatter, #variant_name), + crate::#ident::#variant(v0) => v0.debug(formatter, #variant_name), } } else { let pats = (0..fields.len()) @@ -79,7 +82,7 @@ fn expand_impl_body( .collect::>(); quote! { #cfg - #ident::#variant(#(#pats),*) => { + crate::#ident::#variant(#(#pats),*) => { let mut formatter = formatter.debug_tuple(#variant_name); #(formatter.field(#pats);)* formatter.finish() @@ -88,7 +91,7 @@ fn expand_impl_body( } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(), @@ -124,19 +127,6 @@ fn expand_impl_body( } } Data::Private => unreachable!(), - }; - - if is_syntax_tree_variant { - quote! { - impl #ident { - fn debug(&self, formatter: &mut fmt::Formatter, name: &str) -> fmt::Result { - #body - } - } - self.debug(formatter, #type_name) - } - } else { - body } } @@ -146,7 +136,10 @@ fn expand_impl(defs: &Definitions, node: &Node, syntax_tree_variants: &Set<&str> return TokenStream::new(); } - let ident = Ident::new(&node.ident, Span::call_site()); + let type_name = &node.ident; + let ident = Ident::new(type_name, Span::call_site()); + let is_syntax_tree_variant = syntax_tree_variants.contains(type_name.as_str()); + let cfg_features = cfg::features(&node.features, "extra-traits"); let body = expand_impl_body(defs, node, syntax_tree_variants); let formatter = match &node.data { @@ -154,11 +147,29 @@ fn expand_impl(defs: &Definitions, node: &Node, syntax_tree_variants: &Set<&str> _ => quote!(formatter), }; - quote! { - #cfg_features - impl Debug for #ident { - fn fmt(&self, #formatter: &mut fmt::Formatter) -> fmt::Result { - #body + if is_syntax_tree_variant { + let inherent_cfg_features = cfg::features(&node.features, DocCfg::None); + quote! { + #cfg_features + impl Debug for crate::#ident { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.debug(formatter, #type_name) + } + } + #inherent_cfg_features + impl crate::#ident { + fn debug(&self, #formatter: &mut fmt::Formatter, name: &str) -> fmt::Result { + #body + } + } + } + } else { + quote! { + #cfg_features + impl Debug for crate::#ident { + fn fmt(&self, #formatter: &mut fmt::Formatter) -> fmt::Result { + #body + } } } } @@ -185,8 +196,9 @@ pub fn generate(defs: &Definitions) -> Result<()> { file::write( DEBUG_SRC, quote! { - use crate::*; - use std::fmt::{self, Debug}; + #![allow(unknown_lints, non_local_definitions)] + + use core::fmt::{self, Debug}; #impls }, diff --git a/codegen/src/eq.rs b/codegen/src/eq.rs index d709a90a..11ac24d7 100644 --- a/codegen/src/eq.rs +++ b/codegen/src/eq.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -23,11 +23,12 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants.iter().map(|(variant_name, fields)| { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { quote! { - (#ident::#variant, #ident::#variant) => true, + (crate::#ident::#variant, crate::#ident::#variant) => true, } } else { let mut this_pats = Vec::new(); @@ -57,16 +58,17 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { comparisons.push(quote!(true)); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") { cfg = Some(quote!(#[cfg(feature = "full")])); } } } quote! { #cfg - (#ident::#variant(#(#this_pats),*), #ident::#variant(#(#other_pats),*)) => { + (crate::#ident::#variant(#(#this_pats),*), crate::#ident::#variant(#(#other_pats),*)) => { #(#comparisons)&&* } } @@ -118,7 +120,7 @@ fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { let eq = quote! { #cfg_features - impl Eq for #ident {} + impl Eq for crate::#ident {} }; let manual_partial_eq = node.data == Data::Private; @@ -137,7 +139,7 @@ fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { #eq #cfg_features - impl PartialEq for #ident { + impl PartialEq for crate::#ident { fn eq(&self, #other: &Self) -> bool { #body } @@ -156,7 +158,8 @@ pub fn generate(defs: &Definitions) -> Result<()> { quote! { #[cfg(any(feature = "derive", feature = "full"))] use crate::tt::TokenStreamHelper; - use crate::*; + #[cfg(feature = "extra-traits")] + use alloc::string::ToString; #impls }, diff --git a/codegen/src/fold.rs b/codegen/src/fold.rs index 13a5390a..98d3051c 100644 --- a/codegen/src/fold.rs +++ b/codegen/src/fold.rs @@ -1,3 +1,4 @@ +use crate::cfg::{self, DocCfg}; use crate::{file, full, gen}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; @@ -7,12 +8,14 @@ use syn_codegen::{Data, Definitions, Features, Node, Type}; const FOLD_SRC: &str = "src/gen/fold.rs"; -fn simple_visit(item: &str, name: &TokenStream) -> TokenStream { +fn method_name(item: &str) -> Ident { let ident = gen::under_name(item); - let method = format_ident!("fold_{}", ident); - quote! { - f.#method(#name) - } + format_ident!("fold_{}", ident) +} + +fn simple_fold(item: &str, name: &TokenStream) -> TokenStream { + let method = method_name(item); + quote! { f.#method(#name) } } fn visit( @@ -29,17 +32,24 @@ fn visit( }) } Type::Vec(t) => { - let operand = quote!(it); - let val = visit(t, features, defs, &operand)?; - Some(quote! { - FoldHelper::lift(#name, |it| #val) - }) + let Type::Syn(t) = &**t else { unimplemented!() }; + if t == "Attribute" { + Some(quote! { + f.fold_attributes(#name) + }) + } else { + let method = method_name(t); + Some(quote! { + fold_vec(#name, f, F::#method) + }) + } } Type::Punctuated(p) => { - let operand = quote!(it); - let val = visit(&p.element, features, defs, &operand)?; + let t = &*p.element; + let Type::Syn(t) = t else { unimplemented!() }; + let method = method_name(t); Some(quote! { - FoldHelper::lift(#name, |it| #val) + crate::punctuated::fold(#name, f, F::#method) }) } Type::Option(t) => { @@ -66,21 +76,26 @@ fn visit( fn requires_full(features: &Features) -> bool { features.any.contains("full") && features.any.len() == 1 } - let mut res = simple_visit(t, name); + let mut res = simple_fold(t, name); let target = defs.types.iter().find(|ty| ty.ident == *t).unwrap(); if requires_full(&target.features) && !requires_full(features) { res = quote!(full!(#res)); } Some(res) } - Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_visit(t, name)), + Type::Ext(t) if gen::TERMINAL_TYPES.contains(&&t[..]) => Some(simple_fold(t, name)), Type::Ext(_) | Type::Std(_) | Type::Token(_) | Type::Group(_) => None, } } fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { let under_name = gen::under_name(&s.ident); - let ty = Ident::new(&s.ident, Span::call_site()); + let ident = Ident::new(&s.ident, Span::call_site()); + let ty = if gen::TERMINAL_TYPES.contains(&s.ident.as_str()) { + quote!(proc_macro2::#ident) + } else { + quote!(crate::#ident) + }; let fold_fn = format_ident!("fold_{}", under_name); let mut fold_impl = TokenStream::new(); @@ -149,7 +164,7 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi } if fields.is_empty() { - if ty == "Ident" { + if s.ident == "Ident" { fold_impl.extend(quote! { let mut node = node; let span = f.fold_span(node.span()); @@ -168,7 +183,7 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi } } Data::Private => { - if ty == "Ident" { + if s.ident == "Ident" { fold_impl.extend(quote! { let mut node = node; let span = f.fold_span(node.span()); @@ -192,20 +207,38 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi }; } + let traits_body = if s.ident == "Span" || s.ident == "TokenStream" { + quote!(i) + } else { + quote!(#fold_fn(self, i)) + }; + traits.extend(quote! { fn #fold_fn(&mut self, i: #ty) -> #ty { - #fold_fn(self, i) + #traits_body } }); - impls.extend(quote! { - pub fn #fold_fn(f: &mut F, node: #ty) -> #ty - where - F: Fold + ?Sized, - { - #fold_impl - } - }); + if s.ident != "TokenStream" { + impls.extend(quote! { + pub fn #fold_fn(f: &mut F, node: #ty) -> #ty + where + F: Fold + ?Sized, + { + #fold_impl + } + }); + } + + if s.ident == "Attribute" { + let features = cfg::features(&s.features, DocCfg::Ordinary); + traits.extend(quote! { + #features + fn fold_attributes(&mut self, i: Vec) -> Vec { + fold_vec(i, self, Self::fold_attribute) + } + }); + } } pub fn generate(defs: &Definitions) -> Result<()> { @@ -222,10 +255,10 @@ pub fn generate(defs: &Definitions) -> Result<()> { clippy::needless_pass_by_ref_mut, )] - #[cfg(any(feature = "full", feature = "derive"))] - use crate::gen::helper::fold::*; - use crate::*; - use proc_macro2::Span; + #[cfg(any(feature = "derive", feature = "full"))] + use alloc::boxed::Box; + #[cfg(any(feature = "derive", feature = "full"))] + use alloc::vec::Vec; #full_macro @@ -239,6 +272,15 @@ pub fn generate(defs: &Definitions) -> Result<()> { } #impls + + #[cfg(any(feature = "derive", feature = "full"))] + fn fold_vec(vec: Vec, fold: &mut V, mut f: F) -> Vec + where + V: ?Sized, + F: FnMut(&mut V, T) -> T, + { + vec.into_iter().map(|it| f(fold, it)).collect() + } }, )?; Ok(()) diff --git a/codegen/src/full.rs b/codegen/src/full.rs index a4100318..eeeadd1e 100644 --- a/codegen/src/full.rs +++ b/codegen/src/full.rs @@ -1,5 +1,7 @@ +use crate::lookup; use proc_macro2::TokenStream; use quote::quote; +use syn_codegen::{Data, Definitions, Node, Type}; pub fn get_macro() -> TokenStream { quote! { @@ -18,3 +20,71 @@ pub fn get_macro() -> TokenStream { } } } + +/// Syntax tree enum that has some variants enabled in "derive" mode and the +/// rest enabled in "full" mode. +pub fn is_mixed_derive_full_enum(defs: &Definitions, node: &Node) -> bool { + if !(node.features.any.contains("derive") && node.features.any.contains("full")) { + return false; + } + + let variants = match &node.data { + Data::Enum(variants) => variants, + Data::Private | Data::Struct(_) => return false, + }; + + let mut has_derive = false; + let mut has_full = false; + for fields in variants.values() { + match classify_variant(defs, fields) { + VariantAvailability::Derive => has_derive = true, + VariantAvailability::Full => has_full = true, + VariantAvailability::Other => {} + } + } + has_derive && has_full +} + +enum VariantAvailability { + Derive, + Full, + Other, +} + +fn classify_variant(defs: &Definitions, fields: &[Type]) -> VariantAvailability { + let mut has_derive = false; + let mut has_full = false; + for field in fields { + for_each_syn_type(field, &mut |ty| { + let node = lookup::node(defs, ty); + let derive = node.features.any.contains("derive"); + let full = node.features.any.contains("full"); + match (derive, full) { + (false, false) => {} + (false, true) => has_full = true, + (true, false | true) => has_derive = true, + } + }); + } + if has_full { + VariantAvailability::Full + } else if has_derive { + VariantAvailability::Derive + } else { + VariantAvailability::Other + } +} + +fn for_each_syn_type(ty: &Type, f: &mut dyn FnMut(&str)) { + match ty { + Type::Syn(ty) => f(ty), + Type::Std(_) | Type::Ext(_) | Type::Token(_) | Type::Group(_) => {} + Type::Punctuated(punctuated) => for_each_syn_type(&punctuated.element, f), + Type::Option(ty) | Type::Box(ty) | Type::Vec(ty) => for_each_syn_type(ty, f), + Type::Tuple(elements) => { + for ty in elements { + for_each_syn_type(ty, f); + } + } + } +} diff --git a/codegen/src/gen.rs b/codegen/src/gen.rs index 57a9b68f..7a731902 100644 --- a/codegen/src/gen.rs +++ b/codegen/src/gen.rs @@ -1,9 +1,9 @@ -use crate::cfg; +use crate::cfg::{self, DocCfg}; use inflections::Inflect; use proc_macro2::{Ident, Span, TokenStream}; use syn_codegen::{Data, Definitions, Features, Node}; -pub const TERMINAL_TYPES: &[&str] = &["Span", "Ident"]; +pub const TERMINAL_TYPES: [&str; 3] = ["Ident", "Span", "TokenStream"]; pub fn under_name(name: &str) -> Ident { Ident::new(&name.to_snake_case(), Span::call_site()) @@ -14,7 +14,7 @@ pub fn traverse( node: fn(&mut TokenStream, &mut TokenStream, &Node, &Definitions), ) -> (TokenStream, TokenStream) { let mut types = defs.types.clone(); - for &terminal in TERMINAL_TYPES { + for terminal in TERMINAL_TYPES { types.push(Node { ident: terminal.to_owned(), features: Features::default(), @@ -27,7 +27,7 @@ pub fn traverse( let mut traits = TokenStream::new(); let mut impls = TokenStream::new(); for s in types { - let features = cfg::features(&s.features, None); + let features = cfg::features(&s.features, DocCfg::Ordinary); traits.extend(features.clone()); impls.extend(features); node(&mut traits, &mut impls, &s, defs); diff --git a/codegen/src/hash.rs b/codegen/src/hash.rs index c0868e2f..46fc0247 100644 --- a/codegen/src/hash.rs +++ b/codegen/src/hash.rs @@ -1,4 +1,4 @@ -use crate::{cfg, file, lookup}; +use crate::{cfg, file, full, lookup}; use anyhow::Result; use proc_macro2::{Ident, Span, TokenStream}; use quote::{format_ident, quote}; @@ -23,6 +23,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { match &node.data { Data::Enum(variants) if variants.is_empty() => quote!(match *self {}), Data::Enum(variants) => { + let mixed_derive_full = full::is_mixed_derive_full_enum(defs, node); let arms = variants .iter() .enumerate() @@ -31,7 +32,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { let variant = Ident::new(variant_name, Span::call_site()); if fields.is_empty() { quote! { - #ident::#variant => { + crate::#ident::#variant => { state.write_u8(#i); } } @@ -60,23 +61,25 @@ fn expand_impl_body(defs: &Definitions, node: &Node) -> TokenStream { pats.push(var); } let mut cfg = None; - if node.ident == "Expr" { + if mixed_derive_full { if let Type::Syn(ty) = &fields[0] { - if !lookup::node(defs, ty).features.any.contains("derive") { + let features = &lookup::node(defs, ty).features; + if features.any.contains("full") && !features.any.contains("derive") + { cfg = Some(quote!(#[cfg(feature = "full")])); } } } quote! { #cfg - #ident::#variant(#(#pats),*) => { + crate::#ident::#variant(#(#pats),*) => { state.write_u8(#i); #(#hashes)* } } } }); - let nonexhaustive = if node.ident == "Expr" { + let nonexhaustive = if mixed_derive_full { Some(quote! { #[cfg(not(feature = "full"))] _ => unreachable!(), @@ -135,7 +138,7 @@ fn expand_impl(defs: &Definitions, node: &Node) -> TokenStream { quote! { #cfg_features - impl Hash for #ident { + impl Hash for crate::#ident { fn hash(&self, #hasher: &mut H) where H: Hasher, @@ -157,8 +160,9 @@ pub fn generate(defs: &Definitions) -> Result<()> { quote! { #[cfg(any(feature = "derive", feature = "full"))] use crate::tt::TokenStreamHelper; - use crate::*; - use std::hash::{Hash, Hasher}; + #[cfg(feature = "extra-traits")] + use alloc::string::ToString; + use core::hash::{Hash, Hasher}; #impls }, diff --git a/codegen/src/main.rs b/codegen/src/main.rs index 8b8670c5..8af199ad 100644 --- a/codegen/src/main.rs +++ b/codegen/src/main.rs @@ -10,9 +10,11 @@ // programmatically from the syntax tree description. #![allow( + clippy::format_push_string, clippy::items_after_statements, clippy::manual_let_else, clippy::match_like_matches_macro, + clippy::module_name_repetitions, clippy::similar_names, clippy::too_many_lines, clippy::uninlined_format_args @@ -20,6 +22,7 @@ mod cfg; mod clone; +mod css; mod debug; mod eq; mod file; @@ -27,6 +30,7 @@ mod fold; mod full; mod gen; mod hash; +#[cfg(feature = "json")] mod json; mod lookup; mod operand; @@ -44,10 +48,12 @@ fn main() -> anyhow::Result<()> { debug::generate(&defs)?; eq::generate(&defs)?; hash::generate(&defs)?; + #[cfg(feature = "json")] json::generate(&defs)?; fold::generate(&defs)?; visit::generate(&defs)?; visit_mut::generate(&defs)?; snapshot::generate(&defs)?; + css::generate(&defs)?; Ok(()) } diff --git a/codegen/src/operand.rs b/codegen/src/operand.rs index db3bd187..ff3147c0 100644 --- a/codegen/src/operand.rs +++ b/codegen/src/operand.rs @@ -6,7 +6,7 @@ pub enum Operand { Owned(TokenStream), } -pub use self::Operand::*; +pub use self::Operand::{Borrowed, Owned}; impl Operand { pub fn tokens(&self) -> &TokenStream { diff --git a/codegen/src/parse.rs b/codegen/src/parse.rs index f232fedf..ea800c61 100644 --- a/codegen/src/parse.rs +++ b/codegen/src/parse.rs @@ -3,6 +3,7 @@ use anyhow::{bail, Result}; use indexmap::IndexMap; use quote::quote; use std::collections::BTreeMap; +use std::fmt::{self, Display}; use std::fs; use std::path::{Path, PathBuf}; use syn::parse::{Error, Parser}; @@ -11,7 +12,6 @@ use syn::{ Ident, Item, PathArguments, TypeMacro, TypePath, TypeTuple, UseTree, Visibility, }; use syn_codegen as types; -use thiserror::Error; const SYN_CRATE_ROOT: &str = "src/lib.rs"; const TOKEN_SRC: &str = "src/token.rs"; @@ -86,9 +86,9 @@ fn introspect_item(item: &AstItem, lookup: &Lookup) -> types::Node { types::Data::Private } }, - exhaustive: true, + exhaustive: !is_non_exhaustive(&item.ast.attrs), }, - Data::Union(..) => panic!("Union not supported"), + Data::Union(..) => panic!("union not supported"), } } @@ -106,7 +106,7 @@ fn introspect_enum(item: &DataEnum, lookup: &Lookup) -> types::Variants { .map(|field| introspect_type(&field.ty, lookup)) .collect(), Fields::Unit => vec![], - Fields::Named(_) => panic!("Enum representation not supported"), + Fields::Named(_) => panic!("enum representation not supported"), }; Some((variant.ident.to_string(), fields)) }) @@ -126,7 +126,7 @@ fn introspect_struct(item: &DataStruct, lookup: &Lookup) -> types::Fields { }) .collect(), Fields::Unit => IndexMap::new(), - Fields::Unnamed(_) => panic!("Struct representation not supported"), + Fields::Unnamed(_) => panic!("struct representation not supported"), } } @@ -169,7 +169,7 @@ fn introspect_type(item: &syn::Type, lookup: &Lookup) -> types::Type { while let Some(alias) = lookup.aliases.get(resolved) { resolved = alias; } - if lookup.items.get(resolved).is_some() { + if lookup.items.contains_key(resolved) { types::Type::Syn(resolved.to_string()) } else { unimplemented!("{}", resolved); @@ -185,7 +185,7 @@ fn introspect_type(item: &syn::Type, lookup: &Lookup) -> types::Type { if mac.path.segments.last().unwrap().ident == "Token" => { let content = mac.tokens.to_string(); - let ty = lookup.tokens.get(&content).unwrap().to_string(); + let ty = lookup.tokens.get(&content).unwrap().clone(); types::Type::Token(ty) } @@ -244,32 +244,32 @@ fn is_doc_hidden(attrs: &[Attribute]) -> bool { fn first_arg(params: &PathArguments) -> &syn::Type { let data = match params { PathArguments::AngleBracketed(data) => data, - _ => panic!("Expected at least 1 type argument here"), + _ => panic!("expected at least 1 type argument here"), }; match data .args .first() - .expect("Expected at least 1 type argument here") + .expect("expected at least 1 type argument here") { GenericArgument::Type(ty) => ty, - _ => panic!("Expected at least 1 type argument here"), + _ => panic!("expected at least 1 type argument here"), } } fn last_arg(params: &PathArguments) -> &syn::Type { let data = match params { PathArguments::AngleBracketed(data) => data, - _ => panic!("Expected at least 1 type argument here"), + _ => panic!("expected at least 1 type argument here"), }; match data .args .last() - .expect("Expected at least 1 type argument here") + .expect("expected at least 1 type argument here") { GenericArgument::Type(ty) => ty, - _ => panic!("Expected at least 1 type argument here"), + _ => panic!("expected at least 1 type argument here"), } } @@ -306,27 +306,22 @@ mod parsing { } } - // Parses a simple AstStruct without the `pub struct` prefix. - fn ast_struct_inner(input: ParseStream) -> Result { + pub fn ast_struct(input: ParseStream) -> Result { + let attrs = input.call(Attribute::parse_outer)?; + input.parse::()?; + input.parse::()?; let ident: Ident = input.parse()?; let features = full(input); let rest: TokenStream = input.parse()?; Ok(AstItem { ast: syn::parse2(quote! { + #(#attrs)* pub struct #ident #rest })?, features, }) } - pub fn ast_struct(input: ParseStream) -> Result { - input.call(Attribute::parse_outer)?; - input.parse::()?; - input.parse::()?; - let res = input.call(ast_struct_inner)?; - Ok(res) - } - pub fn ast_enum(input: ParseStream) -> Result { let attrs = input.call(Attribute::parse_outer)?; input.parse::()?; @@ -422,7 +417,7 @@ mod parsing { expansion.parse::()?; let path: Path = expansion.parse()?; let ty = path.segments.last().unwrap().ident.to_string(); - tokens.insert(token, ty.to_string()); + tokens.insert(token, ty.clone()); } Ok(tokens) } @@ -495,8 +490,7 @@ fn get_features(attrs: &[Attribute], base: &[Attribute]) -> Vec { ret } -#[derive(Error, Debug)] -#[error("{path}:{line}:{column}: {error}")] +#[derive(Debug)] struct LoadFileError { path: PathBuf, line: usize, @@ -504,6 +498,21 @@ struct LoadFileError { error: Error, } +impl std::error::Error for LoadFileError {} + +impl Display for LoadFileError { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{path}:{line}:{column}: {error}", + path = self.path.display(), + line = self.line, + column = self.column, + error = self.error, + ) + } +} + fn load_file( relative_to_workspace_root: impl AsRef, features: &[Attribute], @@ -581,13 +590,13 @@ fn do_load_file( let features = get_features(&item.attrs, features); // Try to parse the AstItem declaration out of the item. - let tts = item.mac.tokens.clone(); + let tokens = item.mac.tokens.clone(); let mut found = if item.mac.path.is_ident("ast_struct") { - parsing::ast_struct.parse2(tts) + parsing::ast_struct.parse2(tokens) } else if item.mac.path.is_ident("ast_enum") { - parsing::ast_enum.parse2(tts) + parsing::ast_enum.parse2(tokens) } else if item.mac.path.is_ident("ast_enum_of_structs") { - parsing::ast_enum_of_structs.parse2(tts) + parsing::ast_enum_of_structs.parse2(tokens) } else { continue; }?; @@ -619,7 +628,7 @@ fn do_load_file( } } Item::Use(item) - if relative_to_workspace_root == Path::new(SYN_CRATE_ROOT) + if relative_to_workspace_root == Path::new("src/pat.rs") && matches!(item.vis, Visibility::Public(_)) => { load_aliases(item.tree, lookup); diff --git a/codegen/src/snapshot.rs b/codegen/src/snapshot.rs index 17588dbc..b99521d0 100644 --- a/codegen/src/snapshot.rs +++ b/codegen/src/snapshot.rs @@ -275,7 +275,7 @@ fn expand_impl_body(defs: &Definitions, node: &Node, name: &str, val: &Operand) for node in &defs.types { if node.ident == *inner { if let Data::Enum(variants) = &node.data { - if variants.get("None").map_or(false, Vec::is_empty) { + if variants.get("None").is_some_and(Vec::is_empty) { let ty = rust_type(ty); call = quote! { match #val.#ident { @@ -356,14 +356,14 @@ pub fn generate(defs: &Definitions) -> Result<()> { file::write( TESTS_DEBUG_SRC, quote! { - // False positive: https://github.com/rust-lang/rust/issues/78586#issuecomment-1722680482 - #![allow(repr_transparent_external_private_fields)] + // False positive: https://github.com/rust-lang/rust/issues/115922 + #![allow(repr_transparent_non_zst_fields)] #![allow(clippy::match_wildcard_for_single_variants)] use super::{Lite, Present}; + use core::fmt::{self, Debug, Display}; use ref_cast::RefCast; - use std::fmt::{self, Debug, Display}; #impls }, diff --git a/codegen/src/version.rs b/codegen/src/version.rs index db6a8f8c..ac188e68 100644 --- a/codegen/src/version.rs +++ b/codegen/src/version.rs @@ -1,22 +1,19 @@ use crate::workspace_path; -use anyhow::Result; +use anyhow::{Context as _, Result}; use semver::Version; -use serde_derive::Deserialize; use std::fs; +use toml::Table; pub fn get() -> Result { let syn_cargo_toml = workspace_path::get("Cargo.toml"); - let manifest = fs::read_to_string(syn_cargo_toml)?; - let parsed: Manifest = toml::from_str(&manifest)?; - Ok(parsed.package.version) -} - -#[derive(Debug, Deserialize)] -struct Manifest { - package: Package, -} - -#[derive(Debug, Deserialize)] -struct Package { - version: Version, + let content = fs::read_to_string(syn_cargo_toml)?; + let manifest: Table = toml::from_str(&content)?; + manifest + .get("package") + .context("[package] not found in Cargo.toml")? + .get("version") + .and_then(toml::Value::as_str) + .context("package version not found in Cargo.toml")? + .parse() + .context("failed to parse package version") } diff --git a/codegen/src/visit.rs b/codegen/src/visit.rs index 573798a1..a3f6acd4 100644 --- a/codegen/src/visit.rs +++ b/codegen/src/visit.rs @@ -96,7 +96,12 @@ fn visit( fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { let under_name = gen::under_name(&s.ident); - let ty = Ident::new(&s.ident, Span::call_site()); + let ident = Ident::new(&s.ident, Span::call_site()); + let ty = if gen::TERMINAL_TYPES.contains(&s.ident.as_str()) { + quote!(proc_macro2::#ident) + } else { + quote!(crate::#ident) + }; let visit_fn = format_ident!("visit_{}", under_name); let mut visit_impl = TokenStream::new(); @@ -164,7 +169,7 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi } } Data::Private => { - if ty == "Ident" { + if s.ident == "Ident" { visit_impl.extend(quote! { v.visit_span(&node.span()); }); @@ -178,20 +183,30 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi Some(quote!('ast)) }; + let traits_body = if s.ident == "Span" || s.ident == "TokenStream" { + None + } else { + Some(quote! { + #visit_fn(self, i); + }) + }; + traits.extend(quote! { fn #visit_fn(&mut self, i: &#ast_lifetime #ty) { - #visit_fn(self, i); + #traits_body } }); - impls.extend(quote! { - pub fn #visit_fn<'ast, V>(v: &mut V, node: &#ast_lifetime #ty) - where - V: Visit<'ast> + ?Sized, - { - #visit_impl - } - }); + if s.ident != "TokenStream" { + impls.extend(quote! { + pub fn #visit_fn<'ast, V>(v: &mut V, node: &#ast_lifetime #ty) + where + V: Visit<'ast> + ?Sized, + { + #visit_impl + } + }); + } } pub fn generate(defs: &Definitions) -> Result<()> { @@ -205,8 +220,6 @@ pub fn generate(defs: &Definitions) -> Result<()> { #[cfg(any(feature = "full", feature = "derive"))] use crate::punctuated::Punctuated; - use crate::*; - use proc_macro2::Span; #full_macro diff --git a/codegen/src/visit_mut.rs b/codegen/src/visit_mut.rs index 5b96c871..7f410166 100644 --- a/codegen/src/visit_mut.rs +++ b/codegen/src/visit_mut.rs @@ -1,3 +1,4 @@ +use crate::cfg::{self, DocCfg}; use crate::operand::{Borrowed, Operand, Owned}; use crate::{file, full, gen}; use anyhow::Result; @@ -36,14 +37,20 @@ fn visit( visit(t, features, defs, &Owned(quote!(*#name))) } Type::Vec(t) => { - let operand = Borrowed(quote!(it)); - let val = visit(t, features, defs, &operand)?; let name = name.ref_mut_tokens(); - Some(quote! { - for it in #name { - #val; - } - }) + if matches!(&**t, Type::Syn(t) if t == "Attribute") { + Some(quote! { + v.visit_attributes_mut(#name); + }) + } else { + let operand = Borrowed(quote!(it)); + let val = visit(t, features, defs, &operand)?; + Some(quote! { + for it in #name { + #val; + } + }) + } } Type::Punctuated(p) => { let operand = Borrowed(quote!(it)); @@ -96,7 +103,12 @@ fn visit( fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Definitions) { let under_name = gen::under_name(&s.ident); - let ty = Ident::new(&s.ident, Span::call_site()); + let ident = Ident::new(&s.ident, Span::call_site()); + let ty = if gen::TERMINAL_TYPES.contains(&s.ident.as_str()) { + quote!(proc_macro2::#ident) + } else { + quote!(crate::#ident) + }; let visit_mut_fn = format_ident!("visit_{}_mut", under_name); let mut visit_mut_impl = TokenStream::new(); @@ -164,7 +176,7 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi } } Data::Private => { - if ty == "Ident" { + if s.ident == "Ident" { visit_mut_impl.extend(quote! { let mut span = node.span(); v.visit_span_mut(&mut span); @@ -174,20 +186,42 @@ fn node(traits: &mut TokenStream, impls: &mut TokenStream, s: &Node, defs: &Defi } } + let traits_body = if s.ident == "Span" || s.ident == "TokenStream" { + None + } else { + Some(quote! { + #visit_mut_fn(self, i); + }) + }; + traits.extend(quote! { fn #visit_mut_fn(&mut self, i: &mut #ty) { - #visit_mut_fn(self, i); + #traits_body } }); - impls.extend(quote! { - pub fn #visit_mut_fn(v: &mut V, node: &mut #ty) - where - V: VisitMut + ?Sized, - { - #visit_mut_impl - } - }); + if s.ident != "TokenStream" { + impls.extend(quote! { + pub fn #visit_mut_fn(v: &mut V, node: &mut #ty) + where + V: VisitMut + ?Sized, + { + #visit_mut_impl + } + }); + } + + if s.ident == "Attribute" { + let features = cfg::features(&s.features, DocCfg::Ordinary); + traits.extend(quote! { + #features + fn visit_attributes_mut(&mut self, i: &mut Vec) { + for attr in i { + self.visit_attribute_mut(attr); + } + } + }); + } } pub fn generate(defs: &Definitions) -> Result<()> { @@ -201,8 +235,8 @@ pub fn generate(defs: &Definitions) -> Result<()> { #[cfg(any(feature = "full", feature = "derive"))] use crate::punctuated::Punctuated; - use crate::*; - use proc_macro2::Span; + #[cfg(any(feature = "derive", feature = "full"))] + use alloc::vec::Vec; #full_macro diff --git a/dev/import.sh b/dev/import.sh new file mode 100644 index 00000000..8d443930 --- /dev/null +++ b/dev/import.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [ ! -f "$1" ]; then + echo "Usage: dev/import.sh tests/rust/path/to/mod.rs" >&2 + exit 1 +fi + +set -eu + +main=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/main.rs +echo -n "syn_dev::r#mod! {" > "$main" +cat "$1" >> "$main" +echo "}" >> "$main" diff --git a/examples/dump-syntax/Cargo.toml b/examples/dump-syntax/Cargo.toml index 6239141b..032a3742 100644 --- a/examples/dump-syntax/Cargo.toml +++ b/examples/dump-syntax/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [dependencies] -colored = "2" +colored = "3" proc-macro2 = { version = "1", features = ["span-locations"] } [dependencies.syn] diff --git a/examples/dump-syntax/src/main.rs b/examples/dump-syntax/src/main.rs index 855047a0..a4cdfd92 100644 --- a/examples/dump-syntax/src/main.rs +++ b/examples/dump-syntax/src/main.rs @@ -39,12 +39,10 @@ enum Error { impl Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::Error::*; - match self { - IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"), - ReadFile(error) => write!(f, "Unable to read file: {}", error), - ParseFile { + Error::IncorrectUsage => write!(f, "Usage: dump-syntax path/to/filename.rs"), + Error::ReadFile(error) => write!(f, "Unable to read file: {}", error), + Error::ParseFile { error, filepath, source_code, diff --git a/examples/heapsize/heapsize/src/lib.rs b/examples/heapsize/heapsize/src/lib.rs index 30bb6d60..240d10db 100644 --- a/examples/heapsize/heapsize/src/lib.rs +++ b/examples/heapsize/heapsize/src/lib.rs @@ -1,6 +1,6 @@ use std::mem; -pub use heapsize_derive::*; +pub use heapsize_derive::HeapSize; pub trait HeapSize { /// Total number of bytes of heap memory owned by `self`. diff --git a/fuzz/.gitignore b/fuzz/.gitignore index f83457ae..4bc31dc3 100644 --- a/fuzz/.gitignore +++ b/fuzz/.gitignore @@ -1,4 +1,5 @@ -artifacts/ -corpus/ -coverage/ -target/ +/artifacts/ +/corpus/ +/coverage/ +/target/ +/Cargo.lock diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 94578e62..dddad3f6 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -28,4 +28,10 @@ path = "fuzz_targets/parse_file.rs" test = false doc = false +[[bin]] +name = "parse_literal" +path = "fuzz_targets/parse_literal.rs" +test = false +doc = false + [workspace] diff --git a/fuzz/fuzz_targets/parse_literal.rs b/fuzz/fuzz_targets/parse_literal.rs new file mode 100644 index 00000000..90880ddc --- /dev/null +++ b/fuzz/fuzz_targets/parse_literal.rs @@ -0,0 +1,12 @@ +#![no_main] + +use libfuzzer_sys::fuzz_target; +use std::str; + +fuzz_target!(|data: &[u8]| { + if data.len() < 24 { + if let Ok(string) = str::from_utf8(data) { + let _ = syn::Lit::from_str_for_fuzzing(string); + } + } +}); diff --git a/json/Cargo.toml b/json/Cargo.toml index e6ff2d54..2ebd9ef1 100644 --- a/json/Cargo.toml +++ b/json/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "syn-codegen" -version = "0.4.1" # also update html_root_url +version = "0.4.2" # also update html_root_url authors = ["David Tolnay "] categories = ["development-tools::procedural-macro-helpers"] description = "Syntax tree describing Syn's syntax tree" @@ -9,21 +9,29 @@ edition = "2021" keywords = ["syn"] license = "MIT OR Apache-2.0" repository = "https://github.com/dtolnay/syn" +rust-version = "1.82" + +[features] +default = ["serde"] +serde = ["dep:serde", "dep:serde_derive", "indexmap/serde", "semver/serde"] [dependencies] -indexmap = { version = "2", features = ["serde"] } -semver = { version = "1", features = ["serde"] } -serde = "1.0.88" -serde_derive = "1.0.88" +indexmap = "2" +semver = "1" +serde = { version = "1.0.88", optional = true } +serde_derive = { version = "1.0.88", optional = true } [dev-dependencies] serde_json = "1" -[lib] -doc-scrape-examples = false - [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] -rustdoc-args = ["--generate-link-to-definition"] +rustdoc-args = [ + "--generate-link-to-definition", + "--generate-macro-expansion", + "--extern-html-root-url=core=https://doc.rust-lang.org", + "--extern-html-root-url=alloc=https://doc.rust-lang.org", + "--extern-html-root-url=std=https://doc.rust-lang.org", +] [workspace] diff --git a/json/src/lib.rs b/json/src/lib.rs index 77f47d4c..d3972d88 100644 --- a/json/src/lib.rs +++ b/json/src/lib.rs @@ -44,16 +44,19 @@ //! } //! ``` -#![doc(html_root_url = "https://docs.rs/syn-codegen/0.4.1")] +#![doc(html_root_url = "https://docs.rs/syn-codegen/0.4.2")] use indexmap::IndexMap; use semver::Version; +#[cfg(feature = "serde")] use serde::de::{Deserialize, Deserializer}; +#[cfg(feature = "serde")] use serde_derive::{Deserialize, Serialize}; use std::collections::{BTreeMap, BTreeSet}; /// Top-level content of the syntax tree description. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Definitions { /// The Syn version whose syntax tree is described by this data. pub version: Version, @@ -72,7 +75,8 @@ pub struct Definitions { } /// Syntax tree type defined by Syn. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Node { /// Name of the type. /// @@ -83,29 +87,36 @@ pub struct Node { pub features: Features, /// Content of the data structure. - #[serde( - flatten, - skip_serializing_if = "is_private", - deserialize_with = "private_if_absent" + #[cfg_attr( + feature = "serde", + serde( + flatten, + skip_serializing_if = "is_private", + deserialize_with = "private_if_absent" + ) )] pub data: Data, - #[serde(skip_serializing_if = "is_true", default = "bool_true")] + #[cfg_attr( + feature = "serde", + serde(skip_serializing_if = "is_true", default = "bool_true") + )] pub exhaustive: bool, } /// Content of a syntax tree data structure. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Data { /// This is an opaque type with no publicly accessible structure. Private, /// This type is a braced struct with named fields. - #[serde(rename = "fields")] + #[cfg_attr(feature = "serde", serde(rename = "fields"))] Struct(Fields), /// This type is an enum. - #[serde(rename = "variants")] + #[cfg_attr(feature = "serde", serde(rename = "variants"))] Enum(Variants), } @@ -123,8 +134,12 @@ pub type Fields = IndexMap; pub type Variants = IndexMap>; /// Type of a struct field or tuple variant field in the syntax tree. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr( + feature = "serde", + derive(Serialize, Deserialize), + serde(rename_all = "lowercase") +)] pub enum Type { /// Syntax tree type defined by Syn. /// @@ -141,7 +156,7 @@ pub enum Type { /// /// The type is accessible in the proc-macro2 public API as /// `proc_macro2::#name`. - #[serde(rename = "proc_macro2")] + #[cfg_attr(feature = "serde", serde(rename = "proc_macro2"))] Ext(String), /// Keyword or punctuation token type defined by Syn. @@ -178,20 +193,23 @@ pub enum Type { /// This refers to `syn::punctuated::Punctuated<#element, #punct>`. /// /// The punct string will match one of the keys in the `tokens` map. -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Punctuated { pub element: Box, pub punct: String, } /// Features behind which a syntax tree type is cfg gated. -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Default, PartialEq)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Features { /// Type is accessible if at least one of these features is enabled against /// the Syn dependency. pub any: BTreeSet, } +#[cfg(feature = "serde")] fn is_private(data: &Data) -> bool { match data { Data::Private => true, @@ -199,6 +217,7 @@ fn is_private(data: &Data) -> bool { } } +#[cfg(feature = "serde")] fn private_if_absent<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, @@ -207,10 +226,12 @@ where Ok(option.unwrap_or(Data::Private)) } +#[cfg(feature = "serde")] fn is_true(b: &bool) -> bool { *b } +#[cfg(feature = "serde")] fn bool_true() -> bool { true } diff --git a/src/attr.rs b/src/attr.rs index b6c4675b..5e2d7a71 100644 --- a/src/attr.rs +++ b/src/attr.rs @@ -1,12 +1,24 @@ -use super::*; -use proc_macro2::TokenStream; -use std::iter; -use std::slice; - +#[cfg(feature = "parsing")] +use crate::error::Error; +#[cfg(feature = "parsing")] +use crate::error::Result; +use crate::expr::Expr; +use crate::mac::MacroDelimiter; #[cfg(feature = "parsing")] use crate::meta::{self, ParseNestedMeta}; #[cfg(feature = "parsing")] -use crate::parse::{Parse, ParseStream, Parser, Result}; +use crate::parse::{Parse, ParseStream, Parser}; +use crate::path::Path; +use crate::token; +#[cfg(feature = "parsing")] +use alloc::format; +#[cfg(feature = "parsing")] +use alloc::vec::Vec; +#[cfg(feature = "printing")] +use core::iter; +#[cfg(feature = "printing")] +use core::slice; +use proc_macro2::TokenStream; ast_struct! { /// An attribute, like `#[repr(transparent)]`. @@ -77,9 +89,9 @@ ast_struct! { /// [`Attribute::parse_outer`] or [`Attribute::parse_inner`] depending on /// which you intend to parse. /// - /// [`Parse`]: parse::Parse - /// [`ParseStream::parse`]: parse::ParseBuffer::parse - /// [`ParseStream::call`]: parse::ParseBuffer::call + /// [`Parse`]: crate::parse::Parse + /// [`ParseStream::parse`]: crate::parse::ParseBuffer::parse + /// [`ParseStream::call`]: crate::parse::ParseBuffer::call /// /// ``` /// use syn::{Attribute, Ident, Result, Token}; @@ -161,7 +173,7 @@ ast_struct! { /// }; /// assert_eq!(doc, attr); /// ``` - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Attribute { pub pound_token: Token![#], pub style: AttrStyle, @@ -210,7 +222,7 @@ impl Attribute { /// # anyhow::Ok(()) /// ``` #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_args(&self) -> Result { self.parse_args_with(T::parse) } @@ -233,7 +245,7 @@ impl Attribute { /// # anyhow::Ok(()) /// ``` #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_args_with(&self, parser: F) -> Result { match &self.meta { Meta::Path(path) => Err(crate::error::new2( @@ -379,7 +391,7 @@ impl Attribute { /// # Ok(()) /// ``` #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, @@ -394,7 +406,7 @@ impl Attribute { /// See /// [*Parsing from tokens to Attribute*](#parsing-from-tokens-to-attribute). #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_outer(input: ParseStream) -> Result> { let mut attrs = Vec::new(); while input.peek(Token![#]) { @@ -410,7 +422,7 @@ impl Attribute { /// See /// [*Parsing from tokens to Attribute*](#parsing-from-tokens-to-attribute). #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_inner(input: ParseStream) -> Result> { let mut attrs = Vec::new(); parsing::parse_inner(input, &mut attrs)?; @@ -433,14 +445,14 @@ ast_enum! { /// - `#![feature(proc_macro)]` /// - `//! # Example` /// - `/*! Please file an issue */` - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum AttrStyle { Outer, Inner(Token![!]), } } -ast_enum_of_structs! { +ast_enum! { /// Content of a compile-time structured attribute. /// /// ## Path @@ -460,8 +472,8 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: Expr#syntax-tree-enums - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + /// [syntax tree enum]: crate::expr::Expr#syntax-tree-enums + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Meta { Path(Path), @@ -475,7 +487,7 @@ ast_enum_of_structs! { ast_struct! { /// A structured list within an attribute, like `derive(Copy, Clone)`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaList { pub path: Path, pub delimiter: MacroDelimiter, @@ -485,7 +497,7 @@ ast_struct! { ast_struct! { /// A name-value pair within an attribute, like `feature = "nightly"`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct MetaNameValue { pub path: Path, pub eq_token: Token![=], @@ -508,7 +520,7 @@ impl Meta { /// Error if this is a `Meta::List` or `Meta::NameValue`. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn require_path_only(&self) -> Result<&Path> { let error_span = match self { Meta::Path(path) => return Ok(path), @@ -520,7 +532,7 @@ impl Meta { /// Error if this is a `Meta::Path` or `Meta::NameValue`. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn require_list(&self) -> Result<&MetaList> { match self { Meta::List(meta) => Ok(meta), @@ -538,7 +550,7 @@ impl Meta { /// Error if this is a `Meta::Path` or `Meta::List`. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn require_name_value(&self) -> Result<&MetaNameValue> { match self { Meta::NameValue(meta) => Ok(meta), @@ -558,14 +570,14 @@ impl Meta { impl MetaList { /// See [`Attribute::parse_args`]. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_args(&self) -> Result { self.parse_args_with(T::parse) } /// See [`Attribute::parse_args_with`]. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_args_with(&self, parser: F) -> Result { let scope = self.delimiter.span().close(); crate::parse::parse_scoped(parser, scope, self.tokens.clone()) @@ -573,7 +585,7 @@ impl MetaList { /// See [`Attribute::parse_nested_meta`]. #[cfg(feature = "parsing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_nested_meta( &self, logic: impl FnMut(ParseNestedMeta) -> Result<()>, @@ -582,13 +594,16 @@ impl MetaList { } } +#[cfg(feature = "printing")] pub(crate) trait FilterAttrs<'a> { type Ret: Iterator; fn outer(self) -> Self::Ret; + #[cfg(feature = "full")] fn inner(self) -> Self::Ret; } +#[cfg(feature = "printing")] impl<'a> FilterAttrs<'a> for &'a [Attribute] { type Ret = iter::Filter, fn(&&Attribute) -> bool>; @@ -602,6 +617,7 @@ impl<'a> FilterAttrs<'a> for &'a [Attribute] { self.iter().filter(is_outer) } + #[cfg(feature = "full")] fn inner(self) -> Self::Ret { fn is_inner(attr: &&Attribute) -> bool { match attr.style { @@ -613,16 +629,41 @@ impl<'a> FilterAttrs<'a> for &'a [Attribute] { } } +impl From for Meta { + fn from(meta: Path) -> Meta { + Meta::Path(meta) + } +} + +impl From for Meta { + fn from(meta: MetaList) -> Meta { + Meta::List(meta) + } +} + +impl From for Meta { + fn from(meta: MetaNameValue) -> Meta { + Meta::NameValue(meta) + } +} + #[cfg(feature = "parsing")] pub(crate) mod parsing { - use super::*; + use crate::attr::{AttrStyle, Attribute, Meta, MetaList, MetaNameValue}; + use crate::error::Result; + use crate::expr::{Expr, ExprLit}; + use crate::lit::Lit; use crate::parse::discouraged::Speculative as _; - use crate::parse::{Parse, ParseStream, Result}; - use std::fmt::{self, Display}; + use crate::parse::{Parse, ParseStream}; + use crate::path::Path; + use crate::{mac, token}; + use alloc::vec::Vec; + use core::fmt::{self, Display}; + use proc_macro2::Ident; pub(crate) fn parse_inner(input: ParseStream, attrs: &mut Vec) -> Result<()> { while input.peek(Token![#]) && input.peek2(Token![!]) { - attrs.push(input.call(parsing::single_parse_inner)?); + attrs.push(input.call(single_parse_inner)?); } Ok(()) } @@ -647,34 +688,45 @@ pub(crate) mod parsing { }) } - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for Meta { fn parse(input: ParseStream) -> Result { - let path = input.call(Path::parse_mod_style)?; + let path = parse_outermost_meta_path(input)?; parse_meta_after_path(path, input) } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for MetaList { fn parse(input: ParseStream) -> Result { - let path = input.call(Path::parse_mod_style)?; + let path = parse_outermost_meta_path(input)?; parse_meta_list_after_path(path, input) } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for MetaNameValue { fn parse(input: ParseStream) -> Result { - let path = input.call(Path::parse_mod_style)?; + let path = parse_outermost_meta_path(input)?; parse_meta_name_value_after_path(path, input) } } + // Unlike meta::parse_meta_path which accepts arbitrary keywords in the path, + // only the `unsafe` keyword is accepted as an attribute's outermost path. + fn parse_outermost_meta_path(input: ParseStream) -> Result { + if input.peek(Token![unsafe]) { + let unsafe_token: Token![unsafe] = input.parse()?; + Ok(Path::from(Ident::new("unsafe", unsafe_token.span))) + } else { + Path::parse_mod_style(input) + } + } + pub(crate) fn parse_meta_after_path(path: Path, input: ParseStream) -> Result { if input.peek(token::Paren) || input.peek(token::Bracket) || input.peek(token::Brace) { parse_meta_list_after_path(path, input).map(Meta::List) - } else if input.peek(Token![=]) { + } else if input.peek(Token![=]) && !input.peek(Token![==]) && !input.peek(Token![=>]) { parse_meta_name_value_after_path(path, input).map(Meta::NameValue) } else { Ok(Meta::Path(path)) @@ -740,11 +792,13 @@ pub(crate) mod parsing { #[cfg(feature = "printing")] mod printing { - use super::*; + use crate::attr::{AttrStyle, Attribute, Meta, MetaList, MetaNameValue}; + use crate::path; + use crate::path::printing::PathStyle; use proc_macro2::TokenStream; use quote::ToTokens; - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for Attribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.pound_token.to_tokens(tokens); @@ -757,18 +811,29 @@ mod printing { } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] + impl ToTokens for Meta { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Meta::Path(path) => path::printing::print_path(tokens, path, PathStyle::Mod), + Meta::List(meta_list) => meta_list.to_tokens(tokens), + Meta::NameValue(meta_name_value) => meta_name_value.to_tokens(tokens), + } + } + } + + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for MetaList { fn to_tokens(&self, tokens: &mut TokenStream) { - self.path.to_tokens(tokens); + path::printing::print_path(tokens, &self.path, PathStyle::Mod); self.delimiter.surround(tokens, self.tokens.clone()); } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for MetaNameValue { fn to_tokens(&self, tokens: &mut TokenStream) { - self.path.to_tokens(tokens); + path::printing::print_path(tokens, &self.path, PathStyle::Mod); self.eq_token.to_tokens(tokens); self.value.to_tokens(tokens); } diff --git a/src/bigint.rs b/src/bigint.rs index 66aaa937..d32298b7 100644 --- a/src/bigint.rs +++ b/src/bigint.rs @@ -1,4 +1,6 @@ -use std::ops::{AddAssign, MulAssign}; +use alloc::string::String; +use alloc::vec::Vec; +use core::ops::{AddAssign, MulAssign}; // For implementing base10_digits() accessor on LitInt. pub(crate) struct BigInt { diff --git a/src/buffer.rs b/src/buffer.rs index 86dec46a..277093dc 100644 --- a/src/buffer.rs +++ b/src/buffer.rs @@ -5,11 +5,15 @@ // Syn, and caution should be used when editing it. The public-facing interface // is 100% safe but the implementation is fragile internally. +use crate::ext::TokenStreamExt as _; use crate::Lifetime; +use alloc::boxed::Box; +use alloc::vec::Vec; +use core::cmp::Ordering; +use core::marker::PhantomData; +use core::ptr; use proc_macro2::extra::DelimSpan; use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree}; -use std::cmp::Ordering; -use std::marker::PhantomData; /// Internal type which is used instead of `TokenTree` to represent a token tree /// within a `TokenBuffer`. @@ -20,8 +24,9 @@ enum Entry { Ident(Ident), Punct(Punct), Literal(Literal), - // End entries contain the offset (negative) to the start of the buffer. - End(isize), + // End entries contain the offset (negative) to the start of the buffer, and + // offset (negative) to the matching Group entry. + End(isize, isize), } /// A buffer that can be efficiently traversed multiple times, unlike @@ -42,12 +47,15 @@ impl TokenBuffer { TokenTree::Literal(literal) => entries.push(Entry::Literal(literal)), TokenTree::Group(group) => { let group_start_index = entries.len(); - entries.push(Entry::End(0)); // we replace this below + entries.push(Entry::End(0, 0)); // we replace this below Self::recursive_new(entries, group.stream()); let group_end_index = entries.len(); - entries.push(Entry::End(-(group_end_index as isize))); - let group_end_offset = group_end_index - group_start_index; - entries[group_start_index] = Entry::Group(group, group_end_offset); + let group_offset = group_end_index - group_start_index; + entries.push(Entry::End( + -(group_end_index as isize), + -(group_offset as isize), + )); + entries[group_start_index] = Entry::Group(group, group_offset); } } } @@ -56,7 +64,7 @@ impl TokenBuffer { /// Creates a `TokenBuffer` containing all the tokens from the input /// `proc_macro::TokenStream`. #[cfg(feature = "proc-macro")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "proc-macro")))] + #[cfg_attr(docsrs, doc(cfg(feature = "proc-macro")))] pub fn new(stream: proc_macro::TokenStream) -> Self { Self::new2(stream.into()) } @@ -66,7 +74,7 @@ impl TokenBuffer { pub fn new2(stream: TokenStream) -> Self { let mut entries = Vec::new(); Self::recursive_new(&mut entries, stream); - entries.push(Entry::End(-(entries.len() as isize))); + entries.push(Entry::End(-(entries.len() as isize), 0)); Self { entries: entries.into_boxed_slice(), } @@ -111,7 +119,7 @@ impl<'a> Cursor<'a> { // object in global storage. struct UnsafeSyncEntry(Entry); unsafe impl Sync for UnsafeSyncEntry {} - static EMPTY_ENTRY: UnsafeSyncEntry = UnsafeSyncEntry(Entry::End(0)); + static EMPTY_ENTRY: UnsafeSyncEntry = UnsafeSyncEntry(Entry::End(0, 0)); Cursor { ptr: &EMPTY_ENTRY.0, @@ -128,8 +136,8 @@ impl<'a> Cursor<'a> { // past it, unless `ptr == scope`, which means that we're at the edge of // our cursor's scope. We should only have `ptr != scope` at the exit // from None-delimited groups entered with `ignore_none`. - while let Entry::End(_) = unsafe { &*ptr } { - if ptr == scope { + while let Entry::End(..) = unsafe { &*ptr } { + if ptr::eq(ptr, scope) { break; } ptr = unsafe { ptr.add(1) }; @@ -154,7 +162,7 @@ impl<'a> Cursor<'a> { /// If the cursor is looking at an `Entry::Group`, the bumped cursor will /// point at the first token in the group (with the same scope end). unsafe fn bump_ignore_group(self) -> Cursor<'a> { - unsafe { Cursor::create(self.ptr.offset(1), self.scope) } + unsafe { Cursor::create(self.ptr.add(1), self.scope) } } /// While the cursor is looking at a `None`-delimited group, move it to look @@ -176,53 +184,7 @@ impl<'a> Cursor<'a> { /// scope. pub fn eof(self) -> bool { // We're at eof if we're at the end of our scope. - self.ptr == self.scope - } - - /// If the cursor is pointing at a `Group` with the given delimiter, returns - /// a cursor into that group and one pointing to the next `TokenTree`. - pub fn group(mut self, delim: Delimiter) -> Option<(Cursor<'a>, DelimSpan, Cursor<'a>)> { - // If we're not trying to enter a none-delimited group, we want to - // ignore them. We have to make sure to _not_ ignore them when we want - // to enter them, of course. For obvious reasons. - if delim != Delimiter::None { - self.ignore_none(); - } - - if let Entry::Group(group, end_offset) = self.entry() { - if group.delimiter() == delim { - let span = group.delim_span(); - let end_of_group = unsafe { self.ptr.add(*end_offset) }; - let inside_of_group = unsafe { Cursor::create(self.ptr.add(1), end_of_group) }; - let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; - return Some((inside_of_group, span, after_group)); - } - } - - None - } - - pub(crate) fn any_group(self) -> Option<(Cursor<'a>, Delimiter, DelimSpan, Cursor<'a>)> { - if let Entry::Group(group, end_offset) = self.entry() { - let delimiter = group.delimiter(); - let span = group.delim_span(); - let end_of_group = unsafe { self.ptr.add(*end_offset) }; - let inside_of_group = unsafe { Cursor::create(self.ptr.add(1), end_of_group) }; - let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; - return Some((inside_of_group, delimiter, span, after_group)); - } - - None - } - - pub(crate) fn any_group_token(self) -> Option<(Group, Cursor<'a>)> { - if let Entry::Group(group, end_offset) = self.entry() { - let end_of_group = unsafe { self.ptr.add(*end_offset) }; - let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; - return Some((group.clone(), after_group)); - } - - None + ptr::eq(self.ptr, self.scope) } /// If the cursor is pointing at a `Ident`, returns it along with a cursor @@ -275,16 +237,64 @@ impl<'a> Cursor<'a> { } } + /// If the cursor is pointing at a `Group` with the given delimiter, returns + /// a cursor into that group and one pointing to the next `TokenTree`. + pub fn group(mut self, delim: Delimiter) -> Option<(Cursor<'a>, DelimSpan, Cursor<'a>)> { + // If we're not trying to enter a none-delimited group, we want to + // ignore them. We have to make sure to _not_ ignore them when we want + // to enter them, of course. For obvious reasons. + if delim != Delimiter::None { + self.ignore_none(); + } + + if let Entry::Group(group, end_offset) = self.entry() { + if group.delimiter() == delim { + let span = group.delim_span(); + let end_of_group = unsafe { self.ptr.add(*end_offset) }; + let inside_of_group = unsafe { Cursor::create(self.ptr.add(1), end_of_group) }; + let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; + return Some((inside_of_group, span, after_group)); + } + } + + None + } + + /// If the cursor is pointing at a `Group`, returns a cursor into the group + /// and one pointing to the next `TokenTree`. + pub fn any_group(self) -> Option<(Cursor<'a>, Delimiter, DelimSpan, Cursor<'a>)> { + if let Entry::Group(group, end_offset) = self.entry() { + let delimiter = group.delimiter(); + let span = group.delim_span(); + let end_of_group = unsafe { self.ptr.add(*end_offset) }; + let inside_of_group = unsafe { Cursor::create(self.ptr.add(1), end_of_group) }; + let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; + return Some((inside_of_group, delimiter, span, after_group)); + } + + None + } + + pub(crate) fn any_group_token(self) -> Option<(Group, Cursor<'a>)> { + if let Entry::Group(group, end_offset) = self.entry() { + let end_of_group = unsafe { self.ptr.add(*end_offset) }; + let after_group = unsafe { Cursor::create(end_of_group, self.scope) }; + return Some((group.clone(), after_group)); + } + + None + } + /// Copies all remaining tokens visible from this cursor into a /// `TokenStream`. pub fn token_stream(self) -> TokenStream { - let mut tts = Vec::new(); + let mut tokens = TokenStream::new(); let mut cursor = self; while let Some((tt, rest)) = cursor.token_tree() { - tts.push(tt); + tokens.append(tt); cursor = rest; } - tts.into_iter().collect() + tokens } /// If the cursor is pointing at a `TokenTree`, returns it along with a @@ -300,7 +310,7 @@ impl<'a> Cursor<'a> { Entry::Literal(literal) => (literal.clone().into(), 1), Entry::Ident(ident) => (ident.clone().into(), 1), Entry::Punct(punct) => (punct.clone().into(), 1), - Entry::End(_) => return None, + Entry::End(..) => return None, }; let rest = unsafe { Cursor::create(self.ptr.add(len), self.scope) }; @@ -309,13 +319,20 @@ impl<'a> Cursor<'a> { /// Returns the `Span` of the current token, or `Span::call_site()` if this /// cursor points to eof. - pub fn span(self) -> Span { + pub fn span(mut self) -> Span { match self.entry() { Entry::Group(group, _) => group.span(), Entry::Literal(literal) => literal.span(), Entry::Ident(ident) => ident.span(), Entry::Punct(punct) => punct.span(), - Entry::End(_) => Span::call_site(), + Entry::End(_, offset) => { + self.ptr = unsafe { self.ptr.offset(*offset) }; + if let Entry::Group(group, _) = self.entry() { + group.span_close() + } else { + Span::call_site() + } + } } } @@ -324,35 +341,20 @@ impl<'a> Cursor<'a> { #[cfg(any(feature = "full", feature = "derive"))] pub(crate) fn prev_span(mut self) -> Span { if start_of_buffer(self) < self.ptr { - self.ptr = unsafe { self.ptr.offset(-1) }; - if let Entry::End(_) = self.entry() { - // Locate the matching Group begin token. - let mut depth = 1; - loop { - self.ptr = unsafe { self.ptr.offset(-1) }; - match self.entry() { - Entry::Group(group, _) => { - depth -= 1; - if depth == 0 { - return group.span(); - } - } - Entry::End(_) => depth += 1, - Entry::Literal(_) | Entry::Ident(_) | Entry::Punct(_) => {} - } - } - } + self.ptr = unsafe { self.ptr.sub(1) }; } self.span() } - /// Skip over the next token without cloning it. Returns `None` if this - /// cursor points to eof. + /// Skip over the next token that is not a None-delimited group, without + /// cloning it. Returns `None` if this cursor points to eof. /// /// This method treats `'lifetimes` as a single token. - pub(crate) fn skip(self) -> Option> { + pub(crate) fn skip(mut self) -> Option> { + self.ignore_none(); + let len = match self.entry() { - Entry::End(_) => return None, + Entry::End(..) => return None, // Treat lifetimes as a single tt for the purposes of 'skip'. Entry::Punct(punct) if punct.as_char() == '\'' && punct.spacing() == Spacing::Joint => { @@ -368,6 +370,16 @@ impl<'a> Cursor<'a> { Some(unsafe { Cursor::create(self.ptr.add(len), self.scope) }) } + + pub(crate) fn scope_delimiter(self) -> Delimiter { + match unsafe { &*self.scope } { + Entry::End(_, offset) => match unsafe { &*self.scope.offset(*offset) } { + Entry::Group(group, _) => group.delimiter(), + _ => Delimiter::None, + }, + _ => unreachable!(), + } + } } impl<'a> Copy for Cursor<'a> {} @@ -382,7 +394,7 @@ impl<'a> Eq for Cursor<'a> {} impl<'a> PartialEq for Cursor<'a> { fn eq(&self, other: &Self) -> bool { - self.ptr == other.ptr + ptr::eq(self.ptr, other.ptr) } } @@ -397,17 +409,17 @@ impl<'a> PartialOrd for Cursor<'a> { } pub(crate) fn same_scope(a: Cursor, b: Cursor) -> bool { - a.scope == b.scope + ptr::eq(a.scope, b.scope) } pub(crate) fn same_buffer(a: Cursor, b: Cursor) -> bool { - start_of_buffer(a) == start_of_buffer(b) + ptr::eq(start_of_buffer(a), start_of_buffer(b)) } fn start_of_buffer(cursor: Cursor) -> *const Entry { unsafe { match &*cursor.scope { - Entry::End(offset) => cursor.scope.offset(*offset), + Entry::End(offset, _) => cursor.scope.offset(*offset), _ => unreachable!(), } } @@ -423,10 +435,3 @@ pub(crate) fn open_span_of_group(cursor: Cursor) -> Span { _ => cursor.span(), } } - -pub(crate) fn close_span_of_group(cursor: Cursor) -> Span { - match cursor.entry() { - Entry::Group(group, _) => group.span_close(), - _ => cursor.span(), - } -} diff --git a/src/classify.rs b/src/classify.rs new file mode 100644 index 00000000..6d5edbd5 --- /dev/null +++ b/src/classify.rs @@ -0,0 +1,311 @@ +#[cfg(feature = "full")] +use crate::expr::Expr; +#[cfg(any(feature = "printing", feature = "full"))] +use crate::generics::TypeParamBound; +#[cfg(any(feature = "printing", feature = "full"))] +use crate::path::{Path, PathArguments}; +#[cfg(any(feature = "printing", feature = "full"))] +use crate::punctuated::Punctuated; +#[cfg(any(feature = "printing", feature = "full"))] +use crate::ty::{ReturnType, Type}; +#[cfg(any(feature = "printing", feature = "full"))] +use core::ops::ControlFlow; +#[cfg(feature = "full")] +use proc_macro2::{Delimiter, TokenStream, TokenTree}; + +#[cfg(feature = "full")] +pub(crate) fn requires_semi_to_be_stmt(expr: &Expr) -> bool { + match expr { + Expr::Macro(expr) => !expr.mac.delimiter.is_brace(), + _ => requires_comma_to_be_match_arm(expr), + } +} + +#[cfg(feature = "full")] +pub(crate) fn requires_comma_to_be_match_arm(expr: &Expr) -> bool { + match expr { + Expr::If(_) + | Expr::Match(_) + | Expr::Block(_) | Expr::Unsafe(_) // both under ExprKind::Block in rustc + | Expr::While(_) + | Expr::Loop(_) + | Expr::ForLoop(_) + | Expr::TryBlock(_) + | Expr::Const(_) => false, + + Expr::Array(_) + | Expr::Assign(_) + | Expr::Async(_) + | Expr::Await(_) + | Expr::Binary(_) + | Expr::Break(_) + | Expr::Call(_) + | Expr::Cast(_) + | Expr::Closure(_) + | Expr::Continue(_) + | Expr::Field(_) + | Expr::Group(_) + | Expr::Index(_) + | Expr::Infer(_) + | Expr::Let(_) + | Expr::Lit(_) + | Expr::Macro(_) + | Expr::MethodCall(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::Range(_) + | Expr::RawAddr(_) + | Expr::Reference(_) + | Expr::Repeat(_) + | Expr::Return(_) + | Expr::Struct(_) + | Expr::Try(_) + | Expr::Tuple(_) + | Expr::Unary(_) + | Expr::Yield(_) + | Expr::Verbatim(_) => true, + } +} + +#[cfg(feature = "printing")] +pub(crate) fn trailing_unparameterized_path(mut ty: &Type) -> bool { + loop { + match ty { + Type::BareFn(t) => match &t.output { + ReturnType::Default => return false, + ReturnType::Type(_, ret) => ty = ret, + }, + Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + Type::Path(t) => match last_type_in_path(&t.path) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + Type::Ptr(t) => ty = &t.elem, + Type::Reference(t) => ty = &t.elem, + Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_path) => return trailing_path, + ControlFlow::Continue(t) => ty = t, + }, + + Type::Array(_) + | Type::Group(_) + | Type::Infer(_) + | Type::Macro(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Slice(_) + | Type::Tuple(_) + | Type::Verbatim(_) => return false, + } + } + + fn last_type_in_path(path: &Path) -> ControlFlow { + match &path.segments.last().unwrap().arguments { + PathArguments::None => ControlFlow::Break(true), + PathArguments::AngleBracketed(_) => ControlFlow::Break(false), + PathArguments::Parenthesized(arg) => match &arg.output { + ReturnType::Default => ControlFlow::Break(false), + ReturnType::Type(_, ret) => ControlFlow::Continue(ret), + }, + } + } + + fn last_type_in_bounds( + bounds: &Punctuated, + ) -> ControlFlow { + match bounds.last().unwrap() { + TypeParamBound::Trait(t) => last_type_in_path(&t.path), + TypeParamBound::Lifetime(_) + | TypeParamBound::PreciseCapture(_) + | TypeParamBound::Verbatim(_) => ControlFlow::Break(false), + } + } +} + +/// Whether the expression's first token is the label of a loop/block. +#[cfg(all(feature = "printing", feature = "full"))] +pub(crate) fn expr_leading_label(mut expr: &Expr) -> bool { + loop { + match expr { + Expr::Block(e) => return e.label.is_some(), + Expr::ForLoop(e) => return e.label.is_some(), + Expr::Loop(e) => return e.label.is_some(), + Expr::While(e) => return e.label.is_some(), + + Expr::Assign(e) => expr = &e.left, + Expr::Await(e) => expr = &e.base, + Expr::Binary(e) => expr = &e.left, + Expr::Call(e) => expr = &e.func, + Expr::Cast(e) => expr = &e.expr, + Expr::Field(e) => expr = &e.base, + Expr::Index(e) => expr = &e.expr, + Expr::MethodCall(e) => expr = &e.receiver, + Expr::Range(e) => match &e.start { + Some(start) => expr = start, + None => return false, + }, + Expr::Try(e) => expr = &e.expr, + + Expr::Array(_) + | Expr::Async(_) + | Expr::Break(_) + | Expr::Closure(_) + | Expr::Const(_) + | Expr::Continue(_) + | Expr::Group(_) + | Expr::If(_) + | Expr::Infer(_) + | Expr::Let(_) + | Expr::Lit(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::RawAddr(_) + | Expr::Reference(_) + | Expr::Repeat(_) + | Expr::Return(_) + | Expr::Struct(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unary(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::Yield(_) => return false, + } + } +} + +/// Whether the expression's last token is `}`. +#[cfg(feature = "full")] +pub(crate) fn expr_trailing_brace(mut expr: &Expr) -> bool { + loop { + match expr { + Expr::Async(_) + | Expr::Block(_) + | Expr::Const(_) + | Expr::ForLoop(_) + | Expr::If(_) + | Expr::Loop(_) + | Expr::Match(_) + | Expr::Struct(_) + | Expr::TryBlock(_) + | Expr::Unsafe(_) + | Expr::While(_) => return true, + + Expr::Assign(e) => expr = &e.right, + Expr::Binary(e) => expr = &e.right, + Expr::Break(e) => match &e.expr { + Some(e) => expr = e, + None => return false, + }, + Expr::Cast(e) => return type_trailing_brace(&e.ty), + Expr::Closure(e) => expr = &e.body, + Expr::Let(e) => expr = &e.expr, + Expr::Macro(e) => return e.mac.delimiter.is_brace(), + Expr::Range(e) => match &e.end { + Some(end) => expr = end, + None => return false, + }, + Expr::RawAddr(e) => expr = &e.expr, + Expr::Reference(e) => expr = &e.expr, + Expr::Return(e) => match &e.expr { + Some(e) => expr = e, + None => return false, + }, + Expr::Unary(e) => expr = &e.expr, + Expr::Verbatim(e) => return tokens_trailing_brace(e), + Expr::Yield(e) => match &e.expr { + Some(e) => expr = e, + None => return false, + }, + + Expr::Array(_) + | Expr::Await(_) + | Expr::Call(_) + | Expr::Continue(_) + | Expr::Field(_) + | Expr::Group(_) + | Expr::Index(_) + | Expr::Infer(_) + | Expr::Lit(_) + | Expr::MethodCall(_) + | Expr::Paren(_) + | Expr::Path(_) + | Expr::Repeat(_) + | Expr::Try(_) + | Expr::Tuple(_) => return false, + } + } + + fn type_trailing_brace(mut ty: &Type) -> bool { + loop { + match ty { + Type::BareFn(t) => match &t.output { + ReturnType::Default => return false, + ReturnType::Type(_, ret) => ty = ret, + }, + Type::ImplTrait(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_brace) => return trailing_brace, + ControlFlow::Continue(t) => ty = t, + }, + Type::Macro(t) => return t.mac.delimiter.is_brace(), + Type::Path(t) => match last_type_in_path(&t.path) { + Some(t) => ty = t, + None => return false, + }, + Type::Ptr(t) => ty = &t.elem, + Type::Reference(t) => ty = &t.elem, + Type::TraitObject(t) => match last_type_in_bounds(&t.bounds) { + ControlFlow::Break(trailing_brace) => return trailing_brace, + ControlFlow::Continue(t) => ty = t, + }, + Type::Verbatim(t) => return tokens_trailing_brace(t), + + Type::Array(_) + | Type::Group(_) + | Type::Infer(_) + | Type::Never(_) + | Type::Paren(_) + | Type::Slice(_) + | Type::Tuple(_) => return false, + } + } + } + + fn last_type_in_path(path: &Path) -> Option<&Type> { + match &path.segments.last().unwrap().arguments { + PathArguments::None | PathArguments::AngleBracketed(_) => None, + PathArguments::Parenthesized(arg) => match &arg.output { + ReturnType::Default => None, + ReturnType::Type(_, ret) => Some(ret), + }, + } + } + + fn last_type_in_bounds( + bounds: &Punctuated, + ) -> ControlFlow { + match bounds.last().unwrap() { + TypeParamBound::Trait(t) => match last_type_in_path(&t.path) { + Some(t) => ControlFlow::Continue(t), + None => ControlFlow::Break(false), + }, + TypeParamBound::Lifetime(_) | TypeParamBound::PreciseCapture(_) => { + ControlFlow::Break(false) + } + TypeParamBound::Verbatim(t) => ControlFlow::Break(tokens_trailing_brace(t)), + } + } + + fn tokens_trailing_brace(tokens: &TokenStream) -> bool { + if let Some(TokenTree::Group(last)) = tokens.clone().into_iter().last() { + last.delimiter() == Delimiter::Brace + } else { + false + } + } +} diff --git a/src/custom_keyword.rs b/src/custom_keyword.rs index 6ce23db4..cc4f632c 100644 --- a/src/custom_keyword.rs +++ b/src/custom_keyword.rs @@ -91,6 +91,7 @@ macro_rules! custom_keyword { ($ident:ident) => { #[allow(non_camel_case_types)] pub struct $ident { + #[allow(dead_code)] pub span: $crate::__private::Span, } diff --git a/src/custom_punctuation.rs b/src/custom_punctuation.rs index 1b2c768f..58955c7f 100644 --- a/src/custom_punctuation.rs +++ b/src/custom_punctuation.rs @@ -30,6 +30,7 @@ /// # Example /// /// ``` +/// use core::iter; /// use proc_macro2::{TokenStream, TokenTree}; /// use syn::parse::{Parse, ParseStream, Peek, Result}; /// use syn::punctuated::Punctuated; @@ -64,7 +65,7 @@ /// let mut tokens = TokenStream::new(); /// while !input.is_empty() && !input.peek(end) { /// let next: TokenTree = input.parse()?; -/// tokens.extend(Some(next)); +/// tokens.extend(iter::once(next)); /// } /// Ok(tokens) /// } @@ -78,6 +79,7 @@ macro_rules! custom_punctuation { ($ident:ident, $($tt:tt)+) => { pub struct $ident { + #[allow(dead_code)] pub spans: $crate::custom_punctuation_repr!($($tt)+), } @@ -236,50 +238,51 @@ macro_rules! custom_punctuation_repr { #[macro_export] #[rustfmt::skip] macro_rules! custom_punctuation_len { - ($mode:ident, +) => { 1 }; - ($mode:ident, +=) => { 2 }; ($mode:ident, &) => { 1 }; ($mode:ident, &&) => { 2 }; ($mode:ident, &=) => { 2 }; ($mode:ident, @) => { 1 }; - ($mode:ident, !) => { 1 }; ($mode:ident, ^) => { 1 }; ($mode:ident, ^=) => { 2 }; ($mode:ident, :) => { 1 }; - ($mode:ident, ::) => { 2 }; ($mode:ident, ,) => { 1 }; - ($mode:ident, /) => { 1 }; - ($mode:ident, /=) => { 2 }; + ($mode:ident, $) => { 1 }; ($mode:ident, .) => { 1 }; ($mode:ident, ..) => { 2 }; ($mode:ident, ...) => { 3 }; ($mode:ident, ..=) => { 3 }; ($mode:ident, =) => { 1 }; ($mode:ident, ==) => { 2 }; + ($mode:ident, =>) => { 2 }; ($mode:ident, >=) => { 2 }; ($mode:ident, >) => { 1 }; + ($mode:ident, <-) => { 2 }; ($mode:ident, <=) => { 2 }; ($mode:ident, <) => { 1 }; - ($mode:ident, *=) => { 2 }; + ($mode:ident, -) => { 1 }; + ($mode:ident, -=) => { 2 }; ($mode:ident, !=) => { 2 }; + ($mode:ident, !) => { 1 }; ($mode:ident, |) => { 1 }; ($mode:ident, |=) => { 2 }; ($mode:ident, ||) => { 2 }; + ($mode:ident, ::) => { 2 }; + ($mode:ident, %) => { 1 }; + ($mode:ident, %=) => { 2 }; + ($mode:ident, +) => { 1 }; + ($mode:ident, +=) => { 2 }; ($mode:ident, #) => { 1 }; ($mode:ident, ?) => { 1 }; ($mode:ident, ->) => { 2 }; - ($mode:ident, <-) => { 2 }; - ($mode:ident, %) => { 1 }; - ($mode:ident, %=) => { 2 }; - ($mode:ident, =>) => { 2 }; ($mode:ident, ;) => { 1 }; ($mode:ident, <<) => { 2 }; ($mode:ident, <<=) => { 3 }; ($mode:ident, >>) => { 2 }; ($mode:ident, >>=) => { 3 }; + ($mode:ident, /) => { 1 }; + ($mode:ident, /=) => { 2 }; ($mode:ident, *) => { 1 }; - ($mode:ident, -) => { 1 }; - ($mode:ident, -=) => { 2 }; + ($mode:ident, *=) => { 2 }; ($mode:ident, ~) => { 1 }; (lenient, $tt:tt) => { 0 }; (strict, $tt:tt) => {{ $crate::custom_punctuation_unexpected!($tt); 0 }}; diff --git a/src/data.rs b/src/data.rs index 134b76bb..cfe9de12 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,9 +1,15 @@ -use super::*; -use crate::punctuated::Punctuated; +use crate::attr::Attribute; +use crate::expr::{Expr, Index, Member}; +use crate::ident::Ident; +use crate::punctuated::{self, Punctuated}; +use crate::restriction::{FieldMutability, Visibility}; +use crate::token; +use crate::ty::Type; +use alloc::vec::Vec; ast_struct! { /// An enum variant. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Variant { pub attrs: Vec, @@ -25,8 +31,8 @@ ast_enum_of_structs! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: Expr#syntax-tree-enums - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + /// [syntax tree enum]: crate::expr::Expr#syntax-tree-enums + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub enum Fields { /// Named fields of a struct or struct variant such as `Point { x: f64, /// y: f64 }`. @@ -43,7 +49,7 @@ ast_enum_of_structs! { ast_struct! { /// Named fields of a struct or struct variant such as `Point { x: f64, /// y: f64 }`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct FieldsNamed { pub brace_token: token::Brace, pub named: Punctuated, @@ -52,7 +58,7 @@ ast_struct! { ast_struct! { /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct FieldsUnnamed { pub paren_token: token::Paren, pub unnamed: Punctuated, @@ -99,6 +105,47 @@ impl Fields { Fields::Unnamed(f) => f.unnamed.is_empty(), } } + + return_impl_trait! { + /// Get an iterator over the fields of a struct or variant as [`Member`]s. + /// This iterator can be used to iterate over a named or unnamed struct or + /// variant's fields uniformly. + /// + /// # Example + /// + /// The following is a simplistic [`Clone`] derive for structs. (A more + /// complete implementation would additionally want to infer trait bounds on + /// the generic type parameters.) + /// + /// ``` + /// # use quote::quote; + /// # + /// fn derive_clone(input: &syn::ItemStruct) -> proc_macro2::TokenStream { + /// let ident = &input.ident; + /// let members = input.fields.members(); + /// let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + /// quote! { + /// impl #impl_generics Clone for #ident #ty_generics #where_clause { + /// fn clone(&self) -> Self { + /// Self { + /// #(#members: self.#members.clone()),* + /// } + /// } + /// } + /// } + /// } + /// ``` + /// + /// For structs with named fields, it produces an expression like `Self { a: + /// self.a.clone() }`. For structs with unnamed fields, `Self { 0: + /// self.0.clone() }`. And for unit structs, `Self {}`. + pub fn members(&self) -> impl Iterator + Clone + '_ [Members] { + Members { + fields: self.iter(), + index: 0, + } + } + } } impl IntoIterator for Fields { @@ -134,7 +181,7 @@ impl<'a> IntoIterator for &'a mut Fields { ast_struct! { /// A field of a struct or enum variant. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct Field { pub attrs: Vec, @@ -153,15 +200,62 @@ ast_struct! { } } +pub struct Members<'a> { + fields: punctuated::Iter<'a, Field>, + index: u32, +} + +impl<'a> Iterator for Members<'a> { + type Item = Member; + + fn next(&mut self) -> Option { + let field = self.fields.next()?; + let member = match &field.ident { + Some(ident) => Member::Named(ident.clone()), + None => { + #[cfg(all(feature = "parsing", feature = "printing"))] + let span = crate::spanned::Spanned::span(&field.ty); + #[cfg(not(all(feature = "parsing", feature = "printing")))] + let span = proc_macro2::Span::call_site(); + Member::Unnamed(Index { + index: self.index, + span, + }) + } + }; + self.index += 1; + Some(member) + } +} + +impl<'a> Clone for Members<'a> { + fn clone(&self) -> Self { + Members { + fields: self.fields.clone(), + index: self.index, + } + } +} + #[cfg(feature = "parsing")] pub(crate) mod parsing { - use super::*; + use crate::attr::Attribute; + use crate::data::{Field, Fields, FieldsNamed, FieldsUnnamed, Variant}; + use crate::error::Result; + use crate::expr::Expr; use crate::ext::IdentExt as _; + use crate::ident::Ident; #[cfg(not(feature = "full"))] use crate::parse::discouraged::Speculative as _; - use crate::parse::{Parse, ParseStream, Result}; + use crate::parse::{Parse, ParseStream}; + use crate::restriction::{FieldMutability, Visibility}; + #[cfg(not(feature = "full"))] + use crate::scan_expr::scan_expr; + use crate::token; + use crate::ty::Type; + use crate::verbatim; - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for Variant { fn parse(input: ParseStream) -> Result { let attrs = input.call(Attribute::parse_outer)?; @@ -185,7 +279,7 @@ pub(crate) mod parsing { let mut discriminant: Result = ahead.parse(); if discriminant.is_ok() { input.advance_to(&ahead); - } else if scan_lenient_discriminant(input).is_ok() { + } else if scan_expr(input).is_ok() { discriminant = Ok(Expr::Verbatim(verbatim::between(&begin, input))); } discriminant? @@ -203,80 +297,7 @@ pub(crate) mod parsing { } } - #[cfg(not(feature = "full"))] - pub(crate) fn scan_lenient_discriminant(input: ParseStream) -> Result<()> { - use proc_macro2::Delimiter::{self, Brace, Bracket, Parenthesis}; - - let consume = |delimiter: Delimiter| { - Result::unwrap(input.step(|cursor| match cursor.group(delimiter) { - Some((_inside, _span, rest)) => Ok((true, rest)), - None => Ok((false, *cursor)), - })) - }; - - macro_rules! consume { - [$token:tt] => { - input.parse::>().unwrap().is_some() - }; - } - - let mut initial = true; - let mut depth = 0usize; - loop { - if initial { - if consume![&] { - input.parse::>()?; - } else if consume![if] || consume![match] || consume![while] { - depth += 1; - } else if input.parse::>()?.is_some() - || (consume(Brace) || consume(Bracket) || consume(Parenthesis)) - || (consume![async] || consume![const] || consume![loop] || consume![unsafe]) - && (consume(Brace) || break) - { - initial = false; - } else if consume![let] { - while !consume![=] { - if !((consume![|] || consume![ref] || consume![mut] || consume![@]) - || (consume![!] || input.parse::>()?.is_some()) - || (consume![..=] || consume![..] || consume![&] || consume![_]) - || (consume(Brace) || consume(Bracket) || consume(Parenthesis))) - { - path::parsing::qpath(input, true)?; - } - } - } else if input.parse::>()?.is_some() && !consume![:] { - break; - } else if input.parse::().is_err() { - path::parsing::qpath(input, true)?; - initial = consume![!] || depth == 0 && input.peek(token::Brace); - } - } else if input.is_empty() || input.peek(Token![,]) { - return Ok(()); - } else if depth > 0 && consume(Brace) { - if consume![else] && !consume(Brace) { - initial = consume![if] || break; - } else { - depth -= 1; - } - } else if input.parse::().is_ok() || (consume![..] | consume![=]) { - initial = true; - } else if consume![.] { - if input.parse::>()?.is_none() - && (input.parse::()?.is_named() && consume![::]) - { - AngleBracketedGenericArguments::do_parse(None, input)?; - } - } else if consume![as] { - input.parse::()?; - } else if !(consume(Brace) || consume(Bracket) || consume(Parenthesis)) { - break; - } - } - - Err(input.error("unsupported expression")) - } - - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for FieldsNamed { fn parse(input: ParseStream) -> Result { let content; @@ -287,7 +308,7 @@ pub(crate) mod parsing { } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for FieldsUnnamed { fn parse(input: ParseStream) -> Result { let content; @@ -300,7 +321,7 @@ pub(crate) mod parsing { impl Field { /// Parses a named (braced struct) field. - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_named(input: ParseStream) -> Result { let attrs = input.call(Attribute::parse_outer)?; let vis: Visibility = input.parse()?; @@ -337,7 +358,7 @@ pub(crate) mod parsing { } /// Parses an unnamed (tuple struct) field. - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] pub fn parse_unnamed(input: ParseStream) -> Result { Ok(Field { attrs: input.call(Attribute::parse_outer)?, @@ -353,12 +374,12 @@ pub(crate) mod parsing { #[cfg(feature = "printing")] mod printing { - use super::*; + use crate::data::{Field, FieldsNamed, FieldsUnnamed, Variant}; use crate::print::TokensOrDefault; use proc_macro2::TokenStream; - use quote::{ToTokens, TokenStreamExt}; + use quote::{ToTokens, TokenStreamExt as _}; - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for Variant { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append_all(&self.attrs); @@ -371,7 +392,7 @@ mod printing { } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for FieldsNamed { fn to_tokens(&self, tokens: &mut TokenStream) { self.brace_token.surround(tokens, |tokens| { @@ -380,7 +401,7 @@ mod printing { } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for FieldsUnnamed { fn to_tokens(&self, tokens: &mut TokenStream) { self.paren_token.surround(tokens, |tokens| { @@ -389,7 +410,7 @@ mod printing { } } - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for Field { fn to_tokens(&self, tokens: &mut TokenStream) { tokens.append_all(&self.attrs); diff --git a/src/derive.rs b/src/derive.rs index 25fa4c91..573b88fb 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -1,9 +1,15 @@ -use super::*; +use crate::attr::Attribute; +use crate::data::{Fields, FieldsNamed, Variant}; +use crate::generics::Generics; +use crate::ident::Ident; use crate::punctuated::Punctuated; +use crate::restriction::Visibility; +use crate::token; +use alloc::vec::Vec; ast_struct! { /// Data structure sent to a `proc_macro_derive` macro. - #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] + #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub struct DeriveInput { pub attrs: Vec, pub vis: Visibility, @@ -20,8 +26,8 @@ ast_enum! { /// /// This type is a [syntax tree enum]. /// - /// [syntax tree enum]: Expr#syntax-tree-enums - #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] + /// [syntax tree enum]: crate::expr::Expr#syntax-tree-enums + #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub enum Data { Struct(DataStruct), Enum(DataEnum), @@ -31,7 +37,7 @@ ast_enum! { ast_struct! { /// A struct input to a `proc_macro_derive` macro. - #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] + #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub struct DataStruct { pub struct_token: Token![struct], pub fields: Fields, @@ -41,7 +47,7 @@ ast_struct! { ast_struct! { /// An enum input to a `proc_macro_derive` macro. - #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] + #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub struct DataEnum { pub enum_token: Token![enum], pub brace_token: token::Brace, @@ -51,7 +57,7 @@ ast_struct! { ast_struct! { /// An untagged union input to a `proc_macro_derive` macro. - #[cfg_attr(doc_cfg, doc(cfg(feature = "derive")))] + #[cfg_attr(docsrs, doc(cfg(feature = "derive")))] pub struct DataUnion { pub union_token: Token![union], pub fields: FieldsNamed, @@ -60,10 +66,18 @@ ast_struct! { #[cfg(feature = "parsing")] pub(crate) mod parsing { - use super::*; - use crate::parse::{Parse, ParseStream, Result}; + use crate::attr::Attribute; + use crate::data::{Fields, FieldsNamed, Variant}; + use crate::derive::{Data, DataEnum, DataStruct, DataUnion, DeriveInput}; + use crate::error::Result; + use crate::generics::{Generics, WhereClause}; + use crate::ident::Ident; + use crate::parse::{Parse, ParseStream}; + use crate::punctuated::Punctuated; + use crate::restriction::Visibility; + use crate::token; - #[cfg_attr(doc_cfg, doc(cfg(feature = "parsing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "parsing")))] impl Parse for DeriveInput { fn parse(input: ParseStream) -> Result { let attrs = input.call(Attribute::parse_outer)?; @@ -193,13 +207,14 @@ pub(crate) mod parsing { #[cfg(feature = "printing")] mod printing { - use super::*; use crate::attr::FilterAttrs; + use crate::data::Fields; + use crate::derive::{Data, DeriveInput}; use crate::print::TokensOrDefault; use proc_macro2::TokenStream; use quote::ToTokens; - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] impl ToTokens for DeriveInput { fn to_tokens(&self, tokens: &mut TokenStream) { for attr in self.attrs.outer() { diff --git a/src/discouraged.rs b/src/discouraged.rs index fb98d633..9e26bd9a 100644 --- a/src/discouraged.rs +++ b/src/discouraged.rs @@ -1,7 +1,13 @@ //! Extensions to the parsing API with niche applicability. -use super::*; +use crate::buffer::Cursor; +use crate::error::Result; +use crate::parse::{inner_unexpected, ParseBuffer, Unexpected}; +use alloc::rc::Rc; +use core::cell::Cell; +use core::mem; use proc_macro2::extra::DelimSpan; +use proc_macro2::Delimiter; /// Extensions to the `ParseStream` API to support speculative parsing. pub trait Speculative { @@ -161,7 +167,7 @@ pub trait Speculative { impl<'a> Speculative for ParseBuffer<'a> { fn advance_to(&self, fork: &Self) { if !crate::buffer::same_scope(self.cursor(), fork.cursor()) { - panic!("Fork was not derived from the advancing parse stream"); + panic!("fork was not derived from the advancing parse stream"); } let (self_unexp, self_sp) = inner_unexpected(self); @@ -169,17 +175,17 @@ impl<'a> Speculative for ParseBuffer<'a> { if !Rc::ptr_eq(&self_unexp, &fork_unexp) { match (fork_sp, self_sp) { // Unexpected set on the fork, but not on `self`, copy it over. - (Some(span), None) => { - self_unexp.set(Unexpected::Some(span)); + (Some((span, delimiter)), None) => { + self_unexp.set(Unexpected::Some(span, delimiter)); } // Unexpected unset. Use chain to propagate errors from fork. (None, None) => { fork_unexp.set(Unexpected::Chain(self_unexp)); // Ensure toplevel 'unexpected' tokens from the fork don't - // bubble up the chain by replacing the root `unexpected` + // propagate up the chain by replacing the root `unexpected` // pointer, only 'unexpected' tokens from existing group - // parsers should bubble. + // parsers should propagate. fork.unexpected .set(Some(Rc::new(Cell::new(Unexpected::None)))); } @@ -206,7 +212,7 @@ impl<'a> AnyDelimiter for ParseBuffer<'a> { fn parse_any_delimiter(&self) -> Result<(Delimiter, DelimSpan, ParseBuffer)> { self.step(|cursor| { if let Some((content, delimiter, span, rest)) = cursor.any_group() { - let scope = crate::buffer::close_span_of_group(*cursor); + let scope = span.close(); let nested = crate::parse::advance_step_cursor(cursor, content); let unexpected = crate::parse::get_unexpected(self); let content = crate::parse::new_parse_buffer(scope, nested, unexpected); diff --git a/src/drops.rs b/src/drops.rs index 89b42d82..20ab75e4 100644 --- a/src/drops.rs +++ b/src/drops.rs @@ -1,8 +1,8 @@ -use std::iter; -use std::mem::ManuallyDrop; -use std::ops::{Deref, DerefMut}; -use std::option; -use std::slice; +use core::iter; +use core::mem::ManuallyDrop; +use core::ops::{Deref, DerefMut}; +use core::option; +use core::slice; #[repr(transparent)] pub(crate) struct NoDrop(ManuallyDrop); @@ -32,14 +32,14 @@ impl DerefMut for NoDrop { pub(crate) trait TrivialDrop {} impl TrivialDrop for iter::Empty {} -impl<'a, T> TrivialDrop for slice::Iter<'a, T> {} -impl<'a, T> TrivialDrop for slice::IterMut<'a, T> {} -impl<'a, T> TrivialDrop for option::IntoIter<&'a T> {} -impl<'a, T> TrivialDrop for option::IntoIter<&'a mut T> {} +impl TrivialDrop for slice::Iter<'_, T> {} +impl TrivialDrop for slice::IterMut<'_, T> {} +impl TrivialDrop for option::IntoIter<&T> {} +impl TrivialDrop for option::IntoIter<&mut T> {} #[test] fn test_needs_drop() { - use std::mem::needs_drop; + use core::mem::needs_drop; struct NeedsDrop; diff --git a/src/error.rs b/src/error.rs index 71247cde..2b753ea6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,17 +1,22 @@ #[cfg(feature = "parsing")] use crate::buffer::Cursor; +use crate::ext::{PunctExt as _, TokenStreamExt as _}; use crate::thread::ThreadBound; +#[cfg(feature = "parsing")] +use alloc::format; +use alloc::string::{String, ToString}; +use alloc::vec; +use alloc::vec::Vec; +use core::fmt::{self, Debug, Display}; +use core::slice; use proc_macro2::{ Delimiter, Group, Ident, LexError, Literal, Punct, Spacing, Span, TokenStream, TokenTree, }; #[cfg(feature = "printing")] use quote::ToTokens; -use std::fmt::{self, Debug, Display}; -use std::slice; -use std::vec; /// The result of a Syn parser. -pub type Result = std::result::Result; +pub type Result = core::result::Result; /// Error returned when a Syn parser cannot parse the input tokens. /// @@ -22,7 +27,7 @@ pub type Result = std::result::Result; /// [`compile_error!`] in the generated code. This produces a better diagnostic /// message than simply panicking the macro. /// -/// [`compile_error!`]: std::compile_error! +/// [`compile_error!`]: core::compile_error! /// /// When parsing macro input, the [`parse_macro_input!`] macro handles the /// conversion to `compile_error!` automatically. @@ -111,7 +116,7 @@ struct ErrorMessage { message: String, } -// Cannot use std::ops::Range because that does not implement Copy, +// Cannot use core::ops::Range because that does not implement Copy, // whereas ThreadBound requires a Copy impl as a way to ensure no Drop impls // are involved. struct SpanRange { @@ -185,7 +190,7 @@ impl Error { /// When in doubt it's recommended to stick to `Error::new` (or /// `ParseStream::error`)! #[cfg(feature = "printing")] - #[cfg_attr(doc_cfg, doc(cfg(feature = "printing")))] + #[cfg_attr(docsrs, doc(cfg(feature = "printing")))] pub fn new_spanned(tokens: T, message: U) -> Self { return new_spanned(tokens.into_token_stream(), message.to_string()); @@ -220,18 +225,19 @@ impl Error { /// The [`parse_macro_input!`] macro provides a convenient way to invoke /// this method correctly in a procedural macro. /// - /// [`compile_error!`]: std::compile_error! + /// [`compile_error!`]: core::compile_error! /// [`parse_macro_input!`]: crate::parse_macro_input! pub fn to_compile_error(&self) -> TokenStream { - self.messages - .iter() - .map(ErrorMessage::to_compile_error) - .collect() + let mut tokens = TokenStream::new(); + for msg in &self.messages { + ErrorMessage::to_compile_error(msg, &mut tokens); + } + tokens } /// Render the error as an invocation of [`compile_error!`]. /// - /// [`compile_error!`]: std::compile_error! + /// [`compile_error!`]: core::compile_error! /// /// # Example /// @@ -273,53 +279,52 @@ impl Error { } impl ErrorMessage { - fn to_compile_error(&self) -> TokenStream { + fn to_compile_error(&self, tokens: &mut TokenStream) { let (start, end) = match self.span.get() { Some(range) => (range.start, range.end), None => (Span::call_site(), Span::call_site()), }; // ::core::compile_error!($message) - TokenStream::from_iter(vec![ - TokenTree::Punct({ - let mut punct = Punct::new(':', Spacing::Joint); - punct.set_span(start); - punct - }), - TokenTree::Punct({ - let mut punct = Punct::new(':', Spacing::Alone); - punct.set_span(start); - punct - }), - TokenTree::Ident(Ident::new("core", start)), - TokenTree::Punct({ - let mut punct = Punct::new(':', Spacing::Joint); - punct.set_span(start); - punct - }), - TokenTree::Punct({ - let mut punct = Punct::new(':', Spacing::Alone); - punct.set_span(start); - punct - }), - TokenTree::Ident(Ident::new("compile_error", start)), - TokenTree::Punct({ - let mut punct = Punct::new('!', Spacing::Alone); - punct.set_span(start); - punct - }), - TokenTree::Group({ - let mut group = Group::new(Delimiter::Brace, { - TokenStream::from_iter(vec![TokenTree::Literal({ - let mut string = Literal::string(&self.message); - string.set_span(end); - string - })]) - }); - group.set_span(end); - group - }), - ]) + tokens.append(TokenTree::Punct(Punct::new_spanned( + ':', + Spacing::Joint, + start, + ))); + tokens.append(TokenTree::Punct(Punct::new_spanned( + ':', + Spacing::Alone, + start, + ))); + tokens.append(TokenTree::Ident(Ident::new("core", start))); + tokens.append(TokenTree::Punct(Punct::new_spanned( + ':', + Spacing::Joint, + start, + ))); + tokens.append(TokenTree::Punct(Punct::new_spanned( + ':', + Spacing::Alone, + start, + ))); + tokens.append(TokenTree::Ident(Ident::new("compile_error", start))); + tokens.append(TokenTree::Punct(Punct::new_spanned( + '!', + Spacing::Alone, + start, + ))); + tokens.append(TokenTree::Group({ + let mut group = Group::new( + Delimiter::Brace, + TokenStream::from({ + let mut string = Literal::string(&self.message); + string.set_span(end); + TokenTree::Literal(string) + }), + ); + group.set_span(end); + group + })); } } @@ -400,6 +405,7 @@ impl Clone for SpanRange { impl Copy for SpanRange {} +// TODO: impl core::error::Error (requires Rust 1.81+) impl std::error::Error for Error {} impl From for Error { diff --git a/src/export.rs b/src/export.rs index b9ea5c74..eca67f6a 100644 --- a/src/export.rs +++ b/src/export.rs @@ -1,33 +1,33 @@ #[doc(hidden)] -pub use std::clone::Clone; +pub use core::clone::Clone; #[doc(hidden)] -pub use std::cmp::{Eq, PartialEq}; +pub use core::cmp::{Eq, PartialEq}; #[doc(hidden)] -pub use std::concat; +pub use core::concat; #[doc(hidden)] -pub use std::default::Default; +pub use core::default::Default; #[doc(hidden)] -pub use std::fmt::Debug; +pub use core::fmt::Debug; #[doc(hidden)] -pub use std::hash::{Hash, Hasher}; +pub use core::hash::{Hash, Hasher}; #[doc(hidden)] -pub use std::marker::Copy; +pub use core::marker::Copy; #[doc(hidden)] -pub use std::option::Option::{None, Some}; +pub use core::option::Option::{None, Some}; #[doc(hidden)] -pub use std::result::Result::{Err, Ok}; +pub use core::result::Result::{Err, Ok}; #[doc(hidden)] -pub use std::stringify; +pub use core::stringify; #[doc(hidden)] -pub type Formatter<'a> = std::fmt::Formatter<'a>; +pub type Formatter<'a> = core::fmt::Formatter<'a>; #[doc(hidden)] -pub type FmtResult = std::fmt::Result; +pub type FmtResult = core::fmt::Result; #[doc(hidden)] -pub type bool = std::primitive::bool; +pub type bool = core::primitive::bool; #[doc(hidden)] -pub type str = std::primitive::str; +pub type str = core::primitive::str; #[cfg(feature = "printing")] #[doc(hidden)] diff --git a/src/expr.rs b/src/expr.rs index 7fb0f7b4..0f92701d 100644 --- a/src/expr.rs +++ b/src/expr.rs @@ -1,13 +1,38 @@ -use super::*; +use crate::attr::Attribute; +#[cfg(all(feature = "parsing", feature = "full"))] +use crate::error::Result; +#[cfg(feature = "parsing")] +use crate::ext::IdentExt as _; +#[cfg(feature = "full")] +use crate::generics::BoundLifetimes; +use crate::ident::Ident; +#[cfg(any(feature = "parsing", feature = "full"))] +use crate::lifetime::Lifetime; +use crate::lit::Lit; +use crate::mac::Macro; +use crate::op::{BinOp, UnOp}; +#[cfg(feature = "parsing")] +use crate::parse::ParseStream; +#[cfg(feature = "full")] +use crate::pat::Pat; +use crate::path::{AngleBracketedGenericArguments, Path, QSelf}; use crate::punctuated::Punctuated; +#[cfg(feature = "full")] +use crate::stmt::Block; +use crate::token; +#[cfg(feature = "full")] +use crate::ty::ReturnType; +use crate::ty::Type; +use alloc::boxed::Box; +use alloc::vec::Vec; +#[cfg(feature = "printing")] +use core::fmt::{self, Display}; +use core::hash::{Hash, Hasher}; +#[cfg(all(feature = "parsing", feature = "full"))] +use core::mem; use proc_macro2::{Span, TokenStream}; #[cfg(feature = "printing")] use quote::IdentFragment; -#[cfg(feature = "printing")] -use std::fmt::{self, Display}; -use std::hash::{Hash, Hasher}; -#[cfg(feature = "parsing")] -use std::mem; ast_enum_of_structs! { /// A Rust expression. @@ -84,7 +109,7 @@ ast_enum_of_structs! { /// A sign that you may not be choosing the right variable names is if you /// see names getting repeated in your code, like accessing /// `receiver.receiver` or `pat.pat` or `cond.cond`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] #[non_exhaustive] pub enum Expr { /// A slice literal expression: `[a, b, c, d]`. @@ -172,7 +197,7 @@ ast_enum_of_structs! { /// A parenthesized expression: `(a + b)`. Paren(ExprParen), - /// A path like `std::mem::replace` possibly containing generic + /// A path like `core::mem::replace` possibly containing generic /// parameters and a qualified self-type. /// /// A plain identifier like `x` is a path of length 1. @@ -181,6 +206,9 @@ ast_enum_of_structs! { /// A range expression: `1..2`, `1..`, `..2`, `1..=2`, `..=2`. Range(ExprRange), + /// Address-of operation: `&raw const place` or `&raw mut place`. + RawAddr(ExprRawAddr), + /// A referencing operation: `&a` or `&mut a`. Reference(ExprReference), @@ -242,7 +270,7 @@ ast_enum_of_structs! { ast_struct! { /// A slice literal expression: `[a, b, c, d]`. - #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] + #[cfg_attr(docsrs, doc(cfg(feature = "full")))] pub struct ExprArray #full { pub attrs: Vec, pub bracket_token: token::Bracket, @@ -252,7 +280,7 @@ ast_struct! { ast_struct! { /// An assignment expression: `a = compute()`. - #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] + #[cfg_attr(docsrs, doc(cfg(feature = "full")))] pub struct ExprAssign #full { pub attrs: Vec, pub left: Box, @@ -263,7 +291,7 @@ ast_struct! { ast_struct! { /// An async block: `async { ... }`. - #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] + #[cfg_attr(docsrs, doc(cfg(feature = "full")))] pub struct ExprAsync #full { pub attrs: Vec, pub async_token: Token![async], @@ -274,7 +302,7 @@ ast_struct! { ast_struct! { /// An await expression: `fut.await`. - #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] + #[cfg_attr(docsrs, doc(cfg(feature = "full")))] pub struct ExprAwait #full { pub attrs: Vec, pub base: Box, @@ -285,7 +313,7 @@ ast_struct! { ast_struct! { /// A binary operation: `a + b`, `a += b`. - #[cfg_attr(doc_cfg, doc(cfg(any(feature = "full", feature = "derive"))))] + #[cfg_attr(docsrs, doc(cfg(any(feature = "full", feature = "derive"))))] pub struct ExprBinary { pub attrs: Vec, pub left: Box, @@ -296,7 +324,7 @@ ast_struct! { ast_struct! { /// A blocked scope: `{ ... }`. - #[cfg_attr(doc_cfg, doc(cfg(feature = "full")))] + #[cfg_attr(docsrs, doc(cfg(feature = "full")))] pub struct ExprBlock #full { pub attrs: Vec, pub label: Option