diff --git a/CHANGELOG.md b/CHANGELOG.md index 683cdcbf..997bd20d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added -- Support [`ZeroizeOnDrop`](https://docs.rs/zeroize/1.5.0-pre/zeroize/trait.ZeroizeOnDrop.html). +- Support [`ZeroizeOnDrop`](https://docs.rs/zeroize/1.5.0/zeroize/trait.ZeroizeOnDrop.html). ### Removed - **Breaking Change**: Remove support for `Zeroize(drop)`. diff --git a/Cargo.toml b/Cargo.toml index feb12d0c..122e5b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] default-members = [""] -members = ["", "ensure-no-std", "non-msrv-tests"] +members = ["", "crate_", "ensure-no-std", "non-msrv-tests"] resolver = "2" [package] @@ -32,14 +32,12 @@ quote = { version = "1", default-features = false } syn = { version = "1", default-features = false, features = [ "clone-impls", "derive", + "extra-traits", "full", "parsing", "printing", ] } -[dev-dependencies] -syn = { version = "1", default-features = false, features = ["extra-traits"] } - [[test]] name = "util" path = "tests/util.rs" diff --git a/README.md b/README.md index 2f0588b4..64618b31 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ Unions only support [`Clone`](https://doc.rust-lang.org/core/clone/trait.Clone.h [`unreachable`](https://doc.rust-lang.org/core/macro.unreachable.html). - `zeroize`: Allows deriving [`Zeroize`] and [`method@zeroize`] on [`Drop`](https://doc.rust-lang.org/core/ops/trait.Drop.html). - `zeroize-on-drop`: Allows deriving [`Zeroize`] and [`ZeroizeOnDrop`] and - requires [zeroize] v1.5.0-pre. + requires [zeroize] v1.5.0. ## MSRV @@ -266,11 +266,11 @@ conditions. [CHANGELOG]: https://github.com/ModProg/derive-where/blob/main/CHANGELOG.md [LICENSE-MIT]: https://github.com/ModProg/derive-where/blob/main/LICENSE-MIT [LICENSE-APACHE]: https://github.com/ModProg/derive-where/blob/main/LICENSE-APACHE -[zeroize]: https://crates.io/crates/zeroize/1.5.0-pre +[zeroize]: https://crates.io/crates/zeroize/1.5.0 [`Debug`]: https://doc.rust-lang.org/core/fmt/trait.Debug.html [`Default`]: https://doc.rust-lang.org/core/default/trait.Default.html [`Hash`]: https://doc.rust-lang.org/core/hash/trait.Hash.html [`Zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html -[`ZeroizeOnDrop`]: https://docs.rs/zeroize/1.5.0-pre/zeroize/trait.ZeroizeOnDrop.html +[`ZeroizeOnDrop`]: https://docs.rs/zeroize/1.5.0/zeroize/trait.ZeroizeOnDrop.html [`method@zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html#tymethod.zeroize [#27]: https://github.com/ModProg/derive-where/issues/27 diff --git a/crate_/Cargo.toml b/crate_/Cargo.toml new file mode 100644 index 00000000..faa81477 --- /dev/null +++ b/crate_/Cargo.toml @@ -0,0 +1,19 @@ +[package] +edition = "2018" +name = "crate_" +publish = false +version = "0.0.0" + +[features] +nightly = ["derive-where_/nightly"] +safe = ["derive-where_/safe"] +zeroize = ["derive-where_/zeroize", "zeroize_"] +zeroize-on-drop = ["derive-where_/zeroize-on-drop", "zeroize"] + +[dependencies] +derive-where_ = { path = "..", package = "derive-where" } +zeroize_ = { version = "1.5.0", package = "zeroize", default-features = false, optional = true } + +[lib] +doctest = false +test = false diff --git a/crate_/src/lib.rs b/crate_/src/lib.rs new file mode 100644 index 00000000..fdb13071 --- /dev/null +++ b/crate_/src/lib.rs @@ -0,0 +1,13 @@ +#![no_std] + +#[cfg(feature = "zeroize")] +extern crate zeroize_ as zeroize; + +use core::marker::PhantomData; + +use derive_where_::derive_where; + +#[derive_where(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[cfg_attr(feature = "zeroize", derive_where(Zeroize))] +#[derive_where(crate = "derive_where_")] +pub struct Test(PhantomData); diff --git a/ensure-no-std/Cargo.toml b/ensure-no-std/Cargo.toml index 114fcef0..648a2e05 100644 --- a/ensure-no-std/Cargo.toml +++ b/ensure-no-std/Cargo.toml @@ -12,7 +12,7 @@ zeroize-on-drop = ["derive-where/zeroize-on-drop", "zeroize"] [dependencies] derive-where = { path = ".." } -zeroize_ = { version = "1.5.0-pre", package = "zeroize", default-features = false, optional = true } +zeroize_ = { version = "1.5.0", package = "zeroize", default-features = false, optional = true } [lib] doctest = false diff --git a/non-msrv-tests/Cargo.toml b/non-msrv-tests/Cargo.toml index 465cbaf8..cd75c878 100644 --- a/non-msrv-tests/Cargo.toml +++ b/non-msrv-tests/Cargo.toml @@ -15,7 +15,7 @@ derive-where = { path = ".." } [dev-dependencies] trybuild = { version = "1", default-features = false } -zeroize_ = { version = "1.5.0-pre", package = "zeroize", default-features = false } +zeroize_ = { version = "1.5.0", package = "zeroize", default-features = false } [lib] doctest = false diff --git a/non-msrv-tests/tests/ui/item.rs b/non-msrv-tests/tests/ui/item.rs index 8d69a880..9abbb9c5 100644 --- a/non-msrv-tests/tests/ui/item.rs +++ b/non-msrv-tests/tests/ui/item.rs @@ -8,6 +8,28 @@ struct NoOption(PhantomData); #[derive_where()] struct EmptyAttribute(PhantomData); +#[derive_where(crate(derive_where_))] +struct WrongCrateSyntax(PhantomData); + +#[derive_where(crate = "struct Test")] +struct InvalidPath(PhantomData); + +// The error message here shows that `crate = ".."` should be in it's own +// attribute instead of an error pointing out this is duplicate. This is not +// ideal but much less complicated to implement. +#[derive_where(crate = "derive_where_", crate = "derive_where_")] +struct DuplicateCrate1(PhantomData); + +#[derive_where(crate = "derive_where_")] +#[derive_where(crate = "derive_where_")] +struct DuplicateCrate2(PhantomData); + +#[derive_where(crate = "derive_where_")] +struct OnlyCrate(PhantomData); + +#[derive_where(crate = "::derive_where")] +struct DefaultCrate(PhantomData); + #[derive_where(Clone; T;)] struct SemiColonAtTheEnd(T, PhantomData); @@ -26,4 +48,8 @@ struct MissingCommaBetweenGenerics(T, PhantomData<(U, V)>); #[derive_where("Clone")] struct InvalidTrait(PhantomData); +#[derive_where(Clone)] +#[derive_where::derive_where(Copy)] +struct QualifiedNotFirstMacro(PhantomData); + fn main() {} diff --git a/non-msrv-tests/tests/ui/item.stderr b/non-msrv-tests/tests/ui/item.stderr index 1781e11f..3e1ae9e1 100644 --- a/non-msrv-tests/tests/ui/item.stderr +++ b/non-msrv-tests/tests/ui/item.stderr @@ -14,38 +14,82 @@ error: empty `derive_where` found | = note: this error originates in the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) +error: unexpected option syntax + --> tests/ui/item.rs:11:16 + | +11 | #[derive_where(crate(derive_where_))] + | ^^^^^^^^^^^^^^^^^^^^ + +error: expected path, expected identifier + --> tests/ui/item.rs:14:24 + | +14 | #[derive_where(crate = "struct Test")] + | ^^^^^^^^^^^^^ + +error: the `crate` option has to be defined in it's own `#[derive_where(..)` attribute + --> tests/ui/item.rs:20:16 + | +20 | #[derive_where(crate = "derive_where_", crate = "derive_where_")] + | ^^^^^ + +error: duplicate `crate` option + --> tests/ui/item.rs:24:16 + | +24 | #[derive_where(crate = "derive_where_")] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: no traits found to implement, use `#[derive_where(..)` to specify some + --> tests/ui/item.rs:28:1 + | +28 | struct OnlyCrate(PhantomData); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: unnecessary path qualification, `::derive_where` is used by default + --> tests/ui/item.rs:30:24 + | +30 | #[derive_where(crate = "::derive_where")] + | ^^^^^^^^^^^^^^^^ + error: expected `,` - --> tests/ui/item.rs:11:24 + --> tests/ui/item.rs:33:24 | -11 | #[derive_where(Clone; T;)] +33 | #[derive_where(Clone; T;)] | ^ error: expected type to bind to, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/ui/item.rs:14:25 + --> tests/ui/item.rs:36:25 | -14 | #[derive_where(Clone; T,,)] +36 | #[derive_where(Clone; T,,)] | ^ error: expected type to bind to, expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime - --> tests/ui/item.rs:17:23 + --> tests/ui/item.rs:39:23 | -17 | #[derive_where(Clone; where)] +39 | #[derive_where(Clone; where)] | ^^^^^ error: expected `;` or `, - --> tests/ui/item.rs:20:22 + --> tests/ui/item.rs:42:22 | -20 | #[derive_where(Clone Debug)] +42 | #[derive_where(Clone Debug)] | ^^^^^ error: expected `,` - --> tests/ui/item.rs:23:25 + --> tests/ui/item.rs:45:25 | -23 | #[derive_where(Clone; T U)] +45 | #[derive_where(Clone; T U)] | ^ error: unexpected option syntax - --> tests/ui/item.rs:26:16 + --> tests/ui/item.rs:48:16 | -26 | #[derive_where("Clone")] +48 | #[derive_where("Clone")] | ^^^^^^^ + +error: `#[derive_where(..)` was already applied to this item before, this occurs when using a qualified path for any `#[derive_where(..)`s except the first + --> tests/ui/item.rs:51:1 + | +51 | #[derive_where(Clone)] + | ^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `derive_where` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/non-msrv-tests/tests/ui/not-zeroize/item.stderr b/non-msrv-tests/tests/ui/not-zeroize/item.stderr index b4708918..0a4fadb2 100644 --- a/non-msrv-tests/tests/ui/not-zeroize/item.stderr +++ b/non-msrv-tests/tests/ui/not-zeroize/item.stderr @@ -1,34 +1,34 @@ -error: unsupported trait, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:5:16 | 5 | #[derive_where(skip_inner, Clone)] | ^^^^^^^^^^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:8:24 | 8 | #[derive_where(Debug = invalid; T)] | ^^^^^^^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:11:16 | 11 | #[derive_where(,)] | ^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:14:16 | 14 | #[derive_where(,Clone)] | ^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:17:22 | 17 | #[derive_where(Clone,,)] | ^ -error: unsupported trait, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd --> tests/ui/not-zeroize/item.rs:20:16 | 20 | #[derive_where(T)] diff --git a/non-msrv-tests/tests/ui/zeroize/item.stderr b/non-msrv-tests/tests/ui/zeroize/item.stderr index b407ef7f..e25d8629 100644 --- a/non-msrv-tests/tests/ui/zeroize/item.stderr +++ b/non-msrv-tests/tests/ui/zeroize/item.stderr @@ -1,34 +1,34 @@ -error: unsupported trait, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:5:16 | 5 | #[derive_where(skip_inner, Clone)] | ^^^^^^^^^^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:8:24 | 8 | #[derive_where(Debug = invalid; T)] | ^^^^^^^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:11:16 | 11 | #[derive_where(,)] | ^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:14:16 | 14 | #[derive_where(,Clone)] | ^ -error: unsupported trait syntax, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait syntax, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:17:22 | 17 | #[derive_where(Clone,,)] | ^ -error: unsupported trait, expected one of expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop +error: unsupported trait, expected one of Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd, Zeroize, ZeroizeOnDrop --> tests/ui/zeroize/item.rs:20:16 | 20 | #[derive_where(T)] diff --git a/non-msrv-tests/tests/ui/zeroize/zeroize.rs b/non-msrv-tests/tests/ui/zeroize/zeroize.rs index 5b0df5fb..b7e8a377 100644 --- a/non-msrv-tests/tests/ui/zeroize/zeroize.rs +++ b/non-msrv-tests/tests/ui/zeroize/zeroize.rs @@ -17,9 +17,12 @@ struct WrongOptionSyntax2(PhantomData); struct WrongCrateSyntax(PhantomData); #[derive_where(Zeroize(crate = "struct Test"))] -struct InvalidCrate(PhantomData); +struct InvalidPath(PhantomData); #[derive_where(Zeroize(crate = "zeroize_", crate = "zeroize_"))] struct DuplicateCrate(PhantomData); +#[derive_where(Zeroize(crate = "::zeroize"))] +struct DefaultCrate(PhantomData); + fn main() {} diff --git a/non-msrv-tests/tests/ui/zeroize/zeroize.stderr b/non-msrv-tests/tests/ui/zeroize/zeroize.stderr index 2e1cd6f1..ee412dc1 100644 --- a/non-msrv-tests/tests/ui/zeroize/zeroize.stderr +++ b/non-msrv-tests/tests/ui/zeroize/zeroize.stderr @@ -33,3 +33,9 @@ error: duplicate `crate` option | 22 | #[derive_where(Zeroize(crate = "zeroize_", crate = "zeroize_"))] | ^^^^^^^^^^^^^^^^^^ + +error: unnecessary path qualification, `::zeroize` is used by default + --> tests/ui/zeroize/zeroize.rs:25:32 + | +25 | #[derive_where(Zeroize(crate = "::zeroize"))] + | ^^^^^^^^^^^ diff --git a/src/attr/item.rs b/src/attr/item.rs index 249bd927..43f5e76b 100644 --- a/src/attr/item.rs +++ b/src/attr/item.rs @@ -7,7 +7,7 @@ use syn::{ parse::{discouraged::Speculative, Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Attribute, Data, Ident, Meta, NestedMeta, Path, PredicateType, Result, Token, TraitBound, + Attribute, Data, Ident, Lit, Meta, NestedMeta, Path, PredicateType, Result, Token, TraitBound, TraitBoundModifier, Type, TypeParamBound, TypePath, WhereClause, WherePredicate, }; @@ -16,6 +16,8 @@ use crate::{util, Error, Item, Skip, Trait, TraitImpl, DERIVE_WHERE}; /// Attributes on item. #[derive(Default)] pub struct ItemAttr { + /// [`Path`] to this crate. + pub crate_: Option, /// [`Trait`]s to skip all fields for. pub skip_inner: Skip, /// [`DeriveWhere`]s on this item. @@ -52,8 +54,49 @@ impl ItemAttr { // Don't parse `Skip` yet, because it needs access to all // `DeriveWhere`s. skip_inners.push(meta); + } else if meta.path().is_ident("crate") { + if let Meta::NameValue(name_value) = meta { + if let Lit::Str(lit_str) = &name_value.lit { + match lit_str.parse::() { + Ok(path) => { + if path + == util::path_from_strs(&[DERIVE_WHERE]) + { + return Err(Error::path_unnecessary( + path.span(), + &format!("::{}", DERIVE_WHERE), + )); + } + + match self_.crate_ { + Some(_) => { + return Err( + Error::option_duplicate( + name_value.span(), + "crate", + ), + ) + } + None => self_.crate_ = Some(path), + } + } + Err(error) => { + return Err(Error::path( + lit_str.span(), + error, + )) + } + } + } else { + return Err(Error::option_syntax( + name_value.lit.span(), + )); + } + } else { + return Err(Error::option_syntax(meta.span())); + } } - // The list can have one item but stil not be the `skip_inner` + // The list can have one item but still not be the `skip_inner` // attribute, continue with parsing `DeriveWhere`. else { self_ diff --git a/src/error.rs b/src/error.rs index ea73ed6d..6b150533 100644 --- a/src/error.rs +++ b/src/error.rs @@ -6,6 +6,34 @@ use proc_macro2::Span; pub struct Error; impl Error { + /// `derive_where` was already applied on this item before. + pub fn visited(span: Span) -> syn::Error { + syn::Error::new( + span, + "`#[derive_where(..)` was already applied to this item before, this occurs when using \ + a qualified path for any `#[derive_where(..)`s except the first", + ) + } + + /// Unnecessary `crate` option because it is equal to the default. + pub fn path_unnecessary(span: Span, default: &str) -> syn::Error { + syn::Error::new( + span, + format!( + "unnecessary path qualification, `{}` is used by default", + default + ), + ) + } + + /// The `crate` option was defined together with traits. + pub fn crate_(span: Span) -> syn::Error { + syn::Error::new( + span, + "the `crate` option has to be defined in it's own `#[derive_where(..)` attribute", + ) + } + /// No `derive_where` with [`Trait`](crate::Trait) found. pub fn none(span: Span) -> syn::Error { syn::Error::new( @@ -144,8 +172,7 @@ impl Error { syn::Error::new(span, "trait to be skipped isn't being implemented") } - /// Invalid value for the `Zeroize` `crate` option. - #[cfg(feature = "zeroize")] + /// Invalid value for the `derive_where` or `Zeroize` `crate` option. pub fn path(span: Span, parse_error: syn::Error) -> syn::Error { syn::Error::new(span, format!("expected path, {}", parse_error)) } @@ -154,10 +181,7 @@ impl Error { pub fn trait_(span: Span) -> syn::Error { syn::Error::new( span, - format!( - "unsupported trait, expected one of expected one of {}", - Self::trait_list() - ), + format!("unsupported trait, expected one of {}", Self::trait_list()), ) } @@ -166,7 +190,7 @@ impl Error { syn::Error::new( span, format!( - "unsupported trait syntax, expected one of expected one of {}", + "unsupported trait syntax, expected one of {}", Self::trait_list() ), ) diff --git a/src/input.rs b/src/input.rs index f9e55fbe..33acdb62 100644 --- a/src/input.rs +++ b/src/input.rs @@ -1,14 +1,19 @@ //! Parses [`DeriveInput`] into something more useful. use proc_macro2::Span; -use syn::{DeriveInput, GenericParam, Generics, Result}; +use syn::{DeriveInput, GenericParam, Generics, Path, Result}; #[cfg(feature = "zeroize")] use crate::DeriveTrait; -use crate::{Data, DeriveWhere, Either, Error, Item, ItemAttr, Trait}; +use crate::{ + util, Data, DeriveWhere, Either, Error, Item, ItemAttr, Trait, DERIVE_WHERE, + DERIVE_WHERE_VISITED, +}; /// Parsed input. pub struct Input<'a> { + /// [`Path`] to the `derive_where_visited` proc-macro. + pub derive_where_visited: Path, /// `derive_where` attributes on the item. pub derive_wheres: Vec, /// Generics necessary to define for an `impl`. @@ -31,10 +36,24 @@ impl<'a> Input<'a> { ) -> Result { // Parse `Attribute`s on item. let ItemAttr { + crate_, skip_inner, derive_wheres, } = ItemAttr::from_attrs(span, data, attrs)?; + // Build `derive_where_visited` path. + let derive_where_visited = util::path_from_root_and_strs( + crate_.unwrap_or_else(|| util::path_from_strs(&[DERIVE_WHERE])), + &[DERIVE_WHERE_VISITED], + ); + + // Check if we already parsed this item before. + for attr in attrs { + if attr.path == derive_where_visited { + return Err(Error::visited(span)); + } + } + // Extract fields and variants of this item. let item = match &data { syn::Data::Struct(data) => { @@ -163,6 +182,7 @@ impl<'a> Input<'a> { } Ok(Self { + derive_where_visited, derive_wheres, generics, item, diff --git a/src/lib.rs b/src/lib.rs index 047e2ac4..37c40fef 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -288,7 +288,7 @@ //! [`unreachable`]. //! - `zeroize`: Allows deriving [`Zeroize`] and [`method@zeroize`] on [`Drop`]. //! - `zeroize-on-drop`: Allows deriving [`Zeroize`] and [`ZeroizeOnDrop`] and -//! requires [zeroize] v1.5.0-pre. +//! requires [zeroize] v1.5.0. //! //! # MSRV //! @@ -326,12 +326,12 @@ //! [CHANGELOG]: https://github.com/ModProg/derive-where/blob/main/CHANGELOG.md //! [LICENSE-MIT]: https://github.com/ModProg/derive-where/blob/main/LICENSE-MIT //! [LICENSE-APACHE]: https://github.com/ModProg/derive-where/blob/main/LICENSE-APACHE -//! [zeroize]: https://crates.io/crates/zeroize/1.5.0-pre +//! [zeroize]: https://crates.io/crates/zeroize/1.5.0 //! [`Debug`]: core::fmt::Debug //! [`Default`]: core::default::Default //! [`Hash`]: core::hash::Hash //! [`Zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html -//! [`ZeroizeOnDrop`]: https://docs.rs/zeroize/1.5.0-pre/zeroize/trait.ZeroizeOnDrop.html +//! [`ZeroizeOnDrop`]: https://docs.rs/zeroize/1.5.0/zeroize/trait.ZeroizeOnDrop.html //! [`method@zeroize`]: https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html#tymethod.zeroize //! [#27]: https://github.com/ModProg/derive-where/issues/27 @@ -369,8 +369,10 @@ use self::{ util::Either, }; -/// Token used for attributes. +/// Name of the `derive_where` proc-macro. const DERIVE_WHERE: &str = "derive_where"; +/// Name of the `derive_where_visited` proc-macro. +const DERIVE_WHERE_VISITED: &str = "derive_where_visited"; /// Item-level options: /// - `#[derive_where(Clone, ..; T, ..)]`: Specify traits to implement and @@ -423,32 +425,54 @@ pub fn derive_where( } }; - let cleaned_item = input_without_derive_where_attributes(item.clone()); + let mut cleaned_item = input_without_derive_where_attributes(item.clone()); let span = cleaned_item.span(); match { Input::from_input(span, &item) } { Ok(Input { + derive_where_visited, derive_wheres, generics, item, - }) => iter::once(cleaned_item) - .chain( - derive_wheres - .iter() - .flat_map(|derive_where| iter::repeat(derive_where).zip(&derive_where.traits)) - .map(|(derive_where, trait_)| { - generate_impl(derive_where, trait_, &item, generics) - }), - ) - .collect::() - .into(), - Err(error) => iter::once(cleaned_item) + }) => { + cleaned_item + .attrs + .push(syn::parse_quote! { #[#derive_where_visited] }); + + iter::once(cleaned_item.into_token_stream()) + .chain( + derive_wheres + .iter() + .flat_map(|derive_where| { + iter::repeat(derive_where).zip(&derive_where.traits) + }) + .map(|(derive_where, trait_)| { + generate_impl(derive_where, trait_, &item, generics) + }), + ) + .collect::() + .into() + } + Err(error) => iter::once(cleaned_item.into_token_stream()) .chain(iter::once(error.into_compile_error())) .collect::() .into(), } } +/// Marker attribute signifying that this item was already processed by a +/// `derive_where` attribute before. This should prevent users to wrongly use a +/// qualified path for a `derive_where` attribute except the first one. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn derive_where_visited( + _attr: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // No-op, just here to mark the item as visited. + input +} + /// Generate implementation for a [`Trait`]. fn generate_impl( derive_where: &DeriveWhere, @@ -507,7 +531,7 @@ fn generate_body(trait_: &DeriveTrait, item: &Item) -> TokenStream { /// /// This is necessary because Rust currently does not support helper attributes /// for attribute proc-macros and therefore doesn't automatically remove them. -fn input_without_derive_where_attributes(mut input: DeriveInput) -> TokenStream { +fn input_without_derive_where_attributes(mut input: DeriveInput) -> DeriveInput { use syn::Data; let DeriveInput { data, attrs, .. } = &mut input; @@ -538,6 +562,8 @@ fn input_without_derive_where_attributes(mut input: DeriveInput) -> TokenStream // Remove `derive_where` attributes from the item. remove_derive_where(attrs); + + // Remove `derive_where` attributes from variants or fields. match data { Data::Struct(DataStruct { fields, .. }) => remove_derive_where_from_fields(fields), Data::Enum(DataEnum { variants, .. }) => { @@ -551,5 +577,5 @@ fn input_without_derive_where_attributes(mut input: DeriveInput) -> TokenStream Data::Union(DataUnion { fields, .. }) => remove_derive_where_from_fields_named(fields), } - input.to_token_stream() + input } diff --git a/src/test/mod.rs b/src/test/mod.rs index 5c477f4c..ae4da2cc 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -37,6 +37,7 @@ fn derive_where_internal(input: TokenStream) -> Result { derive_wheres, generics, item, + .. } = Input::from_input(span, &item)?; Ok(derive_wheres diff --git a/src/trait_.rs b/src/trait_.rs index f1280dc5..221b1376 100644 --- a/src/trait_.rs +++ b/src/trait_.rs @@ -89,6 +89,7 @@ impl Trait { "Zeroize" => Ok(Zeroize), #[cfg(feature = "zeroize")] "ZeroizeOnDrop" => Ok(ZeroizeOnDrop), + "crate" => Err(Error::crate_(path.span())), _ => Err(Error::trait_(path.span())), } } else { diff --git a/src/trait_/zeroize.rs b/src/trait_/zeroize.rs index a9ca8f3c..26c3b5a6 100644 --- a/src/trait_/zeroize.rs +++ b/src/trait_/zeroize.rs @@ -2,9 +2,9 @@ use proc_macro2::TokenStream; use quote::quote; -use syn::{spanned::Spanned, Lit, Meta, MetaList, NestedMeta, Result}; +use syn::{spanned::Spanned, Lit, Meta, MetaList, NestedMeta, Path, Result}; -use crate::{Data, DeriveTrait, Error, Item, SimpleType, TraitImpl}; +use crate::{util, Data, DeriveTrait, Error, Item, SimpleType, TraitImpl}; /// Dummy-struct implement [`Trait`](crate::Trait) for [`Zeroize`](https://docs.rs/zeroize/latest/zeroize/trait.Zeroize.html) . pub struct Zeroize; @@ -38,8 +38,15 @@ impl TraitImpl for Zeroize { // Check for duplicate `crate` option. if crate_.is_none() { if let Lit::Str(lit_str) = &name_value.lit { - match lit_str.parse() { + match lit_str.parse::() { Ok(path) => { + if path == util::path_from_strs(&["zeroize"]) { + return Err(Error::path_unnecessary( + path.span(), + "::zeroize", + )); + } + crate_ = Some(path); } Err(error) => return Err(Error::path(lit_str.span(), error)), diff --git a/src/trait_/zeroize_on_drop.rs b/src/trait_/zeroize_on_drop.rs index e3790903..dc93a063 100644 --- a/src/trait_/zeroize_on_drop.rs +++ b/src/trait_/zeroize_on_drop.rs @@ -34,8 +34,15 @@ impl TraitImpl for ZeroizeOnDrop { // Check for duplicate `crate` option. if crate_.is_none() { if let Lit::Str(lit_str) = &name_value.lit { - match lit_str.parse() { + match lit_str.parse::() { Ok(path) => { + if path == util::path_from_strs(&["zeroize"]) { + return Err(Error::path_unnecessary( + path.span(), + "::zeroize", + )); + } + crate_ = Some(path); } Err(error) => return Err(Error::path(lit_str.span(), error)),