├── .github └── workflows │ ├── ci.yaml │ └── publish.yaml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── lib.rs └── transitive │ ├── fallible │ ├── mod.rs │ ├── try_from.rs │ └── try_into.rs │ ├── infallible │ ├── from.rs │ ├── into.rs │ └── mod.rs │ └── mod.rs └── tests ├── combined.rs ├── foreign_types.rs ├── from.rs ├── into.rs ├── macros.rs ├── try_from.rs └── try_into.rs /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | format: 11 | name: Format 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: dtolnay/rust-toolchain@nightly 16 | with: 17 | components: rustfmt 18 | 19 | - name: Check format 20 | run: cargo +nightly fmt --check 21 | 22 | clippy: 23 | name: Clippy 24 | needs: format 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: Swatinem/rust-cache@v2 29 | - uses: dtolnay/rust-toolchain@1.77.0 30 | with: 31 | components: clippy 32 | 33 | - name: Run clippy 34 | run: cargo clippy --tests 35 | env: 36 | RUSTFLAGS: -D warnings 37 | 38 | tests: 39 | name: Tests 40 | needs: clippy 41 | runs-on: ubuntu-latest 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: Swatinem/rust-cache@v2 46 | - uses: dtolnay/rust-toolchain@1.77.0 47 | 48 | - name: Run tests 49 | run: cargo test 50 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | jobs: 9 | tests: 10 | name: Tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: Swatinem/rust-cache@v2 15 | - uses: dtolnay/rust-toolchain@stable 16 | 17 | - name: Run tests 18 | run: cargo test 19 | 20 | publish: 21 | name: Publish 22 | needs: tests 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: Swatinem/rust-cache@v2 27 | - uses: dtolnay/rust-toolchain@stable 28 | 29 | - name: Cargo publish 30 | run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | # rust-analyzer can be configured with nightly rustfmt 2 | # i.e. for vscode in settings.json: 3 | # "rust-analyzer.rustfmt.extraArgs": [ 4 | # "+nightly" 5 | # ], 6 | unstable_features = true 7 | group_imports = "StdExternalCrate" 8 | imports_granularity = "Crate" 9 | format_code_in_doc_comments = true 10 | format_macro_bodies = true 11 | format_macro_matchers = true 12 | format_strings = true 13 | comment_width = 100 14 | wrap_comments = true 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | ## [1.2.0] - 2025-05-23 9 | 10 | ### Added 11 | 12 | - [#15](https://github.com/bobozaur/transitive/pull/15): Check that at least two distinct types are in the type list. 13 | 14 | ### Fixed 15 | 16 | - [#14](https://github.com/bobozaur/transitive/pull/14): Disallow type lists with only one type. 17 | 18 | ## [1.1.0] - 2025-03-13 19 | 20 | ### Changed 21 | 22 | - Allow more complex types in the macro attribute's type path. 23 | 24 | ### Added 25 | 26 | - [#12](https://github.com/bobozaur/transitive/pull/12): Improves type parameters parsing to allow complex types, 27 | such as the ones with generics. 28 | 29 | ## [1.0.1] - 2024-05-02 30 | 31 | ### Changed 32 | 33 | - Clear out derive macro documentation. 34 | 35 | ## [1.0.0] - 2024-05-02 36 | 37 | ### Added 38 | 39 | - [#6](https://github.com/bobozaur/transitive/issues/6): Added generics support on the derived type. 40 | 41 | ### Changed 42 | 43 | - Updated dependencies. 44 | - Removed usage of `darling`. 45 | - Improved path direction parsing. 46 | 47 | ## [0.5.0] - 2023-07-03 48 | 49 | ### Added 50 | 51 | - [#8](https://github.com/bobozaur/transitive/issues/8): Added the ability to specify custom error types for fallible conversions. 52 | 53 | ### Changed 54 | 55 | - Updated dependencies. 56 | - Refactored library using `darling`. 57 | - Merged the two `TransitiveFrom` and `TransitiveTryFrom` derive macros into a single `Transitive` macro (the behavior is now controlled through the attribute modifiers). 58 | - Dropped the `all` macro attribute modifier. 59 | 60 | ## [0.4.3] - 2023-03-09 61 | 62 | First "feature complete" release. 63 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "transitive" 3 | version = "1.2.0" 4 | edition = "2021" 5 | rust-version = "1.77.0" 6 | authors = ["bobozaur"] 7 | description = "Transitive derive macros for Rust." 8 | license = "MIT" 9 | repository = "https://github.com/bobozaur/transitive" 10 | keywords = ["transitive", "macros", "rust"] 11 | exclude = ["tests/*"] 12 | categories = ["rust-patterns", "development-tools"] 13 | 14 | [lib] 15 | proc-macro = true 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | proc-macro2 = "1" 21 | quote = "1" 22 | syn = "2" 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Bogdan Mircea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Crates.io](https://img.shields.io/crates/v/transitive.svg)](https://crates.io/crates/transitive) 2 | 3 | # transitive 4 | Transitive converions through derive macros for Rust. 5 | 6 | ## Rationale: 7 | Assume you have types `A`, `B` and `C` with the following, already implemented, conversions: 8 | - `A -> B` 9 | - `B -> C` 10 | 11 | Sometimes it might be desirable to have an `A -> C` implementation which could easily be represented as `A -> B -> C`. 12 | 13 | That is precisely what this crate does. Through the `Transitive` derive macro, it will implement `From` or `TryFrom` respectively 14 | for converting from/to the derived type and a target type, given a path of transitions to go through. 15 | 16 | ```rust 17 | use transitive::Transitive; 18 | 19 | #[derive(Transitive)] 20 | #[transitive(into(B, C, D))] // impl From for D by doing A -> B -> C -> D 21 | struct A; 22 | 23 | #[derive(Transitive)] 24 | #[transitive(into(C, D))] // impl From for D by doing B -> C -> D 25 | struct B; 26 | struct C; 27 | struct D; 28 | 29 | impl From for B { 30 | fn from(val: A) -> Self { 31 | Self 32 | } 33 | }; 34 | 35 | impl From for C { 36 | fn from(val: B) -> Self { 37 | Self 38 | } 39 | }; 40 | 41 | impl From for D { 42 | fn from(val: C) -> Self { 43 | Self 44 | } 45 | }; 46 | 47 | #[test] 48 | fn into() { 49 | D::from(A); 50 | D::from(B); 51 | } 52 | ``` 53 | 54 | More examples and explanations can be found in the [documentation](https://docs.rs/transitive/latest/transitive/). 55 | 56 | ## License 57 | Licensed under MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT). 58 | 59 | ## Contributing 60 | Contributions to this repository, unless explicitly stated otherwise, will be considered licensed under MIT. 61 | Bugs/issues encountered can be opened [here](https://github.com/bobozaur/transitive/issues). -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate provides a derive macro that takes care of boilerplate code to make transitive 2 | //! conversions in Rust using [`From`] and [`TryFrom`] traits. 3 | //! 4 | //! It's not magic and it's completely static. The derive here merely implements [`From`] or 5 | //! [`TryFrom`] between types by relying on existent impls between items in the path. 6 | //! 7 | //! The path taken for transitions must be annotated (correctly) for transitions to work. 8 | //! Additonally, there can only be one transition between a source and a target type, as otherwise 9 | //! there would be duplicate trait implementations. 10 | //! 11 | //! The path is provided in the [`#[transitive]`] attribute along with a direction: 12 | //! 13 | //! ```ignore, compile_fail 14 | //! #[derive(Transitive)] 15 | //! #[transitive(from(D, C, B))] // Results in `impl From for A` as `D -> C -> B -> A` 16 | //! #[transitive(into(B, C, D))] // Results in `impl From for D` as `A -> B -> C -> D` 17 | //! struct A; 18 | //! ``` 19 | //! 20 | //! # Conversions table: 21 | //! 22 | //! | Derived Type | Annotation | Will impl | Conditions | 23 | //! |--------------|-----------------------------------|---------------------|---------------------------------------------------------------------------------------------------------------------------| 24 | //! | A | #[transitive(into(B, C, D))] | `From for D` | `From for B`; `From for C`; `From for D` | 25 | //! | A | #[transitive(from(D, C, B))] | `From for A` | `From for C`; `From for B`; `From for A` | 26 | //! | A | #[transitive(try_into(B, C, D))] | `TryFrom for D` | `TryFrom for B`; `TryFrom for C`; `TryFrom for D`; errors must impl `From for >::Error` | 27 | //! | A | #[transitive(try_from(D, C, B))] | `TryFrom for A` | `TryFrom for C`; `TryFrom for B`; `TryFrom for A`; errors must impl `From for >::Error` | 28 | //! 29 | //! 30 | //! # Custom error type: 31 | //! 32 | //! For `try_from` and `try_into` annotations, the macro attribute can accept an `error = MyError` 33 | //! argument as the last element, like so: `#[transitive(try_into(A, B, C, error = MyError))]`. This 34 | //! overrides the default behavior and allows specifying a custom error type, but all the error 35 | //! types resulting from conversions must be convertible to this type. 36 | //! 37 | //! # Examples: 38 | //! 39 | //! ``` 40 | //! use transitive::Transitive; 41 | //! 42 | //! #[derive(Transitive)] 43 | //! #[transitive(into(B, C, D))] // impl From for D by doing A -> B -> C -> D 44 | //! struct A; 45 | //! 46 | //! #[derive(Transitive)] 47 | //! #[transitive(into(C, D))] // impl From for D by doing B -> C -> D 48 | //! struct B; 49 | //! struct C; 50 | //! struct D; 51 | //! 52 | //! impl From for B { 53 | //! fn from(val: A) -> Self { 54 | //! Self 55 | //! } 56 | //! }; 57 | //! 58 | //! impl From for C { 59 | //! fn from(val: B) -> Self { 60 | //! Self 61 | //! } 62 | //! }; 63 | //! 64 | //! impl From for D { 65 | //! fn from(val: C) -> Self { 66 | //! Self 67 | //! } 68 | //! }; 69 | //! 70 | //! D::from(A); 71 | //! D::from(B); 72 | //! ``` 73 | //! 74 | //! Note that the macro does nothing for types in the middle: 75 | //! 76 | //! ```compile_fail 77 | //! use transitive::Transitive; 78 | //! 79 | //! #[derive(Transitive)] 80 | //! #[transitive(into(B, C, D))] // impl From for D by doing A -> B -> C -> D 81 | //! struct A; 82 | //! struct B; 83 | //! struct C; 84 | //! struct D; 85 | //! 86 | //! impl From for B { 87 | //! fn from(val: A) -> Self { 88 | //! Self 89 | //! } 90 | //! }; 91 | //! 92 | //! impl From for C { 93 | //! fn from(val: B) -> Self { 94 | //! Self 95 | //! } 96 | //! }; 97 | //! 98 | //! impl From for D { 99 | //! fn from(val: C) -> Self { 100 | //! Self 101 | //! } 102 | //! }; 103 | //! 104 | //! D::from(A); // works 105 | //! C::from(A); // does not compile 106 | //! ``` 107 | //! 108 | //! ``` 109 | //! use transitive::Transitive; 110 | //! 111 | //! #[derive(Transitive)] 112 | //! #[transitive(into(B, C))] // impl From for C by doing A -> B -> C 113 | //! #[transitive(into(C, D))] // impl From for D by doing A -> C -> D 114 | //! struct A; 115 | //! struct B; 116 | //! struct C; 117 | //! struct D; 118 | //! 119 | //! impl From for B { 120 | //! fn from(val: A) -> Self { 121 | //! Self 122 | //! } 123 | //! }; 124 | //! 125 | //! impl From for C { 126 | //! fn from(val: B) -> Self { 127 | //! Self 128 | //! } 129 | //! }; 130 | //! 131 | //! impl From for D { 132 | //! fn from(val: C) -> Self { 133 | //! Self 134 | //! } 135 | //! }; 136 | //! 137 | //! D::from(A); 138 | //! ``` 139 | //! 140 | //! Let's see an example on how to use [`Transitive`] when combining the "reversed" 141 | //! nature of the `from` and `try_from` attribute modifiers and the error transitions constraints: 142 | //! 143 | //! ``` 144 | //! #![allow(non_camel_case_types)] 145 | //! use transitive::Transitive; 146 | //! 147 | //! // Note how the annotation now considers `A` as target type 148 | //! // and `D`, the first element in the type list, as source. 149 | //! #[derive(Transitive)] 150 | //! #[transitive(try_from(D, C, B))] // impl TryFrom for A 151 | //! struct A; 152 | //! 153 | //! #[derive(Transitive)] 154 | //! #[transitive(try_from(D, C, error = ConvErr))] // impl TryFrom for B, with custom error 155 | //! struct B; 156 | //! struct C; 157 | //! struct D; 158 | //! 159 | //! struct ConvErr; 160 | //! 161 | //! struct ErrD_C; 162 | //! struct ErrC_B; 163 | //! 164 | //! #[derive(Transitive)] 165 | //! #[transitive(from(ErrD_C, ErrC_B))] // impl From for ErrB_A 166 | //! struct ErrB_A; 167 | //! 168 | //! impl From for ErrC_B { 169 | //! fn from(val: ErrD_C) -> Self { 170 | //! Self 171 | //! } 172 | //! }; 173 | //! 174 | //! impl From for ErrB_A { 175 | //! fn from(val: ErrC_B) -> Self { 176 | //! Self 177 | //! } 178 | //! }; 179 | //! 180 | //! impl From for ConvErr { 181 | //! fn from(val: ErrD_C) -> Self { 182 | //! Self 183 | //! } 184 | //! }; 185 | //! 186 | //! impl From for ConvErr { 187 | //! fn from(val: ErrC_B) -> Self { 188 | //! Self 189 | //! } 190 | //! }; 191 | //! 192 | //! impl TryFrom for C { 193 | //! type Error = ErrD_C; 194 | //! 195 | //! fn try_from(val: D) -> Result { 196 | //! Ok(Self) 197 | //! } 198 | //! }; 199 | //! 200 | //! impl TryFrom for B { 201 | //! type Error = ErrC_B; 202 | //! 203 | //! fn try_from(val: C) -> Result { 204 | //! Ok(Self) 205 | //! } 206 | //! }; 207 | //! 208 | //! impl TryFrom for A { 209 | //! type Error = ErrB_A; 210 | //! 211 | //! fn try_from(val: B) -> Result { 212 | //! Ok(Self) 213 | //! } 214 | //! }; 215 | //! 216 | //! A::try_from(D); 217 | //! B::try_from(D); 218 | //! ``` 219 | 220 | mod transitive; 221 | 222 | use proc_macro::TokenStream; 223 | use quote::ToTokens; 224 | use syn::parse_macro_input; 225 | 226 | use crate::transitive::TransitiveInput; 227 | 228 | #[proc_macro_derive(Transitive, attributes(transitive))] 229 | pub fn transitive(input: TokenStream) -> TokenStream { 230 | parse_macro_input!(input as TransitiveInput) 231 | .to_token_stream() 232 | .into() 233 | } 234 | -------------------------------------------------------------------------------- /src/transitive/fallible/mod.rs: -------------------------------------------------------------------------------- 1 | mod try_from; 2 | mod try_into; 3 | 4 | use syn::{ 5 | parse::{discouraged::Speculative, Parse, ParseStream}, 6 | Error as SynError, Ident, Result as SynResult, Token, Type, 7 | }; 8 | pub use try_from::TryTransitionFrom; 9 | pub use try_into::TryTransitionInto; 10 | 11 | use crate::transitive::AtLeastTwoTypes; 12 | 13 | /// A path list that may contain a custom error type. 14 | struct FallibleTypeList { 15 | /// First type in the transitive conversion. ie. `A` in 16 | /// `#[transitive(try_from(A, B, C, D, E))]` 17 | first_type: Type, 18 | /// Intermediate types for the transitive conversion. ie. `[B, .., D]` in 19 | /// `#[transitive(try_from(A, B, C, D, E))]` 20 | intermediate_types: Vec, 21 | /// Last type in the transitive conversion. ie. `E` in 22 | /// `#[transitive(try_from(A, B, C, D, E))]` 23 | last_type: Type, 24 | error: Option, 25 | } 26 | 27 | impl Parse for FallibleTypeList { 28 | fn parse(input: ParseStream) -> SynResult { 29 | let AtLeastTwoTypes { 30 | first_type, 31 | second_type: mut last_type, 32 | remaining, 33 | } = AtLeastTwoTypes::parse(input)?; 34 | 35 | let mut intermediate_types = Vec::with_capacity(remaining.len()); 36 | let mut error = None; 37 | 38 | for attr in remaining { 39 | match attr { 40 | Item::Type(ty) if error.is_some() => { 41 | let msg = "types not allowed after 'error'"; 42 | return Err(SynError::new_spanned(ty, msg)); 43 | } 44 | // Just a regular type path in the conversion path 45 | Item::Type(ty) => { 46 | intermediate_types.push(last_type); 47 | last_type = ty; 48 | } 49 | Item::Error(err) if error.is_some() => { 50 | let msg = "'error' not allowed multiple times"; 51 | return Err(SynError::new_spanned(err, msg)); 52 | } 53 | // Custom error, but must check that it's a type path 54 | Item::Error(err) => error = Some(err), 55 | } 56 | } 57 | 58 | let output = Self { 59 | first_type, 60 | intermediate_types, 61 | last_type, 62 | error, 63 | }; 64 | 65 | Ok(output) 66 | } 67 | } 68 | 69 | /// An item in the parameters list of an attribute. 70 | enum Item { 71 | Type(Type), 72 | Error(Type), 73 | } 74 | 75 | impl Parse for Item { 76 | fn parse(input: ParseStream) -> SynResult { 77 | let fork = input.fork(); 78 | // Parse the ident name and the equal sign after it 79 | let res = fork 80 | .parse::() 81 | .and_then(|ident| fork.parse::().map(|_| ident)); 82 | 83 | match res { 84 | // We got an `error = MyType` argument 85 | Ok(path) if path == "error" => { 86 | input.advance_to(&fork); 87 | input.parse().map(Self::Error) 88 | } 89 | // Try to parse anything else as a type in the path list 90 | _ => input.parse().map(Self::Type), 91 | } 92 | } 93 | } 94 | 95 | impl From for Option { 96 | fn from(value: Item) -> Self { 97 | match value { 98 | Item::Type(ty) => Some(ty), 99 | Item::Error(_) => None, 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/transitive/fallible/try_from.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | Result as SynResult, 6 | }; 7 | 8 | use super::FallibleTypeList; 9 | use crate::transitive::{distinct_types_check, TokenizablePath}; 10 | 11 | /// Path corresponding to a [`#[transitive(try_from(..))`] path. 12 | pub struct TryTransitionFrom(FallibleTypeList); 13 | 14 | impl Parse for TryTransitionFrom { 15 | fn parse(input: ParseStream) -> SynResult { 16 | FallibleTypeList::parse(input).map(Self) 17 | } 18 | } 19 | 20 | impl ToTokens for TokenizablePath<'_, &TryTransitionFrom> { 21 | fn to_tokens(&self, tokens: &mut TokenStream) { 22 | let name = self.ident; 23 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 24 | let first = &self.path.0.first_type; 25 | let last = &self.path.0.last_type; 26 | 27 | let stmts = self 28 | .path 29 | .0 30 | .intermediate_types 31 | .iter() 32 | .chain(std::iter::once(last)) 33 | .map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;}) 34 | .chain(std::iter::once( 35 | quote! {let val = core::convert::TryFrom::try_from(val)?;}, 36 | )); 37 | 38 | let error = self 39 | .path 40 | .0 41 | .error 42 | .as_ref() 43 | .map(|e| quote!(#e)) 44 | .unwrap_or_else(|| quote!(>::Error)); 45 | 46 | let types_check = distinct_types_check( 47 | first, 48 | last, 49 | name, 50 | &impl_generics, 51 | &ty_generics, 52 | where_clause, 53 | ); 54 | 55 | let expanded = quote! { 56 | impl #impl_generics core::convert::TryFrom<#first> for #name #ty_generics #where_clause { 57 | type Error = #error; 58 | 59 | fn try_from(val: #first) -> core::result::Result { 60 | #types_check 61 | #(#stmts)* 62 | Ok(val) 63 | } 64 | } 65 | }; 66 | 67 | tokens.extend(expanded); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/transitive/fallible/try_into.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | Result as SynResult, 6 | }; 7 | 8 | use super::FallibleTypeList; 9 | use crate::transitive::{distinct_types_check, TokenizablePath}; 10 | 11 | /// Path corresponding to a [`#[transitive(try_into(..))`] path. 12 | pub struct TryTransitionInto(FallibleTypeList); 13 | 14 | impl Parse for TryTransitionInto { 15 | fn parse(input: ParseStream) -> SynResult { 16 | FallibleTypeList::parse(input).map(Self) 17 | } 18 | } 19 | 20 | impl ToTokens for TokenizablePath<'_, &TryTransitionInto> { 21 | fn to_tokens(&self, tokens: &mut TokenStream) { 22 | let name = self.ident; 23 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 24 | let first = &self.path.0.first_type; 25 | let last = &self.path.0.last_type; 26 | let second_last = self.path.0.intermediate_types.last().unwrap_or(first); 27 | 28 | let stmts = std::iter::once(first) 29 | .chain(&self.path.0.intermediate_types) 30 | .map(|ty| quote! {let val: #ty = core::convert::TryFrom::try_from(val)?;}); 31 | 32 | let error = self 33 | .path 34 | .0 35 | .error 36 | .as_ref() 37 | .map(|e| quote!(#e)) 38 | .unwrap_or_else(|| quote!(<#last as TryFrom<#second_last>>::Error)); 39 | 40 | let types_check = distinct_types_check( 41 | first, 42 | last, 43 | name, 44 | &impl_generics, 45 | &ty_generics, 46 | where_clause, 47 | ); 48 | 49 | let expanded = quote! { 50 | impl #impl_generics core::convert::TryFrom<#name #ty_generics> for #last #where_clause { 51 | type Error = #error; 52 | 53 | fn try_from(val: #name #ty_generics) -> core::result::Result { 54 | #types_check 55 | #(#stmts)* 56 | let val = core::convert::TryFrom::try_from(val)?; 57 | Ok(val) 58 | } 59 | } 60 | }; 61 | 62 | tokens.extend(expanded); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/transitive/infallible/from.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | Result as SynResult, 6 | }; 7 | 8 | use super::TypeList; 9 | use crate::transitive::{distinct_types_check, TokenizablePath}; 10 | 11 | /// Path corresponding to a [`#[transitive(from(..))`] path. 12 | pub struct TransitionFrom(TypeList); 13 | 14 | impl Parse for TransitionFrom { 15 | fn parse(input: ParseStream) -> SynResult { 16 | TypeList::parse(input).map(Self) 17 | } 18 | } 19 | 20 | impl ToTokens for TokenizablePath<'_, &TransitionFrom> { 21 | fn to_tokens(&self, tokens: &mut TokenStream) { 22 | let name = self.ident; 23 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 24 | let first = &self.path.0.first_type; 25 | let last = &self.path.0.last_type; 26 | 27 | let stmts = self 28 | .path 29 | .0 30 | .intermediate_types 31 | .iter() 32 | .chain(std::iter::once(last)) 33 | .map(|ty| quote! {let val: #ty = core::convert::From::from(val);}) 34 | .chain(std::iter::once(quote! {core::convert::From::from(val)})); 35 | 36 | let types_check = distinct_types_check( 37 | first, 38 | last, 39 | name, 40 | &impl_generics, 41 | &ty_generics, 42 | where_clause, 43 | ); 44 | 45 | let expanded = quote! { 46 | impl #impl_generics core::convert::From<#first> for #name #ty_generics #where_clause { 47 | fn from(val: #first) -> Self { 48 | #types_check 49 | #(#stmts)* 50 | } 51 | } 52 | }; 53 | 54 | tokens.extend(expanded); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/transitive/infallible/into.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{ 4 | parse::{Parse, ParseStream}, 5 | Result as SynResult, 6 | }; 7 | 8 | use super::TypeList; 9 | use crate::transitive::{distinct_types_check, TokenizablePath}; 10 | 11 | /// Path corresponding to a [`#[transitive(into(..))`] path. 12 | pub struct TransitionInto(TypeList); 13 | 14 | impl Parse for TransitionInto { 15 | fn parse(input: ParseStream) -> SynResult { 16 | TypeList::parse(input).map(Self) 17 | } 18 | } 19 | 20 | impl ToTokens for TokenizablePath<'_, &TransitionInto> { 21 | fn to_tokens(&self, tokens: &mut TokenStream) { 22 | let name = self.ident; 23 | let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); 24 | let first = &self.path.0.first_type; 25 | let last = &self.path.0.last_type; 26 | 27 | let stmts = std::iter::once(first) 28 | .chain(&self.path.0.intermediate_types) 29 | .map(|ty| quote! {let val: #ty = core::convert::From::from(val);}); 30 | 31 | let types_check = distinct_types_check( 32 | first, 33 | last, 34 | name, 35 | &impl_generics, 36 | &ty_generics, 37 | where_clause, 38 | ); 39 | 40 | let expanded = quote! { 41 | impl #impl_generics core::convert::From<#name #ty_generics> for #last #where_clause { 42 | fn from(val: #name #ty_generics) -> #last { 43 | #types_check 44 | #(#stmts)* 45 | core::convert::From::from(val) 46 | } 47 | } 48 | }; 49 | 50 | tokens.extend(expanded); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/transitive/infallible/mod.rs: -------------------------------------------------------------------------------- 1 | mod from; 2 | mod into; 3 | 4 | pub use from::TransitionFrom; 5 | pub use into::TransitionInto; 6 | use syn::{ 7 | parse::{Parse, ParseStream}, 8 | Result as SynResult, Type, 9 | }; 10 | 11 | use crate::transitive::AtLeastTwoTypes; 12 | 13 | struct TypeList { 14 | /// First type in the transitive conversion. ie. `A` in 15 | /// `#[transitive(from(A, B, C, D, E))]` 16 | first_type: Type, 17 | /// Intermediate types for the transitive conversion. ie. `[B, .., D]` in 18 | /// `#[transitive(from(A, B, C, D, E))]` 19 | intermediate_types: Vec, 20 | /// Last type in the transitive conversion. ie. `E` in 21 | /// `#[transitive(from(A, B, C, D, E))]` 22 | last_type: Type, 23 | } 24 | 25 | impl Parse for TypeList { 26 | fn parse(input: ParseStream) -> SynResult { 27 | let AtLeastTwoTypes { 28 | first_type, 29 | second_type: mut last_type, 30 | remaining, 31 | } = AtLeastTwoTypes::parse(input)?; 32 | 33 | let intermediate_types = remaining 34 | .map(|ty| std::mem::replace(&mut last_type, ty)) 35 | .collect(); 36 | 37 | let output = Self { 38 | first_type, 39 | intermediate_types, 40 | last_type, 41 | }; 42 | 43 | Ok(output) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/transitive/mod.rs: -------------------------------------------------------------------------------- 1 | mod fallible; 2 | mod infallible; 3 | 4 | use fallible::{TryTransitionFrom, TryTransitionInto}; 5 | use infallible::{TransitionFrom, TransitionInto}; 6 | use proc_macro2::TokenStream; 7 | use quote::{quote, ToTokens}; 8 | use syn::{ 9 | parse::{Parse, ParseStream}, 10 | punctuated::Punctuated, 11 | DeriveInput, Error as SynError, Generics, Ident, ImplGenerics, MetaList, Result as SynResult, 12 | Token, Type, TypeGenerics, WhereClause, 13 | }; 14 | 15 | /// The input to the [`crate::Transitive`] derive macro. 16 | pub struct TransitiveInput { 17 | ident: Ident, 18 | generics: Generics, 19 | paths: Vec, 20 | } 21 | 22 | impl TransitiveInput { 23 | const ATTR_NAME: &'static str = "transitive"; 24 | } 25 | 26 | impl Parse for TransitiveInput { 27 | fn parse(input: ParseStream) -> SynResult { 28 | let DeriveInput { 29 | attrs, 30 | ident, 31 | generics, 32 | .. 33 | } = DeriveInput::parse(input)?; 34 | 35 | let fold_fn = |mut vec: Vec, res| { 36 | vec.extend(res?); 37 | Ok(vec) 38 | }; 39 | 40 | let paths = attrs 41 | .into_iter() 42 | .filter(|a| a.path().is_ident(Self::ATTR_NAME)) 43 | .map(|a| a.parse_args_with(Punctuated::<_, Token![,]>::parse_terminated)) 44 | .try_fold::<_, _, SynResult<_>>(Vec::new(), fold_fn)?; 45 | 46 | let output = Self { 47 | ident, 48 | generics, 49 | paths, 50 | }; 51 | 52 | Ok(output) 53 | } 54 | } 55 | 56 | impl ToTokens for TransitiveInput { 57 | fn to_tokens(&self, tokens: &mut TokenStream) { 58 | for path in &self.paths { 59 | TokenizablePath::new(&self.ident, &self.generics, path).to_tokens(tokens); 60 | } 61 | } 62 | } 63 | 64 | /// Enum representing a path to take when transitioning from one type to another. 65 | enum TransitionPath { 66 | From(TransitionFrom), 67 | Into(TransitionInto), 68 | TryFrom(TryTransitionFrom), 69 | TryInto(TryTransitionInto), 70 | } 71 | 72 | impl TransitionPath { 73 | const FROM: &'static str = "from"; 74 | const INTO: &'static str = "into"; 75 | const TRY_FROM: &'static str = "try_from"; 76 | const TRY_INTO: &'static str = "try_into"; 77 | } 78 | 79 | impl Parse for TransitionPath { 80 | fn parse(input: ParseStream) -> SynResult { 81 | let MetaList { path, tokens, .. } = MetaList::parse(input)?; 82 | let tokens = tokens.into(); 83 | 84 | match path.require_ident()? { 85 | ident if ident == Self::FROM => syn::parse(tokens).map(TransitionPath::From), 86 | ident if ident == Self::INTO => syn::parse(tokens).map(TransitionPath::Into), 87 | ident if ident == Self::TRY_FROM => syn::parse(tokens).map(TransitionPath::TryFrom), 88 | ident if ident == Self::TRY_INTO => syn::parse(tokens).map(TransitionPath::TryInto), 89 | ident => Err(SynError::new(ident.span(), "unknown parameter")), 90 | } 91 | } 92 | } 93 | 94 | impl ToTokens for TokenizablePath<'_, &TransitionPath> { 95 | fn to_tokens(&self, tokens: &mut TokenStream) { 96 | match &self.path { 97 | TransitionPath::From(from) => { 98 | TokenizablePath::new(self.ident, self.generics, from).to_tokens(tokens) 99 | } 100 | TransitionPath::Into(into) => { 101 | TokenizablePath::new(self.ident, self.generics, into).to_tokens(tokens) 102 | } 103 | TransitionPath::TryFrom(try_from) => { 104 | TokenizablePath::new(self.ident, self.generics, try_from).to_tokens(tokens) 105 | } 106 | TransitionPath::TryInto(try_into) => { 107 | TokenizablePath::new(self.ident, self.generics, try_into).to_tokens(tokens) 108 | } 109 | } 110 | } 111 | } 112 | 113 | /// Wrapper type that aids in the tokenization of [`TransitionPath`] and its variants. 114 | struct TokenizablePath<'a, T> { 115 | ident: &'a Ident, 116 | generics: &'a Generics, 117 | path: T, 118 | } 119 | 120 | impl<'a, T> TokenizablePath<'a, T> { 121 | fn new(ident: &'a Ident, generics: &'a Generics, path: T) -> Self { 122 | Self { 123 | ident, 124 | generics, 125 | path, 126 | } 127 | } 128 | } 129 | 130 | /// Parsing helper that guarantees that there are at least two [`Type`] items in the list. 131 | /// 132 | /// ```compile_fail 133 | /// use transitive::Transitive; 134 | /// 135 | /// struct A; 136 | /// #[derive(Transitive)] 137 | /// #[transitive(from(A))] // fails to compile, list too short 138 | /// struct B; 139 | /// 140 | /// impl From for B { 141 | /// fn from(_: A) -> B { 142 | /// Self 143 | /// } 144 | /// } 145 | /// ``` 146 | struct AtLeastTwoTypes { 147 | /// First type in the list. 148 | first_type: Type, 149 | /// Second type in the list. 150 | second_type: Type, 151 | /// Remaining items in the input. 152 | /// These are NOT guaranteed to be types! 153 | remaining: syn::punctuated::IntoIter, 154 | } 155 | 156 | impl Parse for AtLeastTwoTypes 157 | where 158 | T: Parse, 159 | Option: From, 160 | { 161 | fn parse(input: ParseStream) -> SynResult { 162 | let error_span = input.span(); 163 | 164 | let mut remaining = Punctuated::::parse_terminated(input)?.into_iter(); 165 | let first_opt = remaining.next().and_then(From::from); 166 | let second_opt = remaining.next().and_then(From::from); 167 | 168 | let (first_type, second_type) = match (first_opt, second_opt) { 169 | (Some(first_type), Some(last_type)) => (first_type, last_type), 170 | _ => return Err(SynError::new(error_span, "at least two types required")), 171 | }; 172 | 173 | let output = Self { 174 | first_type, 175 | second_type, 176 | remaining, 177 | }; 178 | 179 | Ok(output) 180 | } 181 | } 182 | 183 | /// Inserts a compile time check that the first and last types are not the same. 184 | /// 185 | /// ```compile_fail 186 | /// use transitive::Transitive; 187 | /// 188 | /// struct A; 189 | /// #[derive(Transitive)] 190 | /// #[transitive(try_into(A, A))] // fails to compile, first and last types are equal 191 | /// struct B; 192 | /// 193 | /// impl TryFrom for A { 194 | /// type Error = (); 195 | /// 196 | /// fn try_from(_: B) -> Result { 197 | /// Ok(Self) 198 | /// } 199 | /// } 200 | /// ``` 201 | fn distinct_types_check<'a>( 202 | left: &Type, 203 | right: &Type, 204 | derived: &Ident, 205 | impl_generics: &ImplGenerics<'a>, 206 | ty_generics: &TypeGenerics<'a>, 207 | where_clause: Option<&WhereClause>, 208 | ) -> TokenStream { 209 | let turbofish = ty_generics.as_turbofish(); 210 | 211 | // We first declare an inherent constant on the wrapper type when we specifically use the 212 | // `left` type. 213 | // 214 | // We use `(left, derived)` as the generic type for [`Checker`] because `left` might use 215 | // generics of `derived`, but we do not have generic components ([`ImplGenerics`], 216 | // [`TypeGenerics`], etc.) for other types other than `derived`, which is the derived type. 217 | let checker = quote! { 218 | struct Checker(core::marker::PhantomData T>); 219 | 220 | impl #impl_generics Checker<(#left, #derived #ty_generics)> #where_clause { 221 | const FLAG: bool = false; 222 | } 223 | }; 224 | 225 | // We now declare a trait with a constant that has the same name as the inherent one and a 226 | // blanket impl. 227 | // 228 | // The `left` type will also get the trait constant, but the inherent constant has priority. 229 | // Because of that, if the left and right types are the same then the inherent constant gets 230 | // used in the assertion twice. 231 | let flagged = quote! { 232 | #checker 233 | 234 | trait Flagged { 235 | const FLAG: bool; 236 | } 237 | 238 | impl Flagged for Checker { 239 | const FLAG: bool = true; 240 | } 241 | }; 242 | 243 | // The actual assert resides in a trait constant to allow the usage of generics in the 244 | // const context. A regular const declaration would not allow the usage of generics from the 245 | // outer scope. 246 | // 247 | // The assert merely does an XOR on the checker flags. If the types are different, then 248 | // one flag will be the inherent constant while the other will come from the trait blanket 249 | // impl. 250 | // 251 | // We also make sure to invoke the constant to ensure it gets compiled. 252 | quote! { 253 | #flagged 254 | 255 | trait Verifier { 256 | const VALID: (); 257 | } 258 | 259 | impl #impl_generics Verifier for #derived #ty_generics { 260 | const VALID: () = assert!( 261 | Checker::<(#left, #derived #ty_generics)>::FLAG ^ Checker::<(#right, #derived #ty_generics)>::FLAG, 262 | "first and last types are equal" 263 | ); 264 | } 265 | 266 | let _ = #derived #turbofish::VALID; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /tests/combined.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use transitive::Transitive; 4 | 5 | #[derive(Transitive)] 6 | #[transitive(from(D, C, B), from(C, B))] // impl Fromfor A and impl From for A 7 | #[transitive(try_into(B, C, D))] // impl TryFrom for D 8 | struct A; 9 | struct B; 10 | struct C; 11 | struct D; 12 | 13 | impl_from!(B to A); 14 | impl_from!(C to B); 15 | impl_from!(D to C); 16 | 17 | impl_from!(A to B); 18 | impl_from!(B to C); 19 | impl_from!(C to D); 20 | 21 | #[test] 22 | pub fn test_combined_attributes() { 23 | let _ = A::from(D); 24 | let _ = A::from(C); 25 | let _ = D::try_from(A); 26 | } 27 | -------------------------------------------------------------------------------- /tests/foreign_types.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use std::num::ParseIntError; 4 | 5 | use transitive::Transitive; 6 | 7 | #[derive(Transitive)] 8 | #[transitive(try_from(u8, C, B))] // impl TryFrom for A 9 | struct A; 10 | 11 | #[derive(Transitive)] 12 | #[transitive(try_from(u8, C))] // impl TryFrom for B 13 | struct B; 14 | struct C; 15 | 16 | struct ErrCB; 17 | #[derive(Transitive)] 18 | #[transitive(from(ParseIntError, ErrCB))] // impl From for ErrB_A 19 | struct ErrBA; 20 | 21 | impl From for ErrCB { 22 | fn from(_value: ParseIntError) -> Self { 23 | Self 24 | } 25 | } 26 | 27 | impl From for ErrBA { 28 | fn from(_value: ErrCB) -> Self { 29 | Self 30 | } 31 | } 32 | 33 | impl_try_from!(B to A err ErrBA); 34 | impl_try_from!(C to B err ErrCB); 35 | impl_try_from!(u8 to C err ParseIntError); 36 | 37 | #[test] 38 | pub fn test_foreign_types() { 39 | let _ = A::try_from(1); 40 | let _ = B::try_from(1); 41 | } 42 | -------------------------------------------------------------------------------- /tests/from.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use std::marker::PhantomData; 4 | 5 | use transitive::Transitive; 6 | 7 | #[derive(Transitive)] 8 | #[transitive(from(D, C, B))] // impl From for A 9 | struct A; 10 | 11 | #[derive(Transitive)] 12 | #[transitive(from(D, C))] // impl From for B 13 | struct B; 14 | struct C; 15 | struct D; 16 | 17 | impl_from!(B to A); 18 | impl_from!(C to B); 19 | impl_from!(D to C); 20 | 21 | #[derive(Transitive)] 22 | #[transitive(from(D, C, B, A))] // impl From for Z 23 | #[transitive(from(C, B))] // impl From for Z 24 | struct Z(PhantomData); 25 | 26 | impl From for Z { 27 | fn from(_value: A) -> Self { 28 | Self(PhantomData) 29 | } 30 | } 31 | 32 | impl From for Z { 33 | fn from(_value: B) -> Self { 34 | Self(PhantomData) 35 | } 36 | } 37 | 38 | #[derive(Transitive)] 39 | #[transitive(from(D, C, B, A))] // impl From for Y<'a> 40 | struct Y<'a>(PhantomData<&'a ()>); 41 | 42 | impl From for Y<'_> { 43 | fn from(_value: A) -> Self { 44 | Self(PhantomData) 45 | } 46 | } 47 | 48 | #[derive(Transitive)] 49 | #[transitive(from(D, C, B, A))] // impl From for W 50 | struct W; 51 | 52 | impl From for W { 53 | fn from(_value: A) -> Self { 54 | Self 55 | } 56 | } 57 | 58 | #[derive(Transitive)] 59 | #[transitive(from(D, C, B, A))] // impl From for Q<'a, 'b, N, T, U> 60 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>( 61 | PhantomData<(&'a T, &'b U)>, 62 | ); 63 | 64 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> From for Q<'a, 'b, N, T, U> { 65 | fn from(_value: A) -> Self { 66 | Self(PhantomData) 67 | } 68 | } 69 | 70 | #[test] 71 | pub fn test_from() { 72 | let _ = A::from(D); 73 | let _ = B::from(D); 74 | let _ = Z::<()>::from(D); 75 | let _ = Y::from(D); 76 | let _ = W::<2>::from(D); 77 | let _ = Q::<2, (), ()>::from(D); 78 | } 79 | -------------------------------------------------------------------------------- /tests/into.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use std::marker::PhantomData; 4 | 5 | use transitive::Transitive; 6 | 7 | #[derive(Transitive)] 8 | #[transitive(into(B, C, D))] // impl From for D 9 | struct A; 10 | 11 | #[derive(Transitive)] 12 | #[transitive(into(C, D))] // impl From for D 13 | struct B; 14 | struct C; 15 | struct D; 16 | 17 | impl_from!(A to B); 18 | impl_from!(B to C); 19 | impl_from!(C to D); 20 | 21 | #[derive(Transitive)] 22 | #[transitive(into(A, B, C, D))] // impl From> for D 23 | struct Z(PhantomData); 24 | 25 | impl From> for A { 26 | fn from(_value: Z) -> Self { 27 | Self 28 | } 29 | } 30 | 31 | #[derive(Transitive)] 32 | #[transitive(into(A, B, C, D))] // impl From> for D 33 | struct Y<'a>(PhantomData<&'a ()>); 34 | 35 | impl<'a> From> for A { 36 | fn from(_value: Y<'a>) -> Self { 37 | Self 38 | } 39 | } 40 | 41 | #[derive(Transitive)] 42 | #[transitive(into(A, B, C, D))] // impl From> for D 43 | struct W; 44 | 45 | impl From> for A { 46 | fn from(_value: W) -> Self { 47 | Self 48 | } 49 | } 50 | 51 | #[derive(Transitive)] 52 | #[transitive(into(A, B, C, D))] // impl From> for D 53 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>( 54 | PhantomData<(&'a T, &'b U)>, 55 | ); 56 | 57 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> From> for A { 58 | fn from(_value: Q<'a, 'b, N, T, U>) -> Self { 59 | Self 60 | } 61 | } 62 | 63 | #[test] 64 | pub fn test_into() { 65 | let _ = D::from(A); 66 | let _ = D::from(B); 67 | let _ = D::from(Z(PhantomData::<()>)); 68 | let _ = D::from(Y(PhantomData)); 69 | let _ = D::from(W::<2>); 70 | let _ = D::from(Q::<2, (), ()>(PhantomData)); 71 | } 72 | -------------------------------------------------------------------------------- /tests/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! impl_from { 3 | ($source:ident to $target:ident) => { 4 | impl From<$source> for $target { 5 | fn from(_val: $source) -> $target { 6 | $target 7 | } 8 | } 9 | }; 10 | } 11 | 12 | #[macro_export] 13 | macro_rules! impl_try_from { 14 | ($source:ident to $target:ident err $err:ident) => { 15 | impl TryFrom<$source> for $target { 16 | type Error = $err; 17 | 18 | fn try_from(_val: $source) -> Result { 19 | Ok($target) 20 | } 21 | } 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /tests/try_from.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use transitive::Transitive; 4 | 5 | mod try_from_simple { 6 | use std::{convert::Infallible, marker::PhantomData}; 7 | 8 | use super::*; 9 | 10 | #[derive(Transitive)] 11 | #[transitive(try_from(D, C, B))] // impl TryFrom for A 12 | struct A; 13 | 14 | #[derive(Transitive)] 15 | #[transitive(try_from(D, C))] // impl TryFrom for B 16 | struct B; 17 | struct C; 18 | struct D; 19 | 20 | struct ErrDC; 21 | struct ErrCB; 22 | 23 | #[derive(Transitive)] 24 | #[transitive(from(ErrDC, ErrCB))] // impl From for ErrBA 25 | struct ErrBA; 26 | 27 | impl From for ErrCB { 28 | fn from(_value: ErrDC) -> Self { 29 | Self 30 | } 31 | } 32 | 33 | impl From for ErrBA { 34 | fn from(_value: ErrCB) -> Self { 35 | Self 36 | } 37 | } 38 | 39 | impl_try_from!(B to A err ErrBA); 40 | impl_try_from!(C to B err ErrCB); 41 | impl_try_from!(D to C err ErrDC); 42 | 43 | #[derive(Transitive)] 44 | #[transitive(try_from(D, C, B, A))] // impl TryFrom for Z 45 | struct Z(PhantomData); 46 | 47 | impl TryFrom for Z { 48 | type Error = ErrBA; 49 | 50 | fn try_from(_value: A) -> Result { 51 | Ok(Self(PhantomData)) 52 | } 53 | } 54 | 55 | #[derive(Transitive)] 56 | #[transitive(try_from(D, C, B, A))] // impl TryFrom for Y<'a> 57 | struct Y<'a>(PhantomData<&'a ()>); 58 | 59 | impl TryFrom for Y<'_> { 60 | type Error = ErrBA; 61 | 62 | fn try_from(_value: A) -> Result { 63 | Ok(Self(PhantomData)) 64 | } 65 | } 66 | 67 | #[derive(Transitive)] 68 | #[transitive(try_from(D, C, B, A))] // impl TryFrom for W 69 | struct W; 70 | 71 | impl TryFrom for W { 72 | type Error = ErrBA; 73 | 74 | fn try_from(_value: A) -> Result { 75 | Ok(Self) 76 | } 77 | } 78 | 79 | #[derive(Transitive)] 80 | #[transitive(try_from(D, C, B, A))] // impl TryFrom for Q<'a, 'b, N, T, U> 81 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b>(PhantomData<(&'a T, &'b U)>); 82 | 83 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom for Q<'a, 'b, N, T, U> { 84 | type Error = ErrBA; 85 | 86 | fn try_from(_value: A) -> Result { 87 | Ok(Self(PhantomData)) 88 | } 89 | } 90 | 91 | #[derive(Transitive)] 92 | #[transitive(try_from(Q<'static, 'static, 2, () ,()>, A))] // impl TryFrom> for G 93 | struct G; 94 | 95 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom> for A { 96 | type Error = Infallible; 97 | 98 | fn try_from(_: Q<'a, 'b, N, T, U>) -> Result { 99 | Ok(A) 100 | } 101 | } 102 | 103 | impl TryFrom for G { 104 | type Error = Infallible; 105 | 106 | fn try_from(_: A) -> Result { 107 | Ok(G) 108 | } 109 | } 110 | 111 | #[derive(Transitive)] 112 | #[transitive(try_from(Q<'a, 'static, 2, T ,()>, A))] // impl TryFrom> for P<'a, T> 113 | struct P<'a, T: Send + Sync>(PhantomData &'a T>); 114 | 115 | impl TryFrom for P<'_, T> { 116 | type Error = Infallible; 117 | 118 | fn try_from(_: A) -> Result { 119 | Ok(P(PhantomData)) 120 | } 121 | } 122 | 123 | #[test] 124 | pub fn test_try_from() { 125 | let _ = A::try_from(D); 126 | let _ = B::try_from(D); 127 | let _ = Z::<()>::try_from(D); 128 | let _ = Y::try_from(D); 129 | let _ = W::<2>::try_from(D); 130 | let _ = Q::<2, (), ()>::try_from(D); 131 | let _ = G::try_from(Q::<'static, 'static, 2, (), ()>(PhantomData)); 132 | let _ = P::try_from(Q::<'static, 'static, 2, (), ()>(PhantomData)); 133 | } 134 | } 135 | 136 | mod try_from_custom_err { 137 | use std::marker::PhantomData; 138 | 139 | use super::*; 140 | 141 | #[derive(Transitive)] 142 | #[transitive(try_from(D, C, B, error = ConvErr))] // impl TryFrom for A 143 | struct A; 144 | struct B; 145 | struct C; 146 | struct D; 147 | 148 | struct ConvErr; 149 | 150 | struct ErrDC; 151 | struct ErrCB; 152 | struct ErrBA; 153 | 154 | impl From for ConvErr { 155 | fn from(_value: ErrDC) -> Self { 156 | Self 157 | } 158 | } 159 | 160 | impl From for ConvErr { 161 | fn from(_value: ErrCB) -> Self { 162 | Self 163 | } 164 | } 165 | 166 | impl From for ConvErr { 167 | fn from(_value: ErrBA) -> Self { 168 | Self 169 | } 170 | } 171 | 172 | impl_try_from!(B to A err ErrBA); 173 | impl_try_from!(C to B err ErrCB); 174 | impl_try_from!(D to C err ErrDC); 175 | 176 | #[derive(Transitive)] 177 | #[transitive(try_from(D, C, B, A, error = ConvErr))] // impl TryFrom for Q<'a, 'b, N, T, U> 178 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>( 179 | PhantomData<(&'a T, &'b U)>, 180 | ); 181 | 182 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom for Q<'a, 'b, N, T, U> { 183 | type Error = ConvErr; 184 | 185 | fn try_from(_value: A) -> Result { 186 | Ok(Self(PhantomData)) 187 | } 188 | } 189 | 190 | #[derive(Transitive)] 191 | #[transitive(try_from(Q<'static, 'static, 2, () ,()>, A, error = ConvErr))] // impl TryFrom> for G 192 | struct G; 193 | 194 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom> for A { 195 | type Error = ConvErr; 196 | 197 | fn try_from(_: Q<'a, 'b, N, T, U>) -> Result { 198 | Ok(A) 199 | } 200 | } 201 | 202 | impl TryFrom for G { 203 | type Error = ConvErr; 204 | 205 | fn try_from(_: A) -> Result { 206 | Ok(G) 207 | } 208 | } 209 | 210 | #[derive(Transitive)] 211 | #[transitive(try_from(Q<'a, 'static, 2, T ,()>, A))] // impl TryFrom> for P<'a, T> 212 | struct P<'a, T: Send + Sync>(PhantomData &'a T>); 213 | 214 | impl TryFrom for P<'_, T> { 215 | type Error = ConvErr; 216 | 217 | fn try_from(_: A) -> Result { 218 | Ok(P(PhantomData)) 219 | } 220 | } 221 | 222 | #[test] 223 | pub fn test_try_from_custom_err() { 224 | let _ = A::try_from(D); 225 | let _ = Q::<2, (), ()>::try_from(D); 226 | let _ = G::try_from(Q::<'static, 'static, 2, (), ()>(PhantomData)); 227 | let _ = P::try_from(Q::<'static, 'static, 2, (), ()>(PhantomData)); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /tests/try_into.rs: -------------------------------------------------------------------------------- 1 | mod macros; 2 | 3 | use transitive::Transitive; 4 | 5 | mod try_into_simple { 6 | use std::{convert::Infallible, marker::PhantomData}; 7 | 8 | use super::*; 9 | 10 | #[derive(Transitive)] 11 | #[transitive(try_into(B, C, D))] // impl TryFrom for D 12 | struct A; 13 | 14 | #[derive(Transitive)] 15 | #[transitive(try_into(C, D))] // impl TryFrom for D 16 | struct B; 17 | struct C; 18 | struct D; 19 | 20 | struct ErrAB; 21 | struct ErrBC; 22 | #[derive(Transitive)] 23 | #[transitive(from(ErrAB, ErrBC))] // impl From for ErrCD 24 | struct ErrCD; 25 | 26 | impl From for ErrBC { 27 | fn from(_value: ErrAB) -> Self { 28 | Self 29 | } 30 | } 31 | 32 | impl From for ErrCD { 33 | fn from(_value: ErrBC) -> Self { 34 | Self 35 | } 36 | } 37 | 38 | impl_try_from!(A to B err ErrAB); 39 | impl_try_from!(B to C err ErrBC); 40 | impl_try_from!(C to D err ErrCD); 41 | 42 | #[derive(Transitive)] 43 | #[transitive(try_into(A, B, C, D))] // impl TryFrom> for D 44 | struct Z(PhantomData); 45 | 46 | impl TryFrom> for A { 47 | type Error = ErrAB; 48 | 49 | fn try_from(_value: Z) -> Result { 50 | Ok(Self) 51 | } 52 | } 53 | 54 | #[derive(Transitive)] 55 | #[transitive(try_into(A, B, C, D))] // impl TryFrom> for D 56 | struct Y<'a>(PhantomData<&'a ()>); 57 | 58 | impl<'a> TryFrom> for A { 59 | type Error = ErrAB; 60 | 61 | fn try_from(_value: Y<'a>) -> Result { 62 | Ok(Self) 63 | } 64 | } 65 | 66 | #[derive(Transitive)] 67 | #[transitive(try_into(A, B, C, D))] // impl TryFrom> for D 68 | struct W; 69 | 70 | impl TryFrom> for A { 71 | type Error = ErrAB; 72 | 73 | fn try_from(_value: W) -> Result { 74 | Ok(Self) 75 | } 76 | } 77 | 78 | #[derive(Transitive)] 79 | #[transitive(try_into(A, B, C, D))] // impl TryFrom> for D 80 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b = &'b str>( 81 | PhantomData<(&'a T, &'b U)>, 82 | ); 83 | 84 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom> for A { 85 | type Error = ErrAB; 86 | 87 | fn try_from(_value: Q<'a, 'b, N, T, U>) -> Result { 88 | Ok(Self) 89 | } 90 | } 91 | 92 | #[derive(Transitive)] 93 | #[transitive(try_into(A, Q<'static, 'static, 2, () ,()>))] // impl TryFrom for Q<'static, 'static, 2, (), ()> 94 | struct G; 95 | 96 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom for Q<'a, 'b, N, T, U> { 97 | type Error = Infallible; 98 | 99 | fn try_from(_: A) -> Result { 100 | Ok(Q(PhantomData)) 101 | } 102 | } 103 | 104 | impl TryFrom for A { 105 | type Error = Infallible; 106 | 107 | fn try_from(_: G) -> Result { 108 | Ok(A) 109 | } 110 | } 111 | 112 | #[derive(Transitive)] 113 | #[transitive(try_into(A, Q<'a, 'static, 2, T ,()>))] // impl TryFrom> for Q<'a, 'static, 2, T, ()>> 114 | struct P<'a, T: Send + Sync>(PhantomData &'a T>); 115 | 116 | impl<'a, T: Send + Sync> TryFrom> for A { 117 | type Error = Infallible; 118 | 119 | fn try_from(_: P<'a, T>) -> Result { 120 | Ok(A) 121 | } 122 | } 123 | 124 | #[test] 125 | pub fn test_try_into() { 126 | let _ = D::try_from(A); 127 | let _ = D::try_from(B); 128 | let _ = D::try_from(Z(PhantomData::<()>)); 129 | let _ = D::try_from(Y(PhantomData)); 130 | let _ = D::try_from(W::<2>); 131 | let _ = D::try_from(Q::<2, (), ()>(PhantomData)); 132 | let _ = Q::try_from(G); 133 | let _ = Q::try_from(P::<'static, ()>(PhantomData)); 134 | } 135 | } 136 | 137 | mod try_into_custom_err { 138 | use std::marker::PhantomData; 139 | 140 | use super::*; 141 | 142 | #[derive(Transitive)] 143 | #[transitive(try_into(B, C, D, error = ConvErr))] // impl TryFrom for D 144 | struct A; 145 | struct B; 146 | struct C; 147 | struct D; 148 | 149 | struct ConvErr; 150 | struct ErrAB; 151 | struct ErrBC; 152 | struct ErrCD; 153 | 154 | impl From for ConvErr { 155 | fn from(_value: ErrAB) -> Self { 156 | Self 157 | } 158 | } 159 | 160 | impl From for ConvErr { 161 | fn from(_value: ErrBC) -> Self { 162 | Self 163 | } 164 | } 165 | 166 | impl From for ConvErr { 167 | fn from(_value: ErrCD) -> Self { 168 | Self 169 | } 170 | } 171 | 172 | impl_try_from!(A to B err ErrAB); 173 | impl_try_from!(B to C err ErrBC); 174 | impl_try_from!(C to D err ErrCD); 175 | 176 | #[derive(Transitive)] 177 | #[transitive(try_into(A, B, C, D, error = ConvErr))] // impl TryFrom> for D 178 | struct Q<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b, V = String>( 179 | PhantomData<(&'a T, &'b U, V)>, 180 | ); 181 | 182 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b, V> TryFrom> 183 | for A 184 | { 185 | type Error = ConvErr; 186 | 187 | fn try_from(_value: Q<'a, 'b, N, T, U, V>) -> Result { 188 | Ok(Self) 189 | } 190 | } 191 | 192 | #[derive(Transitive)] 193 | #[transitive(try_into(A, Q<'static, 'static, 2, () ,()>))] // impl TryFrom for Q<'static, 'static, 2, (), ()> 194 | struct G; 195 | 196 | impl<'a, 'b: 'a, const N: usize, T: 'a + Send + Sync, U: 'b> TryFrom for Q<'a, 'b, N, T, U> { 197 | type Error = ConvErr; 198 | 199 | fn try_from(_: A) -> Result { 200 | Ok(Q(PhantomData)) 201 | } 202 | } 203 | 204 | impl TryFrom for A { 205 | type Error = ConvErr; 206 | 207 | fn try_from(_: G) -> Result { 208 | Ok(A) 209 | } 210 | } 211 | 212 | #[derive(Transitive)] 213 | #[transitive(try_into(A, Q<'a, 'static, 2, T ,()>))] // impl TryFrom> for Q<'a, 'static, 2, T, ()>> 214 | struct P<'a, T: Send + Sync>(PhantomData &'a T>); 215 | 216 | impl<'a, T: Send + Sync> TryFrom> for A { 217 | type Error = ConvErr; 218 | 219 | fn try_from(_: P<'a, T>) -> Result { 220 | Ok(A) 221 | } 222 | } 223 | 224 | #[test] 225 | pub fn test_try_into_custom_err() { 226 | let _ = D::try_from(A); 227 | let _ = D::try_from(Q::<2, (), ()>(PhantomData)); 228 | let _ = Q::try_from(G); 229 | let _ = Q::try_from(P::<'static, ()>(PhantomData)); 230 | } 231 | } 232 | --------------------------------------------------------------------------------