diff --git a/.github/workflows/ci-version.yml b/.github/workflows/ci-version.yml index 654f66f..d4dbd8b 100644 --- a/.github/workflows/ci-version.yml +++ b/.github/workflows/ci-version.yml @@ -41,7 +41,7 @@ jobs: name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.toolchain }} @@ -57,7 +57,7 @@ jobs: - macos-latest - windows-latest toolchain: - - "1.60" + - "1.89" features: - - --no-default-features --features Debug @@ -79,7 +79,7 @@ jobs: name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.toolchain }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8a686b..6d877e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: rustfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: nightly @@ -19,7 +19,7 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: components: clippy @@ -57,7 +57,7 @@ jobs: name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.toolchain }} @@ -73,7 +73,7 @@ jobs: - macos-latest - windows-latest toolchain: - - "1.60" + - "1.89" features: - - --no-default-features --features Debug @@ -95,7 +95,7 @@ jobs: name: Test ${{ matrix.toolchain }} on ${{ matrix.os }} (${{ matrix.features }}) runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: ${{ matrix.toolchain }} diff --git a/CHANGELOG-0.5.11-to-0.7.0.md b/CHANGELOG-0.5.11-to-0.7.0.md new file mode 100644 index 0000000..fc503cb --- /dev/null +++ b/CHANGELOG-0.5.11-to-0.7.0.md @@ -0,0 +1,58 @@ +# Changelog: 0.5.11 to 0.7.0 + +This document compares `v0.5.11` with the current `0.7.0` worktree. It focuses on changes that can affect users of the crate. Internal refactoring details are not listed. + +## Breaking and Upgrade Notes + +- The minimum supported Rust version was raised from Rust 1.60 to Rust 1.89. +- The crate now uses the Rust 2024 edition. +- Automatic trait bounds changed a lot. The generated `where` clauses are now based on the field types that the generated code really uses. This can remove old unnecessary bounds, but it can also change the public bounds of generated impls. +- Precise generated bounds can show private field types in docs, compiler errors, or public `where` clauses. If your type is public, check that this is acceptable for your API. +- Use `bound(*)` when you want Educe to add `Param: Trait` for every generic type parameter, similar to the built-in derives. This option was added after 0.5.11. +- `Eq` is now treated as a marker trait. Field-level equality settings such as `ignore` and `method` should be written on `PartialEq`, not on `Eq`. +- `Into` derives now generate `impl From for Target` by default. Calling `value.into()` still works through Rust's blanket impl, and `Target::from(value)` also works. Use `#[educe(Into(Target, into))]` if you need a direct one-way `Into` impl instead. +- If you already wrote your own `From for Target` impl, check for conflicts with the new default `Into` behavior. + +## Trait Bounds + +- Automatic bounds are more precise and more clearly documented. +- Ignored fields and fields handled by a custom `method` do not add automatic bounds for that trait. +- Field types without generic parameters do not add constant bounds. +- Common standard library wrapper types are handled more carefully. For example, `Option` can add `T: Trait`, and `Vec>` can add only the needed inner bound. +- Types that always implement a trait, such as `PhantomData`, raw pointers, function pointers, and selected standard library types, can avoid unnecessary bounds. +- Other field types use precise bounds such as `Wrapper: Clone` instead of always falling back to `T: Clone`. +- Self-recursive generic types are handled better. For example, a type like `List` containing `Box>` can avoid trait solver overflow by adding bounds on the type parameters. +- Mutually recursive generic types are still a known limitation. Use `bound(*)` or a custom `bound(...)` for those cases. +- Related traits can inherit generated predicates when they are derived together by Educe: `Copy` from `Clone`, `Eq` and `PartialOrd` from `PartialEq`, and `Ord` from `Eq` and `PartialOrd`. +- Explicit `bound(...)` values are used as written. If an explicit bound does not satisfy a required supertrait, the compiler will ask for the missing bound. +- Older cases where bounds could leak from one generated impl into another were fixed. + +## Trait Behavior + +- `Debug` with explicit field methods was fixed for generic types and now avoids more name clashes with user code. +- `Debug` output handles field names as strings. Tuple field names and custom names are printed more correctly, including tuple indexes such as `0`. +- `Copy`, `Clone`, `Eq`, `PartialOrd`, and `Ord` derive behavior is more consistent with their real Rust trait relationships. +- `Copy` can work with an Educe-generated or built-in `Clone` impl, while still respecting the bounds that `Copy` really needs. +- `PartialOrd` and `Ord` work together more consistently. When both are derived, `PartialOrd` can follow `Ord` field settings such as `ignore`, `rank`, and `method` so that `partial_cmp` and `cmp` stay consistent. +- When both comparison traits are derived, `Ord` can follow `PartialOrd` field settings for `ignore` and `rank`. A `PartialOrd` method cannot be reused for `Ord` because it returns `Option`. +- `Ord` can be built when the `PartialOrd` feature is not enabled. +- `Into` supports the new default `From` generation for concrete target types and for generic target types whose type parameters are covered by the target type. + +## Generated Code and Lints + +- Generated impls are marked with `#[automatically_derived]`. +- Lint-level attributes from the input type, such as `allow`, `expect`, `warn`, and `deny`, are copied to generated impls. This makes generated code behave more like built-in derives under strict lint settings. +- Diagnostics for invalid attributes and helper parsing were improved. + +## Documentation and Safety Notes + +- The README now has a dedicated Trait Bounds section that explains the automatic bound rules, bound inheritance, `bound(*)`, and known limitations. +- Documentation for unsafe union derives is clearer. It now states that `Debug`, `PartialEq`, and `Hash` union impls may read, compare, or hash the whole union memory, including padding bytes. +- The trait documentation was updated so each trait points to the shared bound rules instead of repeating older simplified rules. + +## Dependency and Package Changes + +- `quote` now has a minimum version of `1.0.44`. +- `proc-macro2` now has a minimum version of `1.0.80`. +- `enum-ordinalize` was updated from `4.2` to `4.4`. +- The `rustversion` dev-dependency was removed. \ No newline at end of file diff --git a/CHANGELOG-0.6.0-to-0.7.0.md b/CHANGELOG-0.6.0-to-0.7.0.md new file mode 100644 index 0000000..4721583 --- /dev/null +++ b/CHANGELOG-0.6.0-to-0.7.0.md @@ -0,0 +1,54 @@ +# Changelog: 0.6.0 to 0.7.0 + +This document compares `v0.6.0` with the current `0.7.0` worktree. It focuses on changes that can affect users of the crate. Internal refactoring details are not listed. + +## Breaking and Upgrade Notes + +- The minimum supported Rust version was raised from Rust 1.60 to Rust 1.89. +- The crate now uses the Rust 2024 edition. +- Automatic trait bounds were redesigned again. The generated `where` clauses may differ from 0.6.0, especially for wrapper types, recursive types, and traits with prerequisite traits. +- Precise generated bounds can show private field types in docs, compiler errors, or public `where` clauses. Check public types whose generated impl bounds are part of your API. +- `Eq` is now treated as a marker trait. Field-level equality settings such as `ignore` and `method` should be written on `PartialEq`, not on `Eq`. +- `Into` derives now generate `impl From for Target` by default. Calling `value.into()` still works through Rust's blanket impl, and `Target::from(value)` also works. Use `#[educe(Into(Target, into))]` if you need a direct one-way `Into` impl instead. +- If you already wrote your own `From for Target` impl, check for conflicts with the new default `Into` behavior. + +## Trait Bounds + +- Automatic bounds are now documented with one shared rule set. +- Ignored fields and fields handled by a custom `method` do not add automatic bounds for that trait. +- Field types without generic parameters do not add constant bounds. +- Common standard library wrapper types are handled more carefully. For example, `Option` can add `T: Trait`, and `Vec>` can add only the needed inner bound. +- Types that always implement a trait, such as `PhantomData`, raw pointers, function pointers, and selected standard library types, can avoid unnecessary bounds. +- Other field types use precise bounds such as `Wrapper: Clone` instead of always falling back to `T: Clone`. +- Self-recursive generic types are handled better. For example, a type like `List` containing `Box>` can avoid trait solver overflow by adding bounds on the type parameters. +- Mutually recursive generic types are still a known limitation. Use `bound(*)` or a custom `bound(...)` for those cases. +- Related traits can inherit generated predicates when they are derived together by Educe: `Copy` from `Clone`, `Eq` and `PartialOrd` from `PartialEq`, and `Ord` from `Eq` and `PartialOrd`. +- Explicit `bound(...)` values are used as written. If an explicit bound does not satisfy a required supertrait, the compiler will ask for the missing bound. + +## Trait Behavior + +- `Debug` output handles field names as strings. Tuple field names and custom names are printed more correctly, including tuple indexes such as `0`. +- `Copy`, `Clone`, `Eq`, `PartialOrd`, and `Ord` derive behavior is more consistent with their real Rust trait relationships. +- `Copy` can work with an Educe-generated or built-in `Clone` impl, while still respecting the bounds that `Copy` really needs. +- `PartialOrd` and `Ord` work together more consistently. When both are derived, `PartialOrd` can follow `Ord` field settings such as `ignore`, `rank`, and `method` so that `partial_cmp` and `cmp` stay consistent. +- When both comparison traits are derived, `Ord` can follow `PartialOrd` field settings for `ignore` and `rank`. A `PartialOrd` method cannot be reused for `Ord` because it returns `Option`. +- `Into` supports the new default `From` generation for concrete target types and for generic target types whose type parameters are covered by the target type. + +## Generated Code and Lints + +- Generated impls are marked with `#[automatically_derived]`. +- Lint-level attributes from the input type, such as `allow`, `expect`, `warn`, and `deny`, are copied to generated impls. This makes generated code behave more like built-in derives under strict lint settings. +- Diagnostics for invalid attributes and helper parsing were improved. + +## Documentation and Safety Notes + +- The README now has a dedicated Trait Bounds section that explains the automatic bound rules, bound inheritance, `bound(*)`, and known limitations. +- Documentation for unsafe union derives is clearer. It now states that `Debug`, `PartialEq`, and `Hash` union impls may read, compare, or hash the whole union memory, including padding bytes. +- The trait documentation was updated so each trait points to the shared bound rules instead of repeating older simplified rules. + +## Dependency and Package Changes + +- `quote` now has a minimum version of `1.0.44`. +- `proc-macro2` now has a minimum version of `1.0.80`. +- `enum-ordinalize` was updated from `4.2` to `4.4`. +- The `rustversion` dev-dependency was removed. \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index cb192a2..df05075 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,9 @@ [package] name = "educe" -version = "0.6.0" +version = "0.7.0" authors = ["Magic Len "] -edition = "2021" -rust-version = "1.60" +edition = "2024" +rust-version = "1.89" repository = "https://github.com/magiclen/educe" homepage = "https://magiclen.org/educe" keywords = ["derive", "macro", "trait", "field", "procedural"] @@ -17,15 +17,14 @@ proc-macro = true [dependencies] syn = "2" -quote = "1" -proc-macro2 = "1" +quote = "1.0.44" +proc-macro2 = "1.0.80" -enum-ordinalize = { version = "4.2", default-features = false, features = ["derive"] } +enum-ordinalize = { version = "4.4", default-features = false, features = ["derive"] } [dev-dependencies] syn = { version = "2", features = ["full"] } assert-eq-float = "0.1" -rustversion = "1" [features] default = ["Debug", "Clone", "Copy", "PartialEq", "Eq", "PartialOrd", "Ord", "Hash", "Default", "Deref", "DerefMut", "Into"] diff --git a/README.md b/README.md index 5f266ac..5da2535 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,75 @@ features = ["Debug", "Clone", "Copy", "Hash", "Default"] default-features = false ``` +## Trait Bounds + +When a trait is derived with Educe and no explicit `bound` is set, the where predicates of the generated impl are determined automatically. Every field type that the generated code touches (ignored fields and fields handled by a custom `method` are excluded) is processed with the following rules, in order: + +1. A type that is known to implement the trait unconditionally produces no predicate at all. This covers `PhantomData`, raw pointers, and function pointers for every trait, shared references for `Clone` and `Copy`, plus the types in table A. +2. A type that does not use any generic type parameter produces no predicate, because such a predicate would be constant. +3. A std type that implements the trait whenever its type arguments do (table B) produces the predicates of its type arguments instead, with these rules applied recursively: a field of type `Option` produces `T: Trait`, and one of type `Vec>` produces just `T: Clone` for `Clone`. +4. A type that mentions the derived type itself (e.g. `Box>` inside `List`) produces `Param: Trait` bounds for the type parameters it uses, because a self-referencing predicate would overflow the trait solver (E0275). +5. Any other type produces the precise predicate `FieldType: Trait`, so the compiler verifies the real requirement: a field of type `Wrapper` where `Wrapper` has its own conditional `Clone` impl produces `Wrapper: Clone`, which works for exactly the type arguments that `Wrapper` supports. + +Table A — types whose type arguments never need a bound: + +| Trait | Types | +|-------|-------| +| `Clone`, `Copy` | `Arc`, `Rc`, `Weak`, `NonNull`, `Cow`, `Discriminant` | +| `Debug` | `Weak`, `NonNull`, `AtomicPtr`, `Discriminant` | +| `PartialEq`, `Eq`, `Hash` | `NonNull`, `Discriminant` | +| `PartialOrd`, `Ord` | `NonNull` | +| `Default` | `Option`, `Vec`, `VecDeque`, `LinkedList`, `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, `Weak` | + +Table B — types that forward the trait to their type arguments: + +| Trait | Types | +|-------|-------| +| `Clone` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `BinaryHeap`, `HashMap`, `HashSet`, `RefCell`, `Wrapping`, `Reverse`, `Saturating` | +| `Copy` | `Option`, `Result`, `Wrapping`, `Reverse`, `Saturating` | +| `Debug` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `BinaryHeap`, `HashMap`, `HashSet`, `Arc`, `Rc`, `RefCell`, `Mutex`, `RwLock`, `Wrapping`, `Reverse`, `Saturating` | +| `PartialEq`, `Eq`, `PartialOrd`, `Ord` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `Arc`, `Rc`, `RefCell`, `Wrapping`, `Reverse`, `Saturating` | +| `Hash` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `Arc`, `Rc`, `Wrapping`, `Reverse`, `Saturating` | +| `Default` | `Box`, `Arc`, `Rc`, `Cell`, `RefCell`, `Mutex`, `RwLock`, `Wrapping`, `Reverse`, `Saturating` | + +`HashMap` and `HashSet` are not in the comparison rows of table B because their comparison impls additionally require `K: Eq + Hash`; such fields get the precise whole-type predicate from rule 5 instead. + +Both tables match type names syntactically (by the last path segment), so a user-defined type that happens to share a name with one of these std types is treated the same way; if the resulting bounds do not fit such a type, set them explicitly with `bound(...)`. + +###### Bound Inheritance + +When related traits are derived together with automatic bounds, a trait inherits the final predicates of its prerequisite traits: `Eq` and `PartialOrd` inherit from `PartialEq`, `Ord` inherits from `Eq` and `PartialOrd`, and `Copy` inherits from `Clone`. This way, a custom bound like `#[educe(PartialEq(bound(T: MyTrait)), Eq)]` automatically carries `T: MyTrait` into the `Eq` impl. + +Educe cannot see the traits derived by other derive macros, including the built-in ones, so inheritance only applies between traits listed in the same `#[educe(...)]` attribute; a prerequisite trait implemented elsewhere contributes nothing. + +###### Controlling the Bounds + +* `bound(where_predicates)` or `bound = "where_predicates"` uses exactly the given predicates, without inheritance. +* `bound(*)` adds `Param: Trait` for every generic type parameter, like the built-in derives. +* `bound(false)` adds no predicates at all. + +An explicit bound is used verbatim; if a prerequisite impl carries predicates that the explicit bound does not imply, the compiler reports an unsatisfied supertrait and the missing predicates have to be added by hand. + +###### Limitations + +* Mutually recursive generic types (an `A` containing `Vec>` while `B` contains `A`) cannot be detected from a single type definition, so automatic bounds make the trait solver overflow (E0275) on them; use `bound(*)` or a custom bound for such types. +* The precise predicates appear in the public where clause of the impl, so private field types become visible in documentation and error messages, and changing a private field type can change the public bounds of the impl. + ## Traits +* [Debug](#debug) +* [Clone](#clone) +* [Copy](#copy) +* [PartialEq](#partialeq) +* [Eq](#eq) +* [PartialOrd](#partialord) +* [Ord](#ord) +* [Hash](#hash) +* [Default](#default) +* [Deref](#deref) +* [DerefMut](#derefmut) +* [Into](#into) + #### Debug Use `#[derive(Educe)]` and `#[educe(Debug)]` to implement the `Debug` trait for a struct, enum, or union. This allows you to modify the names of your types, variants, and fields. You can also choose to ignore specific fields or set a method to replace the `Debug` trait. Additionally, you have the option to format a struct as a tuple and vice versa. @@ -166,7 +233,7 @@ enum Enum { ###### Generic Parameters Bound to the `Debug` Trait or Others -Generic parameters will be automatically bound to the `Debug` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust use educe::Educe; @@ -211,26 +278,9 @@ enum Enum { In the above case, `T` is bound to the `Debug` trait, but `K` is not. -Or, you can have `educe` replicate the behaviour of `std`'s `derive`'s, where a bound is produced for *every* generic parameter, without regard to how it's used in the structure: - -```rust -use educe::Educe; - -#[derive(Educe)] -#[educe(Debug(bound(*)))] -struct Struct { - #[educe(Debug(ignore))] - f: T, -} -``` - -This can be useful if you don't want to make the trait implementation part of your permanent public API. In this example, `Struct` doesn't implement `Debug` unless `T` does. I.e., it has a `T: Debug` bound even though that's not needed right now. Later we might want to display `f`; we wouldn't then need to make a breaking API change by adding the bound. - -This was the behaviour of `Trait(bound)` in educe 0.4.x and earlier. - ###### Union -A union will be formatted as a `u8` slice because we don't know its fields at runtime. The fields of a union cannot be ignored, renamed, or formatted with other methods. The implementation is **unsafe** because it may expose uninitialized memory. +A union is formatted as a `u8` slice, because its active field cannot be known at runtime. The fields of a union cannot be ignored, renamed, or formatted with other methods. The implementation is **unsafe** because it deliberately reads the whole memory of the union, including any padding bytes, which are not required to be initialized; the output may therefore expose uninitialized memory. ```rust use educe::Educe; @@ -305,7 +355,7 @@ enum Enum { ###### Generic Parameters Bound to the `Clone` Trait or Others -Generic parameters will be automatically bound to the `Clone` trait if necessary. If the `#[educe(Copy)]` attribute exists, they will be bound to the `Copy` trait. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust use educe::Educe; @@ -352,27 +402,6 @@ enum Enum { In the above case, `T` is bound to the `Clone` trait, but `K` is not. -Or, you can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -use educe::Educe; - -trait A { - fn add(&self, rhs: u8) -> Self; -} - -fn clone(v: &T) -> T { - v.add(100) -} - -#[derive(Educe)] -#[educe(Clone(bound(*)))] -struct Struct { - #[educe(Clone(method(clone)))] - f: T, -} -``` - ###### Union Refer to the introduction of the `#[educe(Copy)]` attribute. @@ -405,7 +434,7 @@ enum Enum { ###### Generic Parameters Bound to the `Copy` Trait or Others -All generic parameters will be automatically bound to the `Copy` trait. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Copy` impl additionally inherits the predicates of the `Clone` impl generated by Educe, because `Copy` requires `Clone`. ```rust use educe::Educe; @@ -557,7 +586,7 @@ enum Enum { ###### Generic Parameters Bound to the `PartialEq` Trait or Others -Generic parameters will be automatically bound to the `PartialEq` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust use educe::Educe; @@ -602,21 +631,6 @@ enum Enum { } ``` -In the above case, `T` is bound to the `PartialEq` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -use educe::Educe; - -#[derive(Educe)] -#[educe(PartialEq(bound(*)))] -struct Struct { - #[educe(PartialEq(ignore))] - f: T, -} -``` - ###### Union The `#[educe(PartialEq(unsafe))]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. @@ -634,7 +648,7 @@ union Union { #### Eq -Use `#[derive(Educe)]` and `#[educe(Eq)]` to implement the `Eq` trait for a struct, enum, or union. You can also choose to ignore specific fields or set a method to replace the `PartialEq` trait. +Use `#[derive(Educe)]` and `#[educe(Eq)]` to implement the `Eq` trait for a struct, enum, or union. `Eq` is a marker trait, so it has no field attributes of its own; field-level equality settings such as `ignore` and `method` belong to the `PartialEq` attribute. ###### Basic Usage @@ -658,72 +672,9 @@ enum Enum { } ``` -###### Ignore Fields +###### Generic Parameters Bound to the `Eq` Trait or Others -The `ignore` parameter can ignore a specific field. - -```rust -use educe::Educe; - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -struct Struct { - #[educe(Eq(ignore))] - f1: u8 -} - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -enum Enum { - V1, - V2 { - #[educe(Eq(ignore))] - f1: u8, - }, - V3( - #[educe(Eq(ignore))] - u8 - ), -} -``` - -###### Use Another Method to Perform Comparison - -The `method` parameter can be utilized to replace the implementation of the `Eq` trait for a field, eliminating the need to implement the `PartialEq` trait for the type of that field. - -```rust -use educe::Educe; - -fn eq(a: &u8, b: &u8) -> bool { - a + 1 == *b -} - -trait A { - fn is_same(&self, other: &Self) -> bool; -} - -fn eq2(a: &T, b: &T) -> bool { - a.is_same(b) -} - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -enum Enum { - V1, - V2 { - #[educe(Eq(method(eq)))] - f1: u8, - }, - V3( - #[educe(Eq(method(eq2)))] - T - ), -} -``` - -###### Generic Parameters Bound to the `PartialEq` Trait or Others - -Generic parameters will be automatically bound to the `PartialEq` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Eq` impl also inherits the predicates of the `PartialEq` impl generated by Educe. ```rust use educe::Educe; @@ -755,11 +706,14 @@ fn eq(a: &T, b: &T) -> bool { } #[derive(Educe)] -#[educe(PartialEq(bound(T: std::cmp::PartialEq, K: A)), Eq)] +#[educe( + PartialEq(bound(T: std::cmp::PartialEq, K: A)), + Eq(bound(T: std::cmp::Eq, K: A)) +)] enum Enum { V1, V2 { - #[educe(Eq(method(eq)))] + #[educe(PartialEq(method(eq)))] f1: K, }, V3( @@ -770,7 +724,7 @@ enum Enum { ###### Union -The `#[educe(PartialEq(unsafe), Eq)]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. +The `#[educe(PartialEq(unsafe), Eq)]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it deliberately compares the whole memory of the two unions byte by byte, including any padding bytes, while disregarding the specific fields it utilizes. ```rust use educe::Educe; @@ -842,6 +796,8 @@ enum Enum { The `method` parameter can be utilized to replace the implementation of the `PartialOrd` trait for a field, eliminating the need to implement the `PartialOrd` trait for the type of that field. +When `Ord` is derived together, a field without its own `PartialOrd` attribute follows the `ignore`, `rank`, and `method` settings of its `Ord` attribute, so `partial_cmp` stays consistent with `cmp`; the result of an `Ord` comparison method is wrapped in `Some` automatically. + ```rust use educe::Educe; @@ -914,7 +870,7 @@ enum Enum { ###### Generic Parameters Bound to the `PartialOrd` Trait or Others -Generic parameters will be automatically bound to the `PartialOrd` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `PartialOrd` impl also inherits the predicates of the `PartialEq` impl generated by Educe. ```rust use educe::Educe; @@ -948,7 +904,7 @@ fn partial_cmp(a: &T, b: &T) -> Option { } #[derive(PartialEq, Educe)] -#[educe(PartialOrd(bound(T: std::cmp::PartialOrd, K: PartialEq + A)))] +#[educe(PartialOrd(bound(T: std::cmp::PartialOrd, K: std::cmp::PartialOrd + A)))] enum Enum { V1, V2 { @@ -961,21 +917,6 @@ enum Enum { } ``` -In the above case, `T` is bound to the `PartialOrd` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -use educe::Educe; - -#[derive(PartialEq, Educe)] -#[educe(PartialOrd(bound(*)))] -struct Struct { - #[educe(PartialOrd(ignore))] - f: T, -} -``` - #### Ord Use `#[derive(Educe)]` and `#[educe(Ord)]` to implement the `Ord` trait for a struct or enum. You can also choose to ignore specific fields or set a method to replace the `Ord` trait. @@ -1035,6 +976,8 @@ enum Enum { The `method` parameter can be utilized to replace the implementation of the `Ord` trait for a field, eliminating the need to implement the `Ord` trait for the type of that field. +When `PartialOrd` is derived together, a field without its own `Ord` attribute follows the `ignore` and `rank` settings of its `PartialOrd` attribute; a `PartialOrd` comparison method returns an `Option` and cannot be used by `cmp`, so such a field is compared with the built-in comparison. + ```rust use educe::Educe; @@ -1107,7 +1050,7 @@ enum Enum { ###### Generic Parameters Bound to the `Ord` Trait or Others -Generic parameters will be automatically bound to the `Ord` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Ord` impl also inherits the predicates of the `Eq` and `PartialOrd` impls generated by Educe. ```rust use educe::Educe; @@ -1141,11 +1084,14 @@ fn cmp(a: &T, b: &T) -> Ordering { } #[derive(PartialEq, Eq, Educe)] -#[educe(PartialOrd, Ord(bound(T: std::cmp::Ord, K: std::cmp::Ord + A)))] +#[educe( + PartialOrd(bound(T: std::cmp::PartialOrd, K: std::cmp::PartialEq + A)), + Ord(bound(T: std::cmp::Ord, K: std::cmp::Ord + A)) +)] enum Enum { V1, V2 { - #[educe(PartialOrd(method(cmp)))] + #[educe(Ord(method(cmp)))] f1: K, }, V3( @@ -1243,7 +1189,7 @@ enum Enum { ###### Generic Parameters Bound to the `Hash` Trait or Others -Generic parameters will be automatically bound to the `Hash` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust use educe::Educe; @@ -1290,24 +1236,9 @@ enum Enum { } ``` -In the above case, `T` is bound to the `Hash` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -use educe::Educe; - -#[derive(Educe)] -#[educe(Hash(bound(*)))] -struct Struct { - #[educe(Hash(ignore))] - f: T, -} -``` - ###### Union -The `#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]` attribute can be used for a union. The fields of a union cannot be hashed with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. +The `#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]` attribute can be used for a union. The fields of a union cannot be hashed with other methods. The implementation is **unsafe** because it deliberately hashes the whole memory of the union byte by byte, including any padding bytes, while disregarding the specific fields it utilizes. ```rust use educe::Educe; @@ -1388,6 +1319,8 @@ union Union { You may need to activate the `full` feature to enable support for advanced expressions. +Note that the expression is pasted into the generated `default` method verbatim, so for a generic type it has to be valid for every possible instantiation; an expression producing a concrete type does not work for a generic field. + ###### The Default Values for Specific Fields ```rust @@ -1450,7 +1383,7 @@ union Union { ###### Generic Parameters Bound to the `Default` Trait or Others -Generic parameters will be automatically bound to the `Default` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust use educe::Educe; @@ -1581,7 +1514,13 @@ The mutable dereferencing fields do not need to be the same as the immutable der #### Into -Use `#[derive(Educe)]` and `#[educe(Into(type))]` to implement the `Into` trait for a struct or enum. +Use `#[derive(Educe)]` and `#[educe(Into(type))]` to make a struct or enum convertible into another type. + +Educe generates an `impl From for type`, which automatically provides the corresponding `Into` through the standard library's blanket implementation. Use the bare `into` flag — `#[educe(Into(type, into))]` — to generate a direct `impl Into` instead. + +###### The `into` Flag + +A `From` impl also lets callers write `Target::from(value)`, whereas a direct `Into` impl only supports `value.into()`. Use the `into` flag when you deliberately want the conversion to be one-directional, exposed only as `value.into()`. ###### Basic Usage @@ -1637,7 +1576,7 @@ enum Enum { ###### Generic Parameters Bound to the `Into` Trait or Others -Generic parameters will be automatically bound to the `Into` trait if necessary. +A generic type parameter is automatically bound to `Into` only when it is itself the type of a field, because a nested parameter cannot meaningfully receive an `Into` bound. ```rust use educe::Educe; diff --git a/src/common/attributes.rs b/src/common/attributes.rs new file mode 100644 index 0000000..d14345e --- /dev/null +++ b/src/common/attributes.rs @@ -0,0 +1,23 @@ +use quote::{ToTokens, quote}; +use syn::Attribute; + +/// Builds the attributes that every generated impl carries. +/// +/// `#[automatically_derived]` tells the compiler and lints such as clippy that the impl is machine generated, and the lint-level attributes (`allow`/`expect`/`warn`/`deny`) written on the derive input are copied onto the impl, so user lint settings also cover the generated code just like with the built-in derives. +pub(crate) fn generated_impl_attributes(attributes: &[Attribute]) -> proc_macro2::TokenStream { + let mut token_stream = quote!(#[automatically_derived]); + + for attribute in attributes { + let path = attribute.path(); + + if path.is_ident("allow") + || path.is_ident("expect") + || path.is_ident("warn") + || path.is_ident("deny") + { + attribute.to_tokens(&mut token_stream); + } + } + + token_stream +} diff --git a/src/common/bound.rs b/src/common/bound.rs index 78f9aed..45d3eaa 100644 --- a/src/common/bound.rs +++ b/src/common/bound.rs @@ -1,16 +1,167 @@ -use syn::{punctuated::Punctuated, token::Comma, GenericParam, Meta, Path, Type, WherePredicate}; +use syn::{ + GenericParam, Ident, Meta, Path, Type, WherePredicate, punctuated::Punctuated, token::Comma, +}; + +use crate::common::{ + r#type::BoundExceptions, + where_predicates_bool::{ + WherePredicates, WherePredicatesOrBool, + create_where_predicates_from_all_generic_parameters, + create_where_predicates_from_bare_field_types, create_where_predicates_from_field_types, + meta_2_where_predicates, + }, +}; + +/// The exception table for `Clone`: `Arc`-like std types clone by duplicating a pointer or a marker, and the container types forward `Clone` to their type arguments. +pub(crate) const BOUND_EXCEPTIONS_CLONE: BoundExceptions = BoundExceptions { + unconditional_types: &["Arc", "Rc", "Weak", "NonNull", "Cow", "Discriminant"], + forwarding_types: &[ + "Option", + "Result", + "Box", + "Vec", + "VecDeque", + "LinkedList", + "BTreeMap", + "BTreeSet", + "BinaryHeap", + "HashMap", + "HashSet", + "RefCell", + "Wrapping", + "Reverse", + "Saturating", + ], + shared_reference_is_unconditional: true, +}; + +/// The exception table for `Copy`: only the by-value std wrappers forward `Copy` to their type arguments, because the heap-owning containers are never `Copy`. +pub(crate) const BOUND_EXCEPTIONS_COPY: BoundExceptions = BoundExceptions { + unconditional_types: &["Arc", "Rc", "Weak", "NonNull", "Cow", "Discriminant"], + forwarding_types: &["Option", "Result", "Wrapping", "Reverse", "Saturating"], + shared_reference_is_unconditional: true, +}; + +/// The exception table for `Debug`: some std types print an address or a fixed placeholder instead of formatting their type arguments, and the container types forward `Debug` to them. +pub(crate) const BOUND_EXCEPTIONS_DEBUG: BoundExceptions = BoundExceptions { + unconditional_types: &["Weak", "NonNull", "AtomicPtr", "Discriminant"], + forwarding_types: &[ + "Option", + "Result", + "Box", + "Vec", + "VecDeque", + "LinkedList", + "BTreeMap", + "BTreeSet", + "BinaryHeap", + "HashMap", + "HashSet", + "Arc", + "Rc", + "RefCell", + "Mutex", + "RwLock", + "Wrapping", + "Reverse", + "Saturating", + ], + shared_reference_is_unconditional: false, +}; + +/// The common forwarding list for the comparison traits: these std types implement `PartialEq`/`Eq`/`PartialOrd`/`Ord` whenever their type arguments do. +/// +/// `HashMap` and `HashSet` are deliberately absent, because their comparison impls additionally require `K: Eq + Hash`, which a plain forwarded bound would not carry. +const FORWARDING_TYPES_COMPARISON: &[&str] = &[ + "Option", + "Result", + "Box", + "Vec", + "VecDeque", + "LinkedList", + "BTreeMap", + "BTreeSet", + "Arc", + "Rc", + "RefCell", + "Wrapping", + "Reverse", + "Saturating", +]; + +/// The exception table for `PartialEq` and `Eq`: `NonNull` and `Discriminant` compare by address or by an opaque token. +pub(crate) const BOUND_EXCEPTIONS_EQUALITY: BoundExceptions = BoundExceptions { + unconditional_types: &["NonNull", "Discriminant"], + forwarding_types: FORWARDING_TYPES_COMPARISON, + shared_reference_is_unconditional: false, +}; + +/// The exception table for `Hash`: like the equality table, but `RefCell` has no `Hash` impl to forward. +pub(crate) const BOUND_EXCEPTIONS_HASH: BoundExceptions = BoundExceptions { + unconditional_types: &["NonNull", "Discriminant"], + forwarding_types: &[ + "Option", + "Result", + "Box", + "Vec", + "VecDeque", + "LinkedList", + "BTreeMap", + "BTreeSet", + "Arc", + "Rc", + "Wrapping", + "Reverse", + "Saturating", + ], + shared_reference_is_unconditional: false, +}; + +/// The exception table for `PartialOrd` and `Ord`: `NonNull` orders by address. +pub(crate) const BOUND_EXCEPTIONS_ORDER: BoundExceptions = BoundExceptions { + unconditional_types: &["NonNull"], + forwarding_types: FORWARDING_TYPES_COMPARISON, + shared_reference_is_unconditional: false, +}; -use crate::common::where_predicates_bool::{ - create_where_predicates_from_all_generic_parameters, - create_where_predicates_from_generic_parameters_check_types, meta_2_where_predicates, - WherePredicates, WherePredicatesOrBool, +/// The exception table for `Default`: the std containers default to an empty value regardless of their type arguments, and the by-value wrappers forward `Default` to them. +pub(crate) const BOUND_EXCEPTIONS_DEFAULT: BoundExceptions = BoundExceptions { + unconditional_types: &[ + "Option", + "Vec", + "VecDeque", + "LinkedList", + "HashMap", + "HashSet", + "BTreeMap", + "BTreeSet", + "Weak", + ], + forwarding_types: &[ + "Box", + "Arc", + "Rc", + "Cell", + "RefCell", + "Mutex", + "RwLock", + "Wrapping", + "Reverse", + "Saturating", + ], + shared_reference_is_unconditional: false, }; +/// How the where clause of a generated impl should be determined, parsed from the `bound` parameter of an `#[educe(...)]` attribute. pub(crate) enum Bound { + /// `bound(false)`: add no predicates at all. Disabled, + /// No `bound` parameter: let Educe work the predicates out from the field types, and inherit the predicates of prerequisite traits. Auto, - Custom(WherePredicates), + /// `bound(*)`: add `Param: Trait` for every generic type parameter, matching the built-in derives. All, + /// `bound(...)` / `bound = "..."`: use exactly the given predicates. + Custom(WherePredicates), } impl Bound { @@ -35,23 +186,45 @@ impl Bound { } impl Bound { + /// Turns the parsed bound setting into concrete where predicates for a generated impl. + /// + /// `types` are the field types that the generated code actually touches, `self_ident` is the name of the type being derived, and `exceptions` is the trait's table of unconditional implementations. #[inline] pub(crate) fn into_where_predicates_by_generic_parameters_check_types( self, params: &Punctuated, bound_trait: &Path, types: &[&Type], - supertraits: &[proc_macro2::TokenStream], + self_ident: &Ident, + exceptions: &BoundExceptions, ) -> Punctuated { match self { Self::Disabled => Punctuated::new(), - Self::Auto => create_where_predicates_from_generic_parameters_check_types( + Self::Auto => create_where_predicates_from_field_types( + params, bound_trait, types, - supertraits, + self_ident, + exceptions, ), + Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait), Self::Custom(where_predicates) => where_predicates, + } + } + + /// A shallow variant used by the `Into` handler, where only field types that are a bare generic parameter can meaningfully receive a bound. + #[inline] + pub(crate) fn into_where_predicates_by_generic_parameters_check_types_shallow( + self, + params: &Punctuated, + bound_trait: &Path, + types: &[&Type], + ) -> Punctuated { + match self { + Self::Disabled => Punctuated::new(), + Self::Auto => create_where_predicates_from_bare_field_types(params, bound_trait, types), Self::All => create_where_predicates_from_all_generic_parameters(params, bound_trait), + Self::Custom(where_predicates) => where_predicates, } } } diff --git a/src/common/expr.rs b/src/common/expr.rs index ab49d3a..e2c4796 100644 --- a/src/common/expr.rs +++ b/src/common/expr.rs @@ -1,5 +1,5 @@ -use quote::{quote, ToTokens}; -use syn::{spanned::Spanned, Expr, Lit, Meta, Type}; +use quote::{ToTokens, quote}; +use syn::{Expr, Lit, Meta, Type}; use super::path::path_to_string; @@ -13,8 +13,8 @@ pub(crate) fn meta_2_expr(meta: &Meta) -> syn::Result { match &meta { Meta::NameValue(name_value) => Ok(name_value.value.clone()), Meta::List(list) => list.parse_args::(), - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!("expected `{path} = Expr` or `{path}(Expr)`", path = path_to_string(path)), )), } @@ -86,16 +86,15 @@ pub(crate) fn auto_adjust_expr(expr: Expr, ty: Option<&Type>) -> Expr { } }, Lit::ByteStr(_) => { - if let Some(Type::Reference(ty)) = ty { - if let Type::Array(ty) = ty.elem.as_ref() { - if let Type::Path(ty) = ty.elem.as_ref() { - let ty_string = ty.into_token_stream().to_string(); + if let Some(Type::Reference(ty)) = ty + && let Type::Array(ty) = ty.elem.as_ref() + && let Type::Path(ty) = ty.elem.as_ref() + { + let ty_string = ty.into_token_stream().to_string(); - if ty_string == "u8" { - // don't call into - return expr; - } - } + if ty_string == "u8" { + // don't call into + return expr; } } }, diff --git a/src/common/ident_bool.rs b/src/common/ident_bool.rs index 89bf2fb..14b5429 100644 --- a/src/common/ident_bool.rs +++ b/src/common/ident_bool.rs @@ -1,7 +1,6 @@ use syn::{ - parse::{Parse, ParseStream}, - spanned::Spanned, Expr, Ident, Lit, LitBool, LitStr, Meta, MetaNameValue, + parse::{Parse, ParseStream}, }; use super::path::path_to_string; @@ -23,7 +22,7 @@ impl Parse for IdentOrBool { Ok(ident) => Ok(Self::Ident(ident)), Err(_) if lit.value().is_empty() => Ok(Self::Bool(false)), Err(error) => Err(error), - } + }; }, _ => (), } @@ -49,8 +48,8 @@ pub(crate) fn meta_name_value_2_ident(name_value: &MetaNameValue) -> syn::Result _ => (), } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!("expected `{path} = Ident`", path = path_to_string(&name_value.path)), )) } @@ -66,23 +65,24 @@ pub(crate) fn meta_2_ident(meta: &Meta) -> syn::Result { list.parse_args() } }, - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!("expected `{path} = Ident` or `{path}(Ident)`", path = path_to_string(path)), )), } } +// The helpers in this module parse the small `name = value` and flag-style parameters that appear inside the educe attributes. #[inline] pub(crate) fn meta_name_value_2_bool(name_value: &MetaNameValue) -> syn::Result { - if let Expr::Lit(lit) = &name_value.value { - if let Lit::Bool(b) = &lit.lit { - return Ok(b.value); - } + if let Expr::Lit(lit) = &name_value.value + && let Lit::Bool(b) = &lit.lit + { + return Ok(b.value); } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!("expected `{path} = false`", path = path_to_string(&name_value.path)), )) } @@ -92,8 +92,8 @@ pub(crate) fn meta_2_bool(meta: &Meta) -> syn::Result { match &meta { Meta::NameValue(name_value) => meta_name_value_2_bool(name_value), Meta::List(list) => Ok(list.parse_args::()?.value), - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!("expected `{path} = false` or `{path}(false)`", path = path_to_string(path)), )), } @@ -136,8 +136,8 @@ pub(crate) fn meta_name_value_2_ident_and_bool( _ => (), } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!( "expected `{path} = Ident` or `{path} = false`", path = path_to_string(&name_value.path) @@ -150,8 +150,8 @@ pub(crate) fn meta_2_ident_and_bool(meta: &Meta) -> syn::Result { match &meta { Meta::NameValue(name_value) => meta_name_value_2_ident_and_bool(name_value), Meta::List(list) => list.parse_args::(), - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!( "expected `{path} = Ident`, `{path}(Ident)`, `{path} = false`, or `{path}(false)`", path = path_to_string(path) diff --git a/src/common/ident_index.rs b/src/common/ident_index.rs index c0e38d4..44c3147 100644 --- a/src/common/ident_index.rs +++ b/src/common/ident_index.rs @@ -1,6 +1,7 @@ use quote::ToTokens; use syn::{Ident, Index}; +/// A field accessor that is either a name (for named fields) or a position index (for tuple fields). pub(crate) enum IdentOrIndex { Ident(Ident), Index(Index), @@ -47,10 +48,6 @@ impl ToTokens for IdentOrIndex { impl IdentOrIndex { #[inline] pub(crate) fn from_ident_with_index(ident: Option<&Ident>, index: usize) -> IdentOrIndex { - if let Some(ident) = ident { - Self::from(ident) - } else { - Self::from(index) - } + if let Some(ident) = ident { Self::from(ident) } else { Self::from(index) } } } diff --git a/src/common/int.rs b/src/common/int.rs index ae52b49..c25a480 100644 --- a/src/common/int.rs +++ b/src/common/int.rs @@ -1,7 +1,8 @@ -use syn::{spanned::Spanned, Expr, Lit, Meta, MetaNameValue, UnOp}; +use syn::{Expr, Lit, Meta, MetaNameValue, UnOp}; use super::path::path_to_string; +// These helpers parse integer parameters such as `rank = -5`, accepting both bare integers and integers in strings. #[inline] pub(crate) fn meta_name_value_2_isize(name_value: &MetaNameValue) -> syn::Result { match &name_value.value { @@ -10,29 +11,26 @@ pub(crate) fn meta_name_value_2_isize(name_value: &MetaNameValue) -> syn::Result return lit .value() .parse::() - .map_err(|error| syn::Error::new(lit.span(), error)) + .map_err(|error| syn::Error::new_spanned(lit, error)); }, Lit::Int(lit) => return lit.base10_parse(), _ => (), }, Expr::Unary(unary) => { - if let UnOp::Neg(_) = unary.op { - if let Expr::Lit(lit) = unary.expr.as_ref() { - if let Lit::Int(lit) = &lit.lit { - let s = format!("-{}", lit.base10_digits()); + if let UnOp::Neg(_) = unary.op + && let Expr::Lit(lit) = unary.expr.as_ref() + && let Lit::Int(lit) = &lit.lit + { + let s = format!("-{}", lit.base10_digits()); - return s - .parse::() - .map_err(|error| syn::Error::new(lit.span(), error)); - } - } + return s.parse::().map_err(|error| syn::Error::new_spanned(lit, error)); } }, _ => (), } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!("expected `{path} = integer`", path = path_to_string(&name_value.path)), )) } @@ -45,15 +43,16 @@ pub(crate) fn meta_2_isize(meta: &Meta) -> syn::Result { let lit = list.parse_args::()?; match &lit { - Lit::Str(lit) => { - lit.value().parse::().map_err(|error| syn::Error::new(lit.span(), error)) - }, + Lit::Str(lit) => lit + .value() + .parse::() + .map_err(|error| syn::Error::new_spanned(lit, error)), Lit::Int(lit) => lit.base10_parse(), - _ => Err(syn::Error::new(lit.span(), "not an integer")), + _ => Err(syn::Error::new_spanned(lit, "not an integer")), } }, - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!( "expected `{path} = integer` or `{path}(integer)`", path = path_to_string(path) diff --git a/src/common/mod.rs b/src/common/mod.rs index 79e10bb..6619b04 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,7 @@ #[allow(dead_code)] +// Shared building blocks for the trait handlers; most submodules are only compiled when a feature that needs them is enabled. +pub(crate) mod attributes; +#[allow(dead_code)] pub(crate) mod bound; #[allow(dead_code)] pub(crate) mod path; diff --git a/src/common/path.rs b/src/common/path.rs index 18f80b2..0968215 100644 --- a/src/common/path.rs +++ b/src/common/path.rs @@ -1,6 +1,7 @@ use quote::ToTokens; -use syn::{spanned::Spanned, Expr, Lit, LitStr, Meta, MetaNameValue, Path}; +use syn::{Expr, Lit, LitStr, Meta, MetaNameValue, Path}; +// These helpers parse path parameters such as `method(my_function)` or `method = "my_function"`. #[inline] pub(crate) fn meta_name_value_2_path(name_value: &MetaNameValue) -> syn::Result { match &name_value.value { @@ -13,8 +14,8 @@ pub(crate) fn meta_name_value_2_path(name_value: &MetaNameValue) -> syn::Result< _ => (), } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!("expected `{path} = Path`", path = path_to_string(&name_value.path)), )) } @@ -30,8 +31,8 @@ pub(crate) fn meta_2_path(meta: &Meta) -> syn::Result { list.parse_args() } }, - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!("expected `{path} = Path` or `{path}(Path)`", path = path_to_string(path)), )), } diff --git a/src/common/tools/discriminant_type.rs b/src/common/tools/discriminant_type.rs index 5bc0d2f..dc7fced 100644 --- a/src/common/tools/discriminant_type.rs +++ b/src/common/tools/discriminant_type.rs @@ -1,10 +1,11 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{ToTokens, TokenStreamExt}; -use syn::{ - punctuated::Punctuated, spanned::Spanned, Data, DeriveInput, Expr, Lit, Meta, Token, UnOp, -}; +use syn::{Data, DeriveInput, Expr, Lit, Meta, Token, UnOp, punctuated::Punctuated}; #[derive(Debug)] +/// The integer type that holds the discriminant values of an enum. +/// +/// The enum comparison handlers read the discriminant by reinterpreting the leading bytes of an enum value as this type, so it has to match the actual layout. pub(crate) enum DiscriminantType { ISize, I8, @@ -67,6 +68,9 @@ impl ToTokens for DiscriminantType { } impl DiscriminantType { + /// Determines the discriminant type of an enum. + /// + /// An explicit `#[repr(intN)]` attribute wins; otherwise the explicit discriminant expressions are evaluated (implicit ones count up from the previous value) and the smallest integer type that covers their range is chosen, mirroring what the compiler does for the default representation. pub(crate) fn from_ast(ast: &DeriveInput) -> syn::Result { if let Data::Enum(data) = &ast.data { for attr in ast.attrs.iter() { @@ -76,15 +80,16 @@ impl DiscriminantType { let result = list.parse_args_with(Punctuated::::parse_terminated)?; - if let Some(value) = result.into_iter().next() { - if let Some(t) = Self::parse_str(value.to_string()) { - return Ok(t); - } + if let Some(value) = result.into_iter().next() + && let Some(t) = Self::parse_str(value.to_string()) + { + return Ok(t); } } } } + // Track the smallest and largest discriminant values while walking the variants; `counter` is the value the next variant gets when it has no explicit discriminant. let mut min = i128::MAX; let mut max = i128::MIN; let mut counter = 0i128; @@ -96,9 +101,9 @@ impl DiscriminantType { if let Lit::Int(lit) = &lit.lit { counter = lit .base10_parse() - .map_err(|error| syn::Error::new(lit.span(), error))?; + .map_err(|error| syn::Error::new_spanned(lit, error))?; } else { - return Err(syn::Error::new(lit.span(), "not an integer")); + return Err(syn::Error::new_spanned(lit, "not an integer")); } }, Expr::Unary(unary) => { @@ -116,27 +121,29 @@ impl DiscriminantType { { counter = i128::MIN; } else { - return Err(syn::Error::new(lit.span(), error)); + return Err(syn::Error::new_spanned( + lit, error, + )); } }, } } else { - return Err(syn::Error::new(lit.span(), "not an integer")); + return Err(syn::Error::new_spanned(lit, "not an integer")); } } else { - return Err(syn::Error::new( - unary.expr.span(), + return Err(syn::Error::new_spanned( + &unary.expr, "not a literal", )); } } else { - return Err(syn::Error::new( - unary.op.span(), + return Err(syn::Error::new_spanned( + unary.op, "this operation is not allow here", )); } }, - _ => return Err(syn::Error::new(exp.span(), "not a literal")), + _ => return Err(syn::Error::new_spanned(exp, "not a literal")), } } @@ -163,7 +170,7 @@ impl DiscriminantType { Self::I128 }) } else { - Err(syn::Error::new(ast.span(), "not an enum")) + Err(syn::Error::new_spanned(ast, "not an enum")) } } } diff --git a/src/common/tools/hash_type.rs b/src/common/tools/hash_type.rs index ece3d03..261296f 100644 --- a/src/common/tools/hash_type.rs +++ b/src/common/tools/hash_type.rs @@ -7,9 +7,12 @@ use std::{ use proc_macro2::Span; use quote::ToTokens; -use syn::{spanned::Spanned, Path, Type}; +use syn::{Path, Type, spanned::Spanned}; #[derive(Debug, Clone)] +/// A type made comparable and hashable by its canonical token string, so it can serve as an ordered map key. +/// +/// The span of the original tokens is kept for error reporting. pub(crate) struct HashType(String, Span); impl PartialEq for HashType { diff --git a/src/common/tools/mod.rs b/src/common/tools/mod.rs index 23cb497..56c5b8e 100644 --- a/src/common/tools/mod.rs +++ b/src/common/tools/mod.rs @@ -1,4 +1,5 @@ #[cfg(any(feature = "PartialOrd", feature = "Ord"))] +// Small standalone tools that need to be shared by several trait handlers. mod discriminant_type; #[cfg(any(feature = "PartialOrd", feature = "Ord"))] diff --git a/src/common/type.rs b/src/common/type.rs index c17ed73..33e2431 100644 --- a/src/common/type.rs +++ b/src/common/type.rs @@ -1,7 +1,11 @@ +use std::collections::HashSet; + use syn::{ + GenericArgument, GenericParam, Ident, Meta, Path, PathArguments, ReturnType, Token, Type, + TypeParamBound, parse::{Parse, ParseStream}, punctuated::Punctuated, - Meta, Token, Type, + token::Comma, }; pub(crate) struct TypeWithPunctuatedMeta { @@ -32,20 +36,321 @@ impl Parse for TypeWithPunctuatedMeta { } } +/// Describes, for one trait, the places where the automatic bound engine can treat a field type specially instead of emitting a plain `FieldType: Trait` predicate. +/// +/// An unconditional field type produces no where predicate at all, because the predicate would always hold and would only be noise in the generated code. +/// A forwarding field type produces the predicates of its type arguments instead, because the standard library guarantees that the arguments satisfying the trait is enough for the whole type. +/// The unconditional table is also used when a self-referencing field type is degraded to per-parameter bounds, to avoid collecting parameters from unconditional positions. +pub(crate) struct BoundExceptions { + /// Type names, matched against the last path segment, whose implementation of the trait never depends on their type arguments, e.g. `Arc` for `Clone`. + pub(crate) unconditional_types: &'static [&'static str], + /// Type names, matched against the last path segment, that implement the trait whenever their type arguments do, e.g. `Option` for `Copy` or `Vec` for `Clone`. + /// + /// Bounding the arguments instead of the whole type keeps the where clause simple and lets the compiler use supertrait elaboration on the parameters, e.g. deriving `T: Clone` from `T: Copy`. + pub(crate) forwarding_types: &'static [&'static str], + /// Whether a shared reference (`&T`) implements the trait unconditionally, which is the case for `Copy` and `Clone`. + pub(crate) shared_reference_is_unconditional: bool, +} + +impl BoundExceptions { + /// Checks whether the last segment of the path names a type that implements the trait unconditionally. + /// + /// `PhantomData` implements every trait supported by Educe unconditionally, so it is always treated as an exception. + #[inline] + fn path_is_unconditional(&self, path: &Path) -> bool { + if let Some(segment) = path.segments.last() { + let ident = &segment.ident; + + if ident == "PhantomData" { + return true; + } + + self.unconditional_types.iter().any(|name| ident == name) + } else { + false + } + } + + /// Checks whether the whole type is known to implement the trait unconditionally, so its predicate can be omitted. + /// + /// Raw pointers and bare function pointers are unconditional for every trait supported by Educe, because their implementations only look at the address value. + #[inline] + pub(crate) fn type_is_unconditional(&self, ty: &Type) -> bool { + match ty { + Type::Path(ty) => ty.qself.is_none() && self.path_is_unconditional(&ty.path), + Type::Ptr(_) | Type::BareFn(_) => true, + Type::Reference(ty) => { + ty.mutability.is_none() && self.shared_reference_is_unconditional + }, + _ => false, + } + } + + /// Returns the type arguments of a type that forwards the trait to them, or `None` if the type is not in the forwarding table. + pub(crate) fn forwarding_type_arguments<'a>(&self, ty: &'a Type) -> Option> { + if let Type::Path(ty) = ty + && ty.qself.is_none() + && let Some(segment) = ty.path.segments.last() + && self.forwarding_types.iter().any(|name| segment.ident == name) + && let PathArguments::AngleBracketed(args) = &segment.arguments + { + let mut types = Vec::new(); + + for arg in &args.args { + if let GenericArgument::Type(ty) = arg { + types.push(ty); + } + } + + return Some(types); + } + + None + } +} + +/// Walks a type and collects every ident that could refer to a generic type parameter. +/// +/// When `exceptions` is provided, positions that the exception table marks as unconditional are not descended into, so parameters that only appear there are not collected. +fn walk_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type, exceptions: Option<&BoundExceptions>) { + match ty { + Type::Array(ty) => walk_type(set, ty.elem.as_ref(), exceptions), + Type::Group(ty) => walk_type(set, ty.elem.as_ref(), exceptions), + Type::Paren(ty) => walk_type(set, ty.elem.as_ref(), exceptions), + Type::Slice(ty) => walk_type(set, ty.elem.as_ref(), exceptions), + Type::Tuple(ty) => { + for ty in &ty.elems { + walk_type(set, ty, exceptions); + } + }, + Type::Reference(ty) => { + // A shared reference implements `Copy`/`Clone` no matter what it points to, so those traits do not need the pointee's parameters. + if let Some(exceptions) = exceptions + && ty.mutability.is_none() + && exceptions.shared_reference_is_unconditional + { + return; + } + + walk_type(set, ty.elem.as_ref(), exceptions); + }, + Type::Ptr(ty) => { + // Raw pointer impls only compare or print the address, so no trait supported by Educe needs the pointee's parameters. + if exceptions.is_some() { + return; + } + + walk_type(set, ty.elem.as_ref(), exceptions); + }, + Type::BareFn(ty) => { + // Function pointer impls also only look at the address. + if exceptions.is_some() { + return; + } + + for arg in &ty.inputs { + walk_type(set, &arg.ty, exceptions); + } + + if let ReturnType::Type(_, ty) = &ty.output { + walk_type(set, ty, exceptions); + } + }, + Type::ImplTrait(ty) => { + for b in &ty.bounds { + if let TypeParamBound::Trait(b) = b { + walk_path(set, &b.path, exceptions); + } + } + }, + Type::TraitObject(ty) => { + for b in &ty.bounds { + if let TypeParamBound::Trait(b) = b { + walk_path(set, &b.path, exceptions); + } + } + }, + Type::Path(ty) => { + if let Some(qself) = &ty.qself { + walk_type(set, qself.ty.as_ref(), exceptions); + } + + walk_path(set, &ty.path, exceptions); + }, + _ => (), + } +} + +/// Walks a path, collecting the first segment ident (the only position where a generic type parameter can appear) and recursing into every segment's arguments. +fn walk_path<'a>( + set: &mut HashSet<&'a Ident>, + path: &'a Path, + exceptions: Option<&BoundExceptions>, +) { + if let Some(exceptions) = exceptions + && exceptions.path_is_unconditional(path) + { + return; + } + + // A path that starts with `::` can never begin with a generic type parameter. + if path.leading_colon.is_none() + && let Some(segment) = path.segments.first() + { + // This may also collect idents like `Vec` or `std`, but the callers intersect the set with the real generic parameters, so false candidates are harmless. + set.insert(&segment.ident); + } + + for segment in &path.segments { + match &segment.arguments { + PathArguments::AngleBracketed(args) => { + for arg in &args.args { + match arg { + GenericArgument::Type(ty) => walk_type(set, ty, exceptions), + GenericArgument::AssocType(ty) => walk_type(set, &ty.ty, exceptions), + _ => (), + } + } + }, + PathArguments::Parenthesized(args) => { + for ty in &args.inputs { + walk_type(set, ty, exceptions); + } + + if let ReturnType::Type(_, ty) = &args.output { + walk_type(set, ty, exceptions); + } + }, + PathArguments::None => (), + } + } +} + +/// Collects every ident in the type that could refer to a generic type parameter, without applying any exceptions. #[inline] -pub(crate) fn dereference(ty: &Type) -> &Type { - if let Type::Reference(ty) = ty { - dereference(ty.elem.as_ref()) - } else { - ty +pub(crate) fn find_all_idents_in_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type) { + walk_type(set, ty, None); +} + +/// Collects the idents in the type that could refer to a generic type parameter, skipping positions that the exception table marks as unconditional. +#[inline] +pub(crate) fn find_idents_in_type<'a>( + set: &mut HashSet<&'a Ident>, + ty: &'a Type, + exceptions: &BoundExceptions, +) { + walk_type(set, ty, Some(exceptions)); +} + +/// Collects only a bare, single-segment type ident such as `T`, without descending into any structure. +/// +/// This is used by the `Into` handler: a bound like `T: Into` only makes sense when the field type is the parameter itself, so nested parameters like the `T` in `Option` must not be collected. +#[inline] +pub(crate) fn find_bare_ident_in_type<'a>(set: &mut HashSet<&'a Ident>, ty: &'a Type) { + if let Type::Path(ty) = ty + && ty.qself.is_none() + && let Some(ident) = ty.path.get_ident() + { + set.insert(ident); } } +/// Returns true if the type syntactically uses any of the generic type parameters. #[inline] -pub(crate) fn dereference_changed(ty: &Type) -> (&Type, bool) { - if let Type::Reference(ty) = ty { - (dereference(ty.elem.as_ref()), true) - } else { - (ty, false) +pub(crate) fn type_uses_type_params(ty: &Type, params: &Punctuated) -> bool { + let mut set = HashSet::new(); + + find_all_idents_in_type(&mut set, ty); + + params.iter().any(|param| { + if let GenericParam::Type(param) = param { set.contains(¶m.ident) } else { false } + }) +} + +/// Returns true if any path segment in the type is exactly the given ident. +/// +/// This is used to detect field types that refer to the type currently being derived, e.g. `Box>` inside `List`, so that the bound engine can avoid generating a self-referencing predicate that would overflow the trait solver. +pub(crate) fn type_mentions_ident(ty: &Type, ident: &Ident) -> bool { + fn path_mentions_ident(path: &Path, ident: &Ident) -> bool { + for segment in &path.segments { + if segment.ident == *ident { + return true; + } + + match &segment.arguments { + PathArguments::AngleBracketed(args) => { + for arg in &args.args { + match arg { + GenericArgument::Type(ty) => { + if type_mentions_ident(ty, ident) { + return true; + } + }, + GenericArgument::AssocType(ty) + if type_mentions_ident(&ty.ty, ident) => + { + return true; + }, + _ => (), + } + } + }, + PathArguments::Parenthesized(args) => { + for ty in &args.inputs { + if type_mentions_ident(ty, ident) { + return true; + } + } + + if let ReturnType::Type(_, ty) = &args.output + && type_mentions_ident(ty, ident) + { + return true; + } + }, + PathArguments::None => (), + } + } + + false + } + + match ty { + Type::Array(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Group(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Paren(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Slice(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Ptr(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Reference(ty) => type_mentions_ident(ty.elem.as_ref(), ident), + Type::Tuple(ty) => ty.elems.iter().any(|ty| type_mentions_ident(ty, ident)), + Type::BareFn(ty) => { + ty.inputs.iter().any(|arg| type_mentions_ident(&arg.ty, ident)) + || matches!(&ty.output, ReturnType::Type(_, ty) if type_mentions_ident(ty, ident)) + }, + Type::ImplTrait(ty) => ty + .bounds + .iter() + .any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident))), + Type::TraitObject(ty) => ty + .bounds + .iter() + .any(|b| matches!(b, TypeParamBound::Trait(b) if path_mentions_ident(&b.path, ident))), + Type::Path(ty) => { + (match &ty.qself { + Some(qself) => type_mentions_ident(qself.ty.as_ref(), ident), + None => false, + }) || path_mentions_ident(&ty.path, ident) + }, + _ => false, } } + +#[inline] +pub(crate) fn dereference(ty: &Type) -> &Type { + if let Type::Reference(ty) = ty { dereference(ty.elem.as_ref()) } else { ty } +} + +#[inline] +pub(crate) fn dereference_changed(ty: &Type) -> (&Type, bool) { + if let Type::Reference(ty) = ty { (dereference(ty.elem.as_ref()), true) } else { (ty, false) } +} diff --git a/src/common/unsafe_punctuated_meta.rs b/src/common/unsafe_punctuated_meta.rs index e8ec103..958bc8d 100644 --- a/src/common/unsafe_punctuated_meta.rs +++ b/src/common/unsafe_punctuated_meta.rs @@ -1,9 +1,12 @@ use syn::{ + Meta, Token, parse::{Parse, ParseStream}, punctuated::Punctuated, - Meta, Token, }; +/// The parsed content of an attribute list that may start with the `unsafe` keyword, e.g. `Debug(unsafe, name = false)`. +/// +/// The unsafe marker is how a user opts in to the byte-based union implementations. pub(crate) struct UnsafePunctuatedMeta { pub(crate) list: Punctuated, pub(crate) has_unsafe: bool, diff --git a/src/common/where_predicates_bool.rs b/src/common/where_predicates_bool.rs index 4aa3695..9171767 100644 --- a/src/common/where_predicates_bool.rs +++ b/src/common/where_predicates_bool.rs @@ -1,50 +1,58 @@ -use quote::{quote, ToTokens}; +use std::collections::HashSet; + +use quote::{ToTokens, quote}; use syn::{ + Expr, GenericParam, Ident, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate, parse::{Parse, ParseStream}, punctuated::Punctuated, - spanned::Spanned, token::Comma, - Expr, GenericParam, Lit, Meta, MetaNameValue, Path, Token, Type, WherePredicate, }; -use super::path::path_to_string; +use super::{ + path::path_to_string, + r#type::{ + BoundExceptions, find_bare_ident_in_type, find_idents_in_type, type_mentions_ident, + type_uses_type_params, + }, +}; pub(crate) type WherePredicates = Punctuated; pub(crate) enum WherePredicatesOrBool { WherePredicates(WherePredicates), Bool(bool), + /// The `bound(*)` form, which asks for a bound on every generic type parameter like the built-in derives do. All, } impl WherePredicatesOrBool { + /// Converts a literal into where predicates or a boolean flag: a bool literal toggles the automatic bound, a string literal is parsed as where predicates, and an empty string means `false`. fn from_lit(lit: &Lit) -> syn::Result { - Ok(match lit { - Lit::Bool(lit) => Self::Bool(lit.value), + match lit { + Lit::Bool(lit) => Ok(Self::Bool(lit.value)), Lit::Str(lit) => match lit.parse_with(WherePredicates::parse_terminated) { - Ok(where_predicates) => Self::WherePredicates(where_predicates), - Err(_) if lit.value().is_empty() => Self::Bool(false), - Err(error) => return Err(error), - }, - other => { - return Err(syn::Error::new( - other.span(), - "unexpected kind of literal (only boolean or string allowed)", - )) + Ok(where_predicates) => Ok(Self::WherePredicates(where_predicates)), + Err(_) if lit.value().is_empty() => Ok(Self::Bool(false)), + Err(error) => Err(error), }, - }) + _ => Err(syn::Error::new_spanned( + lit, + "expected a boolean literal or a string of where predicates", + )), + } } } impl Parse for WherePredicatesOrBool { #[inline] fn parse(input: ParseStream) -> syn::Result { - if let Ok(lit) = input.parse::() { - return Self::from_lit(&lit); + // `bound(*)` requests the built-in-derive behavior: bound every generic type parameter. + if input.parse::().is_ok() { + return Ok(Self::All); } - if let Ok(_star) = input.parse::() { - return Ok(Self::All); + if let Ok(lit) = input.parse::() { + return Self::from_lit(&lit); } Ok(Self::WherePredicates(input.parse_terminated(WherePredicate::parse, Token![,])?)) @@ -55,12 +63,14 @@ impl Parse for WherePredicatesOrBool { pub(crate) fn meta_name_value_2_where_predicates_bool( name_value: &MetaNameValue, ) -> syn::Result { - if let Expr::Lit(lit) = &name_value.value { + if let Expr::Lit(lit) = &name_value.value + && matches!(&lit.lit, Lit::Bool(_) | Lit::Str(_)) + { return WherePredicatesOrBool::from_lit(&lit.lit); } - Err(syn::Error::new( - name_value.value.span(), + Err(syn::Error::new_spanned( + &name_value.value, format!( "expected `{path} = \"where_predicates\"` or `{path} = false`", path = path_to_string(&name_value.path) @@ -73,17 +83,18 @@ pub(crate) fn meta_2_where_predicates(meta: &Meta) -> syn::Result meta_name_value_2_where_predicates_bool(name_value), Meta::List(list) => list.parse_args::(), - Meta::Path(path) => Err(syn::Error::new( - path.span(), + Meta::Path(path) => Err(syn::Error::new_spanned( + path, format!( - "expected `{path} = \"where_predicates\"`, `{path}(where_predicates)`, `{path} = \ - false`, or `{path}(false)`", + "expected `{path} = \"where_predicates\"`, `{path}(where_predicates)`, \ + `{path}(*)`, `{path} = false`, or `{path}(false)`", path = path.clone().into_token_stream() ), )), } } +/// Creates a `Param: Trait` predicate for every generic type parameter, matching the behavior of the built-in derives. #[inline] pub(crate) fn create_where_predicates_from_all_generic_parameters( params: &Punctuated, @@ -102,20 +113,128 @@ pub(crate) fn create_where_predicates_from_all_generic_parameters( where_predicates } -#[inline] -pub(crate) fn create_where_predicates_from_generic_parameters_check_types( +/// The automatic bound engine: turns the field types into where predicates for one trait. +/// +/// Each field type is processed with the following rules, in order: +/// 1. A type that the exception table marks as unconditional (e.g. `Arc` for `Clone`) produces nothing, because its predicate would always hold. +/// 2. A type that uses no generic type parameter produces nothing, because its predicate would be constant and could send the trait solver into an infinite loop on indirectly recursive types. +/// 3. A type that forwards the trait to its type arguments (e.g. `Option` for `Copy`) produces the predicates of its arguments instead, applying these rules recursively, so `Vec>` for `Clone` produces just `T: Clone`. +/// 4. A type that mentions the type currently being derived (e.g. `Box>` inside `List`) is degraded to `Param: Trait` bounds for the parameters it uses, because a self-referencing predicate would overflow the trait solver (E0275). +/// 5. Any other type produces the precise predicate `FieldType: Trait`, so the compiler verifies the real requirement even for types with unusual conditional impls. +pub(crate) fn create_where_predicates_from_field_types( + params: &Punctuated, + bound_trait: &Path, + types: &[&Type], + self_ident: &Ident, + exceptions: &BoundExceptions, +) -> WherePredicates { + fn process_type( + ty: &Type, + params: &Punctuated, + bound_trait: &Path, + self_ident: &Ident, + exceptions: &BoundExceptions, + where_predicates: &mut WherePredicates, + seen: &mut HashSet, + ) { + // Identical predicates can be produced by different fields, so deduplicate them by their token strings. + let mut push = |where_predicates: &mut WherePredicates, predicate: WherePredicate| { + if seen.insert(predicate.to_token_stream().to_string()) { + where_predicates.push(predicate); + } + }; + + // Rule 1: the trait is implemented for this type no matter what the type arguments are. + if exceptions.type_is_unconditional(ty) { + return; + } + + // Rule 2: a type without generic type parameters would produce a constant predicate. + if !type_uses_type_params(ty, params) { + return; + } + + // Rule 3: a forwarding type is replaced by its type arguments, processed recursively. + if let Some(argument_types) = exceptions.forwarding_type_arguments(ty) { + for ty in argument_types { + process_type( + ty, + params, + bound_trait, + self_ident, + exceptions, + where_predicates, + seen, + ); + } + + return; + } + + if type_mentions_ident(ty, self_ident) { + // Rule 4: degrade a self-referencing type to per-parameter bounds. + let mut used = HashSet::new(); + + find_idents_in_type(&mut used, ty, exceptions); + + for param in params { + if let GenericParam::Type(param) = param + && used.contains(¶m.ident) + { + let ident = ¶m.ident; + + push(where_predicates, syn::parse2(quote! { #ident: #bound_trait }).unwrap()); + } + } + } else { + // Rule 5: the precise predicate. + push(where_predicates, syn::parse2(quote! { #ty: #bound_trait }).unwrap()); + } + } + + let mut where_predicates = Punctuated::new(); + + let mut seen: HashSet = HashSet::new(); + + for ty in types { + process_type( + ty, + params, + bound_trait, + self_ident, + exceptions, + &mut where_predicates, + &mut seen, + ); + } + + where_predicates +} + +/// Creates a `Param: Trait` predicate for each generic type parameter that appears bare as one of the field types. +/// +/// This is only used by the `Into` handler, where a `T: Into` bound is meaningful solely when a field's type is `T` itself. +pub(crate) fn create_where_predicates_from_bare_field_types( + params: &Punctuated, bound_trait: &Path, types: &[&Type], - supertraits: &[proc_macro2::TokenStream], ) -> WherePredicates { let mut where_predicates = Punctuated::new(); + let mut set = HashSet::new(); + for t in types { - where_predicates.push(syn::parse2(quote! { #t: #bound_trait }).unwrap()); + find_bare_ident_in_type(&mut set, t); } - for supertrait in supertraits { - where_predicates.push(syn::parse2(quote! { Self: #supertrait }).unwrap()); + for param in params { + if let GenericParam::Type(ty) = param { + let ident = &ty.ident; + + if set.contains(ident) { + where_predicates.push(syn::parse2(quote! { #ident: #bound_trait }).unwrap()); + } + } } where_predicates diff --git a/src/lib.rs b/src/lib.rs index 6b9a26e..2e431c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,8 +16,75 @@ features = ["Debug", "Clone", "Copy", "Hash", "Default"] default-features = false ``` +## Trait Bounds + +When a trait is derived with Educe and no explicit `bound` is set, the where predicates of the generated impl are determined automatically. Every field type that the generated code touches (ignored fields and fields handled by a custom `method` are excluded) is processed with the following rules, in order: + +1. A type that is known to implement the trait unconditionally produces no predicate at all. This covers `PhantomData`, raw pointers, and function pointers for every trait, shared references for `Clone` and `Copy`, plus the types in table A. +2. A type that does not use any generic type parameter produces no predicate, because such a predicate would be constant. +3. A std type that implements the trait whenever its type arguments do (table B) produces the predicates of its type arguments instead, with these rules applied recursively: a field of type `Option` produces `T: Trait`, and one of type `Vec>` produces just `T: Clone` for `Clone`. +4. A type that mentions the derived type itself (e.g. `Box>` inside `List`) produces `Param: Trait` bounds for the type parameters it uses, because a self-referencing predicate would overflow the trait solver (E0275). +5. Any other type produces the precise predicate `FieldType: Trait`, so the compiler verifies the real requirement: a field of type `Wrapper` where `Wrapper` has its own conditional `Clone` impl produces `Wrapper: Clone`, which works for exactly the type arguments that `Wrapper` supports. + +Table A — types whose type arguments never need a bound: + +| Trait | Types | +|-------|-------| +| `Clone`, `Copy` | `Arc`, `Rc`, `Weak`, `NonNull`, `Cow`, `Discriminant` | +| `Debug` | `Weak`, `NonNull`, `AtomicPtr`, `Discriminant` | +| `PartialEq`, `Eq`, `Hash` | `NonNull`, `Discriminant` | +| `PartialOrd`, `Ord` | `NonNull` | +| `Default` | `Option`, `Vec`, `VecDeque`, `LinkedList`, `HashMap`, `HashSet`, `BTreeMap`, `BTreeSet`, `Weak` | + +Table B — types that forward the trait to their type arguments: + +| Trait | Types | +|-------|-------| +| `Clone` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `BinaryHeap`, `HashMap`, `HashSet`, `RefCell`, `Wrapping`, `Reverse`, `Saturating` | +| `Copy` | `Option`, `Result`, `Wrapping`, `Reverse`, `Saturating` | +| `Debug` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `BinaryHeap`, `HashMap`, `HashSet`, `Arc`, `Rc`, `RefCell`, `Mutex`, `RwLock`, `Wrapping`, `Reverse`, `Saturating` | +| `PartialEq`, `Eq`, `PartialOrd`, `Ord` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `Arc`, `Rc`, `RefCell`, `Wrapping`, `Reverse`, `Saturating` | +| `Hash` | `Option`, `Result`, `Box`, `Vec`, `VecDeque`, `LinkedList`, `BTreeMap`, `BTreeSet`, `Arc`, `Rc`, `Wrapping`, `Reverse`, `Saturating` | +| `Default` | `Box`, `Arc`, `Rc`, `Cell`, `RefCell`, `Mutex`, `RwLock`, `Wrapping`, `Reverse`, `Saturating` | + +`HashMap` and `HashSet` are not in the comparison rows of table B because their comparison impls additionally require `K: Eq + Hash`; such fields get the precise whole-type predicate from rule 5 instead. + +Both tables match type names syntactically (by the last path segment), so a user-defined type that happens to share a name with one of these std types is treated the same way; if the resulting bounds do not fit such a type, set them explicitly with `bound(...)`. + +###### Bound Inheritance + +When related traits are derived together with automatic bounds, a trait inherits the final predicates of its prerequisite traits: `Eq` and `PartialOrd` inherit from `PartialEq`, `Ord` inherits from `Eq` and `PartialOrd`, and `Copy` inherits from `Clone`. This way, a custom bound like `#[educe(PartialEq(bound(T: MyTrait)), Eq)]` automatically carries `T: MyTrait` into the `Eq` impl. + +Educe cannot see the traits derived by other derive macros, including the built-in ones, so inheritance only applies between traits listed in the same `#[educe(...)]` attribute; a prerequisite trait implemented elsewhere contributes nothing. + +###### Controlling the Bounds + +* `bound(where_predicates)` or `bound = "where_predicates"` uses exactly the given predicates, without inheritance. +* `bound(*)` adds `Param: Trait` for every generic type parameter, like the built-in derives. +* `bound(false)` adds no predicates at all. + +An explicit bound is used verbatim; if a prerequisite impl carries predicates that the explicit bound does not imply, the compiler reports an unsatisfied supertrait and the missing predicates have to be added by hand. + +###### Limitations + +* Mutually recursive generic types (an `A` containing `Vec>` while `B` contains `A`) cannot be detected from a single type definition, so automatic bounds make the trait solver overflow (E0275) on them; use `bound(*)` or a custom bound for such types. +* The precise predicates appear in the public where clause of the impl, so private field types become visible in documentation and error messages, and changing a private field type can change the public bounds of the impl. + ## Traits +* [Debug](#debug) +* [Clone](#clone) +* [Copy](#copy) +* [PartialEq](#partialeq) +* [Eq](#eq) +* [PartialOrd](#partialord) +* [Ord](#ord) +* [Hash](#hash) +* [Default](#default) +* [Deref](#deref) +* [DerefMut](#derefmut) +* [Into](#into) + #### Debug Use `#[derive(Educe)]` and `#[educe(Debug)]` to implement the `Debug` trait for a struct, enum, or union. This allows you to modify the names of your types, variants, and fields. You can also choose to ignore specific fields or set a method to replace the `Debug` trait. Additionally, you have the option to format a struct as a tuple and vice versa. @@ -179,7 +246,7 @@ enum Enum { ###### Generic Parameters Bound to the `Debug` Trait or Others -Generic parameters will be automatically bound to the `Debug` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust # #[cfg(feature = "Debug")] @@ -230,29 +297,9 @@ enum Enum { In the above case, `T` is bound to the `Debug` trait, but `K` is not. -Or, you can have `educe` replicate the behaviour of `std`'s `derive`'s, where a bound is produced for *every* generic parameter, without regard to how it's used in the structure: - -```rust -# #[cfg(feature = "Debug")] -# { -use educe::Educe; - -#[derive(Educe)] -#[educe(Debug(bound(*)))] -struct Struct { - #[educe(Debug(ignore))] - f: T, -} -# } -``` - -This can be useful if you don't want to make the trait implementation part of your permanent public API. In this example, `Struct` doesn't implement `Debug` unless `T` does. I.e., it has a `T: Debug` bound even though that's not needed right now. Later we might want to display `f`; we wouldn't then need to make a breaking API change by adding the bound. - -This was the behaviour of `Trait(bound)` in educe 0.4.x and earlier. - ###### Union -A union will be formatted as a `u8` slice because we don't know its fields at runtime. The fields of a union cannot be ignored, renamed, or formatted with other methods. The implementation is **unsafe** because it may expose uninitialized memory. +A union is formatted as a `u8` slice, because its active field cannot be known at runtime. The fields of a union cannot be ignored, renamed, or formatted with other methods. The implementation is **unsafe** because it deliberately reads the whole memory of the union, including any padding bytes, which are not required to be initialized; the output may therefore expose uninitialized memory. ```rust # #[cfg(feature = "Debug")] @@ -336,7 +383,7 @@ enum Enum { ###### Generic Parameters Bound to the `Clone` Trait or Others -Generic parameters will be automatically bound to the `Clone` trait if necessary. If the `#[educe(Copy)]` attribute exists, they will be bound to the `Copy` trait. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust # #[cfg(feature = "Clone")] @@ -389,30 +436,6 @@ enum Enum { In the above case, `T` is bound to the `Clone` trait, but `K` is not. -Or, you can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -# #[cfg(feature = "Clone")] -# { -use educe::Educe; - -trait A { - fn add(&self, rhs: u8) -> Self; -} - -fn clone(v: &T) -> T { - v.add(100) -} - -#[derive(Educe)] -#[educe(Clone(bound(*)))] -struct Struct { - #[educe(Clone(method(clone)))] - f: T, -} -# } -``` - ###### Union Refer to the introduction of the `#[educe(Copy)]` attribute. @@ -448,7 +471,7 @@ enum Enum { ###### Generic Parameters Bound to the `Copy` Trait or Others -All generic parameters will be automatically bound to the `Copy` trait. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Copy` impl additionally inherits the predicates of the `Clone` impl generated by Educe, because `Copy` requires `Clone`. ```rust # #[cfg(all(feature = "Clone", feature = "Copy"))] @@ -618,7 +641,7 @@ enum Enum { ###### Generic Parameters Bound to the `PartialEq` Trait or Others -Generic parameters will be automatically bound to the `PartialEq` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust # #[cfg(feature = "PartialEq")] @@ -669,24 +692,6 @@ enum Enum { # } ``` -In the above case, `T` is bound to the `PartialEq` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -# #[cfg(feature = "PartialEq")] -# { -use educe::Educe; - -#[derive(Educe)] -#[educe(PartialEq(bound(*)))] -struct Struct { - #[educe(PartialEq(ignore))] - f: T, -} -# } -``` - ###### Union The `#[educe(PartialEq(unsafe))]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. @@ -707,7 +712,7 @@ union Union { #### Eq -Use `#[derive(Educe)]` and `#[educe(Eq)]` to implement the `Eq` trait for a struct, enum, or union. You can also choose to ignore specific fields or set a method to replace the `PartialEq` trait. +Use `#[derive(Educe)]` and `#[educe(Eq)]` to implement the `Eq` trait for a struct, enum, or union. `Eq` is a marker trait, so it has no field attributes of its own; field-level equality settings such as `ignore` and `method` belong to the `PartialEq` attribute. ###### Basic Usage @@ -734,78 +739,9 @@ enum Enum { # } ``` -###### Ignore Fields +###### Generic Parameters Bound to the `Eq` Trait or Others -The `ignore` parameter can ignore a specific field. - -```rust -# #[cfg(all(feature = "PartialEq", feature = "Eq"))] -# { -use educe::Educe; - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -struct Struct { - #[educe(Eq(ignore))] - f1: u8 -} - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -enum Enum { - V1, - V2 { - #[educe(Eq(ignore))] - f1: u8, - }, - V3( - #[educe(Eq(ignore))] - u8 - ), -} -# } -``` - -###### Use Another Method to Perform Comparison - -The `method` parameter can be utilized to replace the implementation of the `Eq` trait for a field, eliminating the need to implement the `PartialEq` trait for the type of that field. - -```rust -# #[cfg(all(feature = "PartialEq", feature = "Eq"))] -# { -use educe::Educe; - -fn eq(a: &u8, b: &u8) -> bool { - a + 1 == *b -} - -trait A { - fn is_same(&self, other: &Self) -> bool; -} - -fn eq2(a: &T, b: &T) -> bool { - a.is_same(b) -} - -#[derive(Educe)] -#[educe(PartialEq, Eq)] -enum Enum { - V1, - V2 { - #[educe(Eq(method(eq)))] - f1: u8, - }, - V3( - #[educe(Eq(method(eq2)))] - T - ), -} -# } -``` - -###### Generic Parameters Bound to the `PartialEq` Trait or Others - -Generic parameters will be automatically bound to the `PartialEq` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Eq` impl also inherits the predicates of the `PartialEq` impl generated by Educe. ```rust # #[cfg(all(feature = "PartialEq", feature = "Eq"))] @@ -842,11 +778,14 @@ fn eq(a: &T, b: &T) -> bool { } #[derive(Educe)] -#[educe(PartialEq(bound(T: std::cmp::PartialEq, K: A)), Eq)] +#[educe( + PartialEq(bound(T: std::cmp::PartialEq, K: A)), + Eq(bound(T: std::cmp::Eq, K: A)) +)] enum Enum { V1, V2 { - #[educe(Eq(method(eq)))] + #[educe(PartialEq(method(eq)))] f1: K, }, V3( @@ -858,7 +797,7 @@ enum Enum { ###### Union -The `#[educe(PartialEq(unsafe), Eq)]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. +The `#[educe(PartialEq(unsafe), Eq)]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it deliberately compares the whole memory of the two unions byte by byte, including any padding bytes, while disregarding the specific fields it utilizes. ```rust # #[cfg(all(feature = "PartialEq", feature = "Eq"))] @@ -939,6 +878,8 @@ enum Enum { The `method` parameter can be utilized to replace the implementation of the `PartialOrd` trait for a field, eliminating the need to implement the `PartialOrd` trait for the type of that field. +When `Ord` is derived together, a field without its own `PartialOrd` attribute follows the `ignore`, `rank`, and `method` settings of its `Ord` attribute, so `partial_cmp` stays consistent with `cmp`; the result of an `Ord` comparison method is wrapped in `Some` automatically. + ```rust # #[cfg(all(feature = "PartialEq", feature = "PartialOrd"))] # { @@ -1020,7 +961,7 @@ enum Enum { ###### Generic Parameters Bound to the `PartialOrd` Trait or Others -Generic parameters will be automatically bound to the `PartialOrd` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `PartialOrd` impl also inherits the predicates of the `PartialEq` impl generated by Educe. ```rust # #[cfg(feature = "PartialOrd")] @@ -1059,7 +1000,7 @@ fn partial_cmp(a: &T, b: &T) -> Option { } #[derive(PartialEq, Educe)] -#[educe(PartialOrd(bound(T: std::cmp::PartialOrd, K: PartialEq + A)))] +#[educe(PartialOrd(bound(T: std::cmp::PartialOrd, K: std::cmp::PartialOrd + A)))] enum Enum { V1, V2 { @@ -1073,42 +1014,6 @@ enum Enum { # } ``` -In the above case, `T` is bound to the `PartialOrd` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -# #[cfg(feature = "PartialOrd")] -# { -use educe::Educe; - -#[derive(PartialEq, Educe)] -#[educe(PartialOrd(bound(*)))] -struct Struct { - #[educe(PartialOrd(ignore))] - f: T, -} -# } -``` - -###### Union - -The `#[educe(PartialEq(unsafe))]` attribute can be used for a union. The fields of a union cannot be compared with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. - -```rust -# #[cfg(feature = "PartialEq")] -# { -use educe::Educe; - -#[derive(Educe)] -#[educe(PartialEq(unsafe))] -union Union { - f1: u8, - f2: i32 -} -# } -``` - #### Ord Use `#[derive(Educe)]` and `#[educe(Ord)]` to implement the `Ord` trait for a struct or enum. You can also choose to ignore specific fields or set a method to replace the `Ord` trait. @@ -1174,6 +1079,8 @@ enum Enum { The `method` parameter can be utilized to replace the implementation of the `Ord` trait for a field, eliminating the need to implement the `Ord` trait for the type of that field. +When `PartialOrd` is derived together, a field without its own `Ord` attribute follows the `ignore` and `rank` settings of its `PartialOrd` attribute; a `PartialOrd` comparison method returns an `Option` and cannot be used by `cmp`, so such a field is compared with the built-in comparison. + ```rust # #[cfg(all(feature = "PartialEq", feature = "Eq", feature = "PartialOrd", feature = "Ord"))] # { @@ -1255,7 +1162,7 @@ enum Enum { ###### Generic Parameters Bound to the `Ord` Trait or Others -Generic parameters will be automatically bound to the `Ord` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. With automatic bounds, the `Ord` impl also inherits the predicates of the `Eq` and `PartialOrd` impls generated by Educe. ```rust # #[cfg(all(feature = "PartialOrd", feature = "Ord"))] @@ -1294,11 +1201,14 @@ fn cmp(a: &T, b: &T) -> Ordering { } #[derive(PartialEq, Eq, Educe)] -#[educe(PartialOrd, Ord(bound(T: std::cmp::Ord, K: std::cmp::Ord + A)))] +#[educe( + PartialOrd(bound(T: std::cmp::PartialOrd, K: std::cmp::PartialEq + A)), + Ord(bound(T: std::cmp::Ord, K: std::cmp::Ord + A)) +)] enum Enum { V1, V2 { - #[educe(PartialOrd(method(cmp)))] + #[educe(Ord(method(cmp)))] f1: K, }, V3( @@ -1406,7 +1316,7 @@ enum Enum { ###### Generic Parameters Bound to the `Hash` Trait or Others -Generic parameters will be automatically bound to the `Hash` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust # #[cfg(feature = "Hash")] @@ -1459,27 +1369,9 @@ enum Enum { # } ``` -In the above case, `T` is bound to the `Hash` trait, but `K` is not. - -You can have `educe` replicate the behaviour of `std`'s `derive`'s by using `bound(*)`. See the [`Debug`](#debug) section for more information. - -```rust -# #[cfg(feature = "Hash")] -# { -use educe::Educe; - -#[derive(Educe)] -#[educe(Hash(bound(*)))] -struct Struct { - #[educe(Hash(ignore))] - f: T, -} -# } -``` - ###### Union -The `#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]` attribute can be used for a union. The fields of a union cannot be hashed with other methods. The implementation is **unsafe** because it disregards the specific fields it utilizes. +The `#[educe(PartialEq(unsafe), Eq, Hash(unsafe))]` attribute can be used for a union. The fields of a union cannot be hashed with other methods. The implementation is **unsafe** because it deliberately hashes the whole memory of the union byte by byte, including any padding bytes, while disregarding the specific fields it utilizes. ```rust # #[cfg(all(feature = "PartialEq", feature = "Eq", feature = "Hash"))] @@ -1569,6 +1461,8 @@ union Union { You may need to activate the `full` feature to enable support for advanced expressions. +Note that the expression is pasted into the generated `default` method verbatim, so for a generic type it has to be valid for every possible instantiation; an expression producing a concrete type does not work for a generic field. + ###### The Default Values for Specific Fields ```rust @@ -1634,7 +1528,7 @@ union Union { ###### Generic Parameters Bound to the `Default` Trait or Others -Generic parameters will be automatically bound to the `Default` trait if necessary. +The where predicates of the generated impl are determined from the field types automatically; see the "Trait Bounds" section above for the exact rules. ```rust # #[cfg(feature = "Default")] @@ -1780,7 +1674,13 @@ The mutable dereferencing fields do not need to be the same as the immutable der #### Into -Use `#[derive(Educe)]` and `#[educe(Into(type))]` to implement the `Into` trait for a struct or enum. +Use `#[derive(Educe)]` and `#[educe(Into(type))]` to make a struct or enum convertible into another type. + +Educe generates an `impl From for type`, which automatically provides the corresponding `Into` through the standard library's blanket implementation. Use the bare `into` flag — `#[educe(Into(type, into))]` — to generate a direct `impl Into` instead. + +###### The `into` Flag + +A `From` impl also lets callers write `Target::from(value)`, whereas a direct `Into` impl only supports `value.into()`. Use the `into` flag when you deliberately want the conversion to be one-directional, exposed only as `value.into()`. ###### Basic Usage @@ -1842,7 +1742,7 @@ enum Enum { ###### Generic Parameters Bound to the `Into` Trait or Others -Generic parameters will be automatically bound to the `Into` trait if necessary. +A generic type parameter is automatically bound to `Into` only when it is itself the type of a field, because a nested parameter cannot meaningfully receive an `Into` bound. ```rust # #[cfg(feature = "Into")] @@ -1902,14 +1802,19 @@ use std::collections::HashMap; use proc_macro::TokenStream; use supported_traits::Trait; use syn::{ + DeriveInput, Meta, Token, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, - DeriveInput, Meta, Token, }; +#[cfg(feature = "Into")] +use trait_handlers::TraitHandlerMultiple; #[allow(unused)] -use trait_handlers::{TraitHandler, TraitHandlerMultiple}; +use trait_handlers::{TraitHandler, TraitHandlerContext}; +/// The entry point of the expansion: collects the traits requested by the `#[educe(...)]` attributes and dispatches each of them to its handler. +/// +/// The handlers run in a fixed order (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deref, DerefMut, Into) so that a trait always runs after the traits it may inherit bounds from. fn derive_input_handler(ast: DeriveInput) -> syn::Result { let mut token_stream = proc_macro2::TokenStream::new(); let mut trait_meta_map: HashMap> = HashMap::new(); @@ -1956,11 +1861,14 @@ fn derive_input_handler(ast: DeriveInput) -> syn::Result = trait_meta_map.keys().copied().collect(); + let mut ctx = TraitHandlerContext::default(); + #[cfg(feature = "Debug")] { if let Some(meta) = trait_meta_map.get(&Trait::Debug) { trait_handlers::debug::DebugHandler::trait_meta_handler( &ast, + &mut ctx, &mut token_stream, &traits, &meta[0], @@ -1973,6 +1881,7 @@ fn derive_input_handler(ast: DeriveInput) -> syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result syn::Result(&'a [&'static str]); @@ -46,20 +48,19 @@ pub(crate) fn derive_attribute_not_set_up_yet() -> syn::Error { #[inline] pub(crate) fn attribute_incorrect_place(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), format!("the `{name}` attribute cannot be placed here")) + syn::Error::new_spanned(name, format!("the `{name}` attribute cannot be placed here")) } #[inline] -pub(crate) fn attribute_incorrect_format_with_span( +pub(crate) fn attribute_incorrect_format( name: &Ident, - span: Span, correct_usage: &[&'static str], ) -> syn::Error { if correct_usage.is_empty() { attribute_incorrect_place(name) } else { - syn::Error::new( - span, + syn::Error::new_spanned( + name, format!( "you are using an incorrect format of the `{name}` attribute{}", DisplayStringSlice(correct_usage) @@ -68,17 +69,9 @@ pub(crate) fn attribute_incorrect_format_with_span( } } -#[inline] -pub(crate) fn attribute_incorrect_format( - name: &Ident, - correct_usage: &[&'static str], -) -> syn::Error { - attribute_incorrect_format_with_span(name, name.span(), correct_usage) -} - #[inline] pub(crate) fn parameter_reset(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), format!("you are trying to reset the `{name}` parameter")) + syn::Error::new_spanned(name, format!("you are trying to reset the `{name}` parameter")) } #[inline] @@ -88,43 +81,36 @@ pub(crate) fn educe_format_incorrect(name: &Ident) -> syn::Error { #[inline] pub(crate) fn unsupported_trait(name: &Path) -> syn::Error { - let span = name.span(); - - match name.get_ident() { - Some(name) => syn::Error::new( - span, - format!("unsupported trait `{name}`, available traits:{DisplayTraits}"), - ), - None => { - let name = path_to_string(name); - - syn::Error::new( - span, - format!("unsupported trait `{name}`, available traits:{DisplayTraits}"), - ) - }, - } + let name_string = match name.get_ident() { + Some(name) => name.to_string(), + None => path_to_string(name), + }; + + syn::Error::new_spanned( + name, + format!("unsupported trait `{name_string}`, available traits:{DisplayTraits}"), + ) } #[inline] pub(crate) fn reuse_a_trait(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), format!("the trait `{name}` is used repeatedly")) + syn::Error::new_spanned(name, format!("the trait `{name}` is used repeatedly")) } #[inline] pub(crate) fn trait_not_used(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), format!("the trait `{name}` is not used")) + syn::Error::new_spanned(name, format!("the trait `{name}` is not used")) } #[inline] pub(crate) fn trait_not_support_union(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), format!("the trait `{name}` does not support to a union")) + syn::Error::new_spanned(name, format!("the trait `{name}` does not support to a union")) } #[inline] pub(crate) fn trait_not_support_unit_variant(name: &Ident, variant: &Variant) -> syn::Error { - syn::Error::new( - variant.span(), + syn::Error::new_spanned( + variant, format!("the trait `{name}` cannot be implemented for an enum which has unit variants"), ) } diff --git a/src/supported_traits.rs b/src/supported_traits.rs index 50f7f30..4b5d27d 100644 --- a/src/supported_traits.rs +++ b/src/supported_traits.rs @@ -20,6 +20,9 @@ use syn::Path; #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ordinalize)] #[ordinalize(impl_trait = false)] #[ordinalize(variants(pub(crate) const VARIANTS))] +/// Every trait that Educe can derive, each gated behind a cargo feature of the same name. +/// +/// `_Nothing` is a sentinel that only exists so the enum is never empty, no matter which features are enabled. pub(crate) enum Trait { #[cfg(feature = "Debug")] Debug, @@ -52,10 +55,7 @@ pub(crate) enum Trait { impl Trait { #[inline] pub(crate) fn from_path(path: &Path) -> Option { - let ident_string = match path.get_ident() { - Some(ident) => ident.to_string(), - None => return None, - }; + let ident_string = path.get_ident()?.to_string(); match ident_string.as_str() { #[cfg(feature = "Debug")] diff --git a/src/trait_handlers/clone/clone_enum.rs b/src/trait_handlers/clone/clone_enum.rs index 991243a..9977c3b 100644 --- a/src/trait_handlers/clone/clone_enum.rs +++ b/src/trait_handlers/clone/clone_enum.rs @@ -1,21 +1,32 @@ use quote::{format_ident, quote}; -use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Fields, Meta, Type, Variant}; +use syn::{Data, DeriveInput, Field, Fields, Meta, Type, Variant, punctuated::Punctuated}; use super::models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}; +// Only the bitwise-copy fast path, gated on the `Copy` trait, needs to inspect whether a field uses a type parameter. +#[cfg(feature = "Copy")] +use crate::common::r#type::type_uses_type_params; use crate::{ - common::where_predicates_bool::WherePredicates, supported_traits::Trait, TraitHandler, + TraitHandler, + common::{bound::BOUND_EXCEPTIONS_CLONE, where_predicates_bool::WherePredicates}, + supported_traits::Trait, + trait_handlers::TraitHandlerContext, }; +/// Generates the `Clone` implementation for an enum. pub(crate) struct CloneEnumHandler; impl TraitHandler for CloneEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -59,20 +70,29 @@ impl TraitHandler for CloneEnumHandler { variants.push((variant, variant_fields)); } + // Like the built-in derives, `clone` can be a plain bitwise copy only when `Copy` is derived together, no generic type parameter is involved, and no field uses a custom clone method. + // When generic type parameters are involved, a field-wise clone keeps the `Clone` impl usable for type arguments that are `Clone` but not `Copy`. #[cfg(feature = "Copy")] - let contains_copy = !has_custom_clone_method && traits.contains(&Trait::Copy); + let use_bitwise_copy = !has_custom_clone_method + && traits.contains(&Trait::Copy) + && !data.variants.iter().any(|variant| { + variant + .fields + .iter() + .any(|field| type_uses_type_params(&field.ty, &ast.generics.params)) + }); #[cfg(not(feature = "Copy"))] - let contains_copy = false; + let use_bitwise_copy = false; - if contains_copy { + if use_bitwise_copy { clone_token_stream.extend(quote!(*self)); } let mut clone_types: Vec<&Type> = Vec::new(); if variants.is_empty() { - if !contains_copy { + if !use_bitwise_copy { clone_token_stream.extend(quote!(unreachable!())); clone_from_token_stream.extend(quote!(let _ = source;)); } @@ -198,7 +218,7 @@ impl TraitHandler for CloneEnumHandler { } } - if !contains_copy { + if !use_bitwise_copy { clone_token_stream.extend(quote! { match self { #clone_variants_token_stream @@ -213,17 +233,16 @@ impl TraitHandler for CloneEnumHandler { } } + // The bound trait is always `Clone`; the `Copy` impl is emitted by the `Copy` handler with its own bounds. bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( &ast.generics.params, - &syn::parse2(if contains_copy { - quote!(::core::marker::Copy) - } else { - quote!(::core::clone::Clone) - }) - .unwrap(), + &syn::parse2(quote!(::core::clone::Clone)).unwrap(), &clone_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_CLONE, ); + + ctx.record(Trait::Clone, &bound); } let clone_from_fn_token_stream = if clone_from_token_stream.is_empty() { @@ -240,6 +259,7 @@ impl TraitHandler for CloneEnumHandler { let ident = &ast.ident; let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -249,6 +269,7 @@ impl TraitHandler for CloneEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause { #[inline] fn clone(&self) -> Self { @@ -259,14 +280,6 @@ impl TraitHandler for CloneEnumHandler { } }); - #[cfg(feature = "Copy")] - if traits.contains(&Trait::Copy) { - token_stream.extend(quote! { - impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/clone/clone_struct.rs b/src/trait_handlers/clone/clone_struct.rs index 73aa8fa..1a2a72f 100644 --- a/src/trait_handlers/clone/clone_struct.rs +++ b/src/trait_handlers/clone/clone_struct.rs @@ -1,21 +1,32 @@ use quote::quote; -use syn::{punctuated::Punctuated, Data, DeriveInput, Field, Fields, Index, Meta, Type}; +use syn::{Data, DeriveInput, Field, Fields, Index, Meta, Type, punctuated::Punctuated}; use super::models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}; use crate::{ - common::where_predicates_bool::WherePredicates, supported_traits::Trait, TraitHandler, + TraitHandler, + common::{ + bound::BOUND_EXCEPTIONS_CLONE, r#type::type_uses_type_params, + where_predicates_bool::WherePredicates, + }, + supported_traits::Trait, + trait_handlers::TraitHandlerContext, }; +/// Generates the `Clone` implementation for a struct. pub(crate) struct CloneStructHandler; impl TraitHandler for CloneStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -35,24 +46,32 @@ impl TraitHandler for CloneStructHandler { #[cfg(not(feature = "Copy"))] let contains_copy = false; - if contains_copy { - clone_token_stream.extend(quote!(*self)); - } - for field in data.fields.iter() { let field_attribute = FieldAttributeBuilder { - enable_method: !contains_copy + enable_method: true } .build_from_attributes(&field.attrs, traits)?; fields.push((field, field_attribute)); } + let has_custom_method = + fields.iter().any(|(_, field_attribute)| field_attribute.method.is_some()); + + let uses_generics = data + .fields + .iter() + .any(|field| type_uses_type_params(&field.ty, &ast.generics.params)); + + // Like the built-in derives, `clone` can be a plain bitwise copy only when `Copy` is derived together, no generic type parameter is involved, and no field uses a custom clone method. + // When generic type parameters are involved, a field-wise clone keeps the `Clone` impl usable for type arguments that are `Clone` but not `Copy`. + let use_bitwise_copy = contains_copy && !uses_generics && !has_custom_method; + let mut clone_types: Vec<&Type> = Vec::new(); match &data.fields { Fields::Unit => { - if !contains_copy { + if !use_bitwise_copy { clone_token_stream.extend(quote!(Self)); clone_from_token_stream.extend(quote!(let _ = source;)); } @@ -89,7 +108,7 @@ impl TraitHandler for CloneStructHandler { } } - if !contains_copy { + if !use_bitwise_copy { clone_token_stream.extend(quote! { Self { #fields_token_stream @@ -129,24 +148,27 @@ impl TraitHandler for CloneStructHandler { } } - if !contains_copy { + if !use_bitwise_copy { clone_token_stream.extend(quote!(Self ( #fields_token_stream ))); clone_from_token_stream.extend(clone_from_body_token_stream); } }, } + if use_bitwise_copy { + clone_token_stream.extend(quote!(*self)); + } + + // The bound trait is always `Clone`; the `Copy` impl is emitted by the `Copy` handler with its own bounds. bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( &ast.generics.params, - &syn::parse2(if contains_copy { - quote!(::core::marker::Copy) - } else { - quote!(::core::clone::Clone) - }) - .unwrap(), + &syn::parse2(quote!(::core::clone::Clone)).unwrap(), &clone_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_CLONE, ); + + ctx.record(Trait::Clone, &bound); } let clone_from_fn_token_stream = if clone_from_token_stream.is_empty() { @@ -163,6 +185,7 @@ impl TraitHandler for CloneStructHandler { let ident = &ast.ident; let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -172,6 +195,7 @@ impl TraitHandler for CloneStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause { #[inline] fn clone(&self) -> Self { @@ -182,14 +206,6 @@ impl TraitHandler for CloneStructHandler { } }); - #[cfg(feature = "Copy")] - if traits.contains(&Trait::Copy) { - token_stream.extend(quote! { - impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/clone/clone_union.rs b/src/trait_handlers/clone/clone_union.rs index dc3c729..4cee051 100644 --- a/src/trait_handlers/clone/clone_union.rs +++ b/src/trait_handlers/clone/clone_union.rs @@ -2,47 +2,61 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + common::bound::BOUND_EXCEPTIONS_COPY, supported_traits::Trait, + trait_handlers::TraitHandlerContext, }; -use crate::supported_traits::Trait; +/// Generates the `Clone` implementation for a union. pub(crate) struct CloneUnionHandler; impl TraitHandler for CloneUnionHandler { fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } .build_from_clone_meta(meta)?; - let mut field_types = vec![]; + let mut field_types = Vec::new(); if let Data::Union(data) = &ast.data { for field in data.fields.named.iter() { - field_types.push(&field.ty); let _ = FieldAttributeBuilder { enable_method: false } .build_from_attributes(&field.attrs, traits)?; + + field_types.push(&field.ty); } } let ident = &ast.ident; + // A union can only be cloned by a bitwise copy, so the fields must satisfy `Copy` no matter whether `Copy` is derived together or not. let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( &ast.generics.params, &syn::parse2(quote!(::core::marker::Copy)).unwrap(), &field_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_COPY, ); + ctx.record(Trait::Clone, &bound); + let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -52,6 +66,7 @@ impl TraitHandler for CloneUnionHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::clone::Clone for #ident #ty_generics #where_clause { #[inline] fn clone(&self) -> Self { @@ -60,14 +75,6 @@ impl TraitHandler for CloneUnionHandler { } }); - #[cfg(feature = "Copy")] - if traits.contains(&Trait::Copy) { - token_stream.extend(quote! { - impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/clone/mod.rs b/src/trait_handlers/clone/mod.rs index 8c8a929..7ca3fde 100644 --- a/src/trait_handlers/clone/mod.rs +++ b/src/trait_handlers/clone/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod clone_enum; mod clone_struct; mod clone_union; @@ -8,12 +9,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Clone` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct CloneHandler; impl TraitHandler for CloneHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -21,16 +24,25 @@ impl TraitHandler for CloneHandler { match ast.data { Data::Struct(_) => clone_struct::CloneStructHandler::trait_meta_handler( ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Enum(_) => clone_enum::CloneEnumHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Union(_) => clone_union::CloneUnionHandler::trait_meta_handler( + ast, + ctx, token_stream, traits, meta, ), - Data::Enum(_) => { - clone_enum::CloneEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, - Data::Union(_) => { - clone_union::CloneUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, } } } diff --git a/src/trait_handlers/clone/models/field_attribute.rs b/src/trait_handlers/clone/models/field_attribute.rs index 63c7a9c..dab98f1 100644 --- a/src/trait_handlers/clone/models/field_attribute.rs +++ b/src/trait_handlers/clone/models/field_attribute.rs @@ -1,16 +1,19 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated}; use crate::{common::path::meta_2_path, panic, supported_traits::Trait}; +/// The parsed settings of a field-level `Clone` attribute. pub(crate) struct FieldAttribute { pub(crate) method: Option, } +/// Parses field-level `Clone` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_method: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `Clone` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Clone")); @@ -40,24 +43,24 @@ impl FieldAttributeBuilder { let mut method_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "method" { - if !self.enable_method { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "method" + { + if !self.enable_method { + return Ok(false); + } - let v = meta_2_path(&meta)?; + let v = meta_2_path(&meta)?; - if method_is_set { - return Err(panic::parameter_reset(ident)); - } + if method_is_set { + return Err(panic::parameter_reset(ident)); + } - method_is_set = true; + method_is_set = true; - method = Some(v); + method = Some(v); - return Ok(true); - } + return Ok(true); } Ok(false) @@ -79,6 +82,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Clone` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -89,30 +93,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Clone { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_clone_meta(&meta)?); + if t == Trait::Clone { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_clone_meta(&meta)?); } } } diff --git a/src/trait_handlers/clone/models/type_attribute.rs b/src/trait_handlers/clone/models/type_attribute.rs index f657bbf..21f8af2 100644 --- a/src/trait_handlers/clone/models/type_attribute.rs +++ b/src/trait_handlers/clone/models/type_attribute.rs @@ -1,18 +1,21 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{common::bound::Bound, panic, Trait}; +use crate::{Trait, common::bound::Bound, panic}; +/// The parsed settings of a type-level (or variant-level) `Clone` attribute. pub(crate) struct TypeAttribute { pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `Clone` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_bound: bool, } impl TypeAttributeBuilder { + /// Parses one `Clone` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_clone_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Clone")); @@ -55,24 +58,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -94,6 +97,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Clone` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -104,30 +108,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Clone { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_clone_meta(&meta)?); + if t == Trait::Clone { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_clone_meta(&meta)?); } } } diff --git a/src/trait_handlers/copy/mod.rs b/src/trait_handlers/copy/mod.rs index e1ce401..b816ad7 100644 --- a/src/trait_handlers/copy/mod.rs +++ b/src/trait_handlers/copy/mod.rs @@ -5,7 +5,19 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; -use crate::Trait; +use crate::{ + Trait, + common::bound::{BOUND_EXCEPTIONS_COPY, Bound}, + trait_handlers::TraitHandlerContext, +}; + +/// Returns the traits whose recorded bounds `Copy` inherits when its own bound is automatic. +pub(crate) fn prerequisites() -> &'static [Trait] { + &[ + #[cfg(feature = "Clone")] + Trait::Clone, + ] +} pub(crate) struct CopyHandler; @@ -13,82 +25,89 @@ impl TraitHandler for CopyHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { - #[cfg(feature = "Clone")] - let contains_clone = traits.contains(&Trait::Clone); - - #[cfg(not(feature = "Clone"))] - let contains_clone = false; + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); let type_attribute = TypeAttributeBuilder { - enable_flag: true, - enable_bound: !contains_clone, + enable_flag: true, enable_bound: true } .build_from_copy_meta(meta)?; - let mut field_types = vec![]; + let mut field_types = Vec::new(); - // if `contains_clone` is true, the implementation is handled by the `Clone` attribute, and field attributes is also handled by the `Clone` attribute - if !contains_clone { - match &ast.data { - Data::Struct(data) => { - for field in data.fields.iter() { - field_types.push(&field.ty); - let _ = - FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; - } - }, - Data::Enum(data) => { - for variant in data.variants.iter() { - let _ = TypeAttributeBuilder { - enable_flag: false, enable_bound: false - } - .build_from_attributes(&variant.attrs, traits)?; - - for field in variant.fields.iter() { - field_types.push(&field.ty); - let _ = FieldAttributeBuilder - .build_from_attributes(&field.attrs, traits)?; - } + match &ast.data { + Data::Struct(data) => { + for field in data.fields.iter() { + let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; + + field_types.push(&field.ty); + } + }, + Data::Enum(data) => { + for variant in data.variants.iter() { + let _ = TypeAttributeBuilder { + enable_flag: false, enable_bound: false } - }, - Data::Union(data) => { - for field in data.fields.named.iter() { - field_types.push(&field.ty); + .build_from_attributes(&variant.attrs, traits)?; + + for field in variant.fields.iter() { let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; + + field_types.push(&field.ty); } - }, - } + } + }, + Data::Union(data) => { + for field in data.fields.named.iter() { + let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; - let ident = &ast.ident; + field_types.push(&field.ty); + } + }, + } - let bound = - type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::marker::Copy)).unwrap(), - &field_types, - &[quote! {::core::clone::Clone}], - ); + let ident = &ast.ident; - let mut generics = ast.generics.clone(); - let where_clause = generics.make_where_clause(); + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); - for where_predicate in bound { - where_clause.predicates.push(where_predicate); - } + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::marker::Copy)).unwrap(), + &field_types, + &ast.ident, + &BOUND_EXCEPTIONS_COPY, + ); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + // `Copy` requires `Clone`, so an automatic bound also has to carry the predicates of the `Clone` impl that Educe just emitted. + if bound_is_auto { + ctx.inherit_from(prerequisites(), &mut bound); + } - token_stream.extend(quote! { - impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { - } - }); + ctx.record(Trait::Copy, &bound); + + let mut generics = ast.generics.clone(); + + let where_clause = generics.make_where_clause(); + + for where_predicate in bound { + where_clause.predicates.push(where_predicate); } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + token_stream.extend(quote! { + #generated_impl_attributes + impl #impl_generics ::core::marker::Copy for #ident #ty_generics #where_clause { + } + }); + Ok(()) } } diff --git a/src/trait_handlers/copy/models/field_attribute.rs b/src/trait_handlers/copy/models/field_attribute.rs index e03c02d..58bf9f7 100644 --- a/src/trait_handlers/copy/models/field_attribute.rs +++ b/src/trait_handlers/copy/models/field_attribute.rs @@ -1,18 +1,22 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{panic, supported_traits::Trait}; +/// The parsed settings of a field-level `Copy` attribute. pub(crate) struct FieldAttribute; +/// Parses field-level `Copy` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder; impl FieldAttributeBuilder { + /// Parses one field-level `Copy` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Copy")); - return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())); + Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Copy` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -23,30 +27,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Copy { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_copy_meta(&meta)?); + if t == Trait::Copy { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_copy_meta(&meta)?); } } } diff --git a/src/trait_handlers/copy/models/type_attribute.rs b/src/trait_handlers/copy/models/type_attribute.rs index 457acef..d28e7cb 100644 --- a/src/trait_handlers/copy/models/type_attribute.rs +++ b/src/trait_handlers/copy/models/type_attribute.rs @@ -1,18 +1,21 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{common::bound::Bound, panic, Trait}; +use crate::{Trait, common::bound::Bound, panic}; +/// The parsed settings of a type-level (or variant-level) `Copy` attribute. pub(crate) struct TypeAttribute { pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `Copy` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_bound: bool, } impl TypeAttributeBuilder { + /// Parses one `Copy` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_copy_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Copy")); @@ -55,24 +58,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -94,6 +97,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Copy` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -104,30 +108,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Copy { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_copy_meta(&meta)?); + if t == Trait::Copy { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_copy_meta(&meta)?); } } } diff --git a/src/trait_handlers/debug/debug_enum.rs b/src/trait_handlers/debug/debug_enum.rs index 1e42381..bda49b3 100644 --- a/src/trait_handlers/debug/debug_enum.rs +++ b/src/trait_handlers/debug/debug_enum.rs @@ -1,18 +1,27 @@ -use quote::{format_ident, quote, ToTokens}; +use quote::{ToTokens, format_ident, quote}; use syn::{Data, DeriveInput, Fields, Meta, Type}; use super::models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}; -use crate::{common::path::path_to_string, supported_traits::Trait, trait_handlers::TraitHandler}; +use crate::{ + common::{bound::BOUND_EXCEPTIONS_DEBUG, path::path_to_string}, + supported_traits::Trait, + trait_handlers::{TraitHandler, TraitHandlerContext}, +}; +/// Generates the `Debug` implementation for an enum. pub(crate) struct DebugEnumHandler; impl TraitHandler for DebugEnumHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: false, @@ -98,11 +107,14 @@ impl TraitHandler for DebugEnumHandler { continue; } + // The displayed field name is a plain string, matching the tuple-variant handling below. let key = match field_attribute.name { - FieldName::Custom(name) => name, - FieldName::Default => field_name_real.clone(), + FieldName::Custom(name) => name.to_string(), + FieldName::Default => field_name_real.to_string(), }; + let key = syn::LitStr::new(&key, proc_macro2::Span::call_site()); + pattern_token_stream .extend(quote!(#field_name_real: #field_name_var,)); @@ -117,17 +129,17 @@ impl TraitHandler for DebugEnumHandler { )); block_token_stream.extend(if name_string.is_some() { - quote! (builder.field(stringify!(#key), &arg);) + quote! (builder.field(#key, &arg);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);) + quote! (builder.entry(&Educe__RawString(#key), &arg);) }); } else { debug_types.push(ty); block_token_stream.extend(if name_string.is_some() { - quote! (builder.field(stringify!(#key), #field_name_var);) + quote! (builder.field(#key, #field_name_var);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);) + quote! (builder.entry(&Educe__RawString(#key), #field_name_var);) }); } @@ -219,11 +231,14 @@ impl TraitHandler for DebugEnumHandler { let field_name_var = format_ident!("_{}", index); + // The displayed field name is a plain string, so a tuple field can be shown with its real name `0`, which is not a valid ident. let key = match field_attribute.name { - FieldName::Custom(name) => name, - FieldName::Default => field_name_var.clone(), + FieldName::Custom(name) => name.to_string(), + FieldName::Default => index.to_string(), }; + let key = syn::LitStr::new(&key, proc_macro2::Span::call_site()); + pattern_token_stream.extend(quote!(#field_name_var,)); let ty = &field.ty; @@ -237,17 +252,17 @@ impl TraitHandler for DebugEnumHandler { )); block_token_stream.extend(if name_string.is_some() { - quote! (builder.field(stringify!(#key), &arg);) + quote! (builder.field(#key, &arg);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);) + quote! (builder.entry(&Educe__RawString(#key), &arg);) }); } else { debug_types.push(ty); block_token_stream.extend(if name_string.is_some() { - quote! (builder.field(stringify!(#key), #field_name_var);) + quote! (builder.field(#key, #field_name_var);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), #field_name_var);) + quote! (builder.entry(&Educe__RawString(#key), #field_name_var);) }); } @@ -336,10 +351,12 @@ impl TraitHandler for DebugEnumHandler { &ast.generics.params, &syn::parse2(quote!(::core::fmt::Debug)).unwrap(), &debug_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_DEBUG, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -349,6 +366,7 @@ impl TraitHandler for DebugEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { diff --git a/src/trait_handlers/debug/debug_struct.rs b/src/trait_handlers/debug/debug_struct.rs index 6430480..0f76a49 100644 --- a/src/trait_handlers/debug/debug_struct.rs +++ b/src/trait_handlers/debug/debug_struct.rs @@ -1,21 +1,29 @@ -use quote::{format_ident, quote}; +use quote::quote; use syn::{Data, DeriveInput, Fields, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}, TraitHandler, + models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}, +}; +use crate::{ + Trait, + common::{bound::BOUND_EXCEPTIONS_DEBUG, ident_index::IdentOrIndex}, + trait_handlers::TraitHandlerContext, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; pub struct DebugStructHandler; impl TraitHandler for DebugStructHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let is_tuple = { if let Data::Struct(data) = &ast.data { matches!(data.fields, Fields::Unnamed(_)) @@ -63,19 +71,23 @@ impl TraitHandler for DebugStructHandler { continue; } + // The displayed field name is a plain string, so a tuple field can be shown with its real name `0`, which is not a valid ident. let (key, field_name) = match field_attribute.name { - FieldName::Custom(name) => { - (name, IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index)) - }, + FieldName::Custom(name) => ( + name.to_string(), + IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index), + ), FieldName::Default => { if let Some(ident) = field.ident.as_ref() { - (ident.clone(), IdentOrIndex::from(ident)) + (ident.to_string(), IdentOrIndex::from(ident)) } else { - (format_ident!("_{}", index), IdentOrIndex::from(index)) + (index.to_string(), IdentOrIndex::from(index)) } }, }; + let key = syn::LitStr::new(&key, proc_macro2::Span::call_site()); + let ty = &field.ty; if let Some(method) = field_attribute.method { @@ -87,17 +99,17 @@ impl TraitHandler for DebugStructHandler { )); builder_token_stream.extend(if name.is_some() { - quote! (builder.field(stringify!(#key), &arg);) + quote! (builder.field(#key, &arg);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), &arg);) + quote! (builder.entry(&Educe__RawString(#key), &arg);) }); } else { debug_types.push(ty); builder_token_stream.extend(if name.is_some() { - quote! (builder.field(stringify!(#key), &self.#field_name);) + quote! (builder.field(#key, &self.#field_name);) } else { - quote! (builder.entry(&Educe__RawString(stringify!(#key)), &self.#field_name);) + quote! (builder.entry(&Educe__RawString(#key), &self.#field_name);) }); } @@ -157,10 +169,12 @@ impl TraitHandler for DebugStructHandler { &ast.generics.params, &syn::parse2(quote!(::core::fmt::Debug)).unwrap(), &debug_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_DEBUG, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -170,6 +184,7 @@ impl TraitHandler for DebugStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { diff --git a/src/trait_handlers/debug/debug_union.rs b/src/trait_handlers/debug/debug_union.rs index 096ea77..418c8d9 100644 --- a/src/trait_handlers/debug/debug_union.rs +++ b/src/trait_handlers/debug/debug_union.rs @@ -2,20 +2,25 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::{ - models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}, TraitHandler, + models::{FieldAttributeBuilder, FieldName, TypeAttributeBuilder, TypeName}, }; -use crate::supported_traits::Trait; +use crate::{supported_traits::Trait, trait_handlers::TraitHandlerContext}; +/// Generates the `Debug` implementation for a union. pub(crate) struct DebugUnionHandler; impl TraitHandler for DebugUnionHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: true, @@ -50,6 +55,8 @@ impl TraitHandler for DebugUnionHandler { builder_token_stream.extend(quote!( let mut builder = f.debug_tuple(stringify!(#name)); + // The whole memory of the union is dumped as bytes on purpose, including any padding bytes, because the active field cannot be known at runtime. + // The user opted in to this behavior with the `unsafe` keyword in the attribute. let size = ::core::mem::size_of::(); let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; @@ -73,6 +80,7 @@ impl TraitHandler for DebugUnionHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::fmt::Debug for #ident #ty_generics #where_clause { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { diff --git a/src/trait_handlers/debug/mod.rs b/src/trait_handlers/debug/mod.rs index 367ddb7..424b0e1 100644 --- a/src/trait_handlers/debug/mod.rs +++ b/src/trait_handlers/debug/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod common; mod debug_enum; mod debug_struct; @@ -10,12 +11,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Debug` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct DebugHandler; impl TraitHandler for DebugHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -23,16 +26,25 @@ impl TraitHandler for DebugHandler { match ast.data { Data::Struct(_) => debug_struct::DebugStructHandler::trait_meta_handler( ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Enum(_) => debug_enum::DebugEnumHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Union(_) => debug_union::DebugUnionHandler::trait_meta_handler( + ast, + ctx, token_stream, traits, meta, ), - Data::Enum(_) => { - debug_enum::DebugEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, - Data::Union(_) => { - debug_union::DebugUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, } } } diff --git a/src/trait_handlers/debug/models/field_attribute.rs b/src/trait_handlers/debug/models/field_attribute.rs index f2cd3e2..a501b7a 100644 --- a/src/trait_handlers/debug/models/field_attribute.rs +++ b/src/trait_handlers/debug/models/field_attribute.rs @@ -1,10 +1,10 @@ -use syn::{punctuated::Punctuated, Attribute, Ident, Meta, Path, Token}; +use syn::{Attribute, Ident, Meta, Path, Token, punctuated::Punctuated}; use crate::{ common::{ ident_bool::{ - meta_2_bool_allow_path, meta_2_ident, meta_name_value_2_bool, meta_name_value_2_ident, - meta_name_value_2_ident_and_bool, IdentOrBool, + IdentOrBool, meta_2_bool_allow_path, meta_2_ident, meta_name_value_2_bool, + meta_name_value_2_ident, meta_name_value_2_ident_and_bool, }, path::meta_2_path, }, @@ -18,12 +18,14 @@ pub(crate) enum FieldName { Custom(Ident), } +/// The parsed settings of a field-level `Debug` attribute. pub(crate) struct FieldAttribute { pub(crate) name: FieldName, pub(crate) ignore: bool, pub(crate) method: Option, } +/// Parses field-level `Debug` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_name: bool, pub(crate) enable_ignore: bool, @@ -32,6 +34,7 @@ pub(crate) struct FieldAttributeBuilder { } impl FieldAttributeBuilder { + /// Parses one field-level `Debug` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_debug_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Debug")); @@ -176,6 +179,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Debug` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -186,30 +190,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Debug { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_debug_meta(&meta)?); + if t == Trait::Debug { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_debug_meta(&meta)?); } } } diff --git a/src/trait_handlers/debug/models/type_attribute.rs b/src/trait_handlers/debug/models/type_attribute.rs index ed84ddf..2c75c3a 100644 --- a/src/trait_handlers/debug/models/type_attribute.rs +++ b/src/trait_handlers/debug/models/type_attribute.rs @@ -1,13 +1,14 @@ use proc_macro2::Ident; -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{ + Trait, common::{ bound::Bound, - ident_bool::{meta_2_bool, meta_2_ident_and_bool, meta_name_value_2_ident, IdentOrBool}, + ident_bool::{IdentOrBool, meta_2_bool, meta_2_ident_and_bool, meta_name_value_2_ident}, unsafe_punctuated_meta::UnsafePunctuatedMeta, }, - panic, Trait, + panic, }; #[derive(Debug, Clone)] @@ -28,6 +29,7 @@ impl TypeName { } } +/// The parsed settings of a type-level (or variant-level) `Debug` attribute. pub(crate) struct TypeAttribute { pub(crate) has_unsafe: bool, pub(crate) name: TypeName, @@ -36,6 +38,7 @@ pub(crate) struct TypeAttribute { } #[derive(Debug)] +/// Parses `Debug` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_unsafe: bool, @@ -47,6 +50,7 @@ pub(crate) struct TypeAttributeBuilder { } impl TypeAttributeBuilder { + /// Parses one `Debug` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_debug_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Debug")); @@ -215,6 +219,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Debug` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -225,30 +230,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Debug { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_debug_meta(&meta)?); + if t == Trait::Debug { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_debug_meta(&meta)?); } } } diff --git a/src/trait_handlers/debug/panic.rs b/src/trait_handlers/debug/panic.rs index a29646a..48cfe11 100644 --- a/src/trait_handlers/debug/panic.rs +++ b/src/trait_handlers/debug/panic.rs @@ -1,22 +1,22 @@ use quote::ToTokens; -use syn::{spanned::Spanned, Ident, Meta, Variant}; +use syn::{Ident, Meta, Variant}; #[inline] pub(crate) fn unit_struct_need_name(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), "a unit struct needs to have a name") + syn::Error::new_spanned(name, "a unit struct needs to have a name") } #[inline] pub(crate) fn unit_variant_need_name(variant: &Variant) -> syn::Error { - syn::Error::new( - variant.span(), + syn::Error::new_spanned( + variant, "a unit variant which doesn't use an enum name needs to have a name", ) } #[inline] pub(crate) fn unit_enum_need_name(name: &Ident) -> syn::Error { - syn::Error::new(name.span(), "a unit enum needs to have a name") + syn::Error::new_spanned(name, "a unit enum needs to have a name") } #[inline] @@ -29,8 +29,8 @@ pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { _ => s.insert_str(6, "unsafe, "), } - syn::Error::new( - meta.span(), + syn::Error::new_spanned( + meta, format!( "a union's `Debug` implementation may expose uninitialized memory\n* It is \ recommended that, for a union where `Debug` is implemented, types that allow \ diff --git a/src/trait_handlers/default/default_enum.rs b/src/trait_handlers/default/default_enum.rs index 8aa4ec6..f6835f6 100644 --- a/src/trait_handlers/default/default_enum.rs +++ b/src/trait_handlers/default/default_enum.rs @@ -1,21 +1,26 @@ use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Fields, Meta, Type, Variant}; +use syn::{Data, DeriveInput, Fields, Meta, Type, Variant}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::Trait; +use crate::{Trait, common::bound::BOUND_EXCEPTIONS_DEFAULT, trait_handlers::TraitHandlerContext}; +/// Generates the `Default` implementation for an enum. pub(crate) struct DefaultEnumHandler; impl TraitHandler for DefaultEnumHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_new: true, @@ -87,7 +92,7 @@ impl TraitHandler for DefaultEnumHandler { if let Some(default_variant) = default_variant { default_variant } else { - return Err(super::panic::no_default_variant(meta.span())); + return Err(super::panic::no_default_variant(meta)); } } }; @@ -166,10 +171,12 @@ impl TraitHandler for DefaultEnumHandler { &ast.generics.params, &syn::parse2(quote!(::core::default::Default)).unwrap(), &default_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_DEFAULT, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -179,6 +186,7 @@ impl TraitHandler for DefaultEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause { #[inline] fn default() -> Self { diff --git a/src/trait_handlers/default/default_struct.rs b/src/trait_handlers/default/default_struct.rs index ea25b56..131b469 100644 --- a/src/trait_handlers/default/default_struct.rs +++ b/src/trait_handlers/default/default_struct.rs @@ -2,20 +2,25 @@ use quote::quote; use syn::{Data, DeriveInput, Fields, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::Trait; +use crate::{Trait, common::bound::BOUND_EXCEPTIONS_DEFAULT, trait_handlers::TraitHandlerContext}; +/// Generates the `Default` implementation for a struct. pub(crate) struct DefaultStructHandler; impl TraitHandler for DefaultStructHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_new: true, @@ -111,10 +116,12 @@ impl TraitHandler for DefaultStructHandler { &ast.generics.params, &syn::parse2(quote!(::core::default::Default)).unwrap(), &default_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_DEFAULT, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -124,6 +131,7 @@ impl TraitHandler for DefaultStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause { #[inline] fn default() -> Self { diff --git a/src/trait_handlers/default/default_union.rs b/src/trait_handlers/default/default_union.rs index a535c01..8f65c96 100644 --- a/src/trait_handlers/default/default_union.rs +++ b/src/trait_handlers/default/default_union.rs @@ -1,21 +1,26 @@ use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Type}; +use syn::{Data, DeriveInput, Field, Meta, Type}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::Trait; +use crate::{Trait, common::bound::BOUND_EXCEPTIONS_DEFAULT, trait_handlers::TraitHandlerContext}; +/// Generates the `Default` implementation for a union. pub(crate) struct DefaultUnionHandler; impl TraitHandler for DefaultUnionHandler { fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_new: true, @@ -78,7 +83,7 @@ impl TraitHandler for DefaultUnionHandler { if let Some(default_field) = default_field { default_field } else { - return Err(super::panic::no_default_field(meta.span())); + return Err(super::panic::no_default_field(meta)); } } }; @@ -115,10 +120,12 @@ impl TraitHandler for DefaultUnionHandler { &ast.generics.params, &syn::parse2(quote!(::core::default::Default)).unwrap(), &default_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_DEFAULT, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -128,6 +135,7 @@ impl TraitHandler for DefaultUnionHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::default::Default for #ident #ty_generics #where_clause { #[inline] fn default() -> Self { diff --git a/src/trait_handlers/default/mod.rs b/src/trait_handlers/default/mod.rs index 5c1eeb8..bee4805 100644 --- a/src/trait_handlers/default/mod.rs +++ b/src/trait_handlers/default/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod default_enum; mod default_struct; mod default_union; @@ -9,12 +10,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Default` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct DefaultHandler; impl TraitHandler for DefaultHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -22,18 +25,21 @@ impl TraitHandler for DefaultHandler { match ast.data { Data::Struct(_) => default_struct::DefaultStructHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, ), Data::Enum(_) => default_enum::DefaultEnumHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, ), Data::Union(_) => default_union::DefaultUnionHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, diff --git a/src/trait_handlers/default/models/field_attribute.rs b/src/trait_handlers/default/models/field_attribute.rs index 6c82940..e612c45 100644 --- a/src/trait_handlers/default/models/field_attribute.rs +++ b/src/trait_handlers/default/models/field_attribute.rs @@ -1,5 +1,5 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Expr, Meta, Token, Type}; +use syn::{Attribute, Expr, Meta, Token, Type, punctuated::Punctuated, spanned::Spanned}; use crate::{ common::expr::{auto_adjust_expr, meta_2_expr}, @@ -7,18 +7,21 @@ use crate::{ supported_traits::Trait, }; +/// The parsed settings of a field-level `Default` attribute. pub(crate) struct FieldAttribute { pub(crate) flag: bool, pub(crate) expression: Option, pub(crate) span: Span, } +/// Parses field-level `Default` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_expression: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `Default` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_default_meta( &self, meta: &Meta, @@ -117,6 +120,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Default` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -128,30 +132,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Default { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_default_meta(&meta, ty)?); + if t == Trait::Default { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_default_meta(&meta, ty)?); } } } diff --git a/src/trait_handlers/default/models/type_attribute.rs b/src/trait_handlers/default/models/type_attribute.rs index ee0b901..a322be7 100644 --- a/src/trait_handlers/default/models/type_attribute.rs +++ b/src/trait_handlers/default/models/type_attribute.rs @@ -1,15 +1,17 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Expr, Meta, Token}; +use syn::{Attribute, Expr, Meta, Token, punctuated::Punctuated, spanned::Spanned}; use crate::{ + Trait, common::{ bound::Bound, expr::{auto_adjust_expr, meta_2_expr}, ident_bool::meta_2_bool_allow_path, }, - panic, Trait, + panic, }; +/// The parsed settings of a type-level (or variant-level) `Default` attribute. pub(crate) struct TypeAttribute { pub(crate) flag: bool, pub(crate) new: bool, @@ -19,6 +21,7 @@ pub(crate) struct TypeAttribute { } #[derive(Debug)] +/// Parses `Default` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_new: bool, @@ -27,6 +30,7 @@ pub(crate) struct TypeAttributeBuilder { } impl TypeAttributeBuilder { + /// Parses one `Default` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_default_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Default")); @@ -164,6 +168,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Default` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -174,30 +179,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Default { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_default_meta(&meta)?); + if t == Trait::Default { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_default_meta(&meta)?); } } } diff --git a/src/trait_handlers/default/panic.rs b/src/trait_handlers/default/panic.rs index 9f3598e..25af2df 100644 --- a/src/trait_handlers/default/panic.rs +++ b/src/trait_handlers/default/panic.rs @@ -1,4 +1,5 @@ use proc_macro2::Span; +use syn::Meta; #[inline] pub(crate) fn multiple_default_fields(span: Span) -> syn::Error { @@ -6,8 +7,8 @@ pub(crate) fn multiple_default_fields(span: Span) -> syn::Error { } #[inline] -pub(crate) fn no_default_field(span: Span) -> syn::Error { - syn::Error::new(span, "there is no field set as default") +pub(crate) fn no_default_field(meta: &Meta) -> syn::Error { + syn::Error::new_spanned(meta, "there is no field set as default") } #[inline] @@ -16,6 +17,6 @@ pub(crate) fn multiple_default_variants(span: Span) -> syn::Error { } #[inline] -pub(crate) fn no_default_variant(span: Span) -> syn::Error { - syn::Error::new(span, "there is no variant set as default") +pub(crate) fn no_default_variant(meta: &Meta) -> syn::Error { + syn::Error::new_spanned(meta, "there is no variant set as default") } diff --git a/src/trait_handlers/deref/deref_enum.rs b/src/trait_handlers/deref/deref_enum.rs index 1c478cc..e56939b 100644 --- a/src/trait_handlers/deref/deref_enum.rs +++ b/src/trait_handlers/deref/deref_enum.rs @@ -1,22 +1,30 @@ use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Type}; +use syn::{Data, DeriveInput, Field, Fields, Ident, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + common::r#type::dereference, panic, supported_traits::Trait, + trait_handlers::TraitHandlerContext, }; -use crate::{common::r#type::dereference, panic, supported_traits::Trait}; +/// Generates the `Deref` implementation for an enum. pub(crate) struct DerefEnumHandler; impl TraitHandler for DerefEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let _ = TypeAttributeBuilder { enable_flag: true } @@ -45,6 +53,7 @@ impl TraitHandler for DerefEnumHandler { let fields = &variant.fields; + // With exactly one field, that field is the `Deref` target automatically; otherwise exactly one field has to be marked with `#[educe(Deref)]`. let (index, field) = if fields.len() == 1 { let field = fields.into_iter().next().unwrap(); @@ -78,7 +87,7 @@ impl TraitHandler for DerefEnumHandler { if let Some(deref_field) = deref_field { deref_field } else { - return Err(super::panic::no_deref_field_of_variant(meta.span(), variant)); + return Err(super::panic::no_deref_field_of_variant(variant)); } }; @@ -91,7 +100,7 @@ impl TraitHandler for DerefEnumHandler { } if variants.is_empty() { - return Err(super::panic::no_deref_field(meta.span())); + return Err(super::panic::no_deref_field(meta)); } let ty = variants[0].4; @@ -127,6 +136,7 @@ impl TraitHandler for DerefEnumHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause { type Target = #target_token_stream; diff --git a/src/trait_handlers/deref/deref_struct.rs b/src/trait_handlers/deref/deref_struct.rs index 8c97130..f75d2e8 100644 --- a/src/trait_handlers/deref/deref_struct.rs +++ b/src/trait_handlers/deref/deref_struct.rs @@ -1,25 +1,31 @@ use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta}; +use syn::{Data, DeriveInput, Field, Meta}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; use crate::{ - common::{ident_index::IdentOrIndex, r#type::dereference_changed}, Trait, + common::{ident_index::IdentOrIndex, r#type::dereference_changed}, + trait_handlers::TraitHandlerContext, }; +/// Generates the `Deref` implementation for a struct. pub(crate) struct DerefStructHandler; impl TraitHandler for DerefStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let _ = TypeAttributeBuilder { enable_flag: true } @@ -32,6 +38,7 @@ impl TraitHandler for DerefStructHandler { let (index, field) = { let fields = &data.fields; + // With exactly one field, that field is the `Deref` target automatically; otherwise exactly one field has to be marked with `#[educe(Deref)]`. if fields.len() == 1 { let field = fields.into_iter().next().unwrap(); @@ -64,7 +71,7 @@ impl TraitHandler for DerefStructHandler { if let Some(deref_field) = deref_field { deref_field } else { - return Err(super::panic::no_deref_field(meta.span())); + return Err(super::panic::no_deref_field(meta)); } } }; @@ -88,6 +95,7 @@ impl TraitHandler for DerefStructHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::ops::Deref for #ident #ty_generics #where_clause { type Target = #target_token_stream; diff --git a/src/trait_handlers/deref/mod.rs b/src/trait_handlers/deref/mod.rs index 1a3263c..b132a9c 100644 --- a/src/trait_handlers/deref/mod.rs +++ b/src/trait_handlers/deref/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod deref_enum; mod deref_struct; mod models; @@ -8,12 +9,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Deref` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct DerefHandler; impl TraitHandler for DerefHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -21,13 +24,18 @@ impl TraitHandler for DerefHandler { match ast.data { Data::Struct(_) => deref_struct::DerefStructHandler::trait_meta_handler( ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Enum(_) => deref_enum::DerefEnumHandler::trait_meta_handler( + ast, + ctx, token_stream, traits, meta, ), - Data::Enum(_) => { - deref_enum::DerefEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, Data::Union(_) => { Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) }, diff --git a/src/trait_handlers/deref/models/field_attribute.rs b/src/trait_handlers/deref/models/field_attribute.rs index 5f3d3fb..bbef26a 100644 --- a/src/trait_handlers/deref/models/field_attribute.rs +++ b/src/trait_handlers/deref/models/field_attribute.rs @@ -1,18 +1,21 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated, spanned::Spanned}; use crate::{panic, supported_traits::Trait}; +/// The parsed settings of a field-level `Deref` attribute. pub(crate) struct FieldAttribute { pub(crate) flag: bool, pub(crate) span: Span, } +/// Parses field-level `Deref` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_flag: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `Deref` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Deref")); @@ -48,6 +51,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Deref` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -58,30 +62,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Deref { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_deref_meta(&meta)?); + if t == Trait::Deref { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_deref_meta(&meta)?); } } } diff --git a/src/trait_handlers/deref/models/type_attribute.rs b/src/trait_handlers/deref/models/type_attribute.rs index 01b9e5e..965f17e 100644 --- a/src/trait_handlers/deref/models/type_attribute.rs +++ b/src/trait_handlers/deref/models/type_attribute.rs @@ -1,15 +1,17 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{panic, Trait}; +use crate::{Trait, panic}; pub(crate) struct TypeAttribute; #[derive(Debug)] +/// Parses `Deref` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, } impl TypeAttributeBuilder { + /// Parses one `Deref` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_deref_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Deref")); @@ -43,6 +45,7 @@ impl TypeAttributeBuilder { Ok(TypeAttribute) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Deref` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -53,30 +56,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Deref { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_deref_meta(&meta)?); + if t == Trait::Deref { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_deref_meta(&meta)?); } } } diff --git a/src/trait_handlers/deref/panic.rs b/src/trait_handlers/deref/panic.rs index 508b623..083c172 100644 --- a/src/trait_handlers/deref/panic.rs +++ b/src/trait_handlers/deref/panic.rs @@ -1,5 +1,5 @@ use proc_macro2::Span; -use syn::Variant; +use syn::{Meta, Variant}; #[inline] pub(crate) fn multiple_deref_fields(span: Span) -> syn::Error { @@ -15,14 +15,14 @@ pub(crate) fn multiple_deref_fields_of_variant(span: Span, variant: &Variant) -> } #[inline] -pub(crate) fn no_deref_field(span: Span) -> syn::Error { - syn::Error::new(span, "there is no field which is assigned for `Deref`") +pub(crate) fn no_deref_field(meta: &Meta) -> syn::Error { + syn::Error::new_spanned(meta, "there is no field which is assigned for `Deref`") } #[inline] -pub(crate) fn no_deref_field_of_variant(span: Span, variant: &Variant) -> syn::Error { - syn::Error::new( - span, +pub(crate) fn no_deref_field_of_variant(variant: &Variant) -> syn::Error { + syn::Error::new_spanned( + variant, format!( "there is no field for the `{}` variant which is assigned for `Deref`", variant.ident diff --git a/src/trait_handlers/deref_mut/deref_mut_enum.rs b/src/trait_handlers/deref_mut/deref_mut_enum.rs index 6ba2f2e..8521188 100644 --- a/src/trait_handlers/deref_mut/deref_mut_enum.rs +++ b/src/trait_handlers/deref_mut/deref_mut_enum.rs @@ -1,22 +1,27 @@ use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta}; +use syn::{Data, DeriveInput, Field, Fields, Ident, Meta}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::{panic, supported_traits::Trait}; +use crate::{panic, supported_traits::Trait, trait_handlers::TraitHandlerContext}; +/// Generates the `DerefMut` implementation for an enum. pub(crate) struct DerefMutEnumHandler; impl TraitHandler for DerefMutEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let _ = TypeAttributeBuilder { enable_flag: true } @@ -44,6 +49,7 @@ impl TraitHandler for DerefMutEnumHandler { let fields = &variant.fields; + // With exactly one field, that field is the `DerefMut` target automatically; otherwise exactly one field has to be marked with `#[educe(DerefMut)]`. let (index, field) = if fields.len() == 1 { let field = fields.into_iter().next().unwrap(); @@ -77,10 +83,7 @@ impl TraitHandler for DerefMutEnumHandler { if let Some(deref_field) = deref_field { deref_field } else { - return Err(super::panic::no_deref_mut_field_of_variant( - meta.span(), - variant, - )); + return Err(super::panic::no_deref_mut_field_of_variant(variant)); } }; @@ -93,7 +96,7 @@ impl TraitHandler for DerefMutEnumHandler { } if variants.is_empty() { - return Err(super::panic::no_deref_mut_field(meta.span())); + return Err(super::panic::no_deref_mut_field(meta)); } for (variant_ident, is_tuple, index, field_name) in variants { @@ -124,6 +127,7 @@ impl TraitHandler for DerefMutEnumHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { diff --git a/src/trait_handlers/deref_mut/deref_mut_struct.rs b/src/trait_handlers/deref_mut/deref_mut_struct.rs index deb5888..af6acfb 100644 --- a/src/trait_handlers/deref_mut/deref_mut_struct.rs +++ b/src/trait_handlers/deref_mut/deref_mut_struct.rs @@ -1,22 +1,27 @@ use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Type}; +use syn::{Data, DeriveInput, Field, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +use crate::{Trait, common::ident_index::IdentOrIndex, trait_handlers::TraitHandlerContext}; +/// Generates the `DerefMut` implementation for a struct. pub(crate) struct DerefMutStructHandler; impl TraitHandler for DerefMutStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let _ = TypeAttributeBuilder { enable_flag: true } @@ -28,6 +33,7 @@ impl TraitHandler for DerefMutStructHandler { let (index, field) = { let fields = &data.fields; + // With exactly one field, that field is the `DerefMut` target automatically; otherwise exactly one field has to be marked with `#[educe(DerefMut)]`. if fields.len() == 1 { let field = fields.into_iter().next().unwrap(); @@ -60,7 +66,7 @@ impl TraitHandler for DerefMutStructHandler { if let Some(deref_field) = deref_field { deref_field } else { - return Err(super::panic::no_deref_mut_field(meta.span())); + return Err(super::panic::no_deref_mut_field(meta)); } } }; @@ -79,6 +85,7 @@ impl TraitHandler for DerefMutStructHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::ops::DerefMut for #ident #ty_generics #where_clause { #[inline] fn deref_mut(&mut self) -> &mut Self::Target { diff --git a/src/trait_handlers/deref_mut/mod.rs b/src/trait_handlers/deref_mut/mod.rs index 01f0991..ef0014a 100644 --- a/src/trait_handlers/deref_mut/mod.rs +++ b/src/trait_handlers/deref_mut/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod deref_mut_enum; mod deref_mut_struct; mod models; @@ -8,12 +9,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `DerefMut` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct DerefMutHandler; impl TraitHandler for DerefMutHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -21,12 +24,14 @@ impl TraitHandler for DerefMutHandler { match ast.data { Data::Struct(_) => deref_mut_struct::DerefMutStructHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, ), Data::Enum(_) => deref_mut_enum::DerefMutEnumHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, diff --git a/src/trait_handlers/deref_mut/models/field_attribute.rs b/src/trait_handlers/deref_mut/models/field_attribute.rs index e04543e..cf40875 100644 --- a/src/trait_handlers/deref_mut/models/field_attribute.rs +++ b/src/trait_handlers/deref_mut/models/field_attribute.rs @@ -1,18 +1,21 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated, spanned::Spanned}; use crate::{panic, supported_traits::Trait}; +/// The parsed settings of a field-level `DerefMut` attribute. pub(crate) struct FieldAttribute { pub(crate) flag: bool, pub(crate) span: Span, } +/// Parses field-level `DerefMut` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_flag: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `DerefMut` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("DerefMut")); @@ -48,6 +51,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `DerefMut` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -58,30 +62,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::DerefMut { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_deref_mut_meta(&meta)?); + if t == Trait::DerefMut { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_deref_mut_meta(&meta)?); } } } diff --git a/src/trait_handlers/deref_mut/models/type_attribute.rs b/src/trait_handlers/deref_mut/models/type_attribute.rs index 782d136..68acf00 100644 --- a/src/trait_handlers/deref_mut/models/type_attribute.rs +++ b/src/trait_handlers/deref_mut/models/type_attribute.rs @@ -1,15 +1,17 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{panic, Trait}; +use crate::{Trait, panic}; pub(crate) struct TypeAttribute; #[derive(Debug)] +/// Parses `DerefMut` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, } impl TypeAttributeBuilder { + /// Parses one `DerefMut` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_deref_mut_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("DerefMut")); @@ -43,6 +45,7 @@ impl TypeAttributeBuilder { Ok(TypeAttribute) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `DerefMut` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -53,30 +56,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::DerefMut { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_deref_mut_meta(&meta)?); + if t == Trait::DerefMut { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_deref_mut_meta(&meta)?); } } } diff --git a/src/trait_handlers/deref_mut/panic.rs b/src/trait_handlers/deref_mut/panic.rs index d7b1466..9b121b7 100644 --- a/src/trait_handlers/deref_mut/panic.rs +++ b/src/trait_handlers/deref_mut/panic.rs @@ -1,5 +1,5 @@ use proc_macro2::Span; -use syn::Variant; +use syn::{Meta, Variant}; #[inline] pub(crate) fn multiple_deref_mut_fields(span: Span) -> syn::Error { @@ -15,14 +15,14 @@ pub(crate) fn multiple_deref_mut_fields_of_variant(span: Span, variant: &Variant } #[inline] -pub(crate) fn no_deref_mut_field(span: Span) -> syn::Error { - syn::Error::new(span, "there is no field which is assigned for `DerefMut`") +pub(crate) fn no_deref_mut_field(meta: &Meta) -> syn::Error { + syn::Error::new_spanned(meta, "there is no field which is assigned for `DerefMut`") } #[inline] -pub(crate) fn no_deref_mut_field_of_variant(span: Span, variant: &Variant) -> syn::Error { - syn::Error::new( - span, +pub(crate) fn no_deref_mut_field_of_variant(variant: &Variant) -> syn::Error { + syn::Error::new_spanned( + variant, format!( "there is no field for the `{}` variant which is assigned for `DerefMut`", variant.ident diff --git a/src/trait_handlers/eq/mod.rs b/src/trait_handlers/eq/mod.rs index bff020d..25dca5c 100644 --- a/src/trait_handlers/eq/mod.rs +++ b/src/trait_handlers/eq/mod.rs @@ -5,7 +5,19 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; -use crate::Trait; +use crate::{ + Trait, + common::bound::{BOUND_EXCEPTIONS_EQUALITY, Bound}, + trait_handlers::TraitHandlerContext, +}; + +/// Returns the traits whose recorded bounds `Eq` inherits when its own bound is automatic. +pub(crate) fn prerequisites() -> &'static [Trait] { + &[ + #[cfg(feature = "PartialEq")] + Trait::PartialEq, + ] +} pub(crate) struct EqHandler; @@ -13,94 +25,88 @@ impl TraitHandler for EqHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { - #[cfg(feature = "PartialEq")] - let contains_partial_eq = traits.contains(&Trait::PartialEq); - - #[cfg(not(feature = "PartialEq"))] - let contains_partial_eq = false; + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); let type_attribute = TypeAttributeBuilder { - enable_flag: true, - enable_bound: !contains_partial_eq, + enable_flag: true, enable_bound: true } .build_from_eq_meta(meta)?; - let mut field_types = vec![]; + let mut field_types = Vec::new(); - // if `contains_partial_eq` is true, the implementation is handled by the `PartialEq` attribute, and field attributes is also handled by the `PartialEq` attribute - if !contains_partial_eq { - match &ast.data { - Data::Struct(data) => { - for field in data.fields.iter() { - field_types.push(&field.ty); - let _ = - FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; - } - }, - Data::Enum(data) => { - for variant in data.variants.iter() { - let _ = TypeAttributeBuilder { - enable_flag: false, enable_bound: false - } - .build_from_attributes(&variant.attrs, traits)?; - - for field in variant.fields.iter() { - field_types.push(&field.ty); - let _ = FieldAttributeBuilder - .build_from_attributes(&field.attrs, traits)?; - } + match &ast.data { + Data::Struct(data) => { + for field in data.fields.iter() { + let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; + + field_types.push(&field.ty); + } + }, + Data::Enum(data) => { + for variant in data.variants.iter() { + let _ = TypeAttributeBuilder { + enable_flag: false, enable_bound: false } - }, - Data::Union(data) => { - for field in data.fields.named.iter() { - field_types.push(&field.ty); + .build_from_attributes(&variant.attrs, traits)?; + + for field in variant.fields.iter() { let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; + + field_types.push(&field.ty); } - }, - } + } + }, + Data::Union(data) => { + // A union compares itself byte by byte, so the field types never need an `Eq` bound of their own. + for field in data.fields.named.iter() { + let _ = FieldAttributeBuilder.build_from_attributes(&field.attrs, traits)?; + } + }, + } - let ident = &ast.ident; + let ident = &ast.ident; - /* - #[derive(PartialEq)] - struct B { - f1: PhantomData, - } + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); - impl Eq for B { + // The automatic bound uses the `Eq` trait itself (not `PartialEq`), matching the behavior of the built-in `#[derive(Eq)]`. + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::cmp::Eq)).unwrap(), + &field_types, + &ast.ident, + &BOUND_EXCEPTIONS_EQUALITY, + ); - } + if bound_is_auto { + ctx.inherit_from(prerequisites(), &mut bound); + } - // The above code will throw a compile error because T have to be bound to `PartialEq`. However, it seems not to be necessary logically. - */ - let bound = - type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(), - &field_types, - &[quote! {::core::cmp::PartialEq}], - ); - - let mut generics = ast.generics.clone(); - let where_clause = generics.make_where_clause(); - - for where_predicate in bound { - where_clause.predicates.push(where_predicate); - } + ctx.record(Trait::Eq, &bound); - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + let mut generics = ast.generics.clone(); - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { - } - }); + let where_clause = generics.make_where_clause(); + + for where_predicate in bound { + where_clause.predicates.push(where_predicate); } + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + token_stream.extend(quote! { + #generated_impl_attributes + impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { + } + }); + Ok(()) } } diff --git a/src/trait_handlers/eq/models/field_attribute.rs b/src/trait_handlers/eq/models/field_attribute.rs index ce78d6b..023659b 100644 --- a/src/trait_handlers/eq/models/field_attribute.rs +++ b/src/trait_handlers/eq/models/field_attribute.rs @@ -1,18 +1,22 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{panic, supported_traits::Trait}; +/// The parsed settings of a field-level `Eq` attribute. pub(crate) struct FieldAttribute; +/// Parses field-level `Eq` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder; impl FieldAttributeBuilder { + /// Parses one field-level `Eq` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Eq")); - return Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())); + Err(panic::attribute_incorrect_place(meta.path().get_ident().unwrap())) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Eq` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -23,30 +27,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Eq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_eq_meta(&meta)?); + if t == Trait::Eq { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_eq_meta(&meta)?); } } } diff --git a/src/trait_handlers/eq/models/type_attribute.rs b/src/trait_handlers/eq/models/type_attribute.rs index 5c53708..15671c8 100644 --- a/src/trait_handlers/eq/models/type_attribute.rs +++ b/src/trait_handlers/eq/models/type_attribute.rs @@ -1,18 +1,21 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{common::bound::Bound, panic, Trait}; +use crate::{Trait, common::bound::Bound, panic}; +/// The parsed settings of a type-level (or variant-level) `Eq` attribute. pub(crate) struct TypeAttribute { pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `Eq` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_bound: bool, } impl TypeAttributeBuilder { + /// Parses one `Eq` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_eq_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Eq")); @@ -55,24 +58,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -94,6 +97,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Eq` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -104,30 +108,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Eq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_eq_meta(&meta)?); + if t == Trait::Eq { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_eq_meta(&meta)?); } } } diff --git a/src/trait_handlers/hash/hash_enum.rs b/src/trait_handlers/hash/hash_enum.rs index 0133702..7bdf9bb 100644 --- a/src/trait_handlers/hash/hash_enum.rs +++ b/src/trait_handlers/hash/hash_enum.rs @@ -2,21 +2,26 @@ use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields, Meta, Path, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::Trait; +use crate::{Trait, common::bound::BOUND_EXCEPTIONS_HASH, trait_handlers::TraitHandlerContext}; +/// Generates the `Hash` implementation for an enum. pub(crate) struct HashEnumHandler; impl TraitHandler for HashEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: false, enable_bound: true @@ -143,10 +148,12 @@ impl TraitHandler for HashEnumHandler { &ast.generics.params, &syn::parse2(quote!(::core::hash::Hash)).unwrap(), &hash_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_HASH, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -156,6 +163,7 @@ impl TraitHandler for HashEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { #[inline] fn hash(&self, state: &mut H) { diff --git a/src/trait_handlers/hash/hash_struct.rs b/src/trait_handlers/hash/hash_struct.rs index 0444e4b..d46eb92 100644 --- a/src/trait_handlers/hash/hash_struct.rs +++ b/src/trait_handlers/hash/hash_struct.rs @@ -2,21 +2,30 @@ use quote::quote; use syn::{Data, DeriveInput, Meta, Path, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{bound::BOUND_EXCEPTIONS_HASH, ident_index::IdentOrIndex}, + trait_handlers::TraitHandlerContext, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +/// Generates the `Hash` implementation for a struct. pub(crate) struct HashStructHandler; impl TraitHandler for HashStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: false, enable_bound: true @@ -62,10 +71,12 @@ impl TraitHandler for HashStructHandler { &ast.generics.params, &syn::parse2(quote!(::core::hash::Hash)).unwrap(), &hash_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_HASH, ); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -75,6 +86,7 @@ impl TraitHandler for HashStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { #[inline] fn hash(&self, state: &mut H) { diff --git a/src/trait_handlers/hash/hash_union.rs b/src/trait_handlers/hash/hash_union.rs index f430c65..cc0998a 100644 --- a/src/trait_handlers/hash/hash_union.rs +++ b/src/trait_handlers/hash/hash_union.rs @@ -2,18 +2,26 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::models::{FieldAttributeBuilder, TypeAttributeBuilder}; -use crate::{supported_traits::Trait, trait_handlers::TraitHandler}; +use crate::{ + supported_traits::Trait, + trait_handlers::{TraitHandler, TraitHandlerContext}, +}; +/// Generates the `Hash` implementation for a union. pub(crate) struct HashUnionHandler; impl TraitHandler for HashUnionHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: true, enable_bound: false @@ -38,9 +46,12 @@ impl TraitHandler for HashUnionHandler { let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::hash::Hash for #ident #ty_generics #where_clause { #[inline] fn hash(&self, state: &mut H) { + // The whole memory of the union is hashed byte by byte on purpose, including any padding bytes, because the active field cannot be known at runtime. + // The user opted in to this behavior with the `unsafe` keyword in the attribute. let size = ::core::mem::size_of::(); let data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; diff --git a/src/trait_handlers/hash/mod.rs b/src/trait_handlers/hash/mod.rs index c4b07d9..b817d09 100644 --- a/src/trait_handlers/hash/mod.rs +++ b/src/trait_handlers/hash/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod hash_enum; mod hash_struct; mod hash_union; @@ -9,26 +10,36 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Hash` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct HashHandler; impl TraitHandler for HashHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { match ast.data { - Data::Struct(_) => { - hash_struct::HashStructHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, + Data::Struct(_) => hash_struct::HashStructHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), Data::Enum(_) => { - hash_enum::HashEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, - Data::Union(_) => { - hash_union::HashUnionHandler::trait_meta_handler(ast, token_stream, traits, meta) + hash_enum::HashEnumHandler::trait_meta_handler(ast, ctx, token_stream, traits, meta) }, + Data::Union(_) => hash_union::HashUnionHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), } } } diff --git a/src/trait_handlers/hash/models/field_attribute.rs b/src/trait_handlers/hash/models/field_attribute.rs index d6e34b6..3b7fce1 100644 --- a/src/trait_handlers/hash/models/field_attribute.rs +++ b/src/trait_handlers/hash/models/field_attribute.rs @@ -1,4 +1,4 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated}; use crate::{ common::{ @@ -9,17 +9,20 @@ use crate::{ supported_traits::Trait, }; +/// The parsed settings of a field-level `Hash` attribute. pub(crate) struct FieldAttribute { pub(crate) ignore: bool, pub(crate) method: Option, } +/// Parses field-level `Hash` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_ignore: bool, pub(crate) enable_method: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `Hash` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_hash_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Hash")); @@ -126,6 +129,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Hash` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -136,30 +140,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Hash { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_hash_meta(&meta)?); + if t == Trait::Hash { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_hash_meta(&meta)?); } } } diff --git a/src/trait_handlers/hash/models/type_attribute.rs b/src/trait_handlers/hash/models/type_attribute.rs index 33b9999..14bbdcc 100644 --- a/src/trait_handlers/hash/models/type_attribute.rs +++ b/src/trait_handlers/hash/models/type_attribute.rs @@ -1,16 +1,19 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{ + Trait, common::{bound::Bound, unsafe_punctuated_meta::UnsafePunctuatedMeta}, - panic, Trait, + panic, }; +/// The parsed settings of a type-level (or variant-level) `Hash` attribute. pub(crate) struct TypeAttribute { pub(crate) has_unsafe: bool, pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `Hash` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_unsafe: bool, @@ -18,6 +21,7 @@ pub(crate) struct TypeAttributeBuilder { } impl TypeAttributeBuilder { + /// Parses one `Hash` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_hash_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Hash")); @@ -68,24 +72,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -108,6 +112,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Hash` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -118,30 +123,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } - - if t == Trait::Hash { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_hash_meta(&meta)?); + if t == Trait::Hash { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_hash_meta(&meta)?); } } } diff --git a/src/trait_handlers/hash/panic.rs b/src/trait_handlers/hash/panic.rs index 6e29357..716bfaa 100644 --- a/src/trait_handlers/hash/panic.rs +++ b/src/trait_handlers/hash/panic.rs @@ -1,5 +1,5 @@ use quote::ToTokens; -use syn::{spanned::Spanned, Meta}; +use syn::Meta; #[inline] pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { @@ -11,8 +11,8 @@ pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { _ => unreachable!(), } - syn::Error::new( - meta.span(), + syn::Error::new_spanned( + meta, format!( "a union's `Hash` implementation is not precise, because it ignores the type of \ fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \ diff --git a/src/trait_handlers/into/common.rs b/src/trait_handlers/into/common.rs index 2eba04a..fcf822f 100644 --- a/src/trait_handlers/into/common.rs +++ b/src/trait_handlers/into/common.rs @@ -1,9 +1,12 @@ use quote::quote_spanned; -use syn::{spanned::Spanned, Type}; +use syn::{Type, spanned::Spanned}; -use crate::common::{r#type::dereference_changed, tools::HashType}; +use crate::common::{tools::HashType, r#type::dereference_changed}; #[inline] +/// Normalizes a field type into the key used to match it against an `Into` target type. +/// +/// References are stripped and re-added with a `'static` lifetime, so that `&'a str` and `&'static str` compare as the same target. pub(crate) fn to_hash_type(ty: &Type) -> HashType { let (ty, is_ref) = dereference_changed(ty); diff --git a/src/trait_handlers/into/into_enum.rs b/src/trait_handlers/into/into_enum.rs index dd4b198..af63a12 100644 --- a/src/trait_handlers/into/into_enum.rs +++ b/src/trait_handlers/into/into_enum.rs @@ -1,35 +1,40 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandlerMultiple, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::{panic, Trait}; +use crate::{Trait, panic, trait_handlers::TraitHandlerContext}; +/// Generates the `Into` implementation for an enum. pub(crate) struct IntoEnumHandler; impl TraitHandlerMultiple for IntoEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &[Meta], ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_types: true } .build_from_into_meta(meta)?; if let Data::Enum(data) = &ast.data { - let field_attributes: Vec> = { + let field_attributes: Vec> = { let mut map = Vec::new(); for variant in data.variants.iter() { - let mut field_map = HashMap::new(); + let mut field_map = BTreeMap::new(); let _ = TypeAttributeBuilder { enable_types: false @@ -57,11 +62,18 @@ impl TraitHandlerMultiple for IntoEnumHandler { map }; - for (target_ty, bound) in type_attribute.types { + for (target_ty, target) in type_attribute.types { + // By default a `From` impl is generated because it provides `Into` for free; the `into` flag asks for a direct `Into` impl instead. + let generate_from = !target.force_into; + + let bound = target.bound; + let mut into_types: Vec<&Type> = Vec::new(); let mut arms_token_stream = proc_macro2::TokenStream::new(); + let enum_ident = &ast.ident; + type Variants<'a> = Vec<(&'a Ident, bool, usize, Ident, &'a Type, Option<&'a Path>)>; @@ -97,16 +109,15 @@ impl TraitHandlerMultiple for IntoEnumHandler { let mut into_field: Option<(usize, &Field, Option<&Path>)> = None; for (index, field) in fields.iter().enumerate() { - if let Some(field_attribute) = field_attributes.get(&index) { - if let Some((key, method)) = + if let Some(field_attribute) = field_attributes.get(&index) + && let Some((key, method)) = field_attribute.types.get_key_value(&target_ty) - { - if into_field.is_some() { - return Err(super::panic::multiple_into_fields(key)); - } - - into_field = Some((index, field, method.as_ref())); + { + if into_field.is_some() { + return Err(super::panic::multiple_into_fields(key)); } + + into_field = Some((index, field, method.as_ref())); } } @@ -175,24 +186,23 @@ impl TraitHandlerMultiple for IntoEnumHandler { pattern_token_stream.extend(quote!( #field_name, .. )); arms_token_stream.extend( - quote!( Self::#variant_ident ( #pattern_token_stream ) => #body_token_stream, ), + quote!( #enum_ident::#variant_ident ( #pattern_token_stream ) => #body_token_stream, ), ); } else { pattern_token_stream.extend(quote!( #field_name, .. )); arms_token_stream.extend( - quote!( Self::#variant_ident { #pattern_token_stream } => #body_token_stream, ), + quote!( #enum_ident::#variant_ident { #pattern_token_stream } => #body_token_stream, ), ); } } let ident = &ast.ident; - let bound = bound.into_where_predicates_by_generic_parameters_check_types( + let bound = bound.into_where_predicates_by_generic_parameters_check_types_shallow( &ast.generics.params, &syn::parse2(quote!(::core::convert::Into<#target_ty>)).unwrap(), &into_types, - &[], ); // clone generics in order to not to affect other Into implementations @@ -206,12 +216,27 @@ impl TraitHandlerMultiple for IntoEnumHandler { let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); - token_stream.extend(quote! { - impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause { - #[inline] - fn into(self) -> #target_ty { - match self { - #arms_token_stream + token_stream.extend(if generate_from { + quote! { + #generated_impl_attributes + impl #impl_generics ::core::convert::From<#ident #ty_generics> for #target_ty #where_clause { + #[inline] + fn from(value: #ident #ty_generics) -> Self { + match value { + #arms_token_stream + } + } + } + } + } else { + quote! { + #generated_impl_attributes + impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause { + #[inline] + fn into(self) -> #target_ty { + match self { + #arms_token_stream + } } } } diff --git a/src/trait_handlers/into/into_struct.rs b/src/trait_handlers/into/into_struct.rs index ea13948..9fde6c5 100644 --- a/src/trait_handlers/into/into_struct.rs +++ b/src/trait_handlers/into/into_struct.rs @@ -1,24 +1,29 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; use quote::quote; use syn::{Data, DeriveInput, Field, Meta, Path, Type}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandlerMultiple, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +use crate::{Trait, common::ident_index::IdentOrIndex, trait_handlers::TraitHandlerContext}; +/// Generates the `Into` implementation for a struct. pub(crate) struct IntoStructHandler; impl TraitHandlerMultiple for IntoStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + _ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &[Meta], ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_types: true } @@ -27,8 +32,8 @@ impl TraitHandlerMultiple for IntoStructHandler { if let Data::Struct(data) = &ast.data { let fields = &data.fields; - let field_attributes: HashMap = { - let mut map = HashMap::new(); + let field_attributes: BTreeMap = { + let mut map = BTreeMap::new(); for (index, field) in fields.iter().enumerate() { let field_attribute = FieldAttributeBuilder { @@ -48,7 +53,15 @@ impl TraitHandlerMultiple for IntoStructHandler { map }; - for (target_ty, bound) in type_attribute.types { + for (target_ty, target) in type_attribute.types { + // By default a `From` impl is generated because it provides `Into` for free; the `into` flag asks for a direct `Into` impl instead. + let generate_from = !target.force_into; + + // The conversion body takes the source value from `self` in an `Into` impl and from the `value` parameter in a `From` impl. + let source = if generate_from { quote!(value) } else { quote!(self) }; + + let bound = target.bound; + let mut into_types: Vec<&Type> = Vec::new(); let mut into_token_stream = proc_macro2::TokenStream::new(); @@ -74,16 +87,15 @@ impl TraitHandlerMultiple for IntoStructHandler { let mut into_field: Option<(usize, &Field, Option<&Path>)> = None; for (index, field) in fields.iter().enumerate() { - if let Some(field_attribute) = field_attributes.get(&index) { - if let Some((key, method)) = + if let Some(field_attribute) = field_attributes.get(&index) + && let Some((key, method)) = field_attribute.types.get_key_value(&target_ty) - { - if into_field.is_some() { - return Err(super::panic::multiple_into_fields(key)); - } - - into_field = Some((index, field, method.as_ref())); + { + if into_field.is_some() { + return Err(super::panic::multiple_into_fields(key)); } + + into_field = Some((index, field, method.as_ref())); } } @@ -116,29 +128,28 @@ impl TraitHandlerMultiple for IntoStructHandler { let field_name = IdentOrIndex::from_ident_with_index(field.ident.as_ref(), index); if let Some(method) = method { - into_token_stream.extend(quote!( #method(self.#field_name) )); + into_token_stream.extend(quote!( #method(#source.#field_name) )); } else { let ty = &field.ty; let field_ty = super::common::to_hash_type(ty); if target_ty.eq(&field_ty) { - into_token_stream.extend(quote!( self.#field_name )); + into_token_stream.extend(quote!( #source.#field_name )); } else { into_types.push(ty); into_token_stream - .extend(quote!( ::core::convert::Into::into(self.#field_name) )); + .extend(quote!( ::core::convert::Into::into(#source.#field_name) )); } } let ident = &ast.ident; - let bound = bound.into_where_predicates_by_generic_parameters_check_types( + let bound = bound.into_where_predicates_by_generic_parameters_check_types_shallow( &ast.generics.params, &syn::parse2(quote!(::core::convert::Into<#target_ty>)).unwrap(), &into_types, - &[], ); // clone generics in order to not to affect other Into implementations @@ -152,11 +163,24 @@ impl TraitHandlerMultiple for IntoStructHandler { let (impl_generics, ty_generics, _) = ast.generics.split_for_impl(); - token_stream.extend(quote! { - impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause { - #[inline] - fn into(self) -> #target_ty { - #into_token_stream + token_stream.extend(if generate_from { + quote! { + #generated_impl_attributes + impl #impl_generics ::core::convert::From<#ident #ty_generics> for #target_ty #where_clause { + #[inline] + fn from(value: #ident #ty_generics) -> Self { + #into_token_stream + } + } + } + } else { + quote! { + #generated_impl_attributes + impl #impl_generics ::core::convert::Into<#target_ty> for #ident #ty_generics #where_clause { + #[inline] + fn into(self) -> #target_ty { + #into_token_stream + } } } }); diff --git a/src/trait_handlers/into/mod.rs b/src/trait_handlers/into/mod.rs index 1f91777..b4f3fc1 100644 --- a/src/trait_handlers/into/mod.rs +++ b/src/trait_handlers/into/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod common; mod into_enum; mod into_struct; @@ -9,22 +10,28 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandlerMultiple; use crate::Trait; +/// Dispatches the `Into` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct IntoHandler; impl TraitHandlerMultiple for IntoHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &[Meta], ) -> syn::Result<()> { match ast.data { - Data::Struct(_) => { - into_struct::IntoStructHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, + Data::Struct(_) => into_struct::IntoStructHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), Data::Enum(_) => { - into_enum::IntoEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) + into_enum::IntoEnumHandler::trait_meta_handler(ast, ctx, token_stream, traits, meta) }, Data::Union(_) => { Err(crate::panic::trait_not_support_union(meta[0].path().get_ident().unwrap())) diff --git a/src/trait_handlers/into/models/field_attribute.rs b/src/trait_handlers/into/models/field_attribute.rs index 44a7def..8af1bc1 100644 --- a/src/trait_handlers/into/models/field_attribute.rs +++ b/src/trait_handlers/into/models/field_attribute.rs @@ -1,26 +1,30 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; -use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated}; use crate::{ - common::{path::meta_2_path, r#type::TypeWithPunctuatedMeta, tools::HashType}, - panic, Trait, + Trait, + common::{path::meta_2_path, tools::HashType, r#type::TypeWithPunctuatedMeta}, + panic, }; +/// The parsed settings of a field-level `Into` attribute. pub(crate) struct FieldAttribute { - pub(crate) types: HashMap>, + pub(crate) types: BTreeMap>, } #[derive(Debug)] +/// Parses field-level `Into` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_types: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `Into` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result { debug_assert!(!meta.is_empty()); - let mut types = HashMap::new(); + let mut types = BTreeMap::new(); for meta in meta { debug_assert!(meta.path().is_ident("Into")); @@ -62,20 +66,20 @@ impl FieldAttributeBuilder { let mut method_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "method" { - let v = meta_2_path(&meta)?; + if let Some(ident) = meta.path().get_ident() + && ident == "method" + { + let v = meta_2_path(&meta)?; - if method_is_set { - return Err(panic::parameter_reset(ident)); - } + if method_is_set { + return Err(panic::parameter_reset(ident)); + } - method_is_set = true; + method_is_set = true; - method = Some(v); + method = Some(v); - return Ok(true); - } + return Ok(true); } Ok(false) @@ -104,6 +108,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Into` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -116,26 +121,26 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - if t == Trait::Into { - v_meta.push(meta); - } + if t == Trait::Into { + v_meta.push(meta); } } } @@ -146,7 +151,7 @@ impl FieldAttributeBuilder { } Ok(output.unwrap_or_else(|| FieldAttribute { - types: HashMap::new() + types: BTreeMap::new() })) } } diff --git a/src/trait_handlers/into/models/type_attribute.rs b/src/trait_handlers/into/models/type_attribute.rs index 526e31e..721d84b 100644 --- a/src/trait_handlers/into/models/type_attribute.rs +++ b/src/trait_handlers/into/models/type_attribute.rs @@ -1,26 +1,37 @@ -use std::collections::HashMap; +use std::collections::BTreeMap; -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{ - common::{bound::Bound, r#type::TypeWithPunctuatedMeta, tools::HashType}, - panic, Trait, + Trait, + common::{bound::Bound, tools::HashType, r#type::TypeWithPunctuatedMeta}, + panic, }; +/// The settings of one conversion target declared by `#[educe(Into(type, ...))]`. +pub(crate) struct IntoTarget { + pub(crate) bound: Bound, + /// The bare `into` flag, which asks the handler to generate a direct `Into` impl instead of the default `From` impl. + pub(crate) force_into: bool, +} + +/// The parsed settings of a type-level (or variant-level) `Into` attribute. pub(crate) struct TypeAttribute { - pub(crate) types: HashMap, + pub(crate) types: BTreeMap, } #[derive(Debug)] +/// Parses `Into` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_types: bool, } impl TypeAttributeBuilder { + /// Parses one `Into` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_into_meta(&self, meta: &[Meta]) -> syn::Result { debug_assert!(!meta.is_empty()); - let mut types = HashMap::new(); + let mut types = BTreeMap::new(); for meta in meta { debug_assert!(meta.path().is_ident("Into")); @@ -32,6 +43,7 @@ impl TypeAttributeBuilder { usage.push(stringify!(#[educe(Into(type))])); usage.push(stringify!(#[educe(Into(type, bound = false))])); usage.push(stringify!(#[educe(Into(type, bound(where_predicates)))])); + usage.push(stringify!(#[educe(Into(type, into))])); } usage @@ -57,11 +69,13 @@ impl TypeAttributeBuilder { list: result, } = list.parse_args()?; - let ty = super::super::common::to_hash_type(&ty); + let hash_ty = super::super::common::to_hash_type(&ty); let mut bound = Bound::Auto; let mut bound_is_set = false; + let mut force_into = false; + let mut handler = |meta: Meta| -> syn::Result { if let Some(ident) = meta.path().get_ident() { if ident == "bound" { @@ -77,6 +91,17 @@ impl TypeAttributeBuilder { return Ok(true); } + + // The bare `into` flag forces an `Into` impl for this target. + if ident == "into" && matches!(meta, Meta::Path(_)) { + if force_into { + return Err(panic::parameter_reset(ident)); + } + + force_into = true; + + return Ok(true); + } } Ok(false) @@ -91,11 +116,14 @@ impl TypeAttributeBuilder { } } - if types.contains_key(&ty) { - return Err(super::super::panic::reset_a_type(&ty)); + if types.contains_key(&hash_ty) { + return Err(super::super::panic::reset_a_type(&hash_ty)); } - types.insert(ty, bound); + types.insert(hash_ty, IntoTarget { + bound, + force_into, + }); }, } } @@ -105,6 +133,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Into` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -117,26 +146,26 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - if t == Trait::Into { - v_meta.push(meta); - } + if t == Trait::Into { + v_meta.push(meta); } } } @@ -147,7 +176,7 @@ impl TypeAttributeBuilder { } Ok(output.unwrap_or(TypeAttribute { - types: HashMap::new() + types: BTreeMap::new() })) } } diff --git a/src/trait_handlers/mod.rs b/src/trait_handlers/mod.rs index d82242b..5c4c825 100644 --- a/src/trait_handlers/mod.rs +++ b/src/trait_handlers/mod.rs @@ -1,6 +1,9 @@ +use std::collections::{HashMap, HashSet}; + +use quote::ToTokens; use syn::{DeriveInput, Meta}; -use crate::Trait; +use crate::{Trait, common::where_predicates_bool::WherePredicates}; #[cfg(feature = "Clone")] pub(crate) mod clone; @@ -27,20 +30,60 @@ pub(crate) mod partial_eq; #[cfg(feature = "PartialOrd")] pub(crate) mod partial_ord; -pub(crate) trait TraitHandler { +/// Shared state that flows through all trait handlers during a single `#[derive(Educe)]` expansion. +/// +/// Its main job is to let a trait inherit the where predicates of its prerequisite traits, e.g. `Ord` inherits the predicates of `Eq` and `PartialOrd`. +#[derive(Default)] +pub(crate) struct TraitHandlerContext { + /// The final where predicates that each handled trait has actually emitted, keyed by trait. + final_predicates: HashMap, +} + +impl TraitHandlerContext { + /// Records the where predicates that a trait impl has emitted, so that traits handled later can inherit them. + #[allow(dead_code)] + pub(crate) fn record(&mut self, t: Trait, predicates: &WherePredicates) { + self.final_predicates.insert(t, predicates.clone()); + } + + /// Appends the recorded predicates of every prerequisite trait to `own`, skipping predicates that are already present. + /// + /// Prerequisites that were not handled by Educe (e.g. implemented manually by the user) simply have no record and contribute nothing. #[allow(dead_code)] + pub(crate) fn inherit_from(&self, prerequisites: &[Trait], own: &mut WherePredicates) { + // Compare predicates by their token strings because `WherePredicate` implements neither `Eq` nor `Hash`. + let mut seen: HashSet = + own.iter().map(|predicate| predicate.to_token_stream().to_string()).collect(); + + for prerequisite in prerequisites { + if let Some(predicates) = self.final_predicates.get(prerequisite) { + for predicate in predicates { + if seen.insert(predicate.to_token_stream().to_string()) { + own.push(predicate.clone()); + } + } + } + } + } +} + +// Every single-meta handler implements this trait; when `Into` is the only enabled feature none of them are compiled, so the trait would look unused. +#[allow(dead_code)] +pub(crate) trait TraitHandler { fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()>; } +#[cfg(feature = "Into")] pub(crate) trait TraitHandlerMultiple { - #[allow(dead_code)] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &[Meta], diff --git a/src/trait_handlers/ord/mod.rs b/src/trait_handlers/ord/mod.rs index 89b2e15..af75aad 100644 --- a/src/trait_handlers/ord/mod.rs +++ b/src/trait_handlers/ord/mod.rs @@ -1,30 +1,36 @@ +use crate::trait_handlers::TraitHandlerContext; mod models; mod ord_enum; mod ord_struct; mod panic; -use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `Ord` derive to the specialized handler for the shape of the input. pub(crate) struct OrdHandler; impl TraitHandler for OrdHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { match ast.data { - Data::Struct(_) => { - ord_struct::OrdStructHandler::trait_meta_handler(ast, token_stream, traits, meta) - }, + Data::Struct(_) => ord_struct::OrdStructHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), Data::Enum(_) => { - ord_enum::OrdEnumHandler::trait_meta_handler(ast, token_stream, traits, meta) + ord_enum::OrdEnumHandler::trait_meta_handler(ast, ctx, token_stream, traits, meta) }, Data::Union(_) => { Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) @@ -33,17 +39,12 @@ impl TraitHandler for OrdHandler { } } -fn supertraits(#[allow(unused_variables)] traits: &[Trait]) -> Vec { - let mut supertraits = vec![]; - supertraits.push(quote! {::core::cmp::Eq}); - - // We mustn't add the PartialOrd bound to the educed PartialOrd impl. - // When we're educing PartialOrd we can leave it off the Ord impl too, - // since we *know* Self is going to be PartialOrd. - #[cfg(feature = "PartialOrd")] - if !traits.contains(&Trait::PartialOrd) { - supertraits.push(quote! {::core::cmp::PartialOrd}); - }; - - supertraits +/// Returns the traits whose recorded bounds `Ord` inherits when its own bound is automatic. +pub(crate) fn prerequisites() -> &'static [Trait] { + &[ + #[cfg(feature = "Eq")] + Trait::Eq, + #[cfg(feature = "PartialOrd")] + Trait::PartialOrd, + ] } diff --git a/src/trait_handlers/ord/models/field_attribute.rs b/src/trait_handlers/ord/models/field_attribute.rs index 7b2dd96..c967223 100644 --- a/src/trait_handlers/ord/models/field_attribute.rs +++ b/src/trait_handlers/ord/models/field_attribute.rs @@ -1,5 +1,5 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated, spanned::Spanned}; use crate::{ common::{ @@ -11,6 +11,7 @@ use crate::{ supported_traits::Trait, }; +/// The parsed settings of a field-level `Ord` attribute. pub(crate) struct FieldAttribute { pub(crate) ignore: bool, pub(crate) method: Option, @@ -18,6 +19,7 @@ pub(crate) struct FieldAttribute { pub(crate) rank_span: Option, } +/// Parses field-level `Ord` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_ignore: bool, pub(crate) enable_method: bool, @@ -26,6 +28,7 @@ pub(crate) struct FieldAttributeBuilder { } impl FieldAttributeBuilder { + /// Parses one field-level `Ord` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_ord_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Ord") || meta.path().is_ident("PartialOrd")); @@ -159,6 +162,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `Ord` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -166,47 +170,57 @@ impl FieldAttributeBuilder { ) -> syn::Result { let mut output = None; + // When `PartialOrd` is derived together and a field has no `Ord` attribute of its own, the `ignore` and `rank` settings of the field's `PartialOrd` attribute are used instead, so that `cmp` stays consistent with `partial_cmp`. + #[cfg(feature = "PartialOrd")] + let mut fallback = None; + for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Ord { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_ord_meta(&meta)?); + if t == Trait::Ord { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } - #[cfg(feature = "PartialOrd")] - if traits.contains(&Trait::PartialOrd) && t == Trait::PartialOrd { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + output = Some(self.build_from_ord_meta(&meta)?); + } - output = Some(self.build_from_ord_meta(&meta)?); - } + // A malformed `PartialOrd` attribute is silently skipped here; the `PartialOrd` handler itself reports it with the proper usage message. + #[cfg(feature = "PartialOrd")] + if t == Trait::PartialOrd + && fallback.is_none() + && let Ok(mut field_attribute) = self.build_from_ord_meta(&meta) + { + // A `PartialOrd` comparison method returns `Option` and cannot be used by the generated `cmp`, so only `ignore` and `rank` are taken over. + field_attribute.method = None; + + fallback = Some(field_attribute); } } } } + #[cfg(feature = "PartialOrd")] + let output = output.or(fallback); + Ok(output.unwrap_or(FieldAttribute { ignore: false, method: None, diff --git a/src/trait_handlers/ord/models/type_attribute.rs b/src/trait_handlers/ord/models/type_attribute.rs index 5505429..b0fe12a 100644 --- a/src/trait_handlers/ord/models/type_attribute.rs +++ b/src/trait_handlers/ord/models/type_attribute.rs @@ -1,18 +1,21 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{common::bound::Bound, panic, Trait}; +use crate::{Trait, common::bound::Bound, panic}; +/// The parsed settings of a type-level (or variant-level) `Ord` attribute. pub(crate) struct TypeAttribute { pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `Ord` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_bound: bool, } impl TypeAttributeBuilder { + /// Parses one `Ord` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_ord_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("Ord") || meta.path().is_ident("PartialOrd")); @@ -55,24 +58,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -94,6 +97,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `Ord` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -104,39 +108,39 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::Ord { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_ord_meta(&meta)?); + if t == Trait::Ord { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } - #[cfg(feature = "PartialOrd")] - if traits.contains(&Trait::PartialOrd) && t == Trait::PartialOrd { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + output = Some(self.build_from_ord_meta(&meta)?); + } - output = Some(self.build_from_ord_meta(&meta)?); - } + // A variant-level `PartialOrd` attribute is validated by the `PartialOrd` handler itself, so it is only parsed here when there is no `Ord` attribute of its own. + #[cfg(feature = "PartialOrd")] + if t == Trait::PartialOrd + && output.is_none() + && let Ok(type_attribute) = self.build_from_ord_meta(&meta) + { + output = Some(type_attribute); } } } diff --git a/src/trait_handlers/ord/ord_enum.rs b/src/trait_handlers/ord/ord_enum.rs index 0edcb89..3e4631a 100644 --- a/src/trait_handlers/ord/ord_enum.rs +++ b/src/trait_handlers/ord/ord_enum.rs @@ -1,24 +1,36 @@ use std::collections::BTreeMap; use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type}; +use syn::{Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type, spanned::Spanned}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{ + bound::{BOUND_EXCEPTIONS_ORDER, Bound}, + tools::DiscriminantType, + }, + trait_handlers::TraitHandlerContext, }; -use crate::{common::tools::DiscriminantType, Trait}; +/// Generates the `Ord` implementation for an enum. pub(crate) struct OrdEnumHandler; impl TraitHandler for OrdEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -208,9 +220,12 @@ impl TraitHandler for OrdEnumHandler { if arms_token_stream.is_empty() { cmp_token_stream.extend(quote!(::core::cmp::Ordering::Equal)); } else { + // Compare the discriminants by reinterpreting the leading bytes of the enum values as the discriminant integer type. + // This relies on the enum layout starting with its discriminant, which holds for the default representation and for explicit primitive representations. + // The cast goes through `<*const Self>` explicitly so that an exotic `From<&Type>` impl in scope cannot poison type inference. let discriminant_cmp = quote! { unsafe { - ::core::cmp::Ord::cmp(&*<*const _>::from(self).cast::<#discriminant_type>(), &*<*const _>::from(other).cast::<#discriminant_type>()) + ::core::cmp::Ord::cmp(&*<*const Self>::from(self).cast::<#discriminant_type>(), &*<*const Self>::from(other).cast::<#discriminant_type>()) } }; @@ -241,14 +256,25 @@ impl TraitHandler for OrdEnumHandler { let ident = &ast.ident; - let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::cmp::Ord)).unwrap(), - &ord_types, - &crate::trait_handlers::ord::supertraits(traits), - ); + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); + + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::cmp::Ord)).unwrap(), + &ord_types, + &ast.ident, + &BOUND_EXCEPTIONS_ORDER, + ); + + if bound_is_auto { + ctx.inherit_from(super::prerequisites(), &mut bound); + } + + ctx.record(Trait::Ord, &bound); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -258,6 +284,7 @@ impl TraitHandler for OrdEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::Ord for #ident #ty_generics #where_clause { #[inline] fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { @@ -266,18 +293,6 @@ impl TraitHandler for OrdEnumHandler { } }); - #[cfg(feature = "PartialOrd")] - if traits.contains(&Trait::PartialOrd) { - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { - Some(::core::cmp::Ord::cmp(self, other)) - } - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/ord/ord_struct.rs b/src/trait_handlers/ord/ord_struct.rs index fc6c170..668ec54 100644 --- a/src/trait_handlers/ord/ord_struct.rs +++ b/src/trait_handlers/ord/ord_struct.rs @@ -1,24 +1,36 @@ use std::collections::BTreeMap; use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Path, Type}; +use syn::{Data, DeriveInput, Field, Meta, Path, Type, spanned::Spanned}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{ + bound::{BOUND_EXCEPTIONS_ORDER, Bound}, + ident_index::IdentOrIndex, + }, + trait_handlers::TraitHandlerContext, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +/// Generates the `Ord` implementation for a struct. pub(crate) struct OrdStructHandler; impl TraitHandler for OrdStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -29,6 +41,8 @@ impl TraitHandler for OrdStructHandler { let mut cmp_token_stream = proc_macro2::TokenStream::new(); if let Data::Struct(data) = &ast.data { + // Fields are compared in ascending rank order, so they are collected into a map keyed by rank. + // The default rank of a field is `isize::MIN` plus its ordinal position, which keeps the declaration order when no rank is given. let mut fields: BTreeMap = BTreeMap::new(); for (index, field) in data.fields.iter().enumerate() { @@ -79,14 +93,25 @@ impl TraitHandler for OrdStructHandler { let ident = &ast.ident; - let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::cmp::Ord)).unwrap(), - &ord_types, - &crate::trait_handlers::ord::supertraits(traits), - ); + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); + + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::cmp::Ord)).unwrap(), + &ord_types, + &ast.ident, + &BOUND_EXCEPTIONS_ORDER, + ); + + if bound_is_auto { + ctx.inherit_from(super::prerequisites(), &mut bound); + } + + ctx.record(Trait::Ord, &bound); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -96,6 +121,7 @@ impl TraitHandler for OrdStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::Ord for #ident #ty_generics #where_clause { #[inline] fn cmp(&self, other: &Self) -> ::core::cmp::Ordering { @@ -106,18 +132,6 @@ impl TraitHandler for OrdStructHandler { } }); - #[cfg(feature = "PartialOrd")] - if traits.contains(&Trait::PartialOrd) { - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { - Some(::core::cmp::Ord::cmp(self, other)) - } - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/partial_eq/mod.rs b/src/trait_handlers/partial_eq/mod.rs index 2bf7c72..e454cec 100644 --- a/src/trait_handlers/partial_eq/mod.rs +++ b/src/trait_handlers/partial_eq/mod.rs @@ -1,3 +1,4 @@ +use crate::trait_handlers::TraitHandlerContext; mod models; mod panic; mod partial_eq_enum; @@ -9,12 +10,14 @@ use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `PartialEq` derive to the specialized handler for the shape of the input (struct, enum, or union). pub(crate) struct PartialEqHandler; impl TraitHandler for PartialEqHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, @@ -22,18 +25,21 @@ impl TraitHandler for PartialEqHandler { match ast.data { Data::Struct(_) => partial_eq_struct::PartialEqStructHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, ), Data::Enum(_) => partial_eq_enum::PartialEqEnumHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, ), Data::Union(_) => partial_eq_union::PartialEqUnionHandler::trait_meta_handler( ast, + ctx, token_stream, traits, meta, diff --git a/src/trait_handlers/partial_eq/models/field_attribute.rs b/src/trait_handlers/partial_eq/models/field_attribute.rs index 4b67aba..fd21beb 100644 --- a/src/trait_handlers/partial_eq/models/field_attribute.rs +++ b/src/trait_handlers/partial_eq/models/field_attribute.rs @@ -1,4 +1,4 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated}; use crate::{ common::{ @@ -9,17 +9,20 @@ use crate::{ supported_traits::Trait, }; +/// The parsed settings of a field-level `PartialEq` attribute. pub(crate) struct FieldAttribute { pub(crate) ignore: bool, pub(crate) method: Option, } +/// Parses field-level `PartialEq` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_ignore: bool, pub(crate) enable_method: bool, } impl FieldAttributeBuilder { + /// Parses one field-level `PartialEq` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_partial_eq_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("PartialEq") || meta.path().is_ident("Eq")); @@ -126,6 +129,7 @@ impl FieldAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `PartialEq` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -136,39 +140,30 @@ impl FieldAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::PartialEq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_partial_eq_meta(&meta)?); + if t == Trait::PartialEq { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } - #[cfg(feature = "Eq")] - if traits.contains(&Trait::Eq) && t == Trait::Eq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } - - output = Some(self.build_from_partial_eq_meta(&meta)?); - } + output = Some(self.build_from_partial_eq_meta(&meta)?); } } } diff --git a/src/trait_handlers/partial_eq/models/type_attribute.rs b/src/trait_handlers/partial_eq/models/type_attribute.rs index 8377997..5435408 100644 --- a/src/trait_handlers/partial_eq/models/type_attribute.rs +++ b/src/trait_handlers/partial_eq/models/type_attribute.rs @@ -1,16 +1,19 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; use crate::{ + Trait, common::{bound::Bound, unsafe_punctuated_meta::UnsafePunctuatedMeta}, - panic, Trait, + panic, }; +/// The parsed settings of a type-level (or variant-level) `PartialEq` attribute. pub(crate) struct TypeAttribute { pub(crate) has_unsafe: bool, pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `PartialEq` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_unsafe: bool, @@ -18,6 +21,7 @@ pub(crate) struct TypeAttributeBuilder { } impl TypeAttributeBuilder { + /// Parses one `PartialEq` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_partial_eq_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("PartialEq") || meta.path().is_ident("Eq")); @@ -68,24 +72,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -108,6 +112,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `PartialEq` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -118,39 +123,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::PartialEq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_partial_eq_meta(&meta)?); + if t == Trait::PartialEq { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } - #[cfg(feature = "Eq")] - if traits.contains(&Trait::Eq) && t == Trait::Eq { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } - - output = Some(self.build_from_partial_eq_meta(&meta)?); - } + output = Some(self.build_from_partial_eq_meta(&meta)?); } } } diff --git a/src/trait_handlers/partial_eq/panic.rs b/src/trait_handlers/partial_eq/panic.rs index 69d001b..0f24a5c 100644 --- a/src/trait_handlers/partial_eq/panic.rs +++ b/src/trait_handlers/partial_eq/panic.rs @@ -1,5 +1,5 @@ use quote::ToTokens; -use syn::{spanned::Spanned, Meta}; +use syn::Meta; #[inline] pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { @@ -11,8 +11,8 @@ pub(crate) fn union_without_unsafe(meta: &Meta) -> syn::Error { _ => unreachable!(), } - syn::Error::new( - meta.span(), + syn::Error::new_spanned( + meta, format!( "a union's `PartialEq` implementation is not precise, because it ignores the type of \ fields\n* If your union doesn't care about that, use `#[educe({s})]` to implement \ diff --git a/src/trait_handlers/partial_eq/partial_eq_enum.rs b/src/trait_handlers/partial_eq/partial_eq_enum.rs index d000f35..89915d6 100644 --- a/src/trait_handlers/partial_eq/partial_eq_enum.rs +++ b/src/trait_handlers/partial_eq/partial_eq_enum.rs @@ -2,21 +2,26 @@ use quote::{format_ident, quote}; use syn::{Data, DeriveInput, Fields, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, }; -use crate::Trait; +use crate::{Trait, common::bound::BOUND_EXCEPTIONS_EQUALITY, trait_handlers::TraitHandlerContext}; +/// Generates the `PartialEq` implementation for an enum. pub(crate) struct PartialEqEnumHandler; impl TraitHandler for PartialEqEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: false, enable_bound: true @@ -182,10 +187,14 @@ impl TraitHandler for PartialEqEnumHandler { &ast.generics.params, &syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(), &partial_eq_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_EQUALITY, ); + ctx.record(Trait::PartialEq, &bound); + let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -195,6 +204,7 @@ impl TraitHandler for PartialEqEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { #[inline] fn eq(&self, other: &Self) -> bool { @@ -205,14 +215,6 @@ impl TraitHandler for PartialEqEnumHandler { } }); - #[cfg(feature = "Eq")] - if traits.contains(&Trait::Eq) { - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/partial_eq/partial_eq_struct.rs b/src/trait_handlers/partial_eq/partial_eq_struct.rs index 7394b8b..f4c6cbe 100644 --- a/src/trait_handlers/partial_eq/partial_eq_struct.rs +++ b/src/trait_handlers/partial_eq/partial_eq_struct.rs @@ -2,21 +2,30 @@ use quote::quote; use syn::{Data, DeriveInput, Meta, Type}; use super::{ - models::{FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{bound::BOUND_EXCEPTIONS_EQUALITY, ident_index::IdentOrIndex}, + trait_handlers::TraitHandlerContext, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +/// Generates the `PartialEq` implementation for a struct. pub(crate) struct PartialEqStructHandler; impl TraitHandler for PartialEqStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: false, enable_bound: true @@ -67,10 +76,14 @@ impl TraitHandler for PartialEqStructHandler { &ast.generics.params, &syn::parse2(quote!(::core::cmp::PartialEq)).unwrap(), &partial_eq_types, - &[], + &ast.ident, + &BOUND_EXCEPTIONS_EQUALITY, ); + ctx.record(Trait::PartialEq, &bound); + let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -80,6 +93,7 @@ impl TraitHandler for PartialEqStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { #[inline] fn eq(&self, other: &Self) -> bool { @@ -90,14 +104,6 @@ impl TraitHandler for PartialEqStructHandler { } }); - #[cfg(feature = "Eq")] - if traits.contains(&Trait::Eq) { - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/partial_eq/partial_eq_union.rs b/src/trait_handlers/partial_eq/partial_eq_union.rs index 80ac2b7..aed9ec3 100644 --- a/src/trait_handlers/partial_eq/partial_eq_union.rs +++ b/src/trait_handlers/partial_eq/partial_eq_union.rs @@ -2,18 +2,27 @@ use quote::quote; use syn::{Data, DeriveInput, Meta}; use super::models::{FieldAttributeBuilder, TypeAttributeBuilder}; -use crate::{supported_traits::Trait, trait_handlers::TraitHandler}; +use crate::{ + common::where_predicates_bool::WherePredicates, + supported_traits::Trait, + trait_handlers::{TraitHandler, TraitHandlerContext}, +}; +/// Generates the `PartialEq` implementation for a union. pub(crate) struct PartialEqUnionHandler; impl TraitHandler for PartialEqUnionHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_unsafe: true, enable_bound: false @@ -35,12 +44,18 @@ impl TraitHandler for PartialEqUnionHandler { let ident = &ast.ident; + // The union implementation adds no extra bounds, so record an empty predicate set for later inheritance. + ctx.record(Trait::PartialEq, &WherePredicates::new()); + let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::PartialEq for #ident #ty_generics #where_clause { #[inline] fn eq(&self, other: &Self) -> bool { + // The whole memory of the two unions is compared byte by byte on purpose, including any padding bytes, because the active field cannot be known at runtime. + // The user opted in to this behavior with the `unsafe` keyword in the attribute. let size = ::core::mem::size_of::(); let self_data = unsafe { ::core::slice::from_raw_parts(self as *const Self as *const u8, size) }; let other_data = unsafe { ::core::slice::from_raw_parts(other as *const Self as *const u8, size) }; @@ -50,14 +65,6 @@ impl TraitHandler for PartialEqUnionHandler { } }); - #[cfg(feature = "Eq")] - if traits.contains(&Trait::Eq) { - token_stream.extend(quote! { - impl #impl_generics ::core::cmp::Eq for #ident #ty_generics #where_clause { - } - }); - } - Ok(()) } } diff --git a/src/trait_handlers/partial_ord/mod.rs b/src/trait_handlers/partial_ord/mod.rs index c69ff20..163b878 100644 --- a/src/trait_handlers/partial_ord/mod.rs +++ b/src/trait_handlers/partial_ord/mod.rs @@ -1,58 +1,52 @@ +use crate::trait_handlers::TraitHandlerContext; mod models; mod panic; mod partial_ord_enum; mod partial_ord_struct; -use models::TypeAttributeBuilder; use syn::{Data, DeriveInput, Meta}; use super::TraitHandler; use crate::Trait; +/// Dispatches the `PartialOrd` derive to the specialized handler for the shape of the input. pub(crate) struct PartialOrdHandler; impl TraitHandler for PartialOrdHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { - #[cfg(feature = "Ord")] - let contains_ord = traits.contains(&Trait::Ord); - - #[cfg(not(feature = "Ord"))] - let contains_ord = false; - - // if `contains_ord` is true, the implementation is handled by the `Ord` attribute - if contains_ord { - let _ = TypeAttributeBuilder { - enable_flag: true, enable_bound: false - } - .build_from_partial_ord_meta(meta)?; - - // field attributes is also handled by the `Ord` attribute - - Ok(()) - } else { - match ast.data { - Data::Struct(_) => partial_ord_struct::PartialOrdStructHandler::trait_meta_handler( - ast, - token_stream, - traits, - meta, - ), - Data::Enum(_) => partial_ord_enum::PartialOrdEnumHandler::trait_meta_handler( - ast, - token_stream, - traits, - meta, - ), - Data::Union(_) => { - Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) - }, - } + match ast.data { + Data::Struct(_) => partial_ord_struct::PartialOrdStructHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Enum(_) => partial_ord_enum::PartialOrdEnumHandler::trait_meta_handler( + ast, + ctx, + token_stream, + traits, + meta, + ), + Data::Union(_) => { + Err(crate::panic::trait_not_support_union(meta.path().get_ident().unwrap())) + }, } } } + +/// Returns the traits whose recorded bounds `PartialOrd` inherits when its own bound is automatic. +pub(crate) fn prerequisites() -> &'static [Trait] { + &[ + #[cfg(feature = "PartialEq")] + Trait::PartialEq, + ] +} diff --git a/src/trait_handlers/partial_ord/models/field_attribute.rs b/src/trait_handlers/partial_ord/models/field_attribute.rs index be455ba..e7bffe7 100644 --- a/src/trait_handlers/partial_ord/models/field_attribute.rs +++ b/src/trait_handlers/partial_ord/models/field_attribute.rs @@ -1,5 +1,5 @@ use proc_macro2::Span; -use syn::{punctuated::Punctuated, spanned::Spanned, Attribute, Meta, Path, Token}; +use syn::{Attribute, Meta, Path, Token, punctuated::Punctuated, spanned::Spanned}; use crate::{ common::{ @@ -11,13 +11,17 @@ use crate::{ supported_traits::Trait, }; +/// The parsed settings of a field-level `PartialOrd` attribute. pub(crate) struct FieldAttribute { - pub(crate) ignore: bool, - pub(crate) method: Option, - pub(crate) rank: isize, - pub(crate) rank_span: Option, + pub(crate) ignore: bool, + pub(crate) method: Option, + /// Whether the method comes from a fallback `Ord` field attribute; such a method returns `Ordering` instead of `Option`, so the generated `partial_cmp` has to wrap its result in `Some`. + pub(crate) method_returns_ordering: bool, + pub(crate) rank: isize, + pub(crate) rank_span: Option, } +/// Parses field-level `PartialOrd` metas; the `enable_*` switches describe which parameters are allowed for the current shape of data. pub(crate) struct FieldAttributeBuilder { pub(crate) enable_ignore: bool, pub(crate) enable_method: bool, @@ -26,8 +30,10 @@ pub(crate) struct FieldAttributeBuilder { } impl FieldAttributeBuilder { + /// Parses one field-level `PartialOrd` meta into a `FieldAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result { - debug_assert!(meta.path().is_ident("PartialOrd")); + // An `Ord` meta is also accepted here because `build_from_attributes` may fall back to the `Ord` field attribute when `Ord` is derived together. + debug_assert!(meta.path().is_ident("PartialOrd") || meta.path().is_ident("Ord")); let mut ignore = false; let mut method = None; @@ -154,11 +160,13 @@ impl FieldAttributeBuilder { Ok(FieldAttribute { ignore, method, + method_returns_ordering: false, rank, rank_span, }) } + /// Scans the `#[educe(...)]` attributes of a field and parses its `PartialOrd` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -166,43 +174,63 @@ impl FieldAttributeBuilder { ) -> syn::Result { let mut output = None; + // When `Ord` is derived together and a field has no `PartialOrd` attribute of its own, the field's `Ord` attribute is used instead, so that `partial_cmp` stays consistent with `cmp` like in educe 0.5.x where the `Ord` handler generated both impls. + #[cfg(feature = "Ord")] + let mut fallback = None; + for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - for meta in result { - let path = meta.path(); + for meta in result { + let path = meta.path(); - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; + + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); + if t == Trait::PartialOrd { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } - if t == Trait::PartialOrd { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + output = Some(self.build_from_partial_ord_meta(&meta)?); + } - output = Some(self.build_from_partial_ord_meta(&meta)?); - } + // A malformed `Ord` attribute is silently skipped here; the `Ord` handler itself reports it with the proper usage message. + #[cfg(feature = "Ord")] + if t == Trait::Ord + && fallback.is_none() + && let Ok(mut field_attribute) = self.build_from_partial_ord_meta(&meta) + { + // An `Ord` comparison method returns `Ordering`, so the generated `partial_cmp` has to wrap its result in `Some`. + field_attribute.method_returns_ordering = field_attribute.method.is_some(); + + fallback = Some(field_attribute); } } } } + #[cfg(feature = "Ord")] + let output = output.or(fallback); + Ok(output.unwrap_or(FieldAttribute { - ignore: false, - method: None, - rank: self.rank, - rank_span: None, + ignore: false, + method: None, + method_returns_ordering: false, + rank: self.rank, + rank_span: None, })) } } diff --git a/src/trait_handlers/partial_ord/models/type_attribute.rs b/src/trait_handlers/partial_ord/models/type_attribute.rs index 5c318c4..481a6c1 100644 --- a/src/trait_handlers/partial_ord/models/type_attribute.rs +++ b/src/trait_handlers/partial_ord/models/type_attribute.rs @@ -1,18 +1,21 @@ -use syn::{punctuated::Punctuated, Attribute, Meta, Token}; +use syn::{Attribute, Meta, Token, punctuated::Punctuated}; -use crate::{common::bound::Bound, panic, Trait}; +use crate::{Trait, common::bound::Bound, panic}; +/// The parsed settings of a type-level (or variant-level) `PartialOrd` attribute. pub(crate) struct TypeAttribute { pub(crate) bound: Bound, } #[derive(Debug)] +/// Parses `PartialOrd` metas; the `enable_*` switches describe which parameters are allowed at the current position. pub(crate) struct TypeAttributeBuilder { pub(crate) enable_flag: bool, pub(crate) enable_bound: bool, } impl TypeAttributeBuilder { + /// Parses one `PartialOrd` meta into a `TypeAttribute`, rejecting parameters that are not enabled here. pub(crate) fn build_from_partial_ord_meta(&self, meta: &Meta) -> syn::Result { debug_assert!(meta.path().is_ident("PartialOrd")); @@ -55,24 +58,24 @@ impl TypeAttributeBuilder { let mut bound_is_set = false; let mut handler = |meta: Meta| -> syn::Result { - if let Some(ident) = meta.path().get_ident() { - if ident == "bound" { - if !self.enable_bound { - return Ok(false); - } + if let Some(ident) = meta.path().get_ident() + && ident == "bound" + { + if !self.enable_bound { + return Ok(false); + } - let v = Bound::from_meta(&meta)?; + let v = Bound::from_meta(&meta)?; - if bound_is_set { - return Err(panic::parameter_reset(ident)); - } + if bound_is_set { + return Err(panic::parameter_reset(ident)); + } - bound_is_set = true; + bound_is_set = true; - bound = v; + bound = v; - return Ok(true); - } + return Ok(true); } Ok(false) @@ -94,6 +97,7 @@ impl TypeAttributeBuilder { }) } + /// Scans the `#[educe(...)]` attributes of an item (typically an enum variant) and parses its `PartialOrd` meta if present. pub(crate) fn build_from_attributes( &self, attributes: &[Attribute], @@ -104,30 +108,30 @@ impl TypeAttributeBuilder { for attribute in attributes.iter() { let path = attribute.path(); - if path.is_ident("educe") { - if let Meta::List(list) = &attribute.meta { - let result = - list.parse_args_with(Punctuated::::parse_terminated)?; - - for meta in result { - let path = meta.path(); + if path.is_ident("educe") + && let Meta::List(list) = &attribute.meta + { + let result = + list.parse_args_with(Punctuated::::parse_terminated)?; - let t = match Trait::from_path(path) { - Some(t) => t, - None => return Err(panic::unsupported_trait(meta.path())), - }; + for meta in result { + let path = meta.path(); - if !traits.contains(&t) { - return Err(panic::trait_not_used(path.get_ident().unwrap())); - } + let t = match Trait::from_path(path) { + Some(t) => t, + None => return Err(panic::unsupported_trait(meta.path())), + }; - if t == Trait::PartialOrd { - if output.is_some() { - return Err(panic::reuse_a_trait(path.get_ident().unwrap())); - } + if !traits.contains(&t) { + return Err(panic::trait_not_used(path.get_ident().unwrap())); + } - output = Some(self.build_from_partial_ord_meta(&meta)?); + if t == Trait::PartialOrd { + if output.is_some() { + return Err(panic::reuse_a_trait(path.get_ident().unwrap())); } + + output = Some(self.build_from_partial_ord_meta(&meta)?); } } } diff --git a/src/trait_handlers/partial_ord/partial_ord_enum.rs b/src/trait_handlers/partial_ord/partial_ord_enum.rs index c41036f..84c91a5 100644 --- a/src/trait_handlers/partial_ord/partial_ord_enum.rs +++ b/src/trait_handlers/partial_ord/partial_ord_enum.rs @@ -1,24 +1,36 @@ use std::collections::BTreeMap; use quote::{format_ident, quote}; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type}; +use syn::{Data, DeriveInput, Field, Fields, Ident, Meta, Path, Type, spanned::Spanned}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{ + bound::{BOUND_EXCEPTIONS_ORDER, Bound}, + tools::DiscriminantType, + }, + trait_handlers::TraitHandlerContext, }; -use crate::{common::tools::DiscriminantType, Trait}; +/// Generates the `PartialOrd` implementation for an enum. pub(crate) struct PartialOrdEnumHandler; impl TraitHandler for PartialOrdEnumHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -114,8 +126,15 @@ impl TraitHandler for PartialOrdEnumHandler { &built_in_partial_cmp }); + // A method taken from a fallback `Ord` field attribute returns `Ordering`, so its result has to be wrapped in `Some` here. + let comparison = if field_attribute.method_returns_ordering { + quote!(::core::option::Option::Some(#partial_cmp(#field_name_var_self, #field_name_var_other))) + } else { + quote!(#partial_cmp(#field_name_var_self, #field_name_var_other)) + }; + block_token_stream.extend(quote! { - match #partial_cmp(#field_name_var_self, #field_name_var_other) { + match #comparison { Some(::core::cmp::Ordering::Equal) => (), Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater), Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less), @@ -188,8 +207,15 @@ impl TraitHandler for PartialOrdEnumHandler { &built_in_partial_cmp }); + // A method taken from a fallback `Ord` field attribute returns `Ordering`, so its result has to be wrapped in `Some` here. + let comparison = if field_attribute.method_returns_ordering { + quote!(::core::option::Option::Some(#partial_cmp(#field_name, #field_name2))) + } else { + quote!(#partial_cmp(#field_name, #field_name2)) + }; + block_token_stream.extend(quote! { - match #partial_cmp(#field_name, #field_name2) { + match #comparison { Some(::core::cmp::Ordering::Equal) => (), Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater), Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less), @@ -213,9 +239,12 @@ impl TraitHandler for PartialOrdEnumHandler { if arms_token_stream.is_empty() { partial_cmp_token_stream.extend(quote!(Some(::core::cmp::Ordering::Equal))); } else { + // Compare the discriminants by reinterpreting the leading bytes of the enum values as the discriminant integer type. + // This relies on the enum layout starting with its discriminant, which holds for the default representation and for explicit primitive representations. + // The cast goes through `<*const Self>` explicitly so that an exotic `From<&Type>` impl in scope cannot poison type inference. let discriminant_cmp = quote! { unsafe { - ::core::cmp::Ord::cmp(&*<*const _>::from(self).cast::<#discriminant_type>(), &*<*const _>::from(other).cast::<#discriminant_type>()) + ::core::cmp::Ord::cmp(&*<*const Self>::from(self).cast::<#discriminant_type>(), &*<*const Self>::from(other).cast::<#discriminant_type>()) } }; @@ -246,14 +275,25 @@ impl TraitHandler for PartialOrdEnumHandler { let ident = &ast.ident; - let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(), - &partial_ord_types, - &[quote! {::core::cmp::PartialEq}], - ); + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); + + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(), + &partial_ord_types, + &ast.ident, + &BOUND_EXCEPTIONS_ORDER, + ); + + if bound_is_auto { + ctx.inherit_from(super::prerequisites(), &mut bound); + } + + ctx.record(Trait::PartialOrd, &bound); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -263,6 +303,7 @@ impl TraitHandler for PartialOrdEnumHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { #[inline] fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { diff --git a/src/trait_handlers/partial_ord/partial_ord_struct.rs b/src/trait_handlers/partial_ord/partial_ord_struct.rs index 7b66644..e1c631b 100644 --- a/src/trait_handlers/partial_ord/partial_ord_struct.rs +++ b/src/trait_handlers/partial_ord/partial_ord_struct.rs @@ -1,24 +1,36 @@ use std::collections::BTreeMap; use quote::quote; -use syn::{spanned::Spanned, Data, DeriveInput, Field, Meta, Path, Type}; +use syn::{Data, DeriveInput, Field, Meta, Path, Type, spanned::Spanned}; use super::{ - models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, TraitHandler, + models::{FieldAttribute, FieldAttributeBuilder, TypeAttributeBuilder}, +}; +use crate::{ + Trait, + common::{ + bound::{BOUND_EXCEPTIONS_ORDER, Bound}, + ident_index::IdentOrIndex, + }, + trait_handlers::TraitHandlerContext, }; -use crate::{common::ident_index::IdentOrIndex, Trait}; +/// Generates the `PartialOrd` implementation for a struct. pub(crate) struct PartialOrdStructHandler; impl TraitHandler for PartialOrdStructHandler { #[inline] fn trait_meta_handler( ast: &DeriveInput, + ctx: &mut TraitHandlerContext, token_stream: &mut proc_macro2::TokenStream, traits: &[Trait], meta: &Meta, ) -> syn::Result<()> { + let generated_impl_attributes = + crate::common::attributes::generated_impl_attributes(&ast.attrs); + let type_attribute = TypeAttributeBuilder { enable_flag: true, enable_bound: true } @@ -29,6 +41,8 @@ impl TraitHandler for PartialOrdStructHandler { let mut partial_cmp_token_stream = proc_macro2::TokenStream::new(); if let Data::Struct(data) = &ast.data { + // Fields are compared in ascending rank order, so they are collected into a map keyed by rank. + // The default rank of a field is `isize::MIN` plus its ordinal position, which keeps the declaration order when no rank is given. let mut fields: BTreeMap = BTreeMap::new(); for (index, field) in data.fields.iter().enumerate() { @@ -68,8 +82,15 @@ impl TraitHandler for PartialOrdStructHandler { &built_in_partial_cmp }); + // A method taken from a fallback `Ord` field attribute returns `Ordering`, so its result has to be wrapped in `Some` here. + let comparison = if field_attribute.method_returns_ordering { + quote!(::core::option::Option::Some(#partial_cmp(&self.#field_name, &other.#field_name))) + } else { + quote!(#partial_cmp(&self.#field_name, &other.#field_name)) + }; + partial_cmp_token_stream.extend(quote! { - match #partial_cmp(&self.#field_name, &other.#field_name) { + match #comparison { Some(::core::cmp::Ordering::Equal) => (), Some(::core::cmp::Ordering::Greater) => return Some(::core::cmp::Ordering::Greater), Some(::core::cmp::Ordering::Less) => return Some(::core::cmp::Ordering::Less), @@ -81,14 +102,25 @@ impl TraitHandler for PartialOrdStructHandler { let ident = &ast.ident; - let bound = type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( - &ast.generics.params, - &syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(), - &partial_ord_types, - &[quote! {::core::cmp::PartialEq}], - ); + let bound_is_auto = matches!(type_attribute.bound, Bound::Auto); + + let mut bound = + type_attribute.bound.into_where_predicates_by_generic_parameters_check_types( + &ast.generics.params, + &syn::parse2(quote!(::core::cmp::PartialOrd)).unwrap(), + &partial_ord_types, + &ast.ident, + &BOUND_EXCEPTIONS_ORDER, + ); + + if bound_is_auto { + ctx.inherit_from(super::prerequisites(), &mut bound); + } + + ctx.record(Trait::PartialOrd, &bound); let mut generics = ast.generics.clone(); + let where_clause = generics.make_where_clause(); for where_predicate in bound { @@ -98,6 +130,7 @@ impl TraitHandler for PartialOrdStructHandler { let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); token_stream.extend(quote! { + #generated_impl_attributes impl #impl_generics ::core::cmp::PartialOrd for #ident #ty_generics #where_clause { #[inline] fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> { diff --git a/tests/clone_enum.rs b/tests/clone_enum.rs index bdcc6a4..5ecd4cb 100644 --- a/tests/clone_enum.rs +++ b/tests/clone_enum.rs @@ -1,5 +1,7 @@ #![cfg(feature = "Clone")] #![no_std] +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] use educe::Educe; diff --git a/tests/clone_struct.rs b/tests/clone_struct.rs index c59c602..418e5d9 100644 --- a/tests/clone_struct.rs +++ b/tests/clone_struct.rs @@ -1,7 +1,7 @@ #![cfg(feature = "Clone")] #![no_std] - -use core::marker::PhantomData; +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] use educe::Educe; @@ -174,48 +174,95 @@ fn bound_3() { #[test] fn bound_4() { - struct NotClone; - #[derive(Educe)] - // without `bound(*)` we get E0034: multiple applicable items in scope - // when we call Struct.clone(), since .clone() is then ambiguous #[educe(Clone(bound(*)))] struct Struct { - f1: PhantomData, + f1: T, } - trait ClashingFakeClone { - fn clone(&self) {} - } - impl ClashingFakeClone for Struct {} + #[derive(Educe)] + #[educe(Clone(bound(*)))] + struct Tuple(T); - let _: () = Struct { - f1: PhantomData:: + let s = Struct { + f1: 1 } .clone(); + let t = Tuple(1).clone(); - let _: Struct<_> = Struct { - f1: PhantomData::<()> + assert_eq!(1, s.f1); + assert_eq!(1, t.0); +} + +#[test] +fn bound_5() { + #[derive(Educe)] + #[educe(Clone)] + struct Struct { + f1: Option, + } + + #[derive(Educe)] + #[educe(Clone)] + struct Tuple(Option); + + let s = Struct { + f1: Some(1) } .clone(); + let t = Tuple(Some(1)).clone(); + + assert_eq!(Some(1), s.f1); + assert_eq!(Some(1), t.0); } -#[cfg(feature = "Debug")] #[test] -fn leaking_bounds() { - use core::{fmt::Debug, marker::PhantomData}; +fn bound_6() { + extern crate alloc; + struct NotClone; + + // These std types clone by duplicating a pointer or a marker, so their type arguments never receive a `Clone` bound. #[derive(Educe)] - #[educe(Debug(bound(T: Debug)), Clone(bound(T: Clone)))] - struct Struct { - x: PhantomData, + #[educe(Clone)] + struct Struct<'a, T> { + f1: alloc::sync::Arc, + f2: alloc::rc::Rc, + f3: &'a T, + f4: core::marker::PhantomData, + } + + let not_clone = NotClone; + let arc = alloc::sync::Arc::new(NotClone); + let rc = alloc::rc::Rc::new(NotClone); + + let s = Struct { + f1: arc, f2: rc, f3: ¬_clone, f4: core::marker::PhantomData + }; + + let _ = s.clone(); +} + +#[test] +fn bound_7() { + // A field type with a conditional `Clone` impl gets a precise `NotAlwaysClone: Clone` predicate, so the derive works for exactly the type arguments that support it. + struct NotAlwaysClone(T); + + impl Clone for NotAlwaysClone { + fn clone(&self) -> Self { + NotAlwaysClone(self.0) + } } - #[derive(Clone)] - struct NotDebug; + #[derive(Educe)] + #[educe(Clone)] + struct Struct { + f1: NotAlwaysClone, + } - let a = Struct { - x: PhantomData:: + let s = Struct { + f1: NotAlwaysClone(1u8) }; - let _b = a.clone(); + + assert_eq!(1, s.clone().f1.0); } diff --git a/tests/copy_clone_enum.rs b/tests/copy_clone_enum.rs index 969f4ab..570ae88 100644 --- a/tests/copy_clone_enum.rs +++ b/tests/copy_clone_enum.rs @@ -1,6 +1,8 @@ #![cfg(all(feature = "Copy", feature = "Clone"))] #![no_std] #![allow(clippy::clone_on_copy)] +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] use educe::Educe; diff --git a/tests/copy_clone_struct.rs b/tests/copy_clone_struct.rs index 6853e9c..b97dd7f 100644 --- a/tests/copy_clone_struct.rs +++ b/tests/copy_clone_struct.rs @@ -1,8 +1,8 @@ #![cfg(all(feature = "Copy", feature = "Clone"))] #![no_std] #![allow(clippy::clone_on_copy)] - -use core::marker::PhantomData; +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] use educe::Educe; @@ -115,70 +115,72 @@ fn bound_3() { } #[test] -fn bound_4() { +fn generic_1() { #[derive(Educe)] #[educe(Copy, Clone)] - struct Struct { - f1: Option, - f2: PhantomData, + struct Struct { + f1: T, } #[derive(Educe)] #[educe(Copy, Clone)] - struct Tuple(Option, PhantomData); + struct Tuple(T); + fn assert_copy(_v: &T) {} + + // A generic type argument that satisfies `Copy` makes the whole type `Copy`. let s = Struct { - f1: Some(1), f2: PhantomData:: - } - .clone(); - let t = Tuple(Some(1), PhantomData::).clone(); + f1: 1 + }; + let t = Tuple(1); - assert_eq!(Some(1), s.f1); - assert_eq!(Some(1), t.0); + assert_copy(&s); + assert_copy(&t); + + assert_eq!(1, s.clone().f1); + assert_eq!(1, t.clone().0); } #[test] -fn bound_5() { - trait Suitable {} - struct SuitableNotClone; - impl Suitable for SuitableNotClone {} - let phantom = PhantomData::; - - fn copy(t: &T) -> T { - *t - } - - #[derive(Educe)] - #[educe(Copy)] - struct Struct { - f1: Option, - f2: PhantomData, - } +fn generic_2() { + struct NotCopy; - impl Clone for Struct { + impl Clone for NotCopy { fn clone(&self) -> Self { - Struct { - f1: self.f1.clone(), f2: PhantomData - } + NotCopy } } #[derive(Educe)] - #[educe(Copy)] - struct Tuple(Option, PhantomData); + #[educe(Copy, Clone)] + struct Struct { + f1: T, + } - impl Clone for Tuple { - fn clone(&self) -> Self { - Tuple(self.0.clone(), PhantomData) - } + // A type argument that is `Clone` but not `Copy` can still be cloned, like the built-in derives. + let s = Struct { + f1: NotCopy + }; + + let _ = s.clone(); +} + +#[test] +fn generic_3() { + // The Copy impl can come from educe while the Clone impl comes from the built-in derive. + #[derive(Clone, Educe)] + #[educe(Copy)] + struct Struct { + f1: Option, } - let s = copy(&Struct { - f1: Some(1), f2: phantom - }); + fn assert_copy(_v: &T) {} + + let s = Struct { + f1: Some(1) + }; - let t = copy(&Tuple(Some(1), phantom)); + assert_copy(&s); - assert_eq!(Some(1), s.f1); - assert_eq!(Some(1), t.0); + assert_eq!(Some(1), s.clone().f1); } diff --git a/tests/copy_clone_union.rs b/tests/copy_clone_union.rs index 91b3aa6..faa74ff 100644 --- a/tests/copy_clone_union.rs +++ b/tests/copy_clone_union.rs @@ -35,3 +35,21 @@ fn bound() { assert_eq!(1, unsafe { u.f1 }); } + +#[test] +fn generic_union() { + // A generic union stays bitwise-copied, and its parameters receive a `Copy` bound. + #[derive(Educe)] + #[educe(Copy, Clone)] + union Union { + f1: T, + } + + let u = Union { + f1: 1u8 + }; + + let v = u.clone(); + + assert_eq!(1, unsafe { v.f1 }); +} diff --git a/tests/debug_enum.rs b/tests/debug_enum.rs index 05a158a..caa82f2 100644 --- a/tests/debug_enum.rs +++ b/tests/debug_enum.rs @@ -1,5 +1,7 @@ #![cfg(feature = "Debug")] #![no_std] +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] #[macro_use] extern crate alloc; @@ -253,7 +255,7 @@ fn named_field_1() { f1: 1 }) ); - assert_eq!("Tuple { _0: 1 }", format!("{:?}", Enum::Tuple(1))); + assert_eq!("Tuple { 0: 1 }", format!("{:?}", Enum::Tuple(1))); } #[test] @@ -273,7 +275,7 @@ fn named_field_2() { f1: 1 }) ); - assert_eq!("Tuple { _0: 1 }", format!("{:?}", Enum::Tuple(1))); + assert_eq!("Tuple { 0: 1 }", format!("{:?}", Enum::Tuple(1))); } #[test] @@ -302,7 +304,7 @@ fn named_field_3() { f1: 1 }) ); - assert_eq!("Tuple { _0: Hi }", format!("{:?}", Enum::Tuple(1))); + assert_eq!("Tuple { 0: Hi }", format!("{:?}", Enum::Tuple(1))); } #[test] diff --git a/tests/debug_struct.rs b/tests/debug_struct.rs index 4cd8573..40a7188 100644 --- a/tests/debug_struct.rs +++ b/tests/debug_struct.rs @@ -1,14 +1,11 @@ #![cfg(feature = "Debug")] #![no_std] +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] #[macro_use] extern crate alloc; -use core::{ - fmt::{self, Debug, Display}, - marker::PhantomData, -}; - use educe::Educe; #[test] @@ -245,7 +242,7 @@ fn named_field_1() { #[educe(Debug(named_field = true))] struct Tuple(u8); - assert_eq!("Tuple { _0: 1 }", format!("{:?}", Tuple(1))); + assert_eq!("Tuple { 0: 1 }", format!("{:?}", Tuple(1))); } #[test] @@ -267,7 +264,7 @@ fn named_field_2() { #[educe(Debug(named_field(true)))] struct Tuple(u8); - assert_eq!("Tuple { _0: 1 }", format!("{:?}", Tuple(1))); + assert_eq!("Tuple { 0: 1 }", format!("{:?}", Tuple(1))); } #[test] @@ -537,57 +534,3 @@ fn bound_3() { assert_eq!("Tuple(1)", format!("{:?}", Tuple(1))); } - -#[test] -fn bound_4() { - use core::cell::RefCell; - - #[derive(Educe)] - #[educe(Debug)] - struct Struct { - f1: RefCell, - } - - assert_eq!( - "Struct { f1: RefCell { value: 1 } }", - format!("{:?}", Struct { - f1: RefCell::new(1) - }) - ); - - #[derive(Educe)] - #[educe(Debug(bound(T: core::fmt::Debug)))] - struct Tuple(RefCell); - - assert_eq!("Tuple(RefCell { value: 1 })", format!("{:?}", Tuple(RefCell::new(1)))); -} - -#[test] -fn bound_5() { - struct DebugAsDisplay(T); - - struct NotDebug; - - impl Debug for DebugAsDisplay { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.0, f) - } - } - - #[derive(Educe)] - #[educe(Debug)] - struct Struct { - f1: Option, - f2: DebugAsDisplay, - f3: PhantomData, - } - - assert_eq!( - "Struct { f1: Some(1), f2: lit, f3: PhantomData }", - format!("{:?}", Struct { - f1: Some(1), - f2: DebugAsDisplay("lit"), - f3: PhantomData::, - }) - ); -} diff --git a/tests/default_enum.rs b/tests/default_enum.rs index c185fe7..46ea8bc 100644 --- a/tests/default_enum.rs +++ b/tests/default_enum.rs @@ -24,7 +24,7 @@ fn empty() { } assert!(matches!(Enum::default(), Enum::Struct {})); - assert!(matches!(Enum2::default(), Enum2::Tuple {})); + assert!(matches!(Enum2::default(), Enum2::Tuple())); } #[allow(dead_code)] diff --git a/tests/default_struct.rs b/tests/default_struct.rs index ce1548e..1ab5510 100644 --- a/tests/default_struct.rs +++ b/tests/default_struct.rs @@ -371,3 +371,25 @@ fn new() { assert_eq!(0, Struct::new().f1); assert_eq!(0, Tuple::new().0); } + +#[test] +fn bound_4() { + use alloc::{collections::BTreeMap, vec::Vec}; + + struct NotDefault; + + // These std containers default to an empty value, so their type arguments never receive a `Default` bound. + #[derive(Educe)] + #[educe(Default)] + struct Struct { + f1: Vec, + f2: Option, + f3: BTreeMap, + } + + let s: Struct = Struct::default(); + + assert!(s.f1.is_empty()); + assert!(s.f2.is_none()); + assert!(s.f3.is_empty()); +} diff --git a/tests/eq_enum.rs b/tests/eq_enum.rs index b542f31..adc036d 100644 --- a/tests/eq_enum.rs +++ b/tests/eq_enum.rs @@ -62,11 +62,11 @@ fn ignore_1() { #[educe(PartialEq, Eq)] enum Enum { Struct { - #[educe(Eq = false)] + #[educe(PartialEq = false)] f1: u8, f2: u8, }, - Tuple(#[educe(Eq = false)] u8, u8), + Tuple(#[educe(PartialEq = false)] u8, u8), } assert!( @@ -105,11 +105,11 @@ fn ignore_2() { #[educe(PartialEq, Eq)] enum Enum { Struct { - #[educe(Eq(ignore))] + #[educe(PartialEq(ignore))] f1: u8, f2: u8, }, - Tuple(#[educe(Eq(ignore))] u8, u8), + Tuple(#[educe(PartialEq(ignore))] u8, u8), } assert!( @@ -151,11 +151,11 @@ fn method_1() { #[educe(PartialEq, Eq)] enum Enum { Struct { - #[educe(Eq(method = eq))] + #[educe(PartialEq(method = eq))] f1: u8, f2: u8, }, - Tuple(#[educe(Eq(method = eq))] u8, u8), + Tuple(#[educe(PartialEq(method = eq))] u8, u8), } assert!( @@ -197,11 +197,11 @@ fn method_2() { #[educe(PartialEq, Eq)] enum Enum { Struct { - #[educe(Eq(method(eq)))] + #[educe(PartialEq(method(eq)))] f1: u8, f2: u8, }, - Tuple(#[educe(Eq(method(eq)))] u8, u8), + Tuple(#[educe(PartialEq(method(eq)))] u8, u8), } assert!( diff --git a/tests/eq_struct.rs b/tests/eq_struct.rs index e8d96a4..9fd0a3c 100644 --- a/tests/eq_struct.rs +++ b/tests/eq_struct.rs @@ -1,8 +1,6 @@ #![cfg(all(feature = "Eq", feature = "PartialEq"))] #![no_std] -use core::marker::PhantomData; - use educe::Educe; #[test] @@ -63,14 +61,14 @@ fn ignore_1() { #[derive(Educe)] #[educe(PartialEq, Eq)] struct Struct { - #[educe(Eq = false)] + #[educe(PartialEq = false)] f1: u8, f2: u8, } #[derive(Educe)] #[educe(PartialEq, Eq)] - struct Tuple(#[educe(Eq = false)] u8, u8); + struct Tuple(#[educe(PartialEq = false)] u8, u8); assert!( Struct { @@ -107,14 +105,14 @@ fn ignore_2() { #[derive(Educe)] #[educe(PartialEq, Eq)] struct Struct { - #[educe(Eq(ignore))] + #[educe(PartialEq(ignore))] f1: u8, f2: u8, } #[derive(Educe)] #[educe(PartialEq, Eq)] - struct Tuple(#[educe(Eq(ignore))] u8, u8); + struct Tuple(#[educe(PartialEq(ignore))] u8, u8); assert!( Struct { @@ -154,14 +152,14 @@ fn method_1() { #[derive(Educe)] #[educe(PartialEq, Eq)] struct Struct { - #[educe(Eq(method = eq))] + #[educe(PartialEq(method = eq))] f1: u8, f2: u8, } #[derive(Educe)] #[educe(PartialEq, Eq)] - struct Tuple(#[educe(Eq(method = eq))] u8, u8); + struct Tuple(#[educe(PartialEq(method = eq))] u8, u8); assert!( Struct { @@ -201,14 +199,14 @@ fn method_2() { #[derive(Educe)] #[educe(PartialEq, Eq)] struct Struct { - #[educe(Eq(method(eq)))] + #[educe(PartialEq(method(eq)))] f1: u8, f2: u8, } #[derive(Educe)] #[educe(PartialEq, Eq)] - struct Tuple(#[educe(Eq(method(eq)))] u8, u8); + struct Tuple(#[educe(PartialEq(method(eq)))] u8, u8); assert!( Struct { @@ -335,56 +333,6 @@ fn bound_3() { assert!(Tuple(1) != Tuple(2)); } -#[test] -fn bound_4() { - trait Suitable {} - struct SuitableNotEq; - impl Suitable for SuitableNotEq {} - let phantom = PhantomData::; - - #[derive(Educe)] - #[educe(Eq)] - struct Struct { - f1: T, - // PhantomData is Eq (all PhantomData are equal to all others) - f2: PhantomData, - } - - impl PartialEq for Struct { - fn eq(&self, other: &Struct) -> bool { - self.f1.eq(&other.f1) - } - } - - #[derive(Educe)] - #[educe(Eq)] - struct Tuple(T, PhantomData); - - impl PartialEq for Tuple { - fn eq(&self, other: &Tuple) -> bool { - self.0.eq(&other.0) - } - } - assert!( - Struct { - f1: 1, f2: phantom - } == Struct { - f1: 1, f2: phantom - } - ); - - assert!( - Struct { - f1: 1, f2: phantom - } != Struct { - f1: 2, f2: phantom - } - ); - - assert!(Tuple(1, phantom) == Tuple(1, phantom)); - assert!(Tuple(1, phantom) != Tuple(2, phantom)); -} - #[allow(dead_code)] #[test] fn use_partial_eq_attr_ignore() { @@ -428,3 +376,43 @@ fn use_partial_eq_attr_ignore() { assert!(Tuple(1, 2) == Tuple(2, 2)); assert!(Tuple(1, 2) != Tuple(2, 3)); } + +#[test] +fn bound_4() { + trait Suitable: PartialEq {} + + impl Suitable for u8 {} + + // Explicit bounds can be set on `PartialEq` and `Eq` separately. + #[derive(Educe)] + #[educe(PartialEq(bound(T: Suitable)), Eq(bound(T: Suitable)))] + struct Struct { + f1: T, + } + + fn assert_eq_impl(_v: &T) {} + + assert_eq_impl(&Struct { + f1: 1 + }); +} + +#[test] +fn bound_inheritance() { + trait Suitable: PartialEq {} + + impl Suitable for u8 {} + + // With an automatic bound, `Eq` inherits the custom predicates of the `PartialEq` impl. + #[derive(Educe)] + #[educe(PartialEq(bound(T: Suitable)), Eq)] + struct Struct { + f1: T, + } + + fn assert_eq_impl(_v: &T) {} + + assert_eq_impl(&Struct { + f1: 1 + }); +} diff --git a/tests/into_enum.rs b/tests/into_enum.rs index c536e87..6722660 100644 --- a/tests/into_enum.rs +++ b/tests/into_enum.rs @@ -187,3 +187,23 @@ fn bound_3() { assert_eq!(1u8, s1.into()); } + +#[allow(dead_code)] +#[test] +fn from_impl() { + // A concrete target type gets a `From` impl, so both directions of the conversion are available. + #[derive(Educe)] + #[educe(Into(u8))] + enum Enum { + Struct { f1: u8 }, + Tuple(u8), + } + + assert_eq!( + 1u8, + u8::from(Enum::Struct { + f1: 1 + }) + ); + assert_eq!(2u8, u8::from(Enum::Tuple(2))); +} diff --git a/tests/into_struct.rs b/tests/into_struct.rs index a738027..f489a3d 100644 --- a/tests/into_struct.rs +++ b/tests/into_struct.rs @@ -184,3 +184,58 @@ fn bound_3() { assert_eq!(1u8, s1.into()); } + +#[allow(dead_code)] +#[test] +fn from_impl() { + // A concrete target type gets a `From` impl, so both directions of the conversion are available. + #[derive(Educe)] + #[educe(Into(u8))] + struct Struct { + f1: u8, + } + + let s = Struct { + f1: 1 + }; + + assert_eq!(1u8, u8::from(s)); +} + +#[allow(dead_code)] +#[test] +fn generic_target() { + extern crate alloc; + + use alloc::vec::Vec; + + // A generic target type whose type parameter is covered (here by `Vec`) gets a `From` impl, so both directions of the conversion are available. + #[derive(Educe)] + #[educe(Into(Vec))] + struct Struct { + f1: Vec, + } + + let s = Struct { + f1: alloc::vec![1, 2] + }; + + assert_eq!(alloc::vec![1, 2], Vec::from(s)); +} + +#[allow(dead_code)] +#[test] +fn force_into() { + // The `into` flag forces an `Into` impl for a concrete target type. + #[derive(Educe)] + #[educe(Into(u8, into))] + struct Struct { + f1: u8, + } + + let s = Struct { + f1: 1 + }; + + assert_eq!(1u8, Into::::into(s)); +} diff --git a/tests/lint_propagation.rs b/tests/lint_propagation.rs new file mode 100644 index 0000000..e1a52b8 --- /dev/null +++ b/tests/lint_propagation.rs @@ -0,0 +1,38 @@ +#![cfg(feature = "Debug")] +#![no_std] +#![deny(clippy::used_underscore_binding)] +// The types in these tests only exist to exercise the derived impls, and `#[automatically_derived]` impls do not count as uses for dead-code analysis. +#![allow(dead_code)] + +use educe::Educe; + +#[test] +fn automatically_derived() { + // The generated impl is marked `#[automatically_derived]`, so lints like `clippy::used_underscore_binding` do not fire on the generated code. + #[derive(Educe)] + #[educe(Debug)] + enum Enum { + Struct { f1: u8 }, + } + + let _ = Enum::Struct { + f1: 1 + }; +} + +#[test] +fn lint_attribute_propagation() { + #[allow(dead_code)] + #[deprecated] + struct Deprecated; + + // The `#[allow(deprecated)]` on the type is copied onto the generated impl, so using the deprecated field type inside it does not fail under `#![deny(deprecated)]`. + #[allow(dead_code)] + #[allow(deprecated)] + #[derive(Educe)] + #[educe(Debug)] + struct Struct { + #[educe(Debug(ignore))] + f1: Deprecated, + } +} diff --git a/tests/ord_enum.rs b/tests/ord_enum.rs index c5d01a7..be7b778 100644 --- a/tests/ord_enum.rs +++ b/tests/ord_enum.rs @@ -103,7 +103,6 @@ fn basic_3() { assert!(Enum::A > Enum::B); } -#[rustversion::since(1.66)] #[test] fn basic_4() { #[derive(PartialEq, Eq, Educe)] diff --git a/tests/ord_struct.rs b/tests/ord_struct.rs index 59d077b..6168eef 100644 --- a/tests/ord_struct.rs +++ b/tests/ord_struct.rs @@ -1,7 +1,7 @@ #![cfg(all(feature = "Ord", feature = "PartialOrd"))] #![no_std] -use core::{cmp::Ordering, marker::PhantomData}; +use core::cmp::Ordering; use educe::Educe; @@ -528,81 +528,6 @@ fn bound_3() { assert!(Tuple(1) < Tuple(2)); } -#[test] -fn bound_4() { - trait Suitable {} - struct SuitableNotEq; - impl Suitable for SuitableNotEq {} - let phantom = PhantomData::; - - #[derive(Educe)] - #[educe(Ord)] - struct Struct { - f1: T, - // PhantomData is Eq (all PhantomData are equal to all others) - f2: PhantomData, - } - - impl PartialEq for Struct { - fn eq(&self, other: &Struct) -> bool { - self.f1.eq(&other.f1) - } - } - - impl Eq for Struct {} - - impl PartialOrd for Struct { - fn partial_cmp(&self, other: &Struct) -> Option { - self.f1.partial_cmp(&other.f1) - } - } - - #[derive(Educe)] - #[educe(Ord)] - struct Tuple(T, PhantomData); - - impl PartialEq for Tuple { - fn eq(&self, other: &Tuple) -> bool { - self.0.eq(&other.0) - } - } - - impl Eq for Tuple {} - - impl PartialOrd for Tuple { - fn partial_cmp(&self, other: &Tuple) -> Option { - self.0.partial_cmp(&other.0) - } - } - - assert_eq!( - Ord::cmp( - &Struct { - f1: 1, f2: phantom - }, - &Struct { - f1: 1, f2: phantom - } - ), - Ordering::Equal - ); - - assert_eq!( - Ord::cmp( - &Struct { - f1: 1, f2: phantom - }, - &Struct { - f1: 2, f2: phantom - } - ), - Ordering::Less - ); - - assert_eq!(Ord::cmp(&Tuple(1, phantom), &Tuple(1, phantom)), Ordering::Equal); - assert_eq!(Ord::cmp(&Tuple(1, phantom), &Tuple(2, phantom)), Ordering::Less); -} - #[test] fn use_partial_ord_attr_ignore() { #[derive(PartialEq, Eq, Educe)] @@ -651,3 +576,28 @@ fn use_partial_ord_attr_ignore() { assert_eq!(Ordering::Less, Tuple(1, 2).cmp(&Tuple(1, 3))); assert_eq!(Ordering::Equal, Tuple(2, 2).cmp(&Tuple(1, 2))); } + +#[cfg(all(feature = "PartialEq", feature = "Eq"))] +#[test] +fn bound_inheritance() { + trait Suitable: Ord {} + + impl Suitable for u8 {} + + // With automatic bounds, the custom predicate of `PartialEq` flows through `Eq` and `PartialOrd` into `Ord`. + #[derive(Educe)] + #[educe(PartialEq(bound(T: Suitable)), Eq, PartialOrd, Ord)] + struct Struct { + f1: T, + } + + assert!(matches!( + Struct { + f1: 1 + } + .cmp(&Struct { + f1: 2 + }), + core::cmp::Ordering::Less + )); +} diff --git a/tests/partial_ord_enum.rs b/tests/partial_ord_enum.rs index f50cfda..2c81fb5 100644 --- a/tests/partial_ord_enum.rs +++ b/tests/partial_ord_enum.rs @@ -103,7 +103,6 @@ fn basic_3() { assert!(Enum::A > Enum::B); } -#[rustversion::since(1.66)] #[test] fn basic_4() { #[derive(PartialEq, Educe)] diff --git a/tests/partial_ord_struct.rs b/tests/partial_ord_struct.rs index d47d127..acc3844 100644 --- a/tests/partial_ord_struct.rs +++ b/tests/partial_ord_struct.rs @@ -1,7 +1,7 @@ #![cfg(feature = "PartialOrd")] #![no_std] -use core::{cmp::Ordering, marker::PhantomData}; +use core::cmp::Ordering; use educe::Educe; @@ -528,53 +528,73 @@ fn bound_3() { assert!(Tuple(1) < Tuple(2)); } +#[cfg(feature = "Ord")] #[test] fn bound_4() { - trait Suitable {} - struct SuitableNotEq; - impl Suitable for SuitableNotEq {} - let phantom = PhantomData::; - - #[derive(Educe)] - #[educe(PartialOrd)] - struct Struct { - f1: T, - // PhantomData is Eq (all PhantomData are equal to all others) - f2: PhantomData, + #[derive(PartialEq, Eq, Educe)] + #[educe(PartialOrd(bound(*)), Ord(bound(*)))] + struct Struct { + f1: Option, } - impl PartialEq for Struct { - fn eq(&self, other: &Struct) -> bool { - self.f1.eq(&other.f1) + // Explicit bounds can be set on `PartialOrd` and `Ord` separately. + assert!( + Struct { + f1: Some(1) + } < Struct { + f1: Some(2) } + ); +} + +#[cfg(feature = "Ord")] +#[test] +fn rank_with_ord() { + #[derive(PartialEq, Eq, Educe)] + #[educe(PartialOrd, Ord)] + struct Struct { + #[educe(Ord(rank = 1))] + f1: u8, + #[educe(Ord(rank = 0))] + f2: u8, } - #[derive(Educe)] - #[educe(PartialOrd)] - struct Tuple(T, PhantomData); + // The `Ord` rank also applies to the generated `partial_cmp`, so both comparisons stay consistent. + let a = Struct { + f1: 2, f2: 1 + }; + let b = Struct { + f1: 1, f2: 2 + }; - impl PartialEq for Tuple { - fn eq(&self, other: &Tuple) -> bool { - self.0.eq(&other.0) - } + assert!(a < b); + assert!(matches!(a.cmp(&b), core::cmp::Ordering::Less)); +} + +#[cfg(feature = "Ord")] +#[test] +fn use_ord_attr_method() { + use core::cmp::Ordering; + + fn cmp(a: &u8, b: &u8) -> Ordering { + b.cmp(a) } - assert!( - Struct { - f1: 1, f2: phantom - } == Struct { - f1: 1, f2: phantom - } - ); + // A field without its own `PartialOrd` attribute follows its `Ord` attribute, so `partial_cmp` stays consistent with `cmp`. + #[derive(PartialEq, Eq, Educe)] + #[educe(PartialOrd, Ord)] + struct Struct { + #[educe(Ord(method(cmp)))] + f1: u8, + } - assert!( - Struct { - f1: 1, f2: phantom - } != Struct { - f1: 2, f2: phantom - } - ); + let a = Struct { + f1: 1 + }; + let b = Struct { + f1: 2 + }; - assert!(Tuple(1, phantom) == Tuple(1, phantom)); - assert!(Tuple(1, phantom) != Tuple(2, phantom)); + assert!(a > b); + assert!(matches!(a.cmp(&b), Ordering::Greater)); } diff --git a/tests/recursive_type.rs b/tests/recursive_type.rs new file mode 100644 index 0000000..a464cae --- /dev/null +++ b/tests/recursive_type.rs @@ -0,0 +1,53 @@ +#![cfg(all( + feature = "Debug", + feature = "Clone", + feature = "PartialEq", + feature = "Eq", + feature = "PartialOrd", + feature = "Ord", + feature = "Hash" +))] +#![no_std] + +extern crate alloc; + +use alloc::{boxed::Box, vec::Vec}; + +use educe::Educe; + +#[test] +fn recursive_enum() { + #[derive(Educe)] + #[educe(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + enum List { + Nil, + Cons(T, Box>), + } + + let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil)))); + + let cloned = list.clone(); + + assert_eq!(list, cloned); + assert!(list <= cloned); + assert!(matches!(List::::Nil.cmp(&List::Nil), core::cmp::Ordering::Equal)); +} + +#[test] +fn recursive_struct() { + #[derive(Educe)] + #[educe(Debug, Clone, PartialEq)] + struct Node { + value: T, + children: Vec>, + } + + let node = Node { + value: 1, + children: alloc::vec![Node { + value: 2, children: Vec::new() + }], + }; + + assert_eq!(node, node.clone()); +}