├── .github ├── latest.txt └── workflows │ ├── master.yml │ └── rust_version.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASES.md ├── notes.md ├── src ├── args.rs ├── lib.rs ├── lib_macros.rs ├── subst.rs ├── tests.rs └── utils.rs └── tests ├── integration.rs ├── test_ui.rs └── ui ├── trait_gen.rs ├── trait_gen.stderr ├── trait_gen_if.rs └── trait_gen_if.stderr /.github/latest.txt: -------------------------------------------------------------------------------- 1 | rustc 1.87.0 (17067e9ac 2025-05-09) 2 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | paths-ignore: 7 | - .github/workflows/** 8 | pull_request: 9 | branches: [ "master" ] 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: | 23 | rustc -V 24 | cargo test --verbose 25 | cargo test --verbose --all-features 26 | cargo test -r --verbose 27 | -------------------------------------------------------------------------------- /.github/workflows/rust_version.yml: -------------------------------------------------------------------------------- 1 | name: Rust-version 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: '0 16 * * 6' 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | get-version: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | token: ${{ secrets.TOKEN_VERSION }} 18 | - name: Fetch Rust version 19 | run: | 20 | rustc -V 21 | rustc -V > .github/latest.txt 22 | - name: Check for modified files 23 | id: git-check 24 | run: echo modified=$([ -z "`git status --porcelain`" ] && echo "false" || echo "true") >> $GITHUB_OUTPUT 25 | - name: Commit latest release version 26 | if: steps.git-check.outputs.modified == 'true' 27 | run: | 28 | git config --global user.name 'Redglyph' 29 | git config --global user.email 'redglyph@gmail.com' 30 | git commit -am "New Rust version" 31 | git push 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /target 3 | **/*.rs.bk 4 | *.iml 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "equivalent" 7 | version = "1.0.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" 10 | 11 | [[package]] 12 | name = "glob" 13 | version = "0.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 16 | 17 | [[package]] 18 | name = "hashbrown" 19 | version = "0.15.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" 22 | 23 | [[package]] 24 | name = "indexmap" 25 | version = "2.9.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" 28 | dependencies = [ 29 | "equivalent", 30 | "hashbrown", 31 | ] 32 | 33 | [[package]] 34 | name = "itoa" 35 | version = "1.0.15" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" 38 | 39 | [[package]] 40 | name = "memchr" 41 | version = "2.7.4" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 44 | 45 | [[package]] 46 | name = "proc-macro-error-attr2" 47 | version = "2.0.0" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 50 | dependencies = [ 51 | "proc-macro2", 52 | "quote", 53 | ] 54 | 55 | [[package]] 56 | name = "proc-macro-error2" 57 | version = "2.0.1" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 60 | dependencies = [ 61 | "proc-macro-error-attr2", 62 | "proc-macro2", 63 | "quote", 64 | "syn", 65 | ] 66 | 67 | [[package]] 68 | name = "proc-macro2" 69 | version = "1.0.95" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 72 | dependencies = [ 73 | "unicode-ident", 74 | ] 75 | 76 | [[package]] 77 | name = "quote" 78 | version = "1.0.40" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 81 | dependencies = [ 82 | "proc-macro2", 83 | ] 84 | 85 | [[package]] 86 | name = "ryu" 87 | version = "1.0.20" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" 90 | 91 | [[package]] 92 | name = "serde" 93 | version = "1.0.219" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" 96 | dependencies = [ 97 | "serde_derive", 98 | ] 99 | 100 | [[package]] 101 | name = "serde_derive" 102 | version = "1.0.219" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" 105 | dependencies = [ 106 | "proc-macro2", 107 | "quote", 108 | "syn", 109 | ] 110 | 111 | [[package]] 112 | name = "serde_json" 113 | version = "1.0.140" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" 116 | dependencies = [ 117 | "itoa", 118 | "memchr", 119 | "ryu", 120 | "serde", 121 | ] 122 | 123 | [[package]] 124 | name = "serde_spanned" 125 | version = "0.6.8" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 128 | dependencies = [ 129 | "serde", 130 | ] 131 | 132 | [[package]] 133 | name = "syn" 134 | version = "2.0.101" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" 137 | dependencies = [ 138 | "proc-macro2", 139 | "quote", 140 | "unicode-ident", 141 | ] 142 | 143 | [[package]] 144 | name = "target-triple" 145 | version = "0.1.4" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" 148 | 149 | [[package]] 150 | name = "termcolor" 151 | version = "1.4.1" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 154 | dependencies = [ 155 | "winapi-util", 156 | ] 157 | 158 | [[package]] 159 | name = "toml" 160 | version = "0.8.22" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" 163 | dependencies = [ 164 | "serde", 165 | "serde_spanned", 166 | "toml_datetime", 167 | "toml_edit", 168 | ] 169 | 170 | [[package]] 171 | name = "toml_datetime" 172 | version = "0.6.9" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" 175 | dependencies = [ 176 | "serde", 177 | ] 178 | 179 | [[package]] 180 | name = "toml_edit" 181 | version = "0.22.26" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" 184 | dependencies = [ 185 | "indexmap", 186 | "serde", 187 | "serde_spanned", 188 | "toml_datetime", 189 | "toml_write", 190 | "winnow", 191 | ] 192 | 193 | [[package]] 194 | name = "toml_write" 195 | version = "0.1.1" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" 198 | 199 | [[package]] 200 | name = "trait-gen" 201 | version = "2.0.4" 202 | dependencies = [ 203 | "proc-macro-error2", 204 | "proc-macro2", 205 | "quote", 206 | "syn", 207 | "trybuild", 208 | ] 209 | 210 | [[package]] 211 | name = "trybuild" 212 | version = "1.0.104" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "6ae08be68c056db96f0e6c6dd820727cca756ced9e1f4cc7fdd20e2a55e23898" 215 | dependencies = [ 216 | "glob", 217 | "serde", 218 | "serde_derive", 219 | "serde_json", 220 | "target-triple", 221 | "termcolor", 222 | "toml", 223 | ] 224 | 225 | [[package]] 226 | name = "unicode-ident" 227 | version = "1.0.18" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 230 | 231 | [[package]] 232 | name = "winapi-util" 233 | version = "0.1.9" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 236 | dependencies = [ 237 | "windows-sys", 238 | ] 239 | 240 | [[package]] 241 | name = "windows-sys" 242 | version = "0.59.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 245 | dependencies = [ 246 | "windows-targets", 247 | ] 248 | 249 | [[package]] 250 | name = "windows-targets" 251 | version = "0.52.6" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 254 | dependencies = [ 255 | "windows_aarch64_gnullvm", 256 | "windows_aarch64_msvc", 257 | "windows_i686_gnu", 258 | "windows_i686_gnullvm", 259 | "windows_i686_msvc", 260 | "windows_x86_64_gnu", 261 | "windows_x86_64_gnullvm", 262 | "windows_x86_64_msvc", 263 | ] 264 | 265 | [[package]] 266 | name = "windows_aarch64_gnullvm" 267 | version = "0.52.6" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 270 | 271 | [[package]] 272 | name = "windows_aarch64_msvc" 273 | version = "0.52.6" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 276 | 277 | [[package]] 278 | name = "windows_i686_gnu" 279 | version = "0.52.6" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 282 | 283 | [[package]] 284 | name = "windows_i686_gnullvm" 285 | version = "0.52.6" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 288 | 289 | [[package]] 290 | name = "windows_i686_msvc" 291 | version = "0.52.6" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 294 | 295 | [[package]] 296 | name = "windows_x86_64_gnu" 297 | version = "0.52.6" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 300 | 301 | [[package]] 302 | name = "windows_x86_64_gnullvm" 303 | version = "0.52.6" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 306 | 307 | [[package]] 308 | name = "windows_x86_64_msvc" 309 | version = "0.52.6" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 312 | 313 | [[package]] 314 | name = "winnow" 315 | version = "0.7.10" 316 | source = "registry+https://github.com/rust-lang/crates.io-index" 317 | checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" 318 | dependencies = [ 319 | "memchr", 320 | ] 321 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "trait-gen" 3 | description = "Trait implementation generator macro" 4 | version = "2.0.4" 5 | edition = "2021" 6 | rust-version = "1.61.0" 7 | authors = ["Redglyph"] 8 | categories = ["rust-patterns"] 9 | keywords = ["proc-macro", "macro", "trait", "generator"] 10 | documentation = "https://docs.rs/trait-gen" 11 | homepage = "https://github.com/blueglyph/trait_gen" 12 | license = "MIT OR Apache-2.0" 13 | repository = "https://github.com/blueglyph/trait_gen" 14 | readme = "README.md" 15 | 16 | [lib] 17 | proc-macro = true 18 | 19 | [features] 20 | default = [] 21 | no_type_gen = [] 22 | 23 | [dependencies] 24 | quote = "1.0.40" 25 | proc-macro2 = { version = "1.0.95", features = ["span-locations"] } 26 | syn = { version = "2.0.101", features = ["full", "visit-mut", "extra-traits"] } 27 | proc-macro-error2 = "2.0.1" 28 | 29 | [dev-dependencies] 30 | trybuild = "1.0.104" 31 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2023 Redglyph 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crate](https://img.shields.io/crates/v/trait_gen.svg)](https://crates.io/crates/trait-gen) 2 | [![documentation](https://docs.rs/trait-gen/badge.svg)](https://docs.rs/trait-gen) 3 | [![build status](https://github.com/blueglyph/trait_gen/actions/workflows/master.yml/badge.svg)](https://github.com/blueglyph/trait_gen/actions) 4 | [![crate](https://img.shields.io/crates/l/trait_gen.svg)](https://github.com/blueglyph/trait_gen/blob/master/LICENSE-MIT) 5 | 6 |
7 | 8 | 9 | * [The 'trait-gen' Crate](#the-trait-gen-crate) 10 | * [Compositions](#compositions) 11 | * [Tuples, Permutations, and Conditional Generation](#tuples-permutations-and-conditional-generation) 12 | * [Other features](#other-features) 13 | * [Motivation](#motivation) 14 | * [Compatibility](#compatibility) 15 | * [Releases](#releases) 16 | * [Licence](#licence) 17 | 18 | 19 |
20 | 21 | # The 'trait-gen' Crate 22 | 23 | This crate provides attribute macros that generate the attached implementation for all the 24 | types given in argument. It was first intended for trait implementations, hence the crate name, 25 | but it can also be used for any generic implementation. 26 | 27 | Here is a simple example: 28 | 29 | ```rust 30 | use trait_gen::trait_gen; 31 | 32 | trait MyLog { fn my_log2(self) -> u32; } 33 | 34 | #[trait_gen(T -> u8, u16, u32, u64, u128)] 35 | impl MyLog for T { 36 | fn my_log2(self) -> u32 { 37 | T::BITS - 1 - self.leading_zeros() 38 | } 39 | } 40 | ``` 41 | 42 | ## Compositions 43 | `trait_gen` also replaces the content of inner attributes, so it's possible to chain them and extend the above example to references and smart pointers for all the `T` types: 44 | 45 | ```rust 46 | #[trait_gen(T -> u8, u16, u32, u64, u128)] 47 | #[trait_gen(U -> &T, &mut T, Box)] 48 | impl MyLog for U { 49 | /// Logarithm base 2 for `${U}` 50 | fn my_log2(self) -> u32 { 51 | MyLog::my_log2(*self) 52 | } 53 | } 54 | ``` 55 | 56 | ## Tuples, Permutations, and Conditional Generation 57 | 58 | A more concise format can be used when several arguments share the type lists (in other words, when we need _permutations with repetitions_, or _tuples_): 59 | 60 | ```rust,ignore 61 | #[trait_gen(T, U -> u8, u16, u32)] 62 | ``` 63 | 64 | In the following example, we also show the conditional attribute `trait_gen_if`, which 65 | offers more flexibility in the implementations. The condition has the general format 66 | ` in `, or its negation, `! in `. The code is respectively 67 | included or skipped when the argument is identical to one of the types. 68 | 69 | ```rust 70 | use trait_gen::{trait_gen, trait_gen_if}; 71 | 72 | #[derive(Clone, PartialEq, Debug)] 73 | struct Wrapper(T); 74 | 75 | #[trait_gen(T, U -> u8, u16, u32)] 76 | // The types T and U must be different to avoid the compilation error 77 | // "conflicting implementation in crate `core`: impl From for T" 78 | #[trait_gen_if(!T in U)] 79 | impl From> for Wrapper { 80 | /// converts Wrapper<${U}> to Wrapper<${T}> 81 | fn from(value: Wrapper) -> Self { 82 | Wrapper(T::try_from(value.0) 83 | .expect(&format!("overflow when converting {} to ${T}", value.0))) 84 | } 85 | } 86 | ``` 87 | 88 | That will give us all the conversions from/to `u8`, `u16`, and `u32`, except from the same type since they're already covered by a blanket implementation in the standard library. `trait_gen_if` is also very useful for selecting constants or removing methods depending on the implementated type. 89 | 90 | Note: _Thanks to **Daniel Vigovszky** for giving me the idea of conditional generation! He first implemented it, although quite differently, in a fork called [conditional_trait_gen](https://github.com/vigoo/conditional_trait_gen). I had pondered about some use-cases that would require such a feature in an old post but never got around to implementing it until version 1.1.0._ 91 | 92 | The effect above can be achieved with the following, shorter format, which generates all the combinations of types for `T` and `U` such that `T != U` (_2-permutations_): 93 | 94 | ```rust 95 | #[trait_gen(T != U -> u8, u16, u32)] 96 | impl From> for Wrapper { /* ... */ } 97 | ``` 98 | 99 | ## Other features 100 | 101 | Please read the [crate documentation](https://docs.rs/trait-gen) for more details. 102 | 103 | # Motivation 104 | 105 | There are other ways to generate multiple implementations: 106 | - copy them manually, which is tedious, error-prone, and annoying to maintain 107 | - use a declarative macro 108 | - use a blanket implementation 109 | 110 | Using a **declarative macro** would give something like this: 111 | 112 | ```rust 113 | macro_rules! impl_my_log { 114 | ($($t:ty)*) => ( 115 | $(impl MyLog for $t { 116 | fn my_log2(self) -> u32 { 117 | $t::BITS - 1 - self.leading_zeros() 118 | } 119 | })* 120 | ) 121 | } 122 | 123 | impl_my_log! { u8 u16 u32 u64 u128 } 124 | ``` 125 | 126 | It does the job, but it's harder to read than native code, and IDEs can't provide contextual help or apply refactoring in the macro code very often. It's also quite annoying and unhelpful to get the following line of code when we're looking for the definition of a method when it has been generated by a declarative macro: 127 | 128 | ```rust 129 | impl_my_log! { u8 u16 u32 u64 u128 } 130 | ``` 131 | 132 | Using a **blanket implementation** is very powerful but has other drawbacks: 133 | - It forbids any other implementation except for types of the same crate that are not already under the blanket implementation, so it only works when the implementation can be written for all bound types, current and future. 134 | - Finding a trait that corresponds to what we need to write is not always possible. The `num` crate provides a lot of help for primitives, for instance, but not everything is covered. 135 | - Doing the implementation for a whole range of types isn't always desirable. 136 | - Even when the operations and constants are covered by traits, it quickly requires a long list of trait bounds. 137 | 138 | I felt that, by default of a way to implement a generic code for a list of types in the language itself, a procedural macro was the next best thing. 139 | 140 | # Compatibility 141 | 142 | The `trait-gen` crate is tested for Rust versions **1.61.0** and stable on Windows 64-bit and Linux 64/32-bit platforms. 143 | 144 | # Releases 145 | 146 | [RELEASES.md](RELEASES.md) keeps a log of all the releases (most are on the [GitHub release page](https://github.com/blueglyph/trait_gen/releases), too). 147 | 148 | # Licence 149 | 150 | This code is licenced under either [MIT License](https://choosealicense.com/licenses/mit/) or [Apache License 2.0](https://choosealicense.com/licenses/apache-2.0/), at your option. 151 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # 2.0.4 (2025-05-11) 2 | 3 | - improve some error messages 4 | 5 | # 2.0.3 (2025-05-10) 6 | 7 | - change a dependency from `proc-macro-error` to `proc-macro-error2`, in order to avoid compiling `syn` v1. 8 | 9 | # 2.0.2 (2025-05-10) 10 | 11 | - refactor code and split files 12 | 13 | # 2.0.1 (2025-05-07) 14 | 15 | - fix a few lint issues 16 | 17 | # 2.0.0 (2025-05-07) 18 | 19 | - add several permutation formats for typical use-cases: 20 | - `#[trait_gen(T, U -> u8, u16, u32)]` is a handy shortcut for 21 | ``` 22 | #[trait_gen(T -> u8, u16, u32)] 23 | #[trait_gen(U -> u8, u16, u32)] 24 | ``` 25 | - `#[trait_gen(T != U -> u8, u16, u32)]` does the same but only for T != U, so excluding (u8, u8), (u16, u16), and (u32, u32). 26 | ```rust 27 | struct Wrapper(T); 28 | 29 | // The types T and U must be different to avoid the compilation error 30 | // "conflicting implementation in crate `core`: impl From for T" 31 | #[trait_gen(T != U -> u8, u16, u32)] 32 | impl From> for Wrapper { 33 | /// converts ${U} to ${T} 34 | fn from(value: Wrapper) -> Self { 35 | Wrapper(T::try_from(value.0) 36 | .expect(&format!("overflow when converting {} to ${T}", value.0))) 37 | } 38 | } 39 | ``` 40 | 41 | - `#[trait_gen(T < U -> u8, u16, u32)]` generates the code for index(T) < index(U) in the given list, so (T, U) = (u8, u16), (u8, u32), (u16, u32). It's the _position_ in the list that counts; e.g. the types are not sorted (on what criterion would they be sorted anyway?) or checked in any way. 42 | ```rust 43 | // `From` is only defined for integers with fewer bits (and we exclude a conversion to 44 | // the same type for the same reason as above) 45 | #[trait_gen(T < U -> u8, u16, u32)] 46 | impl From> for Wrapper { 47 | /// converts Wrapper<${T}> to Wrapper<${U}> 48 | fn from(value: Wrapper) -> Self { 49 | Wrapper(U::from(value.0)) 50 | } 51 | } 52 | ``` 53 | 54 | - `#[trait_gen(T <= U -> u8, u16, u32)]` generates the code for (T, U) = (u8, u8), (u8, u16), (u8, u32), (u16, u16), (u16, u32), (u32, u32) 55 | ```rust 56 | // We only want to add integers with fewer bits or of the same type: 57 | #[trait_gen(T <= U -> u8, u16, u32)] 58 | impl Add> for Wrapper { 59 | type Output = Wrapper; 60 | 61 | fn add(self, rhs: Wrapper) -> Self::Output { 62 | Wrapper::(self.0 + ::from(rhs.0)) 63 | } 64 | } 65 | ``` 66 | - remove the `in` format, which is now strictly reserved to conditionals 67 | - remove the legacy format 68 | - the generic argument must now have the turbofish format if `<..>` is required. Use `#[trait_gen(T:: -> ...)` and not `#[trait_gen(T -> ...)`. 69 | 70 | # 1.2.1 (2025-05-06) 71 | 72 | - fix a bug with the attribute name when the "type_gen" feature is disabled. 73 | 74 | # 1.2.0 (2025-05-02) 75 | 76 | - add negation in 'trait_gen_if' (the `!` must be in first position after the opening parenthesis): 77 | ```rust 78 | use trait_gen::{trait_gen, trait_gen_if} 79 | 80 | trait TypeEq { 81 | fn same_type(&self, other: &U) -> bool; 82 | } 83 | 84 | #[trait_gen(T -> u8, u16, u32)] 85 | #[trait_gen(U -> u8, u16, u32)] 86 | impl TypeEq for T { 87 | #[trait_gen_if(T in U)] 88 | fn same_type(&self, _other: &U) -> bool { 89 | true 90 | } 91 | #[trait_gen_if(!T in U)] 92 | fn same_type(&self, _other: &U) -> bool { 93 | false 94 | } 95 | } 96 | ``` 97 | Note: Attaching an attribute to an expression is still experimental, so we can't simplify the example above, unfortunately. 98 | - `trait_gen_if` and `type_gen_if` must now be declared. I didn't bump the version to 2.x, even if it's technically not back-compatible because of that; the change is minor and so is the current use of this macro. What can I say; I'm flawed. 99 | 100 | # 1.1.2 (2025-04-28) 101 | 102 | - improve some error messages 103 | 104 | # 1.1.1 (2025-04-27) 105 | 106 | - fix and simplify doc comment processing 107 | - improve some error messages 108 | - update the minimum stable Rust version to 1.61.0 to comply with syn v2.0.100 109 | - update documentation 110 | 111 | # 1.1.0 (2025-04-26) 112 | 113 | - add 'trait_gen_if' conditional code inclusion 114 | - add 'type_gen' and 'type_gen_if' synonyms, since the attribute isn't limited to trait implementations 115 | - update documentation 116 | 117 | # 1.0.0 (2025-04-24) 118 | 119 | - update syn lib to 2.0.100 and fix the [breaking changes](https://github.com/dtolnay/syn/releases/tag/2.0.0) (hopefully) 120 | 121 | # 0.3.2 (2023-06-23) 122 | 123 | - update documentation 124 | 125 | # 0.3.1 (2023-06-02) 126 | 127 | - update documentation 128 | 129 | # 0.3.0 (2023-05-19) 130 | 131 | - move format `#[trait_gen(T in [u64, i64, u32, i32])]` into feature 132 | - add 'deprecated' warnings when using this 'in' format 133 | 134 | # 0.2.2 (2023-05-12) 135 | 136 | - add alternative format `#[trait_gen(T in [u64, i64, u32, i32])]` 137 | 138 | # 0.2.1 (2023-04-11) 139 | 140 | - simplify marcro argument processing 141 | 142 | # 0.2.0 (2023-03-21) 143 | 144 | - add general type substitution: 145 | ```rust 146 | #[trait_gen(my::T -> &i32, &mut i32, Box)] 147 | impl MyLog for my::T { 148 | fn my_log2(self) -> u32 { 149 | MyLog::my_log2(*self) 150 | } 151 | } 152 | ``` 153 | - allow substitution in inner `trait_gen` attributes, so that their order doesn't matter: 154 | ```rust 155 | #[trait_gen(U -> u8, u16, u32, u64, u128)] 156 | #[trait_gen(T -> &U, &mut U, Box)] 157 | impl MyLog for T { 158 | fn my_log2(self) -> u32 { 159 | MyLog::my_log2(*self) 160 | } 161 | } 162 | ``` 163 | 164 | # 0.1.7 (2023-03-07) 165 | 166 | - fix bug in multisegment path substitution 167 | 168 | # 0.1.6 (2023-03-06) 169 | 170 | - add multi-segment paths in parameters: 171 | ```rust 172 | #[trait_gen(inner::U -> super::Meter, super::Foot)] 173 | impl Add for inner::U { 174 | type Output = inner::U; 175 | 176 | fn add(self, rhs: Self) -> Self::Output { 177 | inner::U(self.0 + rhs.0) 178 | } 179 | } 180 | ``` 181 | - fix `U::MAX` not replaced with `#[trait_gen(U -> ...)]` and other bugs 182 | 183 | # 0.1.5 (2023-03-04) 184 | 185 | - add simple type arguments substitution, which can be used in cross-product generation: 186 | ```rust 187 | #[trait_gen(T -> Meter, Foot)] 188 | #[trait_gen(U -> f32, f64)] 189 | impl GetLength for T { 190 | fn length(&self) -> U { 191 | self.0 as U 192 | } 193 | } 194 | ``` 195 | - add real type substitution in docs, expression string literals and macros (`${T}`): 196 | ```rust 197 | #[trait_gen(T -> u32, u64)] 198 | impl Lit for T { 199 | /// Produces a string representation for ${T} 200 | fn text(&self) -> String { 201 | call("${T}"); 202 | format!("${T}: {}", self) 203 | } 204 | } 205 | ``` 206 | 207 | # 0.1.4 (2023-03-01) 208 | 209 | - add constructor substitution with the `T ->` form 210 | - all paths starting with the type parameter are replaced, for example `T::default()` has `T` replaced with the `T ->` form (before, the whole path had to match) 211 | 212 | # 0.1.3 (2023-02-25) 213 | 214 | - add improved attribute format `#[trait_gen(T -> u64, i64, u32, i32)]` 215 | - simplify documentation 216 | 217 | # 0.1.2 (2023-02-24) 218 | 219 | - fix documentation URL & few typos 220 | 221 | # 0.1.1 (2023-02-24) 222 | 223 | First version 224 | 225 | - trait_gen::trait_gen proc macro -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | # Change notes 2 | 3 | 4 | * [Change notes](#change-notes) 5 | * [1. Type constructors](#1-type-constructors) 6 | * [The `syn` library](#the-syn-library) 7 | * [Analysis](#analysis) 8 | * [Choice of implementation](#choice-of-implementation) 9 | * [2. From Path to Type](#2-from-path-to-type) 10 | * [The `syn` library](#the-syn-library-1) 11 | * [Cases](#cases) 12 | 13 | 14 | 15 | ## 1. Type constructors 16 | 17 | Improves the awareness of the position within the AST, to tell the difference between 18 | constants and types in ambiguous situations. 19 | 20 | This allows to use types in expressions when it was not possible before. 21 | 22 | ```rust 23 | trait AddMod { 24 | fn add_mod(self, other: Self, m: Self) -> Self; 25 | } 26 | 27 | #[trait_gen(U -> u32, i32)] 28 | impl AddMod for U { 29 | fn add_mod(self, other: U, m: U) -> U { 30 | // type must change, constant name must stay: 31 | const U: U = 0; 32 | // type must stay: 33 | let offset: super::U = super::U(0); 34 | // constant must stay, cast type must change: 35 | (self + other + U + offset.0 as U) % m 36 | } 37 | } 38 | ``` 39 | 40 | ### The `syn` library 41 | 42 | `visit_path_mut` is used in: 43 | 44 | ```text 45 | visit_attribute_mut `#[mystuff(T -> u64, i64)]` 46 | ^^^^^^^ 47 | visit_expr_path_mut `let x = Some(value)` or `const T: f64 = 0.0; let x = T + 2.0;` 48 | ^^^^ ^ 49 | visit_expr_struct_mut `let x = T { size: 0.0 }` 50 | ^ 51 | visit_item_impl_mut `impl Trait for Data { ... }` 52 | ^^^^^ 53 | visit_macro_mut `println!("...")` 54 | ^^^^^^^ 55 | visit_meta_list_mut `#[derive(Copy, Clone)]` 56 | ^^^^^^ 57 | visit_meta_mut `#[inline]` 58 | ^^^^^^ 59 | visit_meta_name_value_mut `#[path = "sys/windows.rs"]` 60 | ^^^^ 61 | visit_pat_path_mut `match c { Color::Red => ` 62 | ^^^^^^^^^^ 63 | visit_pat_struct_mut `match c { Data::Style { .. } => ` 64 | ^^^^^^^^^^^ 65 | visit_pat_tuple_struct_mut `match c { Some(answer) => ` 66 | ^^^^ 67 | visit_trait_bound_mut `impl Trait for MyType where T: Display` 68 | ^^^^ ^^^^^^^ 69 | visit_type_path_mut `let x: outside::T = x as outside::T` 70 | ^^^^^^^^^^^^^ ^^^^^^^^^^^^^ 71 | visit_vis_restricted_mut `pub(crate)` or `pub(in some::module)` 72 | ^^^^^ ^^^^^^^^^^^^ 73 | ``` 74 | 75 | The only problem, except generics, comes from `expr_path`, which is used for any identifier 76 | in an expression. Normally, the only possible collision is with a constant of the same name 77 | (including the path to it). 78 | 79 | It is acceptable to dismiss the generics problem by generating an error in case of 80 | collision. At worst, it avoids confusing code. 81 | 82 | ### Analysis 83 | 84 | The different cases where `Path` is used, in `Expr`: 85 | 86 | pub enum Expr { 87 | // ... 88 | Call(ExprCall), 89 | Cast(ExprCast), 90 | Path(ExprPath), 91 | Struct(ExprStruct), 92 | // ... 93 | Type(ExprType), 94 | // ... 95 | } 96 | 97 | * `ExprCall` must be translated: 98 | 99 | pub struct ExprCall { 100 | pub attrs: Vec, 101 | pub func: Box, // -> Path(ExprPath) for instance in `T::new(0)` 102 | pub paren_token: token::Paren, 103 | pub args: Punctuated, 104 | } 105 | 106 | * `ExprCast` must be translated: 107 | 108 | pub struct ExprCast { 109 | pub attrs: Vec, 110 | pub expr: Box, 111 | pub as_token: Token![as], 112 | pub ty: Box, // -> Path(TypePath) in `as T` 113 | } 114 | 115 | * `ExprStruct` must be translated: 116 | 117 | pub struct ExprStruct #full { 118 | pub attrs: Vec, 119 | pub path: Path, // -> in `T { field: value }` 120 | pub brace_token: token::Brace, 121 | pub fields: Punctuated, 122 | pub dot2_token: Option, 123 | pub rest: Option>, 124 | } 125 | 126 | * `ExprType` must be translated too (type ascription is still experimental): 127 | 128 | pub struct ExprType #full { 129 | pub attrs: Vec, 130 | pub expr: Box, 131 | pub colon_token: Token![:], 132 | pub ty: Box, // -> Path(TypePath) in `data.collect() : T` 133 | } 134 | 135 | * `ExprPath` is not straightforward: 136 | 137 | pub struct ExprPath { 138 | pub attrs: Vec, 139 | pub qself: Option, 140 | pub path: Path, // -> in `T::CONSTANT` 141 | } 142 | 143 | * `let x = T(value)`: 144 | * `ExprCall -> func:ExprPath -> Path("T")` 145 | 146 | Possible conflicts if an enumeration has an item with a 147 | conflicting name, which should be avoidable by adding the type path in front 148 | (it should be there in the first place) 149 | 150 | => best to replace, it could be a constructor of the attribute type parameter 151 | if it's an `enum`. 152 | 153 | * `const T: f64 = 0.0; let x = T + 2.0;`: 154 | * `Expr -> Path(ExprPath) -> Path("T")` 155 | 156 | => cannot replace. 157 | 158 | **In summary**: 159 | * when `ExprPath` 160 | * when parent is `ExprCall`, replace 161 | * other cases, don't replace 162 | * other cases, replace 163 | 164 | The call hierarchy is shown for typical cases below. 165 | 166 | Must be ENABLED in visit_path_mut: 167 | 168 | visit_expr_mut(self, node0 = Expr::Call(node1: ExprCall)) 169 | - enable - 170 | visit_expr_call_mut(self, node1 = ExprCall { func: Expr::Path(ExprPath) ) 171 | visit_expr_mut(self, node2 = Expr::Path(ExprPath)) 172 | - no change - 173 | visit_expr_path_mut(self, node3 = ExprPath { path: Path, .. }) 174 | visit_path_mut(self, path) 175 | 176 | Must be ENABLED in visit_path_mut: 177 | 178 | visit_expr_mut(self, node0 = Expr::Cast(node1: ExprCast)) 179 | - (enable) - 180 | visit_expr_cast_mut(self, node1 = ExprCast { expr: Expr, ty: Type, ..) 181 | visit_expr_mut(self, node2 = expr) 182 | - (either enable or disable, depending on the expression) - 183 | visit_type_mut(self, node3 = ty) with for example ty = Type::Path(TypePath) 184 | visit_type_path_mut(self, node4 = TypePath { path, ... }) 185 | - enable - 186 | visit_path_mut(self, node5 = path) 187 | 188 | Must be DISABLED in visit_path_mut: 189 | 190 | visit_expr_mut(self, node0 = Expr::Binary(node1: ExprBinary)) 191 | - disable - 192 | visit_expr_binary_mut(self, node1 = ExprBinary { left: Expr, right: Expr, ..) with for ex. left = ExprPath 193 | visit_expr_mut(self, left = Expr::Path(ExprPath)) 194 | - no change - 195 | visit_expr_path_mut(self, node3 = ExprPath { path: Path, .. }) 196 | visit_path_mut(self, path) 197 | ... 198 | 199 | ### Choice of implementation 200 | 201 | Solution adopted: 202 | * Adding a substitution authorization stack in the `Types` object. 203 | * Peeking at the top of the stack tells whether we can substitute or not. 204 | * Authorizations are pushed in nodes like `ExprCall` before calling the internal visitors, 205 | then popped after (the return from) the call. 206 | 207 | This is much safer than changing the state of the `Types` object, for instance, in the 208 | different visitors. And it's much shorter than re-implementing the logic of the visitors, 209 | especially since the helpers and other functions or data are private to `syn`. 210 | 211 | ## 2. From Path to Type 212 | 213 | Instead of simple paths in the attribute parameters, we would like references and possibly other type syntaxes. 214 | 215 | ```rust 216 | #[trait_gen(T -> &U, &mut U, Box)] 217 | #[trait_gen(U -> u8, u16, u32, u64, u128)] 218 | impl IntLog for T { 219 | fn log10(self) -> usize { 220 | IntLog::log10(*self) 221 | } 222 | fn log2(self) -> usize { 223 | IntLog::log10(*self) 224 | } 225 | } 226 | ``` 227 | 228 | where 229 | * `&U` and `&mut U` are `Type::Reference(TypeReference)` 230 | * `Box` is `Type::Path(TypePath)` 231 | 232 | ### The `syn` library 233 | 234 | Currently, the substitution is done when visiting a `syn::Path` and checking if the prefixes of its segments match the attribute generic parameter (`T` in `#[trait_gen(T -> repl1, repl2)]`). 235 | 236 | Allowing more general substitutions requires to parse `syn::ty::Type` parameters. Here are a few relevant variants of this type: 237 | 238 | ```rust 239 | pub enum Type { 240 | /// A fixed size array type: `[T; n]`. 241 | Array(TypeArray), 242 | 243 | /// A bare function type: `fn(usize) -> bool`. 244 | BareFn(TypeBareFn), 245 | 246 | /// A type contained within invisible delimiters. (?) 247 | Group(TypeGroup), 248 | 249 | /// A parenthesized type equivalent to the inner type (?). 250 | Paren(TypeParen), 251 | 252 | /// A path like `super::U`, optionally qualified like `::C`. 253 | Path(TypePath), 254 | 255 | /// A raw pointer type: `*const T` or `*mut T`. 256 | Ptr(TypePtr), 257 | 258 | /// A reference type: `&'a T` or `&'a mut T`. 259 | Reference(TypeReference), 260 | 261 | /// A dynamically sized slice type: `[T]`. 262 | Slice(TypeSlice), 263 | 264 | /// A tuple type: `(A, B, C, String)`. 265 | Tuple(TypeTuple), 266 | 267 | // ... 268 | } 269 | ``` 270 | 271 | ### Cases 272 | 273 | Here is a simple example: 274 | 275 | ```rust 276 | impl AddMod for &U 277 | impl AddMod for &mut U 278 | ``` 279 | 280 | are `ItemImpl`, in which `self_ty` is `Type::Reference(TypeReference)` 281 | 282 | ```rust 283 | impl AddMod for Box 284 | ``` 285 | 286 | is an `ItemImpl`, in which `self_ty` is `Type::Path(TypePath)` 287 | 288 | ```rust 289 | pub struct ItemImpl { 290 | pub attrs: Vec, 291 | pub defaultness: Option, 292 | pub unsafety: Option, 293 | pub impl_token: Token![impl], 294 | pub generics: Generics, 295 | pub trait_: Option<(Option, Path, Token![for])>, 296 | pub self_ty: Box, // <== to replace 297 | pub brace_token: token::Brace, 298 | pub items: Vec, 299 | } 300 | ``` 301 | 302 | What are all the cases? There are many of them: 303 | 304 | * ```rust 305 | /// A field of a struct or enum variant. 306 | pub struct Field { 307 | /// A cast expression: `foo as f64`. 308 | pub struct ExprCast { 309 | /// A type ascription expression: `foo: f64`. 310 | pub struct ExprType #full { 311 | /// An individual generic argument to a method, like `T`. 312 | pub enum GenericMethodArgument { 313 | /// A generic type parameter: `T: Into`. 314 | pub struct TypeParam { 315 | /// A const generic parameter: `const LENGTH: usize`. 316 | pub struct ConstParam { 317 | /// A type predicate in a `where` clause: `for<'c> Foo<'c>: Trait<'c>`. 318 | pub struct PredicateType { 319 | /// An equality predicate in a `where` clause (unsupported). 320 | pub struct PredicateEq { 321 | /// A constant item: `const MAX: u16 = 65535`. 322 | pub struct ItemConst { 323 | /// An impl block providing trait or associated items: `impl Trait 324 | pub struct ItemImpl { 325 | /// A static item: `static BIKE: Shed = Shed(42)`. 326 | pub struct ItemStatic { 327 | /// A type alias: `type Result = std::result::Result`. 328 | pub struct ItemType { 329 | /// A foreign static item in an `extern` block: `static ext: u8`. 330 | pub struct ForeignItemStatic { 331 | /// An associated constant within the definition of a trait. 332 | pub struct TraitItemConst { 333 | /// An associated type within the definition of a trait. 334 | pub struct TraitItemType { 335 | /// An associated constant within an impl block. 336 | pub struct ImplItemConst { 337 | /// An associated type within an impl block. 338 | pub struct ImplItemType { 339 | /// A type ascription pattern: `foo: f64`. 340 | pub struct PatType { 341 | /// An individual generic argument, like `'a`, `T`, or `Item = T`. 342 | pub enum GenericArgument { 343 | /// A binding (equality constraint) on an associated type: `Item = u8`. 344 | pub struct Binding { 345 | /// Arguments of a function path segment: the `(A, B) -> C` in `Fn(A,B) -> C`. 346 | pub struct ParenthesizedGenericArguments { 347 | /// The explicit Self type in a qualified path: the `T` in `::fmt`. 348 | pub struct QSelf { 349 | /// A fixed size array type: `[T; n]`. 350 | pub struct TypeArray { 351 | /// A type contained within invisible delimiters. 352 | pub struct TypeGroup { 353 | /// A parenthesized type equivalent to the inner type. 354 | pub struct TypeParen { 355 | /// A raw pointer type: `*const T` or `*mut T`. 356 | pub struct TypePtr { 357 | /// A reference type: `&'a T` or `&'a mut T`. 358 | pub struct TypeReference { 359 | /// A dynamically sized slice type: `[T]`. 360 | pub struct TypeSlice { 361 | /// A tuple type: `(A, B, C, String)`. 362 | pub struct TypeTuple { 363 | /// An argument in a function type: the `usize` in `fn(usize) -> bool`. 364 | pub struct BareFnArg { 365 | /// Return type of a function signature. 366 | pub enum ReturnType { 367 | ``` 368 | 369 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Attribute argument parsing and related objects. Independent of proc_macro. 4 | 5 | use std::collections::HashSet; 6 | use std::fmt::{Debug, Formatter}; 7 | use proc_macro2::{Punct, Spacing}; 8 | use quote::{ToTokens, TokenStreamExt}; 9 | use syn::{bracketed, token, Error, ExprPath, Path, Token, Type}; 10 | use syn::parse::{Parse, ParseStream}; 11 | use syn::punctuated::Punctuated; 12 | use crate::utils::pathname; 13 | 14 | #[derive(Clone)] 15 | /// Variants of attribute left-hand-side arguments. 16 | pub(crate) enum ArgType { 17 | None, 18 | /// Conditional argument. Must be a general `Type` because it's interchangeable with the right-hand side 19 | /// types. When this attribute is processed by `#[trait_gen]`, it's replaced by a type. 20 | /// 21 | /// - `#[trait_gen_if(T in U)` 22 | Cond(Type), 23 | /// List of arguments from which all permutations with repetition in a list are generated 24 | /// (it can have one or more arguments). 25 | /// 26 | /// The types in the list are not verified, so if the same type is present multiple times in the list, 27 | /// instances where the two arguments have the same type will be generated. 28 | /// 29 | /// Example: 30 | /// - `#[trait_gen(T -> u8, u16)]` 31 | /// 32 | /// (T) = (u8), (u16) 33 | /// - `#[trait_gen(T, U -> u8, u16, u32)]` 34 | /// 35 | /// (T, U) = (u8, u8), (u8, u16), (u8, u32), (u16, u8), (u16, u16), (u16, u32) , ... 36 | Tuple(Vec), 37 | /// Pair of arguments from which all 2-permutations in a list are generated. 38 | /// 39 | /// The types in the list are not verified, so if the same type is present multiple times in the list, 40 | /// instances where the two arguments have the same type will be generated. 41 | /// 42 | /// Example: 43 | /// - `#[trait_gen(T != U -> u8, u16, u32)]` 44 | /// 45 | /// (T, U) = (u8, u16), (u8, u32), (u16, u8), (u16, u32), (u32, u8), (u32, u16) 46 | Permutation(Path, Path), 47 | /// Pair of arguments from which all 2-permutations with strict order in a list are generated. 48 | /// In other words, the position of the first argument is lower than the position of the second. 49 | /// 50 | /// A typical use is when you can safely combine integers with fewer bits into an integer with more bits 51 | /// but not the other way around. 52 | /// 53 | /// The types in the list are not verified, so if the same type is present multiple times in the list, 54 | /// instances where the two arguments have the same type will be generated. 55 | /// 56 | /// Example: 57 | /// - `#[trait_gen(T < U -> u8, u16, u32)]` 58 | /// 59 | /// (T, U) = (u8, u16), (u8, u32), (u16, u32) 60 | StrictOrder(Path, Path), 61 | /// Pair of arguments from which all 2-permutations with non-strict order in a list are generated. 62 | /// In other words, the position of the first argument is lower than or equal to the position of the second. 63 | /// 64 | /// A typical use is when you can safely convert an integer with fewer bits to an integer with 65 | /// at least as many bits but not the other way around. 66 | /// 67 | /// The types in the list are not verified, so if the same type is present multiple times in the list, 68 | /// instances where the two arguments have the same type will be generated. 69 | /// 70 | /// Example: 71 | /// - `#[trait_gen(T <= U -> u8, u16, u32)]` 72 | /// 73 | /// (T, U) = (u8, u8), (u8, u16), (u8, u32), (u16, u16), (u16, u32), (u32, u32) 74 | NonStrictOrder(Path, Path), 75 | } 76 | 77 | impl ToTokens for ArgType { 78 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 79 | match self { 80 | ArgType::None => {} 81 | ArgType::Cond(ty) => ty.to_tokens(tokens), 82 | ArgType::Tuple(paths) => tokens.append_separated(paths, Punct::new(',', Spacing::Alone)), 83 | ArgType::Permutation(path1, path2) => { 84 | path1.to_tokens(tokens); 85 | tokens.append(Punct::new('!', Spacing::Joint)); 86 | tokens.append(Punct::new('=', Spacing::Alone)); 87 | path2.to_tokens(tokens); 88 | } 89 | ArgType::StrictOrder(path1, path2) => { 90 | path1.to_tokens(tokens); 91 | tokens.append(Punct::new('!', Spacing::Joint)); 92 | tokens.append(Punct::new('<', Spacing::Alone)); 93 | path2.to_tokens(tokens); 94 | } 95 | ArgType::NonStrictOrder(path1, path2) => { 96 | path1.to_tokens(tokens); 97 | tokens.append(Punct::new('=', Spacing::Joint)); 98 | tokens.append(Punct::new('<', Spacing::Alone)); 99 | path2.to_tokens(tokens); 100 | } 101 | } 102 | } 103 | } 104 | 105 | impl Debug for ArgType { 106 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 107 | match self { 108 | ArgType::None => write!(f, "None"), 109 | ArgType::Cond(c) => write!(f, "Cond({})", pathname(c)), 110 | ArgType::Tuple(a) => write!(f, "Tuple({})", a.iter().map(pathname).collect::>().join(", ")), 111 | ArgType::Permutation(p1, p2) => write!(f, "Permutation({}, {})", pathname(p1), pathname(p2)), 112 | ArgType::StrictOrder(p1, p2) => write!(f, "StrictOrder({}, {})", pathname(p1), pathname(p2)), 113 | ArgType::NonStrictOrder(p1, p2) => write!(f, "NonStrictOrder({}, {})", pathname(p1), pathname(p2)), 114 | } 115 | } 116 | } 117 | 118 | //============================================================================== 119 | // Attribute types 120 | 121 | #[derive(Clone, Debug)] 122 | /// Attribute data used to substitute arguments in inner `trait_gen`/`type_gen` attributes 123 | pub(crate) struct TraitGen { 124 | /// generic arguments 125 | pub args: ArgType, 126 | /// types that replace the generic argument 127 | pub types: Vec, 128 | } 129 | 130 | #[derive(Clone)] 131 | /// Attribute data used in `trait_gen_if`/`type_gen_if` conditionals. We store the generic 132 | /// argument and the types as [String], to make the comparison easier. 133 | pub(crate) struct CondParams { 134 | /// generic argument 135 | pub generic_arg: Type, 136 | /// if the argument matches at least one of those types, the attached code is enabled 137 | pub types: HashSet, 138 | /// negate the condition: the condition becomes true when the argument doesn't match any of the `types` 139 | pub is_negated: bool 140 | } 141 | 142 | //============================================================================== 143 | // Attribute argument parsing 144 | 145 | /// Parses the attribute arguments, and extracts the generic argument and the types that must substitute it. 146 | /// 147 | /// There are two main syntax formats: 148 | /// - `T -> Type1, Type2, Type3` with variations like `T, U -> ...`, `T != U -> ...`, etc. 149 | /// - `T in [Type1, Type2, Type3]` or `T in Type1, Type2, Type3` (when `is_conditional` is true) 150 | /// 151 | /// The `is_conditional` parameter forces the "in" format and allows the negation: `!T in Type1, Type2, Type3`. 152 | /// It also returns a `Type` argument (`SubstType::Type`) instead of a `Path` because the trait-gen attribute 153 | /// will replace it by a type. 154 | /// 155 | /// Returns (args, types, is_negated), where 156 | /// - `args` contains the generic arguments `T`, `U`, ... and the type of permutation (`,`, `!=`, `<`, or `<=`) 157 | /// - `types` is a vector of parsed `Type` items: `Type1, Type2, Type3` 158 | /// - `is_negated` is true if the `!T in` format was found instead of `T in` (when `is_conditional` is true) 159 | pub(crate) fn parse_arguments(input: ParseStream, is_conditional: bool) -> syn::parse::Result<(ArgType, Vec, bool)> { 160 | let is_negated = is_conditional && input.peek(Token![!]) && input.parse::().is_ok(); 161 | let args = if is_conditional { 162 | ArgType::Cond(input.parse::().map_err(|e| 163 | // default message unhelpful (expected one of: `for`, parentheses, `fn`, `unsafe`, ...) 164 | Error::new(e.span(), "expected substitution identifier or type") 165 | )?) 166 | } else { 167 | // Determines the format of the left-hand arguments. 168 | // ExprPath is used instead of Path in order to force a turbofish syntax; this allows the use 169 | // of '<' as a separator between arguments: "U < V", or if a generic argument is really required, 170 | // "U:: < V". Without that, it wouldn't be possible to parse a path followed by a '<' token. 171 | let path1 = input.parse::()?.path; 172 | if input.peek(Token![,]) && input.parse::().is_ok() { 173 | // Tuple: "W, X, Y -> Type1, Type2" 174 | let mut list_args = vec![path1]; 175 | loop { 176 | let p = input.parse::()?.path; 177 | list_args.push(p); 178 | if input.peek(Token![,]) { 179 | _ = input.parse::(); 180 | } else { 181 | break; 182 | } 183 | } 184 | ArgType::Tuple(list_args) 185 | } else if input.peek(Token![!]) && input.parse::().is_ok() { 186 | // Permutation: "W != X -> Type1, Type2" 187 | input.parse::()?; 188 | ArgType::Permutation(path1, input.parse::()?) 189 | } else if input.peek(Token![<]) && input.parse::().is_ok() { 190 | // Permutation with strict or non-strict order: "W < X -> Type1, Type2" or "W <= X -> Type1, Type2" 191 | if input.peek(Token![=]) && input.parse::().is_ok() { 192 | ArgType::NonStrictOrder(path1, input.parse::()?) 193 | } else { 194 | ArgType::StrictOrder(path1, input.parse::()?) 195 | } 196 | } else { 197 | // that something else must be '->', so we return a single "normal" argument 198 | ArgType::Tuple(vec![path1]) 199 | } 200 | }; 201 | 202 | if is_conditional { 203 | input.parse::()?; 204 | } else { 205 | input.parse::]>()?; 206 | } 207 | 208 | // collects the right-hand arguments depending on format 209 | let types: Vec; 210 | let vars = if is_conditional { 211 | // brackets are optional: 212 | if input.peek(token::Bracket) { 213 | let content; 214 | bracketed!(content in input); 215 | let inner_vars: ParseStream = &content; 216 | Punctuated::::parse_terminated(inner_vars)? 217 | } else { 218 | Punctuated::::parse_terminated(input)? 219 | } 220 | } else { 221 | Punctuated::::parse_terminated(input)? 222 | }; 223 | types = vars.into_iter().collect(); 224 | if types.is_empty() { 225 | return Err(Error::new(input.span(), format!("expected type after '{}'", if is_conditional { "in" } else { "->" }))); 226 | } 227 | Ok((args, types, is_negated)) 228 | } 229 | 230 | /// Attribute argument parser used for the procedural macro being processed 231 | impl Parse for TraitGen { 232 | fn parse(input: ParseStream) -> syn::parse::Result { 233 | let (args, types, _) = parse_arguments(input, false)?; 234 | Ok(TraitGen { args, types }) 235 | } 236 | } 237 | 238 | /// Attribute argument parser used for the inner conditional attributes 239 | impl Parse for CondParams { 240 | fn parse(input: ParseStream) -> syn::parse::Result { 241 | let (args, types, is_negated) = parse_arguments(input, true)?; 242 | let generic_arg = if let ArgType::Cond(t) = args { t } else { panic!("can't happen") }; 243 | Ok(CondParams { 244 | generic_arg, 245 | types: types.into_iter().collect(), 246 | is_negated, 247 | }) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Publicly exposed macros. 4 | 5 | //! This crate provides attribute macros that generate the attached implementation for all the 6 | //! types given in argument. It was first intended for trait implementations, hence the crate name, 7 | //! but it can also be used for any generic implementation. 8 | //! 9 | //! ## Usage 10 | //! The attribute is placed before the pseudo-generic code to implement. The _generic arguments_ 11 | //! are given first, followed by a right arrow (`->`) and a list of types that will replace the 12 | //! argument in the generated implementations: 13 | //! 14 | //! ```rust 15 | //! # use trait_gen::trait_gen; 16 | //! # struct Type1; struct Type2; struct Type3; 17 | //! # trait Trait {} 18 | //! #[trait_gen(T -> Type1, Type2, Type3)] 19 | //! impl Trait for T { 20 | //! // ... 21 | //! } 22 | //! ``` 23 | //! 24 | //! The attribute macro successively substitutes the generic argument `T` in the code with 25 | //! the given types (`Type1`, `Type2`, `Type3`) to generate each implementation. 26 | //! 27 | //! All the [type paths](https://doc.rust-lang.org/reference/paths.html#paths-in-types) beginning 28 | //! with `T` in the code have that part replaced. For example, `T::default()` generates 29 | //! `Type1::default()`, `Type2::default()` and so on, but `super::T` is unchanged. Similarly, all 30 | //! the [types](https://doc.rust-lang.org/reference/types.html) including `T` in the code have that 31 | //! part replaced; for example, `&T` or `Box`. 32 | //! 33 | //! The compiler will trigger an error if the resulting code is wrong. For example 34 | //! `#[trait_gen(T -> u64, f64)]` cannot be applied to `let x: T = 0;` because `0` is not a valid 35 | //! floating-point literal. 36 | //! 37 | //! Finally, the actual type of `T` replaces any occurrence of `${T}` in doc comments, macros, and 38 | //! string literals. 39 | //! 40 | //! _Notes:_ 41 | //! - _Using the letter "T" is not mandatory; any type path will do. For example, `g::Type` is fine 42 | //! too. But to make it easy to read and similar to a generic implementation, short upper-case identifiers 43 | //! are preferred._ 44 | //! - _If a `<..>` is required in the generic argument, the 45 | //! [turbofish syntax](https://doc.rust-lang.org/reference/paths.html#r-paths.expr.turbofish) must be used. 46 | //! For example, use `#[trait_gen(T:: -> ...)` and not `#[trait_gen(T -> ...)`._ 47 | //! - _`type_gen` is a synonym attribute that can be used instead of `trait_gen`. This can be disabled with 48 | //! the `no_type_gen` feature, in case it conflicts with another 3rd-party attribute._ 49 | //! - _There is no escape code to avoid the substitution in string literals; if you need `${T}` for another 50 | //! purpose and you don't want it to be replaced, you can use this work-around: 51 | //! `#[doc = concat!("my ${", "T} variable")]`. Or you can choose another generic argument, like `U` or `my::T`._ 52 | //! - _More complex formats with several arguments and conditions are shown in later examples._ 53 | //! 54 | //! Here is a simple example: 55 | //! 56 | //! ```rust 57 | //! # use trait_gen::trait_gen; 58 | //! # trait MyLog { fn my_log2(self) -> u32; } 59 | //! #[trait_gen(T -> u8, u16, u32, u64, u128)] 60 | //! impl MyLog for T { 61 | //! fn my_log2(self) -> u32 { 62 | //! T::BITS - 1 - self.leading_zeros() 63 | //! } 64 | //! } 65 | //! ``` 66 | //! 67 | //! The `trait_gen` attribute generates the following code by replacing `T` with the types given as 68 | //! arguments: 69 | //! 70 | //! ```rust 71 | //! # trait MyLog { fn my_log2(self) -> u32; } 72 | //! impl MyLog for u8 { 73 | //! fn my_log2(self) -> u32 { 74 | //! u8::BITS - 1 - self.leading_zeros() 75 | //! } 76 | //! } 77 | //! 78 | //! impl MyLog for u16 { 79 | //! fn my_log2(self) -> u32 { 80 | //! u16::BITS - 1 - self.leading_zeros() 81 | //! } 82 | //! } 83 | //! 84 | //! // ... and so on for the remaining types 85 | //! ``` 86 | //! 87 | //! ## Compositions 88 | //! `trait_gen` also replaces the content of inner attributes, so it's possible to chain them 89 | //! and extend the above example to references and smart pointers for all the `T` types: 90 | //! 91 | //! ```rust 92 | //! # use trait_gen::trait_gen; 93 | //! # trait MyLog { fn my_log2(self) -> u32; } 94 | //! # #[trait_gen(T -> u8, u16, u32, u64, u128)] 95 | //! # impl MyLog for T { 96 | //! # fn my_log2(self) -> u32 { 97 | //! # T::BITS - 1 - self.leading_zeros() 98 | //! # } 99 | //! # } 100 | //! #[trait_gen(T -> u8, u16, u32, u64, u128)] 101 | //! #[trait_gen(U -> &T, &mut T, Box)] 102 | //! impl MyLog for U { 103 | //! /// Logarithm base 2 for `${U}` 104 | //! fn my_log2(self) -> u32 { 105 | //! MyLog::my_log2(*self) 106 | //! } 107 | //! } 108 | //! ``` 109 | //! 110 | //! ## Tuples and Conditional Generation 111 | //! A more concise format can be used when several arguments share the type lists (in other 112 | //! words, when we need _permutations with repetitions_, or _tuples_): 113 | //! 114 | //! ```rust,ignore 115 | //! #[trait_gen(T, U -> u8, u16, u32)] 116 | //! ``` 117 | //! 118 | //! In the following example, we also show the conditional attribute `trait_gen_if`, which 119 | //! offers more flexibility in the implementations. The condition has the general format 120 | //! ` in `, or its negation, `! in `. The code is respectively 121 | //! included or skipped when the argument is identical to one of the types. 122 | //! 123 | //! ```rust 124 | //! use trait_gen::{trait_gen, trait_gen_if}; 125 | //! 126 | //! #[derive(Clone, PartialEq, Debug)] 127 | //! struct Wrapper(T); 128 | //! 129 | //! #[trait_gen(T, U -> u8, u16, u32)] 130 | //! // The types T and U must be different to avoid the compilation error 131 | //! // "conflicting implementation in crate `core`: impl From for T" 132 | //! #[trait_gen_if(!T in U)] 133 | //! impl From> for Wrapper { 134 | //! /// converts Wrapper<${U}> to Wrapper<${T}> 135 | //! fn from(value: Wrapper) -> Self { 136 | //! Wrapper(T::try_from(value.0) 137 | //! .expect(&format!("overflow when converting {} to ${T}", value.0))) 138 | //! } 139 | //! } 140 | //! ``` 141 | //! 142 | //! That will give us all the conversions from/to `u8`, `u16`, and `u32`, except from the 143 | //! same type since they're already covered by a blanket implementation in the standard library. 144 | //! `trait_gen_if` is also very useful for selecting constants or removing methods depending on the 145 | //! implementated type. 146 | //! 147 | //! _Notes:_ 148 | //! - _The number of generic arguments is not limited in this particular form, though it's arguably 149 | //! hard to find relevant cases where more than two are required._ 150 | //! - _We've seen earlier that `type_gen` was a synonym of `trait_gen`. For the sake of 151 | //! coherency, a `type_gen_if` is provided as a synonym of `trait_gen_if`, too._ 152 | //! 153 | //! ## Other Permutations 154 | //! The implementation above could have been written more concisely with a _2-permutation_, where 155 | //! `T != U`: 156 | //! 157 | //! ```rust 158 | //! # use trait_gen::trait_gen; 159 | //! # 160 | //! # #[derive(Clone, PartialEq, Debug)] 161 | //! # struct Wrapper(T); 162 | //! # 163 | //! #[trait_gen(T != U -> u8, u16, u32)] 164 | //! impl From> for Wrapper { 165 | //! /// converts Wrapper<${U}> to Wrapper<${T}> 166 | //! fn from(value: Wrapper) -> Self { 167 | //! Wrapper(T::try_from(value.0) 168 | //! .expect(&format!("overflow when converting {} to ${T}", value.0))) 169 | //! } 170 | //! } 171 | //! ``` 172 | //! 173 | //! If we want to generate all the conversions from smaller integers to bigger integers, 174 | //! similarly to what is done in the [standard library](https://github.com/rust-lang/rust/blob/1.86.0/library/core/src/convert/num.rs#L514-L526) 175 | //! (with a cascade of declarative macros), we can use a _2-permutation with strict order_, 176 | //! meaning that `index(T) < index(U)`—remember we can't convert to the same type 177 | //! because it conflicts with the blanket implementation in `core`. 178 | //! 179 | //! This will generate the code for `(T, U)` = `(u8, u16)`, `(u8, u32)`, and `(u16, u32)` 180 | //! (picture a triangle): 181 | //! 182 | //! ```rust 183 | //! # use trait_gen::trait_gen; 184 | //! # 185 | //! # #[derive(Clone, PartialEq, Debug)] 186 | //! # struct Wrapper(T); 187 | //! # 188 | //! #[trait_gen(T < U -> u8, u16, u32)] 189 | //! impl From> for Wrapper { 190 | //! /// converts Wrapper<${T}> to Wrapper<${U}> 191 | //! fn from(value: Wrapper) -> Self { 192 | //! Wrapper(U::from(value.0)) 193 | //! } 194 | //! } 195 | //! ``` 196 | //! 197 | //! 198 | //! The _non-strict order_, where `index(T) <= index(U)`, also exists for cases like 199 | //! adding from another integer which has a smaller or equal length. This will generate 200 | //! the code for `(T, U)` = `(u8, u8)`, `(u8, u16)`, `(u8, u32)`, `(u16, u16)`, `(u16, u32)`, 201 | //! and `(u32, u32)`. 202 | //! 203 | //! ```rust 204 | //! # use std::ops::Add; 205 | //! # use trait_gen::trait_gen; 206 | //! # 207 | //! # #[derive(Clone, PartialEq, Debug)] 208 | //! # struct Wrapper(T); 209 | //! # 210 | //! #[trait_gen(T <= U -> u8, u16, u32)] 211 | //! impl Add> for Wrapper { 212 | //! type Output = Wrapper; 213 | //! 214 | //! fn add(self, rhs: Wrapper) -> Self::Output { 215 | //! Wrapper::(self.0 + ::from(rhs.0)) 216 | //! } 217 | //! } 218 | //! ``` 219 | //! 220 | //! _Notes:_ 221 | //! - _`!=`, `<`, and `<=` are limited to two generic arguments._ 222 | //! 223 | //! That covers all the forms of these attributes. For more examples, look at the crate's 224 | //! [integration tests](https://github.com/blueglyph/trait_gen/blob/v2.0.0/tests/integration.rs). 225 | //! 226 | //! ## Limitations 227 | //! 228 | //! * The procedural macro of the `trait_gen` attribute can't handle scopes, so it doesn't support any 229 | //! type declaration with the same literal as the generic argument. For instance, this code fails to compile 230 | //! because of the generic function: 231 | //! 232 | //! ```rust, ignore 233 | //! #[trait_gen(T -> u64, i64, u32, i32)] 234 | //! impl AddMod for T { 235 | //! type Output = T; 236 | //! 237 | //! fn add_mod(self, rhs: Self, modulo: Self) -> Self::Output { 238 | //! fn int_mod (a: T, m: T) -> T { // <== ERROR, conflicting 'T' 239 | //! a % m 240 | //! } 241 | //! int_mod(self + rhs, modulo) 242 | //! } 243 | //! } 244 | //! ``` 245 | //! 246 | //! * The generic argument must be a [type path](https://doc.rust-lang.org/reference/paths.html#paths-in-types); 247 | //! it cannot be a more complex type like a reference or a slice. So you can use `g::T -> ...` 248 | //! but not `&T -> ...`. 249 | 250 | mod tests; 251 | mod lib_macros; 252 | mod subst; 253 | mod args; 254 | mod utils; 255 | 256 | use proc_macro::TokenStream; 257 | use proc_macro_error2::proc_macro_error; 258 | use crate::lib_macros::{macro_trait_gen, macro_trait_gen_if}; 259 | 260 | //============================================================================== 261 | // Substitution attributes 262 | 263 | /// Generates the attached implementation code for all the types given in argument. 264 | /// 265 | /// The attribute is placed before the pseudo-generic code to implement. The _generic arguments_ 266 | /// are given first, followed by a right arrow (`->`) and a list of types that will replace the 267 | /// argument in the generated implementations: 268 | /// 269 | /// ```rust 270 | /// # use trait_gen::trait_gen; 271 | /// # struct Type1; struct Type2; struct Type3; 272 | /// # trait Trait {} 273 | /// #[trait_gen(T -> Type1, Type2, Type3)] 274 | /// impl Trait for T { 275 | /// // ... 276 | /// } 277 | /// ``` 278 | /// 279 | /// The attribute macro successively substitutes the generic argument `T` in the code with 280 | /// the given types (`Type1`, `Type2`, `Type3`) to generate each implementation. 281 | /// 282 | /// All the [type paths](https://doc.rust-lang.org/reference/paths.html#paths-in-types) beginning 283 | /// with `T` in the code have that part replaced. For example, `T::default()` generates 284 | /// `Type1::default()`, `Type2::default()` and so on, but `super::T` is unchanged. Similarly, all 285 | /// the [types](https://doc.rust-lang.org/reference/types.html) including `T` in the code have that 286 | /// part replaced; for example, `&T` or `Box`. 287 | /// 288 | /// The compiler will trigger an error if the resulting code is wrong. For example 289 | /// `#[trait_gen(T -> u64, f64)]` cannot be applied to `let x: T = 0;` because `0` is not a valid 290 | /// floating-point literal. 291 | /// 292 | /// Finally, the actual type of `T` replaces any occurrence of `${T}` in doc comments, macros, and 293 | /// string literals. 294 | /// 295 | /// _Notes:_ 296 | /// - _Using the letter "T" is not mandatory; any type path will do. For example, `g::Type` is fine 297 | /// too. But to make it easy to read and similar to a generic implementation, short upper-case identifiers 298 | /// are preferred._ 299 | /// - _If a `<..>` is required in the generic argument, the 300 | /// [turbofish syntax](https://doc.rust-lang.org/reference/paths.html#r-paths.expr.turbofish) must be used. 301 | /// For example, use `T::` and not `T`._ 302 | /// - _`type_gen` is a synonym attribute that can be used instead of `trait_gen`. This can be disabled with 303 | /// the `no_type_gen` feature, in case it conflicts with another 3rd-party attribute._ 304 | /// 305 | /// See the [crate documentation](crate) for more details. 306 | /// 307 | /// ## Example 308 | /// 309 | /// ```rust 310 | /// # use trait_gen::trait_gen; 311 | /// # trait MyLog { fn my_log2(self) -> u32; } 312 | /// #[trait_gen(T -> u8, u16, u32, u64, u128)] 313 | /// impl MyLog for T { 314 | /// /// Logarithm base 2 for `${T}` 315 | /// fn my_log2(self) -> u32 { 316 | /// T::BITS - 1 - self.leading_zeros() 317 | /// } 318 | /// } 319 | /// 320 | /// #[trait_gen(T -> u8, u16, u32, u64, u128)] 321 | /// #[trait_gen(U -> &T, &mut T, Box)] 322 | /// impl MyLog for U { 323 | /// /// Logarithm base 2 for `${U}` 324 | /// fn my_log2(self) -> u32 { 325 | /// MyLog::my_log2(*self) 326 | /// } 327 | /// } 328 | /// ``` 329 | #[proc_macro_attribute] 330 | #[proc_macro_error] 331 | pub fn trait_gen(args: TokenStream, item: TokenStream) -> TokenStream { 332 | macro_trait_gen(args.into(), item.into()).into() 333 | } 334 | 335 | #[cfg(not(feature = "no_type_gen"))] 336 | /// Generates the attached implementation code for all the types given in argument. 337 | /// 338 | /// This is a synonym of the [trait_gen()] attribute, provided because the attribute can be used with 339 | /// other elements than trait implementations. 340 | #[proc_macro_attribute] 341 | #[proc_macro_error] 342 | pub fn type_gen(args: TokenStream, item: TokenStream) -> TokenStream { 343 | macro_trait_gen(args.into(), item.into()).into() 344 | } 345 | 346 | //============================================================================== 347 | // Conditional attributes 348 | 349 | /// Generates the attached code if the condition is met. 350 | /// 351 | /// Please refer to the [crate documentation](crate#conditional-code). 352 | #[proc_macro_attribute] 353 | #[proc_macro_error] 354 | pub fn trait_gen_if(args: TokenStream, item: TokenStream) -> TokenStream { 355 | macro_trait_gen_if("trait_gen_if", args.into(), item.into()).into() 356 | } 357 | 358 | #[cfg(not(feature = "no_type_gen"))] 359 | /// Generates the attached code if the condition is met. 360 | /// 361 | /// This is a synonym of the [trait_gen_if()] attribute, provided because the attribute can be used with 362 | /// other elements than trait implementations. 363 | /// 364 | /// Please refer to the [crate documentation](crate#conditional-code). 365 | #[proc_macro_attribute] 366 | #[proc_macro_error] 367 | pub fn type_gen_if(args: TokenStream, item: TokenStream) -> TokenStream { 368 | macro_trait_gen_if("type_gen_if", args.into(), item.into()).into() 369 | } 370 | -------------------------------------------------------------------------------- /src/lib_macros.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Top-level macro code. Independent of proc_macro. 4 | 5 | use proc_macro2::TokenStream; 6 | use proc_macro_error2::abort; 7 | use quote::quote; 8 | use syn::File; 9 | use syn::visit_mut::VisitMut; 10 | use crate::args::{ArgType, CondParams, TraitGen}; 11 | use crate::subst::{to_subst_types, Subst}; 12 | use crate::utils::pathname; 13 | 14 | // For verbose debugging 15 | pub(crate) const VERBOSE: bool = false; 16 | pub(crate) const VERBOSE_TF: bool = false; 17 | 18 | //============================================================================== 19 | // Main code of the substitution attribute 20 | 21 | fn substitute(item: TokenStream, mut types: Subst) -> TokenStream { 22 | if VERBOSE || VERBOSE_TF { 23 | println!("{}\ntrait_gen for {} -> {}: {}", 24 | "=".repeat(80), 25 | pathname(&types.generic_arg), 26 | if types.is_path { "PATH" } else { "TYPE" }, 27 | &types.types.iter().map(pathname).collect::>().join(", ") 28 | ) 29 | } 30 | if VERBOSE || VERBOSE_TF { println!("\n{}\n{}", item, "-".repeat(80)); } 31 | let mut output = TokenStream::new(); 32 | let ast: File = syn::parse2(item).unwrap(); 33 | while !types.types.is_empty() { 34 | let mut modified_ast = ast.clone(); 35 | types.visit_file_mut(&mut modified_ast); 36 | output.extend(quote!(#modified_ast)); 37 | assert!(types.can_subst_path.is_empty(), "self.enabled has {} entries after type {}", 38 | types.can_subst_path.len(), pathname(types.types.first().unwrap())); 39 | types.types.remove(0); 40 | } 41 | if VERBOSE { println!("end trait_gen for {}\n{}", pathname(&types.generic_arg), "-".repeat(80)); } 42 | output 43 | } 44 | 45 | pub(crate) fn macro_trait_gen(args: TokenStream, item: TokenStream) -> TokenStream { 46 | let mut attribute = match syn::parse2::(args) { 47 | Ok(types) => types, 48 | Err(err) => abort!(err.span(), err; 49 | help = "The expected format is: #[trait_gen(T -> Type1, Type2, Type3)]"), 50 | }; 51 | let mut output = TokenStream::new(); 52 | let args = std::mem::replace(&mut attribute.args, ArgType::None); 53 | match &args { 54 | ArgType::Tuple(paths) => { 55 | // generates all the permutations 56 | let mut subst = Subst::from_trait_gen(attribute.clone(), paths[0].clone()); 57 | let types = std::mem::take(&mut subst.types); 58 | subst.type_helper = Some(&types); 59 | let new_iterators = (0..paths.len()).map(|_| types.iter()).collect::>(); 60 | let mut values = vec![]; 61 | let mut iterators = vec![]; 62 | loop { 63 | // fill missing iterators with fresh ones: 64 | for mut new_iter in new_iterators.iter().skip(iterators.len()).cloned() { 65 | values.push(new_iter.next().unwrap()); 66 | iterators.push(new_iter); 67 | } 68 | // do the substitutions: 69 | let mut stream = item.clone(); 70 | for (arg, &ty) in paths.iter().zip(values.iter()) { 71 | subst.generic_arg = arg.clone(); 72 | subst.types = vec![ty.clone()]; 73 | stream = substitute(stream, subst.clone()); 74 | } 75 | output.extend(stream); 76 | // pops dead iterators and increases the next one: 77 | while let Some(mut it) = iterators.pop() { 78 | values.pop(); 79 | if let Some(v) = it.next() { 80 | values.push(v); 81 | iterators.push(it); 82 | break; 83 | } 84 | } 85 | if values.is_empty() { break } 86 | } 87 | } 88 | ArgType::Permutation(path1, path2) | ArgType::StrictOrder(path1, path2) | ArgType::NonStrictOrder(path1, path2) => { 89 | // we could translate the attribute into simple attributes using conditionals, but it's 90 | // easier, lighter, and safer to simply generate and filter the combinations 91 | let (_, types) = to_subst_types(attribute.types.clone()); 92 | let mut subst = Subst::from_trait_gen(attribute.clone(), path1.clone()); 93 | for (i1, p1) in types.iter().enumerate() { 94 | for (i2, p2) in types.iter().enumerate() { 95 | let cond = match &args { 96 | ArgType::Permutation(_, _) => i1 != i2, 97 | ArgType::StrictOrder(_, _) => i1 < i2, 98 | ArgType::NonStrictOrder(_, _) => i1 <= i2, 99 | _ => panic!("can't happen") 100 | }; 101 | if cond { 102 | subst.types = vec![p1.clone()]; 103 | subst.generic_arg = path1.clone(); 104 | let stream = substitute(item.clone(), subst.clone()); 105 | subst.types = vec![p2.clone()]; 106 | subst.generic_arg = path2.clone(); 107 | output.extend(substitute(stream, subst.clone())); 108 | } 109 | } 110 | } 111 | } 112 | _ => panic!("can't happen"), 113 | } 114 | if VERBOSE { println!("{}\n{}", output, "=".repeat(80)); } 115 | output 116 | } 117 | 118 | //============================================================================== 119 | // Main code of the conditional attribute 120 | 121 | pub(crate) fn macro_trait_gen_if(name: &str, args: TokenStream, item: TokenStream) -> TokenStream { 122 | if VERBOSE { println!("process_conditional_attribute({}, {})", args.to_string(), item.to_string()); } 123 | let new_code = match syn::parse2::(args) { 124 | Ok(attr) => { 125 | if attr.types.contains(&attr.generic_arg) ^ attr.is_negated { 126 | item // enables the code 127 | } else { 128 | TokenStream::new() // disables the code 129 | } 130 | } 131 | Err(err) => { 132 | // shouldn't happen, unless the attribute is used out of a #[trait_gen] attribute scope 133 | abort!(err.span(), err; 134 | help = "The expected format is: #[{}(T in Type1, Type2, Type3)]", name); 135 | } 136 | }; 137 | new_code 138 | } 139 | 140 | -------------------------------------------------------------------------------- /src/subst.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Code substitution code. Independent of proc_macro. 4 | 5 | use std::fmt::{Debug, Formatter}; 6 | use proc_macro_error2::abort; 7 | use quote::{quote, ToTokens}; 8 | use syn::{Generics, GenericParam, TypePath, Path, PathArguments, Expr, Lit, LitStr, ExprLit, Macro, parse_str, Attribute, PathSegment, Type, MetaList}; 9 | use syn::spanned::Spanned; 10 | use syn::token::PathSep; 11 | use syn::visit_mut::VisitMut; 12 | use crate::args::{CondParams, TraitGen}; 13 | use crate::lib_macros::{VERBOSE, VERBOSE_TF}; 14 | use crate::utils::{path_prefix_len, pathname, replace_str}; 15 | 16 | //============================================================================== 17 | // Attributes that may be inside the content scanned by trait-gen, and which need 18 | // to be processed (the other attributes, either standard or from 3rd-party crates 19 | // are attached normally on the code generated by trait-gen). 20 | 21 | // Embedded trait-gen attributes (trait-gen will check for types / paths that must 22 | // be changed). 23 | // Note: when the feature "type_gen" is disabled, we avoid matching "type_gen" by 24 | // making both constants equal (those constants are used in a match statement). 25 | const TRAIT_GEN: &str = "trait_gen"; 26 | const TYPE_GEN: &str = if cfg!(feature = "no_type_gen") { TRAIT_GEN } else { "type_gen" }; 27 | 28 | // Attributes for conditional implementation. 29 | // Note: when the feature "type_gen" is disabled, we avoid matching "type_gen_if" by 30 | // making both constants equal (those constants are used in a match statement). 31 | const TRAIT_GEN_IF: &str = "trait_gen_if"; 32 | const TYPE_GEN_IF: &str = if cfg!(feature = "no_type_gen") { TRAIT_GEN_IF } else { "type_gen_if" }; 33 | 34 | //============================================================================== 35 | // Substitution types 36 | 37 | #[derive(Clone, PartialEq)] 38 | /// Substitution item, either a Path (`super::Type`) or a Type (`&mut Type`) 39 | pub enum SubstType { 40 | Path(Path), 41 | Type(Type) 42 | } 43 | 44 | impl ToTokens for SubstType { 45 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 46 | match self { 47 | SubstType::Path(path) => path.to_tokens(tokens), 48 | SubstType::Type(ty) => ty.to_tokens(tokens) 49 | } 50 | } 51 | } 52 | 53 | impl Debug for SubstType { 54 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 55 | match self { 56 | SubstType::Path(p) => write!(f, "Path({})", pathname(p)), 57 | SubstType::Type(t) => write!(f, "Type({})", pathname(t)), 58 | } 59 | } 60 | } 61 | 62 | /// Converts a list of `Type` to a list of `SubstType`, making them all `Path` if possible, 63 | /// otherwise all `Type`. 64 | pub(crate) fn to_subst_types(mut types: Vec) -> (bool, Vec) { 65 | let mut visitor = TurboFish; 66 | for ty in types.iter_mut() { 67 | visitor.visit_type_mut(ty); 68 | } 69 | let is_path = types.iter().all(|ty| matches!(ty, Type::Path(_))); 70 | let subst_types = types.into_iter() 71 | .map(|ty| 72 | if is_path { 73 | if let Type::Path(p) = ty { 74 | SubstType::Path(p.path) 75 | } else { 76 | panic!("this should match Type::Path: {:?}", ty) 77 | } 78 | } else { 79 | SubstType::Type(ty) 80 | }) 81 | .collect::>(); 82 | (is_path, subst_types) 83 | } 84 | 85 | /// This type is only used to implement the VisitMut trait. 86 | struct TurboFish; 87 | 88 | /// Adds the turbofish double-colon whenever possible to avoid post-substitution problems. 89 | /// 90 | /// The replaced part may be an expression requiring it, or a type that doesn't require the 91 | /// double-colon but accepts it. Handling both cases would be complicated so we always include it. 92 | impl VisitMut for TurboFish { 93 | fn visit_path_mut(&mut self, node: &mut Path) { 94 | if VERBOSE_TF { 95 | println!("TURBOF: path '{}'", pathname(node)); 96 | } 97 | for segment in &mut node.segments { 98 | if let PathArguments::AngleBracketed(generic_args) = &mut segment.arguments { 99 | generic_args.colon2_token = Some(PathSep::default()); 100 | } 101 | } 102 | } 103 | } 104 | 105 | #[derive(Clone)] 106 | /// Attribute substitution data used to replace the generic argument in `generic_arg` with the 107 | /// types in `types`. The first item in `types` is the one currently being replaced in the 108 | /// `VisitMut` implementation. 109 | pub(crate) struct Subst<'a> { 110 | /// generic argument to replace 111 | pub generic_arg: Path, 112 | /// types that replace the generic argument 113 | pub types: Vec, 114 | /// Path substitution items if true, Type items if false 115 | pub is_path: bool, 116 | /// Context stack, cannot substitue paths when last is false (can substitute if empty) 117 | pub can_subst_path: Vec, 118 | pub type_helper: Option<&'a Vec> 119 | } 120 | 121 | impl<'a> Subst<'a> { 122 | pub fn from_trait_gen(attribute: TraitGen, generic_arg: Path) -> Self { 123 | let (is_path, types) = to_subst_types(attribute.types); 124 | Subst { 125 | generic_arg, 126 | types, 127 | is_path, 128 | can_subst_path: vec![], 129 | type_helper: None 130 | } 131 | } 132 | 133 | fn can_subst_path(&self) -> bool { 134 | *self.can_subst_path.last().unwrap_or(&true) 135 | } 136 | 137 | /// Gives a helpful list of type names that might be used in a substitution list. 138 | fn get_example_types(&self) -> String { 139 | // This is called for error messages, which happen only during the first visit_mut pass over 140 | // the inner attributes: we know that Subst still has all the types in `self.new_types`. 141 | let mut examples = self.type_helper.unwrap_or(&Vec::new()).iter().map(pathname).take(3).collect::>(); 142 | while examples.len() < 3 { 143 | examples.push(format!("Type{}", examples.len() + 1)); 144 | } 145 | examples.join(", ") 146 | } 147 | } 148 | 149 | impl Debug for Subst<'_> { 150 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 151 | write!(f, "PathTypes {{ generic_arg: {}, types: [{}], is_path: {}, enabled: {}, type_helper: {} }}", 152 | pathname(&self.generic_arg), 153 | self.types.iter().map(pathname).collect::>().join(", "), 154 | self.is_path, 155 | self.can_subst_path.iter().map(|e| e.to_string()).collect::>().join(", "), 156 | self.type_helper.unwrap_or(&Vec::new()).iter().map(pathname).collect::>().join(", "), 157 | ) 158 | } 159 | } 160 | 161 | //============================================================================== 162 | // Main substitution code 163 | 164 | impl VisitMut for Subst<'_> { 165 | fn visit_attribute_mut(&mut self, node: &mut Attribute) { 166 | // Takes the last segment in case there's a specific path to the attribute. This means we don't support other attributes 167 | // with the same name inside the outer attribute, but it shouldn't be a problem as long as they could be renamed in the `use` 168 | // declaration (in the unlikely event that another attribute shared the same name). 169 | if let Some(PathSegment { ident, .. }) = node.path().segments.last() { 170 | let ident = ident.to_string(); 171 | match ident.as_str() { 172 | // conditional pseudo-attribute (TYPE_GEN_IF == TRAIT_GEN_IF when type_gen is disabled) 173 | TRAIT_GEN_IF | TYPE_GEN_IF => { 174 | // checks that the syntax is fine and performs the type substitutions if required 175 | match node.parse_args::() { 176 | Ok(cond) => { 177 | let mut output = proc_macro2::TokenStream::new(); 178 | if VERBOSE { println!("- {} -> {}", pathname(&self.generic_arg), pathname(self.types.first().unwrap())); } 179 | let mut g = cond.generic_arg; 180 | self.visit_type_mut(&mut g); 181 | if cond.is_negated { 182 | output.extend(quote!(!#g in)); 183 | } else { 184 | output.extend(quote!(#g in)); 185 | } 186 | let mut first = true; 187 | for mut ty in cond.types { 188 | // checks if substitutions must be made in that argument: 189 | self.visit_type_mut(&mut ty); 190 | if !first { 191 | output.extend(quote!(, )); 192 | } 193 | output.extend(quote!(#ty)); 194 | first = false; 195 | } 196 | let original = pathname(&node); 197 | if let syn::Meta::List(MetaList { ref mut tokens, .. }) = node.meta { 198 | if VERBOSE { println!(" {original} => {}", pathname(&output)); } 199 | *tokens = output; 200 | return; 201 | } else { 202 | // shouldn't happen 203 | abort!(node.meta.span(), "invalid {} attribute format", ident; 204 | help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types()); 205 | }; 206 | } 207 | Err(err) => { 208 | // gives a personalized hint 209 | abort!(err.span(), err; 210 | help = "The expected format is: #[{}({} in {})]", ident, pathname(&self.generic_arg), self.get_example_types()); 211 | } 212 | }; 213 | } 214 | // embedded trait-gen attributes 215 | TRAIT_GEN | TYPE_GEN => { 216 | // Perform substitutions in the arguments of the inner attribute if necessary. 217 | // #[trait_gen(U -> i32, u32)] // <== self 218 | // #[trait_gen(T -> &U, &mut U)] // <== change 'U' to 'i32' and 'u32' 219 | // impl Neg for T { /* .... */ } 220 | match node.parse_args::() { 221 | Ok(mut types) => { 222 | let mut output = proc_macro2::TokenStream::new(); 223 | // It would be nice to give a warning if the format of the attributes were different, 224 | // once there is a way to generate custom warnings (an error for that seems too harsh). 225 | let g = types.args; 226 | output.extend(quote!(#g -> )); 227 | let mut first = true; 228 | for ty in &mut types.types { 229 | // checks if substitutions must be made in that argument: 230 | self.visit_type_mut(ty); 231 | if !first { 232 | output.extend(quote!(, )); 233 | } 234 | output.extend(quote!(#ty)); 235 | first = false; 236 | } 237 | if let syn::Meta::List(MetaList { ref mut tokens, .. }) = node.meta { 238 | *tokens = output; 239 | return; 240 | } else { 241 | // shouldn't happen 242 | abort!(node.meta.span(), "invalid {} attribute format", ident; 243 | help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types()); 244 | }; 245 | } 246 | Err(err) => { 247 | // gives a personalized hint 248 | abort!(err.span(), err; 249 | help = "The expected format is: #[{}({} -> {})]", ident, pathname(&self.generic_arg), self.get_example_types()); 250 | } 251 | }; 252 | 253 | } 254 | _ => () 255 | } 256 | } 257 | syn::visit_mut::visit_attribute_mut(self, node); 258 | } 259 | 260 | fn visit_expr_mut(&mut self, node: &mut Expr) { 261 | let mut enabled = self.can_subst_path(); 262 | match node { 263 | // allows substitutions for the nodes below, and until a new Expr is met: 264 | Expr::Call(_) => enabled = true, 265 | Expr::Cast(_) => enabled = true, 266 | Expr::Struct(_) => enabled = true, 267 | 268 | // 'ExprPath' is the node checking for authorization through ExprPath.path, 269 | // so the current 'enabled' is preserved: (see also visit_path_mut()) 270 | Expr::Path(_) => { /* don't change */ } 271 | 272 | // all other expressions in general must disable substitution: 273 | _ => enabled = false, 274 | }; 275 | self.can_subst_path.push(enabled); 276 | syn::visit_mut::visit_expr_mut(self, node); 277 | self.can_subst_path.pop(); 278 | } 279 | 280 | fn visit_expr_lit_mut(&mut self, node: &mut ExprLit) { 281 | if let Lit::Str(lit) = &node.lit { 282 | // substitutes "${T}" in expression literals (not used in macros, see visit_macro_mut) 283 | if let Some(ts_str) = replace_str( 284 | &lit.to_token_stream().to_string(), 285 | &format!("${{{}}}", pathname(&self.generic_arg)), 286 | &pathname(self.types.first().unwrap())) 287 | { 288 | let new_lit: LitStr = parse_str(&ts_str).unwrap_or_else(|_| panic!("parsing LitStr failed: {}", ts_str)); 289 | node.lit = Lit::Str(new_lit); 290 | } else { 291 | syn::visit_mut::visit_expr_lit_mut(self, node); 292 | } 293 | } 294 | } 295 | 296 | fn visit_generics_mut(&mut self, i: &mut Generics) { 297 | if let Some(segment) = self.generic_arg.segments.first() { 298 | let current_ident = &segment.ident; 299 | for t in i.params.iter() { 300 | match &t { 301 | GenericParam::Type(t) => { 302 | if &t.ident == current_ident { 303 | abort!(t.span(), 304 | "Type '{}' is reserved for the substitution.", current_ident.to_string(); 305 | help = "Use another identifier for this local generic type." 306 | ); 307 | 308 | // replace the 'abort!' above with this once it is stable: 309 | // 310 | // t.span().unwrap() 311 | // .error(format!("Type '{}' is reserved for the substitution.", self.current_type.to_string())) 312 | // .help("Use another identifier for this local generic type.") 313 | // .emit(); 314 | } 315 | } 316 | _ => {} 317 | } 318 | } 319 | } 320 | syn::visit_mut::visit_generics_mut(self, i); 321 | } 322 | 323 | fn visit_macro_mut(&mut self, node: &mut Macro) { 324 | // substitutes "${T}" in macros 325 | if let Some(ts_str) = replace_str( 326 | &node.tokens.to_string(), 327 | &format!("${{{}}}", pathname(&self.generic_arg)), 328 | &pathname(self.types.first().unwrap())) 329 | { 330 | let new_ts: proc_macro2::TokenStream = ts_str.parse().unwrap_or_else(|_| panic!("parsing Macro failed: {}", ts_str)); 331 | node.tokens = new_ts; 332 | } else { 333 | syn::visit_mut::visit_macro_mut(self, node); 334 | } 335 | } 336 | 337 | fn visit_path_mut(&mut self, path: &mut Path) { 338 | let path_name = pathname(path); 339 | let path_length = path.segments.len(); 340 | if let Some(length) = path_prefix_len(&self.generic_arg, path) { 341 | // If U is both a constant and the generic argument, in an expression (so when 342 | // self.substitution_enabled() == false), we must distinguish two cases: 343 | // - U::MAX must be replaced (length < path_length) 344 | // - U or U.add(1) must stay 345 | if length < path_length || self.can_subst_path() { 346 | if VERBOSE { print!("[path] path: {} length = {}", path_name, length); } 347 | match self.types.first().unwrap() { 348 | SubstType::Path(p) => { 349 | let mut new_seg = p.segments.clone(); 350 | for seg in path.segments.iter().skip(length) { 351 | new_seg.push(seg.clone()); 352 | } 353 | // check if orphan arguments: 354 | // #[trait_gen(g::T -> mod::Name, ...) { ... g::T<'_> ... } 355 | // path = g :: T <'_> len = 2, subst enabled 356 | // new_path = mod :: Name len = 2 357 | // => new_seg = mod :: Name<'_> 358 | let nth_new_seg = new_seg.last_mut().unwrap(); 359 | let nth_seg = path.segments.iter().nth(length - 1).unwrap(); 360 | if nth_new_seg.arguments.is_empty() && !nth_seg.arguments.is_empty() { 361 | nth_new_seg.arguments = nth_seg.arguments.clone(); 362 | } 363 | path.segments = new_seg; 364 | if VERBOSE { println!(" -> {}", pathname(path)); } 365 | } 366 | SubstType::Type(ty) => { 367 | if VERBOSE { println!(" -> Path '{}' cannot be substituted by type '{}'", path_name, pathname(ty)); } 368 | // note: emit-warning is unstable... 369 | abort!(ty.span(), "Path '{}' cannot be substituted by type '{}'", path_name, pathname(ty)); 370 | } 371 | } 372 | } else { 373 | if VERBOSE { println!("disabled path: {}", path_name); } 374 | syn::visit_mut::visit_path_mut(self, path); 375 | } 376 | } else { 377 | if VERBOSE { println!("path: {} mismatch", path_name); } 378 | syn::visit_mut::visit_path_mut(self, path); 379 | } 380 | } 381 | 382 | fn visit_type_mut(&mut self, node: &mut Type) { 383 | if !self.is_path { 384 | match node { 385 | Type::Path(TypePath { path, .. }) => { 386 | let path_name = pathname(path); 387 | let path_length = path.segments.len(); 388 | if let Some(length) = path_prefix_len(&self.generic_arg, path) { 389 | if /*length < path_length ||*/ self.can_subst_path() { 390 | assert!(length == path_length, "length={length}, path_length={path_length}"); 391 | // must implement length < path_length if such a case can be found, but it's been elusive 392 | if VERBOSE { 393 | print!("[type] type path: {} length = {length}, path length = {path_length} {} -> ", 394 | path_name, if self.can_subst_path() { ", can_subst" } else { "" }); 395 | } 396 | *node = if let SubstType::Type(ty) = self.types.first().unwrap() { 397 | if VERBOSE { println!("{}", pathname(ty)); } 398 | ty.clone() 399 | } else { 400 | panic!("found path item instead of type in SubstType") 401 | }; 402 | } 403 | } else { 404 | syn::visit_mut::visit_type_mut(self, node); 405 | } 406 | } 407 | _ => syn::visit_mut::visit_type_mut(self, node), 408 | } 409 | } else { 410 | syn::visit_mut::visit_type_mut(self, node); 411 | } 412 | } 413 | 414 | fn visit_type_path_mut(&mut self, typepath: &mut TypePath) { 415 | self.can_subst_path.push(true); 416 | let TypePath { path, .. } = typepath; 417 | if VERBOSE { println!("typepath: {}", pathname(path)); } 418 | syn::visit_mut::visit_type_path_mut(self, typepath); 419 | self.can_subst_path.pop(); 420 | } 421 | } 422 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Unit tests. 4 | 5 | #![cfg(test)] 6 | 7 | use std::str::FromStr; 8 | 9 | use proc_macro2::{Span, TokenStream}; 10 | use syn::parse::Parse; 11 | use syn::{Path, Type}; 12 | use crate::args::TraitGen; 13 | use crate::utils::{path_prefix_len, pathname, replace_str}; 14 | 15 | fn annotate_error(text: &str, msg: &str, span: Span) -> String { 16 | // only works for single-lined sources: 17 | assert!(!text.contains('\n')); 18 | let mut msg = msg.to_string(); 19 | msg.push('\n'); 20 | msg.push_str(text); 21 | msg.push('\n'); 22 | let start = span.start().column; 23 | let end = span.end().column; 24 | if start > 0 || end > 0 { 25 | msg.push_str(&" ".repeat(start)); 26 | } else { 27 | msg.push_str(&" ".repeat(text.len())); 28 | } 29 | msg.push_str(&"^".repeat(end - start + 1)); 30 | msg 31 | } 32 | 33 | fn try_parse(args: TokenStream, text: &str) -> Result { 34 | match syn::parse2::(args.clone()) { 35 | Ok(subst) => Ok(subst), 36 | Err(err) => { 37 | let msg = annotate_error(text, &err.to_string(), err.span()); 38 | // println!("{msg}"); 39 | Err(msg) 40 | } 41 | } 42 | } 43 | 44 | fn try_get_tokenstream(string: &str) -> Result { 45 | match TokenStream::from_str(string) { 46 | Ok(s) => Ok(s), 47 | Err(err) => { 48 | Err(format!("could not transform test string into TokenStream: {}", 49 | annotate_error(string, &err.to_string(), err.span()))) 50 | } 51 | } 52 | } 53 | 54 | /// `tokenstream!(text: &str, error: mut int)` 55 | /// 56 | /// Transforms the string slice `text` into a TokenStream. In case of error, 57 | /// displays the location of the problem, increments the `error` variable, 58 | /// and `continue` to the next loop iteration. 59 | /// 60 | /// Must be used within a loop. 61 | macro_rules! tokenstream { 62 | ($s:expr, $e:ident) => { 63 | match try_get_tokenstream($s) { 64 | Ok(s) => s, 65 | Err(err) => { 66 | println!("{}", err); 67 | $e += 1; 68 | continue 69 | } 70 | } 71 | } 72 | } 73 | 74 | /// `parse_str!(T: Parse, text: &str, error: mut int)` 75 | /// 76 | /// Parses the string slice `text` as type `T`, which must implement the `Parse` trait. 77 | /// In case of error, displays the location of the problem, increments the `error` variable 78 | /// and `continue` to the next loop iteration. 79 | /// 80 | /// Must be used within a loop. 81 | macro_rules! parse_str { 82 | ($t:ty, $s:expr, $e:ident) => { 83 | match try_parse::<$t>(tokenstream!($s, $e), $s) { 84 | Ok(item) => item, 85 | Err(err) => { 86 | println!("could not parse {} from {}: {}", stringify!($t), $s, err); 87 | $e += 1; 88 | continue 89 | } 90 | } 91 | } 92 | } 93 | 94 | #[test] 95 | fn parse_args() { 96 | let tests: &[(&str, &str, bool, bool)] = &[ 97 | // parameters generic path error 98 | ("T -> i32, u32", "T", true, false), 99 | ("my::U -> my::T", "my::U", true, false), 100 | ("T -> Box", "T", true, false), 101 | ("T -> Box, &X, &mut X", "T", false, false), 102 | ("T::U:: -> X, Y", "T::U::", true, false), 103 | ("T ->", "", true, true), 104 | ("[&T] -> [&mut T]", "", false, true), 105 | ]; 106 | let mut error = 0; 107 | for (idx, &(string, generic, path, parse_error)) in tests.iter().enumerate() { 108 | let report = format!("test #{idx} on '{string}': "); 109 | let stream = tokenstream!(string, error); 110 | // tests Subst::parse 111 | let mut new_error = true; 112 | match try_parse::(stream, string) { 113 | Ok(subst) => { 114 | match () { 115 | _ if parse_error => 116 | println!("{report}expecting parse error"), 117 | _ if pathname(&subst.args) != generic => 118 | println!("{report}expecting generic '{}' instead of '{}'", generic, pathname(&subst.args)), 119 | _ if subst.types.iter().all(|ty| matches!(ty, Type::Path(_))) != path => 120 | println!("{report}expecting {} mode", if path { "path" } else { "type" }), 121 | _ => new_error = false 122 | } 123 | } 124 | Err(e) => { 125 | if !parse_error { 126 | println!("{report}parse error (Subst):\n{e}"); 127 | } else { 128 | new_error = false; 129 | } 130 | } 131 | } 132 | if !new_error { 133 | // tests TraitGen::parse 134 | new_error = true; 135 | let stream = tokenstream!(&string, error); 136 | match try_parse::(stream, &string) { 137 | Ok(params) => { 138 | match () { 139 | _ if parse_error => 140 | println!("{report}expecting parse error"), 141 | _ if pathname(¶ms.args) != generic => 142 | println!("{report}expecting generic '{}' instead of '{}'", generic, pathname(¶ms.args)), 143 | _ => new_error = false 144 | } 145 | } 146 | Err(e) => { 147 | if !parse_error { 148 | println!("{report}parse error (TraitGen):\n{e}"); 149 | } else { 150 | new_error = false; 151 | } 152 | } 153 | } 154 | } 155 | if new_error { 156 | error += 1; 157 | }; 158 | } 159 | assert!(error == 0, "{} error(s)", error); 160 | } 161 | 162 | #[test] 163 | fn test_path_prefix_len() { 164 | let tests = &[ 165 | // prefix full # segments 166 | ("T", "T", Some(1)), 167 | ("T", "T::U", Some(1)), 168 | ("T", "T", Some(1)), 169 | ("T", "U", None), 170 | ("T", "::T", None), 171 | ("T", "U::T", None), 172 | ("T", "U", None), 173 | ("T::U", "T::U", Some(2)), 174 | ("T::U", "T::U::V", Some(2)), 175 | ("T", "T", None), 176 | ("T", "T::V", Some(1)), 177 | ("T", "T::V", None), 178 | ]; 179 | let mut error = 0; 180 | for (idx, &(prefix, full, exp_len)) in tests.iter().enumerate() { 181 | let report = format!("test #{idx} on '{prefix}' in '{full}': "); 182 | let prefix_path = parse_str!(Path, prefix, error); 183 | let full_path = parse_str!(Path, full, error); 184 | let len = path_prefix_len(&prefix_path, &full_path); 185 | if len != exp_len { 186 | println!("{report}expecting {exp_len:?} instead of {len:?}"); 187 | error += 1; 188 | } 189 | } 190 | assert!(error == 0, "{} error(s)", error); 191 | } 192 | 193 | #[test] 194 | fn test_replace_str() { 195 | assert_eq!(replace_str("ab cd ab ef", "ab", "X"), Some("X cd X ef".to_string())); 196 | } 197 | 198 | mod test_parse_parameters { 199 | use proc_macro2::TokenStream; 200 | use std::str::FromStr; 201 | use syn::parse::{Parse, ParseStream}; 202 | use syn::Type; 203 | use crate::args::{parse_arguments, ArgType}; 204 | use crate::utils::pathname; 205 | 206 | struct ArgsResult { 207 | args: ArgType, 208 | types: Vec, 209 | is_negated: bool, 210 | } 211 | 212 | impl std::fmt::Debug for ArgsResult { 213 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 214 | write!(f, "ArgsResult {{ args: {:?}, types: [{}], is_negated: {} }}", 215 | self.args, 216 | self.types.iter().map(|t| pathname(t)).collect::>().join(", "), 217 | self.is_negated 218 | ) 219 | } 220 | } 221 | 222 | struct CondWrapper(ArgsResult); 223 | 224 | impl Parse for ArgsResult { 225 | fn parse(input: ParseStream) -> syn::Result { 226 | match parse_arguments(input, false) { 227 | Ok((args, types, is_negated)) => Ok(ArgsResult { args, types, is_negated }), 228 | Err(e) => Err(e), 229 | } 230 | } 231 | } 232 | 233 | impl Parse for CondWrapper { 234 | fn parse(input: ParseStream) -> syn::Result { 235 | match parse_arguments(input, true) { 236 | Ok((args, types, is_negated)) => Ok(CondWrapper(ArgsResult { args, types, is_negated })), 237 | Err(e) => Err(e), 238 | } 239 | } 240 | } 241 | 242 | 243 | #[test] 244 | fn test1() { 245 | const VERBOSE: bool = false; 246 | let tests = vec![ 247 | (false, "T -> u8, u16", Some("ArgsResult { args: Tuple(T), types: [u8, u16], is_negated: false }")), 248 | (false, "T, U -> u8, u16, u32", Some("ArgsResult { args: Tuple(T, U), types: [u8, u16, u32], is_negated: false }")), 249 | (false, "T != U -> u8, u16, u32", Some("ArgsResult { args: Permutation(T, U), types: [u8, u16, u32], is_negated: false }")), 250 | (false, "T < U -> u8, u16, u32", Some("ArgsResult { args: StrictOrder(T, U), types: [u8, u16, u32], is_negated: false }")), 251 | (false, "T <= U -> u8, u16, u32", Some("ArgsResult { args: NonStrictOrder(T, U), types: [u8, u16, u32], is_negated: false }")), 252 | (true, "T in u8, u16", Some("ArgsResult { args: Cond(T), types: [u8, u16], is_negated: false }")), 253 | ]; 254 | for (is_cond, string, expected) in tests { 255 | let token_stream = TokenStream::from_str(string).expect(&format!("can't create tokens from '{string}'")); 256 | let args_maybe = if is_cond { 257 | match syn::parse2::(token_stream) { 258 | Ok(types) => Some(types.0), 259 | Err(_err) => None, 260 | } 261 | } else { 262 | match syn::parse2::(token_stream) { 263 | Ok(types) => Some(types), 264 | Err(_err) => None, 265 | } 266 | }; 267 | if VERBOSE { 268 | if let Some(ref args) = args_maybe { 269 | println!("{string}: {args:?}"); 270 | } 271 | } 272 | let result = args_maybe.map(|a| format!("{a:?}")); 273 | assert_eq!(result, expected.map(|s| s.to_string()), "test {string} failed"); 274 | } 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Helper functions and traits. Independent of proc_macro. 4 | 5 | use quote::ToTokens; 6 | use syn::{GenericArgument, Path, PathArguments, PathSegment}; 7 | 8 | //============================================================================== 9 | // Helper traits 10 | 11 | trait NodeMatch { 12 | /// Checks if the `self` node is a prefix of `other`. 13 | fn match_prefix(&self, other: &Self) -> bool; 14 | } 15 | 16 | impl NodeMatch for GenericArgument { 17 | /// Compares both generic arguments, disregarding lifetime argument names 18 | fn match_prefix(&self, other: &Self) -> bool { 19 | if let GenericArgument::Lifetime(_) = self { 20 | // ignoring the actual lifetime ident 21 | matches!(other, GenericArgument::Lifetime(_)) 22 | } else { 23 | self == other 24 | } 25 | } 26 | } 27 | 28 | impl NodeMatch for PathSegment { 29 | /// Compares both segments and returns true if `self` is similar to `seg_pat`, disregarding 30 | /// * any "turbofish" difference when there are angle bracket arguments 31 | /// * the arguments if `seg_pat` doesn't have any 32 | fn match_prefix(&self, seg_pat: &PathSegment) -> bool { 33 | self.ident == seg_pat.ident && match &seg_pat.arguments { 34 | PathArguments::None => 35 | true, //matches!(seg_pat.arguments, PathArguments::None), 36 | PathArguments::AngleBracketed(ab_pat) => { 37 | if let PathArguments::AngleBracketed(ab) = &self.arguments { 38 | // ignoring turbofish in colon2_token 39 | ab.args.len() == ab_pat.args.len() && 40 | ab.args.iter().zip(&ab_pat.args).all(|(a, b)| a.match_prefix(b)) 41 | } else { 42 | false 43 | } 44 | } 45 | PathArguments::Parenthesized(p_pat) => { 46 | if let PathArguments::Parenthesized(p) = &self.arguments { 47 | p == p_pat 48 | } else { 49 | false 50 | } 51 | } 52 | } 53 | } 54 | } 55 | 56 | //============================================================================== 57 | // Helper functions 58 | 59 | /// Creates a string from a tokenizable item and removes the annoying spaces. 60 | pub(crate) fn pathname(path: &T) -> String { 61 | path.to_token_stream().to_string() 62 | .replace(" :: ", "::") 63 | .replace(" <", "<") 64 | .replace("< ", "<") 65 | .replace(" >", ">") 66 | .replace("> ", ">") 67 | .replace("& ", "&") 68 | .replace(", ", ",") 69 | .replace(") ", ")") 70 | .replace(" ;", ";") 71 | .replace("; ", ";") 72 | } 73 | 74 | /// Compares two type paths and, if `prefix` is a prefix of `full_path`, returns the number of 75 | /// matching segments. 76 | pub(crate) fn path_prefix_len(prefix: &Path, full_path: &Path) -> Option { 77 | let prefix_len = prefix.segments.len(); 78 | if full_path.leading_colon == prefix.leading_colon && full_path.segments.len() >= prefix_len { 79 | for (seg_full, seg_prefix) in full_path.segments.iter().zip(&prefix.segments) { 80 | if !seg_full.match_prefix(seg_prefix) { 81 | return None; 82 | } 83 | } 84 | return Some(prefix_len) 85 | } 86 | None 87 | } 88 | 89 | /// Replaces the pattern `pat` with `repl` in `string`. Returns `Some(resulting string)` if 90 | /// the string changed, None if there was no replacement. 91 | pub(crate) fn replace_str(string: &str, pat: &str, repl: &str) -> Option { 92 | if string.contains(pat) { 93 | Some(string.replace(pat, repl)) 94 | } else { 95 | None 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /tests/integration.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2025 Redglyph (@gmail.com). All Rights Reserved. 2 | // 3 | // Integration tests. 4 | 5 | /// Tests the `#[trait_gen(T -> i16, u16)]` format 6 | mod simple_formats { 7 | use trait_gen::trait_gen; 8 | 9 | struct Test(T); 10 | 11 | // main format 12 | #[trait_gen(T -> i16, u16)] 13 | impl Test { 14 | fn test() -> bool { true } 15 | } 16 | 17 | // verifies that brackets can be used for types with the '->' syntax 18 | #[trait_gen(T -> [i64;2])] 19 | impl Test { 20 | fn test() -> bool { true } 21 | } 22 | 23 | #[trait_gen(T -> &[u64])] 24 | impl Test { 25 | fn test() -> bool { true } 26 | } 27 | 28 | #[trait_gen(T -> (u32, u32), (u8, u8))] 29 | impl Test { 30 | #[allow(unused)] 31 | fn test() -> bool { true } 32 | } 33 | 34 | #[test] 35 | fn test() { 36 | assert!(Test::::test()); 37 | assert!(Test::::test()); 38 | assert!(Test::<[i64;2]>::test()); 39 | assert!(Test::<&[u64]>::test()); 40 | assert!(Test::<(u32, u32)>::test()); 41 | } 42 | } 43 | 44 | /// Tests the `#[trait_gen(A, B -> u8, u16)]` format 45 | mod advanced_format_tuple { 46 | use std::ops::Add; 47 | use trait_gen::{trait_gen}; 48 | 49 | #[derive(PartialEq, Debug)] 50 | struct Wrapper(T); 51 | 52 | #[trait_gen(A, B -> u8, u16)] 53 | impl Add> for Wrapper { 54 | type Output = Wrapper; 55 | 56 | fn add(self, rhs: Wrapper) -> Self::Output { 57 | Wrapper::(self.0 + A::try_from(rhs.0).expect(&format!("overflow when converting {} to ${A}", rhs.0))) 58 | } 59 | } 60 | 61 | #[test] 62 | fn test() { 63 | assert_eq!(Wrapper(1_u8) + Wrapper(2_u8), Wrapper(3_u8)); 64 | assert_eq!(Wrapper(1_u8) + Wrapper(2_u16), Wrapper(3_u8)); 65 | assert_eq!(Wrapper(1_u16) + Wrapper(2_u8), Wrapper(3_u16)); 66 | assert_eq!(Wrapper(1_u16) + Wrapper(2_u16), Wrapper(3_u16)); 67 | } 68 | 69 | #[test] 70 | #[should_panic] 71 | fn test_fail() { 72 | let x = Wrapper(1_u8) + Wrapper(1000_u16); 73 | println!("{x:?}"); 74 | } 75 | } 76 | 77 | mod advanced_format_permutation_long { 78 | use trait_gen::{trait_gen, trait_gen_if}; 79 | 80 | #[derive(Clone, PartialEq, Debug)] 81 | struct Wrapper(T); 82 | 83 | // The type must be different to avoid the error "conflicting implementation in crate `core`: impl From for T" 84 | #[trait_gen(T, U -> u8, u16, u32)] 85 | #[trait_gen_if(!T in U)] 86 | impl From> for Wrapper { 87 | /// converts ${U} to ${T} 88 | fn from(value: Wrapper) -> Self { 89 | Wrapper(T::try_from(value.0).expect(&format!("overflow when converting {} to ${T}", value.0))) 90 | } 91 | } 92 | 93 | #[test] 94 | fn test() { 95 | // other combinations would trigger the "conflicting implementation" error, 96 | // so no need to explicitly test they're not implemented 97 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u8)); 98 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u16)); 99 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u32)); 100 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u8)); 101 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u16)); 102 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u32)); 103 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u8)); 104 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u16)); 105 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u32)); 106 | } 107 | } 108 | 109 | /// Tests the `#[trait_gen(T != U -> u8, u16, u32)]` format 110 | mod advanced_format_permutation { 111 | use trait_gen::trait_gen; 112 | 113 | #[derive(Clone, PartialEq, Debug)] 114 | struct Wrapper(T); 115 | 116 | // The type must be different to avoid the error "conflicting implementation in crate `core`: impl From for T" 117 | #[trait_gen(T != U -> u8, u16, u32)] 118 | impl From> for Wrapper { 119 | /// converts ${U} to ${T} 120 | fn from(value: Wrapper) -> Self { 121 | Wrapper(T::try_from(value.0).expect(&format!("overflow when converting {} to ${T}", value.0))) 122 | } 123 | } 124 | 125 | #[test] 126 | fn test() { 127 | // other combinations would trigger the "conflicting implementation" error, 128 | // so no need to explicitly test they're not implemented 129 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u8)); 130 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u16)); 131 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u32)); 132 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u8)); 133 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u16)); 134 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u32)); 135 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u8)); 136 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u16)); 137 | assert_eq!(Wrapper::::from(Wrapper(30_u32)), Wrapper(30_u32)); 138 | } 139 | } 140 | 141 | /// Tests the `#[trait_gen(T < U -> u8, u16, u32)]` format 142 | mod advanced_format_strict_order { 143 | use trait_gen::trait_gen; 144 | 145 | #[derive(Clone, PartialEq, Debug)] 146 | struct Wrapper(T); 147 | 148 | // The type must be different to avoid the error "conflicting implementation in crate `core`: impl From for T" 149 | #[trait_gen(T < U -> u8, u16, u32)] 150 | impl From> for Wrapper { 151 | /// converts ${T} to ${U} 152 | fn from(value: Wrapper) -> Self { 153 | Wrapper(U::from(value.0)) 154 | } 155 | } 156 | 157 | #[test] 158 | fn test() { 159 | // other combinations would trigger the "conflicting implementation" or 160 | // "trait `From` is not implemented for `u8`" error, 161 | // so no need to explicitly test they're not implemented 162 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u16)); 163 | assert_eq!(Wrapper::::from(Wrapper(10_u8)), Wrapper(10_u32)); 164 | assert_eq!(Wrapper::::from(Wrapper(20_u16)), Wrapper(20_u32)); 165 | } 166 | } 167 | 168 | /// Tests the `#[trait_gen(T <= U -> u8, u16, u32)]` format 169 | mod advanced_format_non_strict_order { 170 | use std::ops::Add; 171 | use trait_gen::trait_gen; 172 | 173 | #[derive(PartialEq, Debug)] 174 | struct Wrapper(T); 175 | 176 | #[trait_gen(T <= U -> u8, u16, u32)] 177 | impl Add> for Wrapper { 178 | type Output = Wrapper; 179 | 180 | fn add(self, rhs: Wrapper) -> Self::Output { 181 | Wrapper::(self.0 + ::from(rhs.0)) 182 | } 183 | } 184 | 185 | #[test] 186 | fn test() { 187 | // other combinations would trigger the "trait `From` is not implemented for `u8`" 188 | // error, so no need to explicitly test they're not implemented 189 | assert_eq!(Wrapper(1_u8) + Wrapper(10_u8), Wrapper(11_u8)); 190 | assert_eq!(Wrapper(1_u16) + Wrapper(10_u8), Wrapper(11_u16)); 191 | assert_eq!(Wrapper(1_u32) + Wrapper(10_u8), Wrapper(11_u32)); 192 | assert_eq!(Wrapper(1_u16) + Wrapper(10_u16), Wrapper(11_u16)); 193 | assert_eq!(Wrapper(1_u32) + Wrapper(10_u16), Wrapper(11_u32)); 194 | assert_eq!(Wrapper(1_u32) + Wrapper(10_u32), Wrapper(11_u32)); 195 | } 196 | } 197 | 198 | /// Tests the attribute without declaring it in a `use` 199 | mod no_use_example { 200 | #[derive(Clone, PartialEq, Debug)] 201 | struct Wrapper(T); 202 | 203 | #[trait_gen::trait_gen(T -> u8, u16, u32)] 204 | #[trait_gen::trait_gen_if(!T in u8)] 205 | impl From> for Wrapper { 206 | fn from(value: Wrapper) -> Self { 207 | Wrapper(T::from(value.0)) 208 | } 209 | } 210 | 211 | #[test] 212 | fn test() { 213 | let a = Wrapper(10_u8); 214 | assert_eq!(Wrapper::::from(a.clone()), Wrapper(10_u8)); 215 | assert_eq!(Wrapper::::from(a.clone()), Wrapper(10_u16)); 216 | assert_eq!(Wrapper::::from(a), Wrapper(10_u32)); 217 | } 218 | } 219 | 220 | /// Tests the `#[trait_gen_if(T in Metre)]` conditional attribute with one item 221 | mod conditional_code { 222 | use std::ops::Add; 223 | use trait_gen::{trait_gen, trait_gen_if}; 224 | 225 | #[derive(Clone, Copy, Debug, PartialEq)] 226 | struct Metre(f64); 227 | #[derive(Clone, Copy, Debug, PartialEq)] 228 | struct Foot(f64); 229 | // to do: 230 | // struct QuarterPounderWithCheese(u32); 231 | // struct RoyaleWithCheese(u32); 232 | 233 | trait Metrics { 234 | const TO_METRE: f64; 235 | fn to_metre(&self) -> f64; 236 | } 237 | 238 | #[trait_gen(T -> Metre, Foot)] 239 | impl Metrics for T { 240 | #[trait_gen_if(T in Metre)] 241 | const TO_METRE: f64 = 1.0; 242 | #[trait_gen_if(T in Foot)] 243 | const TO_METRE: f64 = 0.3048; 244 | 245 | fn to_metre(&self) -> f64 { 246 | self.0 * Self::TO_METRE 247 | } 248 | } 249 | 250 | #[trait_gen(A, B -> Metre, Foot)] 251 | impl Add for A { 252 | type Output = A; 253 | 254 | fn add(self, rhs: B) -> Self::Output { 255 | A((self.to_metre() + rhs.to_metre())/Self::TO_METRE) 256 | } 257 | } 258 | 259 | #[test] 260 | fn test() { 261 | let a = Metre(1.0); 262 | let b = Metre(3.048); 263 | let c = Foot(20.0); 264 | let d = Foot(25.0); 265 | 266 | let a_b = a + b; 267 | let a_c = a + c; 268 | let c_d = c + d; 269 | let c_b = c + b; 270 | 271 | assert_eq!(a_b, Metre(4.048)); 272 | assert_eq!(a_c, Metre(7.096)); 273 | assert_eq!(c_d, Foot(45.0)); 274 | assert_eq!(c_b, Foot(30.0)) 275 | } 276 | } 277 | 278 | /// Tests the `#[trait_gen_if(T in i8, u8)]` conditional attribute with several items 279 | mod conditional_code2 { 280 | use trait_gen::{trait_gen, trait_gen_if}; 281 | 282 | trait Binary { 283 | const DECIMAL_DIGITS: usize; 284 | const SIGN: bool = false; 285 | 286 | fn display_length() -> usize { 287 | Self::DECIMAL_DIGITS + if Self::SIGN { 1 } else { 0 } 288 | } 289 | 290 | fn try_neg(self) -> Option where Self: Sized { 291 | None 292 | } 293 | } 294 | 295 | #[trait_gen(T -> i8, u8, i32, u32)] 296 | impl Binary for T { 297 | #[trait_gen_if(T in i8, u8)] 298 | const DECIMAL_DIGITS: usize = 3; 299 | #[trait_gen_if(T in i32, u32)] 300 | const DECIMAL_DIGITS: usize = 10; 301 | #[trait_gen_if(T in i8, i32)] 302 | const SIGN: bool = true; 303 | 304 | #[trait_gen_if(T in i8, i32)] 305 | fn try_neg(self) -> Option { 306 | Some(-self) 307 | } 308 | } 309 | 310 | #[test] 311 | fn test() { 312 | assert_eq!(10_i8.try_neg(), Some(-10)); 313 | assert_eq!(20_u8.try_neg(), None); 314 | assert_eq!(30_i32.try_neg(), Some(-30)); 315 | assert_eq!(40_u32.try_neg(), None); 316 | assert_eq!(i8::display_length(), 4); 317 | assert_eq!(u8::display_length(), 3); 318 | assert_eq!(i32::display_length(), 11); 319 | assert_eq!(u32::display_length(), 10); 320 | } 321 | } 322 | 323 | /// Tests the `#[trait_gen_if(!T in U)]` conditional attribute with a negation 324 | mod conditional_code3 { 325 | use trait_gen::{trait_gen, trait_gen_if}; 326 | 327 | trait TypeEq { 328 | fn same_type(&self, other: &U) -> bool; 329 | } 330 | 331 | #[trait_gen(T, U -> u8, u16, u32)] 332 | impl TypeEq for T { 333 | #[trait_gen_if(T in U)] 334 | fn same_type(&self, _other: &U) -> bool { 335 | true 336 | } 337 | #[trait_gen_if(!T in U)] 338 | fn same_type(&self, _other: &U) -> bool { 339 | false 340 | } 341 | } 342 | 343 | #[test] 344 | fn test() { 345 | let a = 10_u8; 346 | let b = 20_u16; 347 | let c = 30_u32; 348 | assert_eq!(a.same_type(&b), false, "a.same_type(&b) failed"); 349 | assert_eq!(a.same_type(&c), false, "a.same_type(&c) failed"); 350 | assert_eq!(b.same_type(&a), false, "b.same_type(&a) failed"); 351 | assert_eq!(b.same_type(&c), false, "b.same_type(&c) failed"); 352 | assert_eq!(c.same_type(&a), false, "c.same_type(&a) failed"); 353 | assert_eq!(c.same_type(&b), false, "c.same_type(&b) failed"); 354 | assert_eq!(a.same_type(&a), true, "a.same_type(&a) failed"); 355 | assert_eq!(b.same_type(&b), true, "b.same_type(&b) failed"); 356 | assert_eq!(c.same_type(&c), true, "c.same_type(&c) failed"); 357 | } 358 | } 359 | 360 | mod type_case_01 { 361 | use trait_gen::trait_gen; 362 | 363 | trait MyLog { 364 | fn my_log2(self) -> u32; 365 | } 366 | 367 | impl MyLog for i32 { 368 | fn my_log2(self) -> u32 { 369 | i32::BITS - 1 - self.leading_zeros() 370 | } 371 | } 372 | 373 | #[trait_gen(my::T -> &i32, &mut i32, Box)] 374 | impl MyLog for my::T { 375 | fn my_log2(self) -> u32 { 376 | MyLog::my_log2(*self) 377 | } 378 | } 379 | 380 | fn show_log2(x: impl MyLog) -> u32 { 381 | x.my_log2() 382 | } 383 | 384 | #[test] 385 | fn test() { 386 | let a = 6; 387 | let p_a = &a; 388 | let mut b = 1023; 389 | let p_b = &mut b; 390 | let box_a = Box::new(a); 391 | 392 | assert_eq!(show_log2(a), 2); 393 | assert_eq!(show_log2(p_a), 2); 394 | assert_eq!(show_log2(p_b), 9); 395 | assert_eq!(show_log2(box_a), 2); 396 | } 397 | } 398 | 399 | mod type_case_02 { 400 | use trait_gen::trait_gen; 401 | 402 | trait MyLog { 403 | fn my_log2(self) -> u32; 404 | } 405 | 406 | #[trait_gen(T -> u8, u16, u32, u64, u128)] 407 | impl MyLog for T { 408 | fn my_log2(self) -> u32 { 409 | T::BITS - 1 - self.leading_zeros() 410 | } 411 | } 412 | 413 | // The order of the attributes doesn't matter: 414 | #[trait_gen(T -> u8, u16, u32, u64, u128)] 415 | #[trait_gen(U -> &T, &mut T, Box)] 416 | impl MyLog for U { 417 | /// Logarithm base 2 for `${U}` 418 | fn my_log2(self) -> u32 { 419 | MyLog::my_log2(*self) 420 | } 421 | } 422 | 423 | fn show_log2(x: impl MyLog) -> u32 { 424 | x.my_log2() 425 | } 426 | 427 | #[test] 428 | fn test() { 429 | let a = 6_u32; 430 | let p_a = &a; 431 | let mut b = 1023_u64; 432 | let p_b = &mut b; 433 | let box_a = Box::new(a); 434 | 435 | assert_eq!(show_log2(a), 2); 436 | assert_eq!(show_log2(p_a), 2); 437 | assert_eq!(show_log2(p_b), 9); 438 | assert_eq!(show_log2(box_a.clone()), 2); 439 | 440 | assert_eq!(a.my_log2(), 2); 441 | assert_eq!((&a).my_log2(), 2); 442 | assert_eq!((&mut b).my_log2(), 9); 443 | assert_eq!(box_a.my_log2(), 2); 444 | } 445 | } 446 | 447 | mod type_case_03 { 448 | use trait_gen::trait_gen; 449 | 450 | trait Name { 451 | fn name(&self) -> String; 452 | } 453 | 454 | #[trait_gen(my::U -> i8, u8, i16, u16, i32, u32, i64, u64, i128, u128)] 455 | #[trait_gen(my::T -> &[my::U; N], &mut [my::U; N], Box<[my::U; N]>)] 456 | impl Name for my::T { 457 | fn name(&self) -> String { 458 | format!("slice of ${my::T} with N = {}", N) 459 | } 460 | } 461 | 462 | fn show_name(value: &impl Name) -> String { 463 | value.name() 464 | } 465 | 466 | #[test] 467 | fn test() { 468 | let a = &[10, 20]; 469 | let b = &mut [10_u32, 15, 20]; 470 | let c = Box::new([5_u64, 6, 7, 8]); 471 | 472 | assert_eq!(a.name(), "slice of &[i32;N] with N = 2"); 473 | assert_eq!(b.name(), "slice of &mut [u32;N] with N = 3"); 474 | assert_eq!(c.name(), "slice of Box::<[u64;N]> with N = 4"); 475 | 476 | assert_eq!(show_name(&a), "slice of &[i32;N] with N = 2"); 477 | assert_eq!(show_name(&b), "slice of &mut [u32;N] with N = 3"); 478 | assert_eq!(show_name(&c), "slice of Box::<[u64;N]> with N = 4"); 479 | } 480 | } 481 | 482 | mod type_case_04 { 483 | use std::ops::Deref; 484 | use trait_gen::trait_gen; 485 | 486 | #[derive(Debug, PartialEq)] 487 | struct Meter(i64); 488 | #[derive(Debug, PartialEq)] 489 | struct Foot(i64); 490 | 491 | trait Negate { 492 | type Output; 493 | fn negate(self) -> Self::Output; 494 | } 495 | 496 | #[trait_gen(T -> Meter, Foot)] 497 | impl Negate for T { 498 | type Output = T; 499 | fn negate(self) -> Self::Output { 500 | T(-self.0) 501 | } 502 | } 503 | 504 | #[trait_gen(U -> &T, &mut T, Box)] 505 | #[trait_gen(T -> Meter, Foot)] 506 | impl Negate for U { 507 | type Output = T; 508 | fn negate(self) -> Self::Output { 509 | #[allow(suspicious_double_ref_op)] // to fix with conditional? 510 | T(-self.deref().0) 511 | } 512 | } 513 | 514 | fn negate(x: T) -> O 515 | where T: Negate 516 | { 517 | x.negate() 518 | } 519 | 520 | #[test] 521 | fn test() { 522 | let x: Meter = Meter(5); 523 | let x_ref: &Meter = &Meter(5); 524 | let y = negate(x); 525 | let y_ref = negate(x_ref); 526 | assert_eq!(y, Meter(-5)); // doesn't need forward definition 527 | assert_eq!(y_ref, Meter(-5)); 528 | } 529 | } 530 | 531 | // Fake types for the tests 532 | struct T { pub offset: u64 } 533 | struct U(u32); 534 | struct Meter(T); 535 | struct Foot(T); 536 | 537 | mod path_case_01 { 538 | use trait_gen::trait_gen; 539 | use std::ops::{Add, Neg}; 540 | 541 | pub mod inner {} 542 | 543 | #[trait_gen(U -> super::Meter, super::Foot)] 544 | impl Add for U { 545 | type Output = U; 546 | 547 | fn add(self, rhs: Self) -> Self::Output { 548 | U(self.0 + rhs.0) 549 | } 550 | } 551 | 552 | #[trait_gen(U -> super::Meter, super::Foot)] 553 | impl Neg for U { 554 | type Output = U; 555 | 556 | fn neg(self) -> Self::Output { 557 | U(-self.0) 558 | } 559 | } 560 | 561 | #[test] 562 | fn test() { 563 | let a = super::Meter::(1.0); 564 | let b = super::Meter::(4.0); 565 | 566 | let c = a + b; 567 | assert_eq!(c.0, 5.0); 568 | let d = -c; 569 | assert_eq!(d.0, -5.0); 570 | 571 | let a = super::Foot::(1.0); 572 | let b = super::Foot::(4.0); 573 | 574 | let c = a + b; 575 | assert_eq!(c.0, 5.0); 576 | let d = -c; 577 | assert_eq!(d.0, -5.0); 578 | } 579 | } 580 | 581 | mod path_case_02 { 582 | struct Meter(T); 583 | struct Foot(T); 584 | 585 | pub mod inner { 586 | use trait_gen::trait_gen; 587 | use std::ops::Add; 588 | 589 | #[trait_gen(g::U -> super::Meter, super::Foot)] 590 | impl Add for g::U { 591 | type Output = g::U; 592 | 593 | fn add(self, rhs: Self) -> Self::Output { 594 | g::U(self.0 + rhs.0) 595 | } 596 | } 597 | 598 | #[test] 599 | fn test() { 600 | let a = super::Meter::(1.0); 601 | let b = super::Meter::(4.0); 602 | 603 | let c = a + b; 604 | assert_eq!(c.0, 5.0); 605 | 606 | let a = super::Foot::(1.0); 607 | let b = super::Foot::(4.0); 608 | 609 | let c = a + b; 610 | assert_eq!(c.0, 5.0); 611 | } 612 | } 613 | } 614 | 615 | mod path_case_03 { 616 | use trait_gen::trait_gen; 617 | use std::fmt::Display; 618 | 619 | struct Name<'a>(&'a str); 620 | struct Value(i32); 621 | struct AnyValue(T); 622 | struct AnyData(T); 623 | 624 | trait Show { 625 | fn show(&self) -> String; 626 | } 627 | 628 | #[trait_gen(T -> Name<'_>, Value)] 629 | impl Show for T { 630 | fn show(&self) -> String { 631 | self.0.to_string() 632 | } 633 | } 634 | 635 | // this would be easier, but testing the more complicated case to illustrate 636 | // how to avoid collisions, and to test a blanket implementation: 637 | // #[trait_gen(T -> AnyValue, AnyData)] 638 | // impl Show for T { 639 | #[trait_gen(T -> AnyValue, AnyData)] 640 | impl Show for T { 641 | fn show(&self) -> String { 642 | self.0.to_string() 643 | } 644 | } 645 | 646 | #[test] 647 | fn test() { 648 | assert_eq!(Name("Bob").show(), "Bob"); 649 | assert_eq!(Value(10).show(), "10"); 650 | assert_eq!(AnyValue(5.0).show(), "5"); 651 | assert_eq!(AnyData("name".to_string()).show(), "name"); 652 | } 653 | } 654 | 655 | mod path_case_04 { 656 | use trait_gen::trait_gen; 657 | 658 | struct Name<'a>(&'a str); 659 | struct Value<'a>(&'a f64); 660 | 661 | trait Show { 662 | fn show(&self) -> String; 663 | } 664 | 665 | #[trait_gen(T -> Name, Value)] 666 | impl Show for T<'_> { 667 | fn show(&self) -> String { 668 | self.0.to_string() 669 | } 670 | } 671 | 672 | #[test] 673 | fn test() { 674 | assert_eq!(Name("Bob").show(), "Bob"); 675 | assert_eq!(Value(&10.0).show(), "10"); 676 | } 677 | } 678 | 679 | mod path_case_05 { 680 | struct Name<'a>(&'a str); 681 | struct Value<'a>(&'a f64); 682 | mod inner { 683 | mod depth { 684 | use trait_gen::trait_gen; 685 | 686 | trait Show { 687 | fn show(&self) -> String; 688 | } 689 | 690 | #[trait_gen(T -> super::super::Name, super::super::Value)] 691 | impl Show for T<'_> { 692 | fn show(&self) -> String { 693 | self.0.to_string() 694 | } 695 | } 696 | 697 | #[test] 698 | fn test() { 699 | assert_eq!(super::super::Name("Bob").show(), "Bob"); 700 | assert_eq!(super::super::Value(&10.0).show(), "10"); 701 | } 702 | } 703 | } 704 | } 705 | 706 | mod path_case_06 { 707 | use trait_gen::trait_gen; 708 | 709 | struct Name<'a>(&'a str); 710 | struct Value<'a>(&'a f64); 711 | 712 | trait Show { 713 | fn show(&self) -> String; 714 | } 715 | 716 | #[trait_gen(g::par -> Name, Value)] 717 | impl Show for g::par<'_> { 718 | fn show(&self) -> String { 719 | self.0.to_string() 720 | } 721 | } 722 | 723 | #[test] 724 | fn test() { 725 | assert_eq!(Name("Bob").show(), "Bob"); 726 | assert_eq!(Value(&10.0).show(), "10"); 727 | } 728 | } 729 | 730 | mod path_case_07 { 731 | use trait_gen::trait_gen; 732 | 733 | #[derive(PartialEq, Debug)] 734 | struct Wrapper(T); 735 | 736 | trait Convert { 737 | fn get(self) -> Wrapper; 738 | } 739 | 740 | #[trait_gen(T -> u16, u32)] 741 | #[trait_gen(U -> Wrapper)] 742 | impl Convert for U { 743 | fn get(self) -> U { 744 | let result: Option>; 745 | result = Some(U::(self.0 as u64)); 746 | result.unwrap() 747 | } 748 | } 749 | 750 | #[test] 751 | fn test() { 752 | assert_eq!(Wrapper(12_u16).get(), Wrapper(12_u64)); 753 | assert_eq!(Wrapper(15_u32).get(), Wrapper(15_u64)); 754 | } 755 | } 756 | 757 | mod turbofish { 758 | use trait_gen::trait_gen; 759 | 760 | struct Wrapper(T); 761 | 762 | trait Data { 763 | type Inner; 764 | fn get(&self) -> Self::Inner; 765 | } 766 | 767 | #[trait_gen(T -> u8, u16)] 768 | #[trait_gen(U:: -> Wrapper)] 769 | impl Data for U:: { 770 | type Inner = T; 771 | fn get(&self) -> Self::Inner { 772 | self.0 773 | } 774 | } 775 | 776 | #[test] 777 | fn test() { 778 | assert_eq!(Wrapper::(1).get(), 1_u8); 779 | assert_eq!(Wrapper(10_u16).get(), 10_u16); 780 | } 781 | } 782 | 783 | mod literals { 784 | use trait_gen::trait_gen; 785 | 786 | trait Lit { 787 | fn text(&self, calls: &mut Vec) -> String; 788 | } 789 | 790 | fn call(calls: &mut Vec, s: &str) { 791 | calls.push(s.to_string()); 792 | } 793 | 794 | #[trait_gen(T -> u32, u64)] 795 | impl Lit for T { 796 | /// Produces a string representation for ${T} 797 | fn text(&self, calls: &mut Vec) -> String { 798 | call(calls, "${T}"); 799 | format!("${T}: {}", self) 800 | } 801 | } 802 | 803 | #[test] 804 | fn test() { 805 | let mut calls = Vec::::new(); 806 | let s_32 = 10_u32.text(&mut calls); 807 | let s_64 = 20_u64.text(&mut calls); 808 | assert_eq!(s_32, "u32: 10"); 809 | assert_eq!(s_64, "u64: 20"); 810 | assert_eq!(calls.join(","), "u32,u64"); 811 | } 812 | } 813 | 814 | mod subst_cases { 815 | use std::ops::{Add, Sub}; 816 | use trait_gen::trait_gen; 817 | 818 | trait AddMod { 819 | fn add_mod(self, other: Self, m: Self) -> Self; 820 | } 821 | 822 | #[trait_gen(U -> u32, i32)] 823 | impl AddMod for U { 824 | fn add_mod(self, other: U, m: U) -> U { 825 | // constant name must stay, type must change: 826 | const U: U = 0; 827 | // U:: type must change, U.add(U) must stay: 828 | let zero1 = U::default() + U.add(U); 829 | let zero2 = U::MAX.sub(U::MAX); 830 | // type must stay: 831 | let offset: super::U = super::U(0); 832 | // constant must stay, cast type must change: 833 | (self + other + U + zero1 + zero2 + offset.0 as U) % m 834 | } 835 | } 836 | 837 | #[test] 838 | fn test_add_mod() { 839 | assert_eq!(10_u32.add_mod(5, 8), 7); 840 | assert_eq!(10_i32.add_mod(5, 8), 7); 841 | } 842 | } 843 | 844 | mod type_args { 845 | use trait_gen::trait_gen; 846 | 847 | trait Number { 848 | #[allow(unused)] 849 | fn fake(x: X) -> T; 850 | } 851 | 852 | #[trait_gen(T -> f32, f64)] 853 | // all trait arguments must change: 854 | impl Number for T { 855 | #[allow(unused)] 856 | /// my fake doc 857 | fn fake(_x: T) -> T { 1.0 as T } 858 | } 859 | 860 | struct Meter(U); 861 | 862 | trait GetLength { 863 | fn length(&self) -> T; 864 | } 865 | 866 | #[trait_gen(U -> f32, f64)] 867 | impl GetLength for Meter { 868 | #[doc = "length for type `Meter<${U}>`"] 869 | fn length(&self) -> U { 870 | // generic ident must not collide, but bound arguments must change: 871 | fn identity>(x: T) -> T { x } 872 | identity(self.0 as U) 873 | } 874 | } 875 | 876 | #[test] 877 | fn test() { 878 | let m_32 = Meter(1.0_f32); 879 | let m_64 = Meter(2.0); 880 | assert_eq!(m_32.length(), 1.0_f32); 881 | assert_eq!(m_64.length(), 2.0_f64); 882 | } 883 | } 884 | 885 | mod type_fn_args { 886 | use trait_gen::trait_gen; 887 | 888 | trait Transformer { 889 | fn transform T>(&self, f: F) -> Vec; 890 | } 891 | 892 | #[trait_gen(T -> i64, f64)] 893 | impl Transformer for Vec { 894 | fn transform T>(&self, f: F) -> Vec { 895 | self.iter().map(|&x| f(x)).collect() 896 | } 897 | } 898 | 899 | #[test] 900 | fn test() { 901 | let vi = vec![1_i64, 2, 3, 4, 5]; 902 | let vf = vec![1.0_f64, 4.0, 9.0, 16.0, 25.0]; 903 | let transformed_vi = vi.transform(|x| x * x); 904 | let transformed_vf = vf.transform(|x| x.sqrt()); 905 | assert_eq!(transformed_vi, [1, 4, 9, 16, 25]); 906 | assert_eq!(transformed_vf, [1.0, 2.0, 3.0, 4.0, 5.0]); 907 | } 908 | } 909 | 910 | mod cross_product { 911 | use std::ops::Neg; 912 | use trait_gen::trait_gen; 913 | 914 | #[derive(PartialEq, Debug)] 915 | struct Meter(U); 916 | #[derive(PartialEq, Debug)] 917 | struct Foot(U); 918 | 919 | trait GetLength { 920 | fn length(&self) -> T; 921 | } 922 | 923 | // Type method implementations 924 | 925 | #[trait_gen(T -> f32, f64)] 926 | #[trait_gen(U -> Meter, Foot)] 927 | impl U { 928 | fn scale(&self, value: T) -> U { 929 | U(self.0 * value) 930 | } 931 | } 932 | 933 | // Without the macro: 934 | // 935 | // use std::ops::Mul; 936 | // 937 | // impl + Copy> Meter { 938 | // fn scale(&self, value: T) -> Meter { 939 | // Meter(self.0 * value) 940 | // } 941 | // } 942 | // 943 | // impl + Copy> Foot { 944 | // fn scale(&self, value: T) -> Foot { 945 | // Foot(self.0 * value) 946 | // } 947 | // } 948 | 949 | // Trait implementations 950 | 951 | #[trait_gen(T -> Meter, Foot)] 952 | #[trait_gen(U -> f32, f64)] 953 | impl GetLength for T { 954 | fn length(&self) -> U { 955 | self.0 as U 956 | } 957 | } 958 | 959 | #[trait_gen(T -> Meter, Foot)] 960 | #[trait_gen(U -> f32, f64)] 961 | impl Neg for T { 962 | type Output = T; 963 | 964 | fn neg(self) -> Self::Output { 965 | T(-self.0) 966 | } 967 | } 968 | 969 | #[test] 970 | fn test() { 971 | let m_32 = Meter(1.0_f32); 972 | let m_64 = Meter(2.0_f64); 973 | let f_32 = Foot(3.0_f32); 974 | let f_64 = Foot(4.0_f64); 975 | assert_eq!(m_32.length(), 1.0_f32); 976 | assert_eq!(m_64.length(), 2.0_f64); 977 | assert_eq!(f_32.length(), 3.0_f32); 978 | assert_eq!(f_64.length(), 4.0_f64); 979 | assert_eq!(m_32.scale(10.0), Meter(10.0_f32)); 980 | assert_eq!(m_64.scale(10.0), Meter(20.0_f64)); 981 | assert_eq!(f_32.scale(10.0), Foot(30.0_f32)); 982 | assert_eq!(f_64.scale(10.0), Foot(40.0_f64)); 983 | assert_eq!(-m_32, Meter(-1.0_f32)); 984 | assert_eq!(-m_64, Meter(-2.0_f64)); 985 | assert_eq!(-f_32, Foot(-3.0_f32)); 986 | assert_eq!(-f_64, Foot(-4.0_f64)); 987 | } 988 | } 989 | 990 | mod impl_cond { 991 | use trait_gen::{trait_gen, trait_gen_if}; 992 | 993 | trait Binary { 994 | const MSB: u32; 995 | const IS_REF: bool; 996 | fn msb(self) -> u32; 997 | fn is_ref(self) -> bool; 998 | } 999 | 1000 | // with this ordering, the #[trait_gen_if] see either 1001 | // - T = u8, u16, or u32 1002 | // - U = u8, u16, u32, &u8, &u16, or &u32 1003 | #[trait_gen(T -> u8, u16, u32)] 1004 | #[trait_gen(U -> T, &T)] 1005 | impl Binary for U { 1006 | #[trait_gen_if(U in [u8, &u8])] // #[trait_gen_if(T in u8])] works, too 1007 | const MSB: u32 = 7; 1008 | #[trait_gen_if(U in u16, &u16)] 1009 | const MSB: u32 = 15; 1010 | #[trait_gen_if(U in u32, &u32)] 1011 | const MSB: u32 = 31; 1012 | #[trait_gen_if(U in u64, &u64)] 1013 | const MSB: u32 = 63; 1014 | #[trait_gen_if(U in u128, &u128)] 1015 | const MSB: u32 = 127; 1016 | #[trait_gen_if(U in u8, u16, u32)] 1017 | const IS_REF: bool = false; 1018 | #[trait_gen_if(U in &u8, &u16, &u32)] 1019 | const IS_REF: bool = true; 1020 | 1021 | fn msb(self) -> u32 { 1022 | Self::MSB 1023 | } 1024 | 1025 | /// Is ${U} a reference? 1026 | fn is_ref(self) -> bool { 1027 | Self::IS_REF 1028 | } 1029 | } 1030 | 1031 | #[test] 1032 | fn test_msb() { 1033 | let tests = vec![ 1034 | (1_u8.msb(), 7, 1_u8.is_ref(), false), 1035 | ((&1_u8).msb(), 7, (&1_u8).is_ref(), true), 1036 | (1_u16.msb(), 15, 1_u16.is_ref(), false), 1037 | ((&1_u16).msb(), 15, (&1_u8).is_ref(), true), 1038 | (1_u32.msb(), 31, 1_u32.is_ref(), false), 1039 | ((&1_u32).msb(), 31, (&1_u32).is_ref(), true), 1040 | ]; 1041 | for (index, (result_msb, expected_msb, result_is_ref, expected_is_ref)) in tests.into_iter().enumerate() { 1042 | assert_eq!(result_msb, expected_msb, "test {index} failed on msb"); 1043 | assert_eq!(result_is_ref, expected_is_ref, "test {index} failed on is_ref"); 1044 | } 1045 | } 1046 | } 1047 | 1048 | mod impl_cond2 { 1049 | use trait_gen::{trait_gen, trait_gen_if}; 1050 | 1051 | trait Binary { 1052 | const MSB: u32; 1053 | const IS_REF: bool; 1054 | fn msb(self) -> u32; 1055 | fn is_ref(self) -> bool; 1056 | } 1057 | 1058 | // with this ordering, the #[trait_gen_if] see either 1059 | // - U = T or &T 1060 | // - T = u8, u16, or u32 1061 | #[trait_gen(U -> T, &T)] 1062 | #[trait_gen(T -> u8, u16, u32)] 1063 | impl Binary for U { 1064 | #[trait_gen_if(T in u8)] 1065 | const MSB: u32 = 7; 1066 | #[trait_gen_if(T in u16)] 1067 | const MSB: u32 = 15; 1068 | #[trait_gen_if(T in u32)] 1069 | const MSB: u32 = 31; 1070 | #[trait_gen_if(T in u64)] 1071 | const MSB: u32 = 63; 1072 | #[trait_gen_if(T in u128)] 1073 | const MSB: u32 = 127; 1074 | 1075 | #[trait_gen_if(U in T)] 1076 | const IS_REF: bool = false; 1077 | #[trait_gen_if(U in &T)] 1078 | const IS_REF: bool = true; 1079 | 1080 | fn msb(self) -> u32 { 1081 | Self::MSB 1082 | } 1083 | 1084 | /// Is ${U} a reference? 1085 | fn is_ref(self) -> bool { 1086 | Self::IS_REF 1087 | } 1088 | } 1089 | 1090 | #[test] 1091 | fn test_msb() { 1092 | let tests = vec![ 1093 | (1_u8.msb(), 7, 1_u8.is_ref(), false), 1094 | ((&1_u8).msb(), 7, (&1_u8).is_ref(), true), 1095 | (1_u16.msb(), 15, 1_u16.is_ref(), false), 1096 | ((&1_u16).msb(), 15, (&1_u8).is_ref(), true), 1097 | (1_u32.msb(), 31, 1_u32.is_ref(), false), 1098 | ((&1_u32).msb(), 31, (&1_u32).is_ref(), true), 1099 | ]; 1100 | for (index, (result_msb, expected_msb, result_is_ref, expected_is_ref)) in tests.into_iter().enumerate() { 1101 | assert_eq!(result_msb, expected_msb, "test {index} failed on msb"); 1102 | assert_eq!(result_is_ref, expected_is_ref, "test {index} failed on is_ref"); 1103 | } 1104 | } 1105 | } 1106 | 1107 | mod ex01a { 1108 | use std::ops::Add; 1109 | use trait_gen::trait_gen; 1110 | 1111 | #[derive(Clone, Copy)] 1112 | /// Length in meter 1113 | struct Meter(f64); 1114 | 1115 | #[derive(Clone, Copy)] 1116 | /// Length in foot 1117 | struct Foot(f64); 1118 | 1119 | #[derive(Clone, Copy)] 1120 | /// Length in miles 1121 | struct Mile(f64); 1122 | 1123 | // T may be defined as a work-around to get syntactic awareness with the IntelliJ plugin, 1124 | // which doesn't support procedural macros at the moment. With this macro syntax, it 1125 | // doesn't matter whether T is defined or not. 1126 | #[allow(dead_code)] 1127 | type T = Meter; 1128 | 1129 | #[trait_gen(T -> Meter, Foot, Mile)] 1130 | impl Add for T { 1131 | type Output = T; 1132 | 1133 | fn add(self, rhs: T) -> Self::Output { 1134 | const T: f64 = 0.0; 1135 | // constructor must change: 1136 | T(self.0 + rhs.0 + T) 1137 | } 1138 | } 1139 | 1140 | // Usage of `Self(value)` since an alias cannot be used as constructor: 1141 | #[trait_gen(T -> Meter, Foot, Mile)] 1142 | impl Default for T { 1143 | fn default() -> Self { 1144 | Self(0.0) 1145 | } 1146 | } 1147 | 1148 | #[test] 1149 | fn test_original_type() { 1150 | let a_m = Meter(1.0); 1151 | let b_m = Meter(2.0); 1152 | let c_m = a_m + b_m + Meter::default(); 1153 | assert_eq!(c_m.0, 3.0); 1154 | } 1155 | 1156 | #[test] 1157 | fn test_generated_types() { 1158 | let a_ft = Foot(1.0); 1159 | let b_ft = Foot(2.0); 1160 | let c_ft = a_ft + b_ft + Foot::default(); 1161 | assert_eq!(c_ft.0, 3.0); 1162 | 1163 | let a_mi = Mile(1.0); 1164 | let b_mi = Mile(2.0); 1165 | let c_mi = a_mi + b_mi + Mile::default(); 1166 | assert_eq!(c_mi.0, 3.0); 1167 | } 1168 | } 1169 | 1170 | mod ex02a { 1171 | use trait_gen::trait_gen; 1172 | 1173 | trait AddMod { 1174 | fn add_mod(self, other: Self, m: Self) -> Self; 1175 | } 1176 | 1177 | // No need to use `type T = u32` in such a simple case: 1178 | #[trait_gen(u32 -> u32, i32, u64, i64, f32, f64)] 1179 | impl AddMod for u32 { 1180 | fn add_mod(self, other: Self, m: Self) -> Self { 1181 | (self + other) % m 1182 | } 1183 | } 1184 | 1185 | #[test] 1186 | fn test_add_mod() { 1187 | assert_eq!(10_u32.add_mod(5, 8), 7); 1188 | assert_eq!(10_i32.add_mod(5, 8), 7); 1189 | assert_eq!(10_u64.add_mod(5, 8), 7); 1190 | assert_eq!(10_i64.add_mod(5, 8), 7); 1191 | assert_eq!(10_f32.add_mod(5.0, 8.0), 7.0); 1192 | assert_eq!(10_f64.add_mod(5.0, 8.0), 7.0); 1193 | } 1194 | } 1195 | 1196 | mod ex03a { 1197 | use trait_gen::trait_gen; 1198 | 1199 | trait ToU64 { 1200 | fn into_u64(self) -> u64; 1201 | } 1202 | 1203 | #[trait_gen(T -> u64, i64, u32, i32, u16, i16, u8, i8)] 1204 | impl ToU64 for T { 1205 | /// Transforms the value into a `u64` type 1206 | fn into_u64(self) -> u64 { 1207 | // type and constructor must not change because it doesn't start with 'T': 1208 | let x: super::T = super::T { offset: 0 }; 1209 | const T: u64 = 0; 1210 | self as u64 + T + x.offset 1211 | } 1212 | } 1213 | 1214 | #[test] 1215 | fn test() { 1216 | let a = 10_u64; 1217 | let b = 10_i64; 1218 | let c = 10_u32; 1219 | let d = 10_i32; 1220 | let e = 10_u16; 1221 | let f = 10_i16; 1222 | let g = 10_u8; 1223 | let h = 10_i8; 1224 | 1225 | assert_eq!(a.into_u64(), 10_u64); 1226 | assert_eq!(b.into_u64(), 10_u64); 1227 | assert_eq!(c.into_u64(), 10_u64); 1228 | assert_eq!(d.into_u64(), 10_u64); 1229 | assert_eq!(e.into_u64(), 10_u64); 1230 | assert_eq!(f.into_u64(), 10_u64); 1231 | assert_eq!(g.into_u64(), 10_u64); 1232 | assert_eq!(h.into_u64(), 10_u64); 1233 | } 1234 | } 1235 | 1236 | mod ex04 { 1237 | use trait_gen::trait_gen; 1238 | 1239 | trait A where Self: Sized { 1240 | fn a(self) -> i32 { 1 } 1241 | } 1242 | 1243 | trait B where Self: Sized { 1244 | fn b(self) -> i32 { 2 } 1245 | } 1246 | 1247 | // Use the macro to generate multiple traits for different types 1248 | #[trait_gen(T -> u32, i32, u64, i64)] 1249 | #[trait_gen(U -> A, B)] 1250 | impl U for T {} 1251 | 1252 | #[test] 1253 | fn test() { 1254 | let u = 10_u32; 1255 | let v = 10_i32; 1256 | let w = 10_u64; 1257 | let x = 10_i64; 1258 | 1259 | assert_eq!(u.a(), 1); 1260 | assert_eq!(v.a(), 1); 1261 | assert_eq!(w.a(), 1); 1262 | assert_eq!(x.a(), 1); 1263 | 1264 | assert_eq!(u.b(), 2); 1265 | assert_eq!(v.b(), 2); 1266 | assert_eq!(w.b(), 2); 1267 | assert_eq!(x.b(), 2); 1268 | } 1269 | } 1270 | 1271 | mod ex03b { 1272 | use trait_gen::trait_gen; 1273 | 1274 | trait ToU64 { 1275 | fn into_u64(self) -> u64; 1276 | } 1277 | 1278 | // This doesn't work because the 'u64' return type of 'into_u64' would be substituted too: 1279 | // 1280 | // #[trait_gen(u64, i64, u32, i32, u16, i16, u8, i8)] 1281 | // impl ToU64 for u64 { 1282 | // fn into_u64(self) -> u64 { 1283 | // self as u64 1284 | // } 1285 | // } 1286 | 1287 | // type T = u64; 1288 | 1289 | #[trait_gen(T -> u64, i64, u32, i32, u16, i16, u8, i8)] 1290 | impl ToU64 for T { 1291 | /// Transforms the value into a `u64` type 1292 | fn into_u64(self) -> u64 { 1293 | // Type paths with a 'T' segment are fine, they won't be substituted: 1294 | let x: super::T = super::T { offset: 0 }; 1295 | 1296 | // Constant names with the same name as the substituted type are fine: 1297 | // (same for variable and functions, though they shouldn't have the same case) 1298 | const T: u64 = 0; 1299 | 1300 | self as u64 + T + x.offset 1301 | } 1302 | } 1303 | 1304 | #[test] 1305 | fn test() { 1306 | let a = 10_u64; 1307 | let b = 10_i64; 1308 | let c = 10_u32; 1309 | let d = 10_i32; 1310 | let e = 10_u16; 1311 | let f = 10_i16; 1312 | let g = 10_u8; 1313 | let h = 10_i8; 1314 | 1315 | assert_eq!(a.into_u64(), 10_u64); 1316 | assert_eq!(b.into_u64(), 10_u64); 1317 | assert_eq!(c.into_u64(), 10_u64); 1318 | assert_eq!(d.into_u64(), 10_u64); 1319 | assert_eq!(e.into_u64(), 10_u64); 1320 | assert_eq!(f.into_u64(), 10_u64); 1321 | assert_eq!(g.into_u64(), 10_u64); 1322 | assert_eq!(h.into_u64(), 10_u64); 1323 | } 1324 | } 1325 | 1326 | // ============================================================================= 1327 | // Non-trait implementations. 1328 | // ----------------------------------------------------------------------------- 1329 | 1330 | mod impl_type_01 { 1331 | use trait_gen::trait_gen; 1332 | use super::{Foot, Meter}; 1333 | 1334 | #[trait_gen(T -> f32, f64)] 1335 | impl Foot { 1336 | const METERS_TO_FEET: T = 3.372; 1337 | fn from_meter(x: Meter) -> Self { 1338 | Foot(x.0 * Self::METERS_TO_FEET) 1339 | } 1340 | } 1341 | 1342 | #[test] 1343 | fn test() { 1344 | assert_eq!(Foot::::from_meter(Meter(1.0_f32)).0, 3.372_f32); 1345 | assert_eq!(Foot::::from_meter(Meter(1.0_f64)).0, 3.372_f64); 1346 | assert_eq!(Foot::::METERS_TO_FEET, 3.372_f32); 1347 | assert_eq!(Foot::::METERS_TO_FEET, 3.372_f64); 1348 | } 1349 | } 1350 | 1351 | mod impl_type_02 { 1352 | use trait_gen::trait_gen; 1353 | use super::{Foot, Meter}; 1354 | 1355 | #[trait_gen(T -> f32, f64)] 1356 | impl Meter { 1357 | fn from_foot(x: Foot) -> Self where T: From { 1358 | Meter(T::from(x.0) / 3.372) 1359 | } 1360 | } 1361 | 1362 | #[test] 1363 | fn test() { 1364 | assert!((Meter::::from_foot(Foot(1.0_f32)).0 - 0.29656_f32).abs() < 1e-5); 1365 | assert!((Meter::::from_foot(Foot(1.0_f32)).0 - 0.29656_f64).abs() < 1e-5); 1366 | assert!((Meter::::from_foot(Foot(1.0_f64)).0 - 0.29656_f64).abs() < 1e-5); 1367 | } 1368 | } 1369 | 1370 | #[cfg(not(feature = "no_type_gen"))] 1371 | mod type_gen { 1372 | use trait_gen::{type_gen, type_gen_if}; 1373 | 1374 | struct Meter(T); 1375 | struct Foot(T); 1376 | 1377 | #[type_gen(T -> f32, f64)] 1378 | impl Foot { 1379 | #[type_gen_if(T in f32)] 1380 | const METERS_TO_FEET: T = 3.37; // rounded for the sake of the test 1381 | #[type_gen_if(T in f64)] 1382 | const METERS_TO_FEET: T = 3.372; 1383 | fn from_meter(x: Meter) -> Self { 1384 | Foot(x.0 * Self::METERS_TO_FEET) 1385 | } 1386 | } 1387 | 1388 | #[test] 1389 | fn test() { 1390 | assert_eq!(Foot::::from_meter(Meter(1.0_f32)).0, 3.37_f32); 1391 | assert_eq!(Foot::::from_meter(Meter(1.0_f64)).0, 3.372_f64); 1392 | assert_eq!(Foot::::METERS_TO_FEET, 3.37_f32); 1393 | assert_eq!(Foot::::METERS_TO_FEET, 3.372_f64); 1394 | } 1395 | } 1396 | 1397 | mod trait_gen_if_alone { 1398 | use trait_gen::trait_gen_if; 1399 | 1400 | // Note: this attribute really shouldn't be used alone. If you want to disable code, use `#[cfg(any())]`. 1401 | 1402 | #[trait_gen_if(() in ())] 1403 | struct Necessary; 1404 | 1405 | #[trait_gen_if(!() in ())] 1406 | #[test] 1407 | fn failure() { 1408 | panic!("This shouldn't exist") 1409 | } 1410 | 1411 | #[test] 1412 | fn test() { 1413 | let _a: Necessary; 1414 | } 1415 | } 1416 | -------------------------------------------------------------------------------- /tests/test_ui.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn ui() { 3 | let t = trybuild::TestCases::new(); 4 | t.compile_fail("tests/ui/*.rs"); 5 | } 6 | -------------------------------------------------------------------------------- /tests/ui/trait_gen.rs: -------------------------------------------------------------------------------- 1 | use trait_gen::trait_gen; 2 | 3 | struct Test(T); 4 | 5 | // missing generic argument 6 | #[trait_gen(-> i16, u16)] 7 | impl Test { 8 | fn test() -> bool { true } 9 | } 10 | 11 | // old 'in' format instead of '->' 12 | #[trait_gen(T in i16, u16)] 13 | impl Test { 14 | fn test() -> bool { true } 15 | } 16 | 17 | // missing types 18 | #[trait_gen(T -> )] 19 | impl Test { 20 | fn test() -> bool { true } 21 | } 22 | 23 | // missing punctuation 24 | #[trait_gen(T -> i16 u16)] 25 | impl Test { 26 | fn test() -> bool { true } 27 | } 28 | 29 | // missing arguments 30 | #[trait_gen] 31 | impl Test { 32 | fn test() -> bool { true } 33 | } 34 | 35 | // missing arguments 36 | #[trait_gen()] 37 | impl Test { 38 | fn test() -> bool { true } 39 | } 40 | 41 | // bad punctuation: ';' instead of ',' 42 | #[trait_gen(T; U -> u16, u32)] 43 | impl Test { 44 | fn test() -> bool { true } 45 | } 46 | 47 | // bad punctuation: '!!' instead of '!=' 48 | #[trait_gen(T !! U -> u16, u32)] 49 | impl Test { 50 | fn test() -> bool { true } 51 | } 52 | 53 | // conflict between generic arguments 54 | #[trait_gen(T -> u64, i64, u32, i32)] 55 | impl AddMod for T { 56 | type Output = T; 57 | 58 | fn add_mod(self, rhs: Self, modulo: Self) -> Self::Output { 59 | fn int_mod (a: T, m: T) -> T { // <== ERROR, conflicting 'T' 60 | a % m 61 | } 62 | int_mod(self + rhs, modulo) 63 | } 64 | } 65 | 66 | fn main() {} -------------------------------------------------------------------------------- /tests/ui/trait_gen.stderr: -------------------------------------------------------------------------------- 1 | error: expected identifier 2 | 3 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 4 | 5 | --> tests/ui/trait_gen.rs:6:13 6 | | 7 | 6 | #[trait_gen(-> i16, u16)] 8 | | ^ 9 | 10 | error: expected `->` 11 | 12 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 13 | 14 | --> tests/ui/trait_gen.rs:12:15 15 | | 16 | 12 | #[trait_gen(T in i16, u16)] 17 | | ^^ 18 | 19 | error: expected type after '->' 20 | 21 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 22 | 23 | --> tests/ui/trait_gen.rs:18:1 24 | | 25 | 18 | #[trait_gen(T -> )] 26 | | ^^^^^^^^^^^^^^^^^^^ 27 | | 28 | = note: this error originates in the attribute macro `trait_gen` (in Nightly builds, run with -Z macro-backtrace for more info) 29 | 30 | error: expected `,` 31 | 32 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 33 | 34 | --> tests/ui/trait_gen.rs:24:22 35 | | 36 | 24 | #[trait_gen(T -> i16 u16)] 37 | | ^^^ 38 | 39 | error: unexpected end of input, expected identifier 40 | 41 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 42 | 43 | --> tests/ui/trait_gen.rs:30:1 44 | | 45 | 30 | #[trait_gen] 46 | | ^^^^^^^^^^^^ 47 | | 48 | = note: this error originates in the attribute macro `trait_gen` (in Nightly builds, run with -Z macro-backtrace for more info) 49 | 50 | error: unexpected end of input, expected identifier 51 | 52 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 53 | 54 | --> tests/ui/trait_gen.rs:36:1 55 | | 56 | 36 | #[trait_gen()] 57 | | ^^^^^^^^^^^^^^ 58 | | 59 | = note: this error originates in the attribute macro `trait_gen` (in Nightly builds, run with -Z macro-backtrace for more info) 60 | 61 | error: expected `->` 62 | 63 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 64 | 65 | --> tests/ui/trait_gen.rs:42:14 66 | | 67 | 42 | #[trait_gen(T; U -> u16, u32)] 68 | | ^ 69 | 70 | error: expected `=` 71 | 72 | = help: The expected format is: #[trait_gen(T -> Type1, Type2, Type3)] 73 | 74 | --> tests/ui/trait_gen.rs:48:16 75 | | 76 | 48 | #[trait_gen(T !! U -> u16, u32)] 77 | | ^ 78 | 79 | error: Type 'T' is reserved for the substitution. 80 | 81 | = help: Use another identifier for this local generic type. 82 | 83 | --> tests/ui/trait_gen.rs:59:20 84 | | 85 | 59 | fn int_mod (a: T, m: T) -> T { // <== ERROR, conflicting 'T' 86 | | ^ 87 | -------------------------------------------------------------------------------- /tests/ui/trait_gen_if.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | use trait_gen::{trait_gen, trait_gen_if}; 3 | 4 | trait Binary { 5 | const MSB: u32; 6 | } 7 | 8 | // missing generic argument 9 | #[trait_gen(T -> u8, u16, u32)] 10 | impl Binary for U { 11 | #[trait_gen_if( in u8)] 12 | const MSB: u32 = 7; 13 | } 14 | 15 | // '->' instead of 'in' 16 | #[trait_gen(T -> u8, u16, u32)] 17 | impl Binary for U { 18 | #[trait_gen_if(U -> u8)] 19 | const MSB: u32 = 7; 20 | } 21 | 22 | // missing types 23 | #[trait_gen(T -> u8, u16, u32)] 24 | impl Binary for U { 25 | #[trait_gen_if(T in)] 26 | const MSB: u32 = 7; 27 | } 28 | 29 | // missing punctuation 30 | #[trait_gen(T -> u8, &u8, u16, &u16)] 31 | impl Binary for U { 32 | #[trait_gen_if(T in u8 &u8)] 33 | const MSB: u32 = 7; 34 | } 35 | 36 | // missing arguments 37 | #[trait_gen(T -> u8, &u8, u16, &u16)] 38 | impl Binary for U { 39 | #[trait_gen_if] 40 | const MSB: u32 = 7; 41 | } 42 | 43 | // missing arguments 44 | #[trait_gen(T -> u8, &u8, u16, &u16)] 45 | impl Binary for U { 46 | #[trait_gen_if()] 47 | const MSB: u32 = 7; 48 | } 49 | 50 | fn main() {} -------------------------------------------------------------------------------- /tests/ui/trait_gen_if.stderr: -------------------------------------------------------------------------------- 1 | error: expected substitution identifier or type 2 | 3 | = help: The expected format is: #[trait_gen_if(T in u8, u16, u32)] 4 | 5 | --> tests/ui/trait_gen_if.rs:11:21 6 | | 7 | 11 | #[trait_gen_if( in u8)] 8 | | ^^ 9 | 10 | error: expected `in` 11 | 12 | = help: The expected format is: #[trait_gen_if(T in u8, u16, u32)] 13 | 14 | --> tests/ui/trait_gen_if.rs:18:22 15 | | 16 | 18 | #[trait_gen_if(U -> u8)] 17 | | ^ 18 | 19 | error: expected type after 'in' 20 | 21 | = help: The expected format is: #[trait_gen_if(T in u8, u16, u32)] 22 | 23 | --> tests/ui/trait_gen_if.rs:25:24 24 | | 25 | 25 | #[trait_gen_if(T in)] 26 | | ^ 27 | 28 | error: expected `,` 29 | 30 | = help: The expected format is: #[trait_gen_if(T in u8, &u8, u16)] 31 | 32 | --> tests/ui/trait_gen_if.rs:32:28 33 | | 34 | 32 | #[trait_gen_if(T in u8 &u8)] 35 | | ^ 36 | 37 | error: expected attribute arguments in parentheses: #[trait_gen_if(...)] 38 | 39 | = help: The expected format is: #[trait_gen_if(T in u8, &u8, u16)] 40 | 41 | --> tests/ui/trait_gen_if.rs:39:7 42 | | 43 | 39 | #[trait_gen_if] 44 | | ^^^^^^^^^^^^ 45 | 46 | error: expected substitution identifier or type 47 | 48 | = help: The expected format is: #[trait_gen_if(T in u8, &u8, u16)] 49 | 50 | --> tests/ui/trait_gen_if.rs:46:20 51 | | 52 | 46 | #[trait_gen_if()] 53 | | ^ 54 | --------------------------------------------------------------------------------