├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src └── lib.rs ├── testcrate ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── compile-fail.rs │ └── compile-fail │ ├── enum-with-discriminants.rs │ ├── enum-zero-variants.rs │ ├── multiple-new-attrs.rs │ ├── new-invalid-attr-type.rs │ ├── new-invalid-value.rs │ ├── new-list-invalid.rs │ ├── new-name-value-invalid.rs │ ├── new-nested-list.rs │ ├── new-nested-literal.rs │ └── new-non-literal-value.rs └── tests └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: rust 3 | 4 | matrix: 5 | include: 6 | - rust: 1.56.1 7 | - rust: stable 8 | - rust: beta 9 | - rust: nightly 10 | script: 11 | - cargo test --verbose 12 | - rm target/debug/deps/libderive_new-*.so 13 | - cargo test --verbose --manifest-path testcrate/Cargo.toml 14 | 15 | env: 16 | global: 17 | - RUST_BACKTRACE=1 18 | 19 | script: 20 | - cargo test --verbose 21 | -------------------------------------------------------------------------------- /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 = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "bitflags" 16 | version = "2.6.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 19 | 20 | [[package]] 21 | name = "cfg-if" 22 | version = "1.0.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 25 | 26 | [[package]] 27 | name = "compiletest_rs" 28 | version = "0.11.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "2b0f4b0a27f9efcea6a012305682f0f7c5691df7097b9eaf6abb50b75c89a8af" 31 | dependencies = [ 32 | "diff", 33 | "filetime", 34 | "getopts", 35 | "lazy_static", 36 | "libc", 37 | "log", 38 | "miow", 39 | "regex", 40 | "rustfix", 41 | "serde", 42 | "serde_derive", 43 | "serde_json", 44 | "tester", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "derive-new" 50 | version = "0.7.0" 51 | dependencies = [ 52 | "proc-macro2", 53 | "quote", 54 | "syn", 55 | ] 56 | 57 | [[package]] 58 | name = "diff" 59 | version = "0.1.13" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 62 | 63 | [[package]] 64 | name = "dirs-next" 65 | version = "2.0.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 68 | dependencies = [ 69 | "cfg-if", 70 | "dirs-sys-next", 71 | ] 72 | 73 | [[package]] 74 | name = "dirs-sys-next" 75 | version = "0.1.2" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" 78 | dependencies = [ 79 | "libc", 80 | "redox_users", 81 | "winapi", 82 | ] 83 | 84 | [[package]] 85 | name = "filetime" 86 | version = "0.2.25" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" 89 | dependencies = [ 90 | "cfg-if", 91 | "libc", 92 | "libredox", 93 | "windows-sys 0.59.0", 94 | ] 95 | 96 | [[package]] 97 | name = "getopts" 98 | version = "0.2.21" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" 101 | dependencies = [ 102 | "unicode-width", 103 | ] 104 | 105 | [[package]] 106 | name = "getrandom" 107 | version = "0.2.15" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 110 | dependencies = [ 111 | "cfg-if", 112 | "libc", 113 | "wasi", 114 | ] 115 | 116 | [[package]] 117 | name = "hermit-abi" 118 | version = "0.3.9" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 121 | 122 | [[package]] 123 | name = "itoa" 124 | version = "1.0.11" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 127 | 128 | [[package]] 129 | name = "lazy_static" 130 | version = "1.5.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 133 | 134 | [[package]] 135 | name = "libc" 136 | version = "0.2.158" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 139 | 140 | [[package]] 141 | name = "libredox" 142 | version = "0.1.3" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" 145 | dependencies = [ 146 | "bitflags", 147 | "libc", 148 | "redox_syscall", 149 | ] 150 | 151 | [[package]] 152 | name = "log" 153 | version = "0.4.22" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 156 | 157 | [[package]] 158 | name = "memchr" 159 | version = "2.7.4" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 162 | 163 | [[package]] 164 | name = "miow" 165 | version = "0.5.0" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "52ffbca2f655e33c08be35d87278e5b18b89550a37dbd598c20db92f6a471123" 168 | dependencies = [ 169 | "windows-sys 0.42.0", 170 | ] 171 | 172 | [[package]] 173 | name = "num_cpus" 174 | version = "1.16.0" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" 177 | dependencies = [ 178 | "hermit-abi", 179 | "libc", 180 | ] 181 | 182 | [[package]] 183 | name = "once_cell" 184 | version = "1.19.0" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 187 | 188 | [[package]] 189 | name = "pin-project-lite" 190 | version = "0.2.14" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 193 | 194 | [[package]] 195 | name = "proc-macro2" 196 | version = "1.0.86" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 199 | dependencies = [ 200 | "unicode-ident", 201 | ] 202 | 203 | [[package]] 204 | name = "quote" 205 | version = "1.0.37" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 208 | dependencies = [ 209 | "proc-macro2", 210 | ] 211 | 212 | [[package]] 213 | name = "redox_syscall" 214 | version = "0.5.3" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" 217 | dependencies = [ 218 | "bitflags", 219 | ] 220 | 221 | [[package]] 222 | name = "redox_users" 223 | version = "0.4.6" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" 226 | dependencies = [ 227 | "getrandom", 228 | "libredox", 229 | "thiserror", 230 | ] 231 | 232 | [[package]] 233 | name = "regex" 234 | version = "1.10.6" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 237 | dependencies = [ 238 | "aho-corasick", 239 | "memchr", 240 | "regex-automata", 241 | "regex-syntax", 242 | ] 243 | 244 | [[package]] 245 | name = "regex-automata" 246 | version = "0.4.7" 247 | source = "registry+https://github.com/rust-lang/crates.io-index" 248 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 249 | dependencies = [ 250 | "aho-corasick", 251 | "memchr", 252 | "regex-syntax", 253 | ] 254 | 255 | [[package]] 256 | name = "regex-syntax" 257 | version = "0.8.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 260 | 261 | [[package]] 262 | name = "rustfix" 263 | version = "0.8.4" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "4cef0c817217c330b3ef879e06455d726c1cffc800eaf7734d3b4ac63213636b" 266 | dependencies = [ 267 | "serde", 268 | "serde_json", 269 | "thiserror", 270 | "tracing", 271 | ] 272 | 273 | [[package]] 274 | name = "rustversion" 275 | version = "1.0.17" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" 278 | 279 | [[package]] 280 | name = "ryu" 281 | version = "1.0.18" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 284 | 285 | [[package]] 286 | name = "serde" 287 | version = "1.0.209" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" 290 | dependencies = [ 291 | "serde_derive", 292 | ] 293 | 294 | [[package]] 295 | name = "serde_derive" 296 | version = "1.0.209" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" 299 | dependencies = [ 300 | "proc-macro2", 301 | "quote", 302 | "syn", 303 | ] 304 | 305 | [[package]] 306 | name = "serde_json" 307 | version = "1.0.127" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" 310 | dependencies = [ 311 | "itoa", 312 | "memchr", 313 | "ryu", 314 | "serde", 315 | ] 316 | 317 | [[package]] 318 | name = "syn" 319 | version = "2.0.76" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" 322 | dependencies = [ 323 | "proc-macro2", 324 | "quote", 325 | "unicode-ident", 326 | ] 327 | 328 | [[package]] 329 | name = "term" 330 | version = "0.7.0" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" 333 | dependencies = [ 334 | "dirs-next", 335 | "rustversion", 336 | "winapi", 337 | ] 338 | 339 | [[package]] 340 | name = "testcrate" 341 | version = "0.1.0" 342 | dependencies = [ 343 | "compiletest_rs", 344 | "derive-new", 345 | ] 346 | 347 | [[package]] 348 | name = "tester" 349 | version = "0.9.1" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "89e8bf7e0eb2dd7b4228cc1b6821fc5114cd6841ae59f652a85488c016091e5f" 352 | dependencies = [ 353 | "cfg-if", 354 | "getopts", 355 | "libc", 356 | "num_cpus", 357 | "term", 358 | ] 359 | 360 | [[package]] 361 | name = "thiserror" 362 | version = "1.0.63" 363 | source = "registry+https://github.com/rust-lang/crates.io-index" 364 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 365 | dependencies = [ 366 | "thiserror-impl", 367 | ] 368 | 369 | [[package]] 370 | name = "thiserror-impl" 371 | version = "1.0.63" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 374 | dependencies = [ 375 | "proc-macro2", 376 | "quote", 377 | "syn", 378 | ] 379 | 380 | [[package]] 381 | name = "tracing" 382 | version = "0.1.40" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 385 | dependencies = [ 386 | "pin-project-lite", 387 | "tracing-attributes", 388 | "tracing-core", 389 | ] 390 | 391 | [[package]] 392 | name = "tracing-attributes" 393 | version = "0.1.27" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" 396 | dependencies = [ 397 | "proc-macro2", 398 | "quote", 399 | "syn", 400 | ] 401 | 402 | [[package]] 403 | name = "tracing-core" 404 | version = "0.1.32" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 407 | dependencies = [ 408 | "once_cell", 409 | ] 410 | 411 | [[package]] 412 | name = "unicode-ident" 413 | version = "1.0.12" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 416 | 417 | [[package]] 418 | name = "unicode-width" 419 | version = "0.1.13" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" 422 | 423 | [[package]] 424 | name = "wasi" 425 | version = "0.11.0+wasi-snapshot-preview1" 426 | source = "registry+https://github.com/rust-lang/crates.io-index" 427 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 428 | 429 | [[package]] 430 | name = "winapi" 431 | version = "0.3.9" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 434 | dependencies = [ 435 | "winapi-i686-pc-windows-gnu", 436 | "winapi-x86_64-pc-windows-gnu", 437 | ] 438 | 439 | [[package]] 440 | name = "winapi-i686-pc-windows-gnu" 441 | version = "0.4.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 444 | 445 | [[package]] 446 | name = "winapi-x86_64-pc-windows-gnu" 447 | version = "0.4.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 450 | 451 | [[package]] 452 | name = "windows-sys" 453 | version = "0.42.0" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 456 | dependencies = [ 457 | "windows_aarch64_gnullvm 0.42.2", 458 | "windows_aarch64_msvc 0.42.2", 459 | "windows_i686_gnu 0.42.2", 460 | "windows_i686_msvc 0.42.2", 461 | "windows_x86_64_gnu 0.42.2", 462 | "windows_x86_64_gnullvm 0.42.2", 463 | "windows_x86_64_msvc 0.42.2", 464 | ] 465 | 466 | [[package]] 467 | name = "windows-sys" 468 | version = "0.59.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 471 | dependencies = [ 472 | "windows-targets", 473 | ] 474 | 475 | [[package]] 476 | name = "windows-targets" 477 | version = "0.52.6" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 480 | dependencies = [ 481 | "windows_aarch64_gnullvm 0.52.6", 482 | "windows_aarch64_msvc 0.52.6", 483 | "windows_i686_gnu 0.52.6", 484 | "windows_i686_gnullvm", 485 | "windows_i686_msvc 0.52.6", 486 | "windows_x86_64_gnu 0.52.6", 487 | "windows_x86_64_gnullvm 0.52.6", 488 | "windows_x86_64_msvc 0.52.6", 489 | ] 490 | 491 | [[package]] 492 | name = "windows_aarch64_gnullvm" 493 | version = "0.42.2" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 496 | 497 | [[package]] 498 | name = "windows_aarch64_gnullvm" 499 | version = "0.52.6" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 502 | 503 | [[package]] 504 | name = "windows_aarch64_msvc" 505 | version = "0.42.2" 506 | source = "registry+https://github.com/rust-lang/crates.io-index" 507 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 508 | 509 | [[package]] 510 | name = "windows_aarch64_msvc" 511 | version = "0.52.6" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 514 | 515 | [[package]] 516 | name = "windows_i686_gnu" 517 | version = "0.42.2" 518 | source = "registry+https://github.com/rust-lang/crates.io-index" 519 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 520 | 521 | [[package]] 522 | name = "windows_i686_gnu" 523 | version = "0.52.6" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 526 | 527 | [[package]] 528 | name = "windows_i686_gnullvm" 529 | version = "0.52.6" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 532 | 533 | [[package]] 534 | name = "windows_i686_msvc" 535 | version = "0.42.2" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 538 | 539 | [[package]] 540 | name = "windows_i686_msvc" 541 | version = "0.52.6" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 544 | 545 | [[package]] 546 | name = "windows_x86_64_gnu" 547 | version = "0.42.2" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 550 | 551 | [[package]] 552 | name = "windows_x86_64_gnu" 553 | version = "0.52.6" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 556 | 557 | [[package]] 558 | name = "windows_x86_64_gnullvm" 559 | version = "0.42.2" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 562 | 563 | [[package]] 564 | name = "windows_x86_64_gnullvm" 565 | version = "0.52.6" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 568 | 569 | [[package]] 570 | name = "windows_x86_64_msvc" 571 | version = "0.42.2" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 574 | 575 | [[package]] 576 | name = "windows_x86_64_msvc" 577 | version = "0.52.6" 578 | source = "registry+https://github.com/rust-lang/crates.io-index" 579 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 580 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive-new" 3 | version = "0.7.0" 4 | authors = ["Nick Cameron "] 5 | description = "`#[derive(new)]` implements simple constructor functions for structs and enums." 6 | license = "MIT" 7 | repository = "https://github.com/nrc/derive-new" 8 | edition = "2021" 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | proc-macro2 = "1" 15 | quote = "1" 16 | syn = {version = "2", features = ["parsing"]} 17 | 18 | [features] 19 | default = ["std"] 20 | std = [] 21 | 22 | [workspace] 23 | members = ["testcrate"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2021 nrc (Nick Cameron) and the derive-new contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A custom derive implementation for `#[derive(new)]` 2 | 3 | A `derive(new)` attribute creates a `new` constructor function for the annotated 4 | type. That function takes an argument for each field in the type giving a 5 | trivial constructor. This is useful since as your type evolves you can make the 6 | constructor non-trivial (and add or remove fields) without changing client code 7 | (i.e., without breaking backwards compatibility). It is also the most succinct 8 | way to initialise a struct or an enum. 9 | 10 | Implementation uses macros 1.1 custom derive (which works in stable Rust from 11 | 1.15 onwards). 12 | 13 | `#[no_std]` is fully supported if you switch off the default feature `"std"`. 14 | 15 | ## Examples 16 | 17 | Cargo.toml: 18 | 19 | ```toml 20 | [dependencies] 21 | derive-new = "0.5" 22 | ``` 23 | 24 | Include the macro: 25 | 26 | * Rust Edition 2015 27 | 28 | ```rust 29 | #[macro_use] 30 | extern crate derive_new; 31 | ``` 32 | 33 | * Rust Edition 2018 34 | ```rust 35 | use derive_new::new; 36 | ``` 37 | 38 | Generating constructor for a simple struct: 39 | 40 | ```rust 41 | #[derive(new)] 42 | struct Bar { 43 | a: i32, 44 | b: String, 45 | } 46 | 47 | let _ = Bar::new(42, "Hello".to_owned()); 48 | ``` 49 | 50 | Default values can be specified either via `#[new(default)]` attribute which removes 51 | the argument from the constructor and populates the field with `Default::default()`, 52 | or via `#[new(value = "..")]` which initializes the field with a given expression: 53 | 54 | ```rust 55 | #[derive(new)] 56 | struct Foo { 57 | x: bool, 58 | #[new(value = "42")] 59 | y: i32, 60 | #[new(default)] 61 | z: Vec, 62 | } 63 | 64 | let _ = Foo::new(true); 65 | ``` 66 | 67 | To make type conversion easier, `#[new(into)]` attribute changes the parameter type 68 | to `impl Into`, and populates the field with `value.into()`: 69 | 70 | ```rust 71 | #[derive(new)] 72 | struct Foo { 73 | #[new(into)] 74 | x: String, 75 | } 76 | 77 | let _ = Foo::new("Hello"); 78 | ``` 79 | 80 | For iterators/collections, `#[new(into_iter = "T")]` attribute changes the parameter type 81 | to `impl IntoIterator`, and populates the field with `value.into_iter().collect()`: 82 | 83 | ```rust 84 | #[derive(new)] 85 | struct Foo { 86 | #[new(into_iter = "bool")] 87 | x: Vec, 88 | } 89 | 90 | let _ = Foo::new([true, false]); 91 | let _ = Foo::new(Some(true)); 92 | ``` 93 | 94 | Generic types are supported; in particular, `PhantomData` fields will be not 95 | included in the argument list and will be initialized automatically: 96 | 97 | ```rust 98 | use std::marker::PhantomData; 99 | 100 | #[derive(new)] 101 | struct Generic<'a, T: Default, P> { 102 | x: &'a str, 103 | y: PhantomData

, 104 | #[new(default)] 105 | z: T, 106 | } 107 | 108 | let _ = Generic::::new("Hello"); 109 | ``` 110 | 111 | For enums, one constructor method is generated for each variant, with the type 112 | name being converted to snake case; otherwise, all features supported for 113 | structs work for enum variants as well: 114 | 115 | ```rust 116 | #[derive(new)] 117 | enum Enum { 118 | FirstVariant, 119 | SecondVariant(bool, #[new(default)] u8), 120 | ThirdVariant { x: i32, #[new(value = "vec![1]")] y: Vec } 121 | } 122 | 123 | let _ = Enum::new_first_variant(); 124 | let _ = Enum::new_second_variant(true); 125 | let _ = Enum::new_third_variant(42); 126 | ``` 127 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # A custom derive implementation for `#[derive(new)]` 2 | //! 3 | //! A `derive(new)` attribute creates a `new` constructor function for the annotated 4 | //! type. That function takes an argument for each field in the type giving a 5 | //! trivial constructor. This is useful since as your type evolves you can make the 6 | //! constructor non-trivial (and add or remove fields) without changing client code 7 | //! (i.e., without breaking backwards compatibility). It is also the most succinct 8 | //! way to initialise a struct or an enum. 9 | //! 10 | //! Implementation uses macros 1.1 custom derive (which works in stable Rust from 11 | //! 1.15 onwards). 12 | //! 13 | //! ## Examples 14 | //! 15 | //! Cargo.toml: 16 | //! 17 | //! ```toml 18 | //! [dependencies] 19 | //! derive-new = "0.5" 20 | //! ``` 21 | //! 22 | //! Include the macro: 23 | //! 24 | //! ```rust 25 | //! use derive_new::new; 26 | //! 27 | //! fn main() {} 28 | //! ``` 29 | //! 30 | //! Generating constructor for a simple struct: 31 | //! 32 | //! ```rust 33 | //! use derive_new::new; 34 | //! 35 | //! #[derive(new)] 36 | //! struct Bar { 37 | //! a: i32, 38 | //! b: String, 39 | //! } 40 | //! 41 | //! fn main() { 42 | //! let _ = Bar::new(42, "Hello".to_owned()); 43 | //! } 44 | //! ``` 45 | //! 46 | //! Default values can be specified either via `#[new(default)]` attribute which removes 47 | //! the argument from the constructor and populates the field with `Default::default()`, 48 | //! or via `#[new(value = "..")]` which initializes the field with a given expression: 49 | //! 50 | //! ```rust 51 | //! use derive_new::new; 52 | //! 53 | //! #[derive(new)] 54 | //! struct Foo { 55 | //! x: bool, 56 | //! #[new(value = "42")] 57 | //! y: i32, 58 | //! #[new(default)] 59 | //! z: Vec, 60 | //! } 61 | //! 62 | //! fn main() { 63 | //! let _ = Foo::new(true); 64 | //! } 65 | //! ``` 66 | //! 67 | //! To make type conversion easier, `#[new(into)]` attribute changes the parameter type 68 | //! to `impl Into`, and populates the field with `value.into()`: 69 | //! 70 | //! ```rust 71 | //! # use derive_new::new; 72 | //! #[derive(new)] 73 | //! struct Foo { 74 | //! #[new(into)] 75 | //! x: String, 76 | //! } 77 | //! 78 | //! let _ = Foo::new("Hello"); 79 | //! ``` 80 | //! 81 | //! For iterators/collections, `#[new(into_iter = "T")]` attribute changes the parameter type 82 | //! to `impl IntoIterator`, and populates the field with `value.into_iter().collect()`: 83 | //! 84 | //! ```rust 85 | //! # use derive_new::new; 86 | //! #[derive(new)] 87 | //! struct Foo { 88 | //! #[new(into_iter = "bool")] 89 | //! x: Vec, 90 | //! } 91 | //! 92 | //! let _ = Foo::new([true, false]); 93 | //! let _ = Foo::new(Some(true)); 94 | //! ``` 95 | //! 96 | //! Generic types are supported; in particular, `PhantomData` fields will be not 97 | //! included in the argument list and will be initialized automatically: 98 | //! 99 | //! ```rust 100 | //! use derive_new::new; 101 | //! 102 | //! use std::marker::PhantomData; 103 | //! 104 | //! #[derive(new)] 105 | //! struct Generic<'a, T: Default, P> { 106 | //! x: &'a str, 107 | //! y: PhantomData

, 108 | //! #[new(default)] 109 | //! z: T, 110 | //! } 111 | //! 112 | //! fn main() { 113 | //! let _ = Generic::::new("Hello"); 114 | //! } 115 | //! ``` 116 | //! 117 | //! For enums, one constructor method is generated for each variant, with the type 118 | //! name being converted to snake case; otherwise, all features supported for 119 | //! structs work for enum variants as well: 120 | //! 121 | //! ```rust 122 | //! use derive_new::new; 123 | //! 124 | //! #[derive(new)] 125 | //! enum Enum { 126 | //! FirstVariant, 127 | //! SecondVariant(bool, #[new(default)] u8), 128 | //! ThirdVariant { x: i32, #[new(value = "vec![1]")] y: Vec } 129 | //! } 130 | //! 131 | //! fn main() { 132 | //! let _ = Enum::new_first_variant(); 133 | //! let _ = Enum::new_second_variant(true); 134 | //! let _ = Enum::new_third_variant(42); 135 | //! } 136 | //! ``` 137 | //! ### Setting Visibility for the Constructor 138 | //! 139 | //! By default, the generated constructor will be `pub`. However, you can control the visibility of the constructor using the `#[new(visibility = "...")]` attribute. 140 | //! 141 | //! #### Public Constructor (default) 142 | //! 143 | //! ```rust 144 | //! use derive_new::new; 145 | //! 146 | //! #[derive(new)] 147 | //! pub struct Bar { 148 | //! a: i32, 149 | //! b: String, 150 | //! } 151 | //! 152 | //! fn main() { 153 | //! let _ = Bar::new(42, "Hello".to_owned()); 154 | //! } 155 | //! ``` 156 | //! 157 | //! #### Crate-Visible Constructor 158 | //! 159 | //! ```rust 160 | //! use derive_new::new; 161 | //! 162 | //! #[derive(new)] 163 | //! #[new(visibility = "pub(crate)")] 164 | //! pub struct Bar { 165 | //! a: i32, 166 | //! b: String, 167 | //! } 168 | //! 169 | //! fn main() { 170 | //! let _ = Bar::new(42, "Hello".to_owned()); 171 | //! } 172 | //! ``` 173 | //! 174 | //! #### Private Constructor 175 | //! 176 | //! ```rust 177 | //! use derive_new::new; 178 | //! 179 | //! #[derive(new)] 180 | //! #[new(visibility = "")] 181 | //! pub struct Bar { 182 | //! a: i32, 183 | //! b: String, 184 | //! } 185 | //! 186 | //! fn main() { 187 | //! // Bar::new is not accessible here as it is private 188 | //! let _ = Bar::new(42, "Hello".to_owned()); // This will cause a compile error 189 | //! } 190 | //! ``` 191 | #![crate_type = "proc-macro"] 192 | #![recursion_limit = "192"] 193 | 194 | extern crate proc_macro; 195 | extern crate proc_macro2; 196 | #[macro_use] 197 | extern crate quote; 198 | extern crate syn; 199 | 200 | macro_rules! my_quote { 201 | ($($t:tt)*) => (quote_spanned!(proc_macro2::Span::call_site() => $($t)*)) 202 | } 203 | 204 | fn path_to_string(path: &syn::Path) -> String { 205 | path.segments 206 | .iter() 207 | .map(|s| s.ident.to_string()) 208 | .collect::>() 209 | .join("::") 210 | } 211 | 212 | use proc_macro::TokenStream; 213 | use proc_macro2::TokenStream as TokenStream2; 214 | use syn::{punctuated::Punctuated, Attribute, Lit, Token, Visibility}; 215 | 216 | #[proc_macro_derive(new, attributes(new))] 217 | pub fn derive(input: TokenStream) -> TokenStream { 218 | let ast: syn::DeriveInput = syn::parse(input).expect("Couldn't parse item"); 219 | let options = NewOptions::from_attributes(&ast.attrs); 220 | let result = match ast.data { 221 | syn::Data::Enum(ref e) => new_for_enum(&ast, e, &options), 222 | syn::Data::Struct(ref s) => new_for_struct(&ast, &s.fields, None, &options), 223 | syn::Data::Union(_) => panic!("doesn't work with unions yet"), 224 | }; 225 | result.into() 226 | } 227 | 228 | fn new_for_struct( 229 | ast: &syn::DeriveInput, 230 | fields: &syn::Fields, 231 | variant: Option<&syn::Ident>, 232 | options: &NewOptions, 233 | ) -> proc_macro2::TokenStream { 234 | match *fields { 235 | syn::Fields::Named(ref fields) => { 236 | new_impl(ast, Some(&fields.named), true, variant, options) 237 | } 238 | syn::Fields::Unit => new_impl(ast, None, false, variant, options), 239 | syn::Fields::Unnamed(ref fields) => { 240 | new_impl(ast, Some(&fields.unnamed), false, variant, options) 241 | } 242 | } 243 | } 244 | 245 | fn new_for_enum( 246 | ast: &syn::DeriveInput, 247 | data: &syn::DataEnum, 248 | options: &NewOptions, 249 | ) -> proc_macro2::TokenStream { 250 | if data.variants.is_empty() { 251 | panic!("#[derive(new)] cannot be implemented for enums with zero variants"); 252 | } 253 | let impls = data.variants.iter().map(|v| { 254 | if v.discriminant.is_some() { 255 | panic!("#[derive(new)] cannot be implemented for enums with discriminants"); 256 | } 257 | new_for_struct(ast, &v.fields, Some(&v.ident), options) 258 | }); 259 | my_quote!(#(#impls)*) 260 | } 261 | 262 | fn new_impl( 263 | ast: &syn::DeriveInput, 264 | fields: Option<&Punctuated>, 265 | named: bool, 266 | variant: Option<&syn::Ident>, 267 | options: &NewOptions, 268 | ) -> proc_macro2::TokenStream { 269 | let name = &ast.ident; 270 | let unit = fields.is_none(); 271 | let empty = Default::default(); 272 | let fields: Vec<_> = fields 273 | .unwrap_or(&empty) 274 | .iter() 275 | .enumerate() 276 | .map(|(i, f)| FieldExt::new(f, i, named)) 277 | .collect(); 278 | let args = fields.iter().filter_map(|f| f.as_arg()); 279 | let inits = fields.iter().map(|f| f.as_init()); 280 | let inits = if unit { 281 | my_quote!() 282 | } else if named { 283 | my_quote![{ #(#inits),* }] 284 | } else { 285 | my_quote![( #(#inits),* )] 286 | }; 287 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl(); 288 | let (mut new, qual, doc) = match variant { 289 | None => ( 290 | syn::Ident::new("new", proc_macro2::Span::call_site()), 291 | my_quote!(), 292 | format!("Constructs a new `{}`.", name), 293 | ), 294 | Some(ref variant) => ( 295 | syn::Ident::new( 296 | &format!("new_{}", to_snake_case(&variant.to_string())), 297 | proc_macro2::Span::call_site(), 298 | ), 299 | my_quote!(::#variant), 300 | format!("Constructs a new `{}::{}`.", name, variant), 301 | ), 302 | }; 303 | new.set_span(proc_macro2::Span::call_site()); 304 | let lint_attrs = collect_parent_lint_attrs(&ast.attrs); 305 | let lint_attrs = my_quote![#(#lint_attrs),*]; 306 | let visibility = &options.visibility; 307 | my_quote! { 308 | impl #impl_generics #name #ty_generics #where_clause { 309 | #[doc = #doc] 310 | #lint_attrs 311 | #visibility fn #new(#(#args),*) -> Self { 312 | #name #qual #inits 313 | } 314 | } 315 | } 316 | } 317 | 318 | fn collect_parent_lint_attrs(attrs: &[syn::Attribute]) -> Vec { 319 | fn is_lint(item: &syn::Meta) -> bool { 320 | if let syn::Meta::List(ref l) = *item { 321 | let path = &l.path; 322 | return path.is_ident("allow") 323 | || path.is_ident("deny") 324 | || path.is_ident("forbid") 325 | || path.is_ident("warn"); 326 | } 327 | false 328 | } 329 | 330 | fn is_cfg_attr_lint(item: &syn::Meta) -> bool { 331 | if let syn::Meta::List(ref l) = *item { 332 | if l.path.is_ident("cfg_attr") { 333 | if let Ok(nested) = 334 | l.parse_args_with(Punctuated::::parse_terminated) 335 | { 336 | return nested.len() == 2 && is_lint(&nested[1]); 337 | } 338 | } 339 | } 340 | false 341 | } 342 | 343 | attrs 344 | .iter() 345 | .filter(|a| is_lint(&a.meta) || is_cfg_attr_lint(&a.meta)) 346 | .cloned() 347 | .collect() 348 | } 349 | 350 | struct NewOptions { 351 | visibility: Option, 352 | } 353 | 354 | impl NewOptions { 355 | fn from_attributes(attrs: &[Attribute]) -> Self { 356 | // Default visibility is public 357 | let mut visibility = Some(Visibility::Public(syn::token::Pub { 358 | span: proc_macro2::Span::call_site(), 359 | })); 360 | 361 | for attr in attrs { 362 | if attr.path().is_ident("new") { 363 | attr.parse_nested_meta(|meta| { 364 | if meta.path.is_ident("visibility") { 365 | let value: Lit = meta.value()?.parse()?; 366 | if let Lit::Str(lit_str) = value { 367 | // Parse the visibility string into a syn::Visibility type 368 | let parsed_visibility: Visibility = 369 | lit_str.parse().expect("Invalid visibility"); 370 | visibility = Some(parsed_visibility); 371 | } 372 | Ok(()) 373 | } else { 374 | Err(meta.error("unsupported attribute")) 375 | } 376 | }) 377 | .unwrap_or(()); 378 | } 379 | } 380 | 381 | NewOptions { visibility } 382 | } 383 | } 384 | 385 | enum FieldAttr { 386 | Default, 387 | Into, 388 | IntoIter(proc_macro2::TokenStream), 389 | Value(proc_macro2::TokenStream), 390 | } 391 | 392 | impl FieldAttr { 393 | pub fn as_tokens(&self, name: &syn::Ident) -> proc_macro2::TokenStream { 394 | match *self { 395 | FieldAttr::Default => my_quote!(::core::default::Default::default()), 396 | FieldAttr::Into => my_quote!(::core::convert::Into::into(#name)), 397 | FieldAttr::IntoIter(_) => { 398 | my_quote!(::core::iter::Iterator::collect(::core::iter::IntoIterator::into_iter(#name))) 399 | } 400 | FieldAttr::Value(ref s) => my_quote!(#s), 401 | } 402 | } 403 | 404 | pub fn parse(attrs: &[syn::Attribute]) -> Option { 405 | let mut result = None; 406 | for attr in attrs.iter() { 407 | match attr.style { 408 | syn::AttrStyle::Outer => {} 409 | _ => continue, 410 | } 411 | let last_attr_path = attr 412 | .path() 413 | .segments 414 | .last() 415 | .expect("Expected at least one segment where #[segment[::segment*](..)]"); 416 | if last_attr_path.ident != "new" { 417 | continue; 418 | } 419 | let list = match attr.meta { 420 | syn::Meta::List(ref l) => l, 421 | _ if attr.path().is_ident("new") => { 422 | panic!("Invalid #[new] attribute, expected #[new(..)]") 423 | } 424 | _ => continue, 425 | }; 426 | if result.is_some() { 427 | panic!("Expected at most one #[new] attribute"); 428 | } 429 | for item in list 430 | .parse_args_with(Punctuated::::parse_terminated) 431 | .unwrap_or_else(|err| panic!("Invalid #[new] attribute: {}", err)) 432 | { 433 | match item { 434 | syn::Meta::Path(path) => match path.get_ident() { 435 | Some(ident) if ident == "default" => { 436 | result = Some(FieldAttr::Default); 437 | } 438 | Some(ident) if ident == "into" => { 439 | result = Some(FieldAttr::Into); 440 | } 441 | _ => panic!( 442 | "Invalid #[new] attribute: #[new({})]", 443 | path_to_string(&path) 444 | ), 445 | }, 446 | syn::Meta::NameValue(kv) => { 447 | if let syn::Expr::Lit(syn::ExprLit { 448 | lit: syn::Lit::Str(ref s), 449 | .. 450 | }) = kv.value 451 | { 452 | let tokens = lit_str_to_token_stream(s) 453 | .ok() 454 | .expect(&format!("Invalid expression in #[new]: `{}`", s.value())); 455 | 456 | match kv.path.get_ident() { 457 | Some(ident) if ident == "into_iter" => { 458 | result = Some(FieldAttr::IntoIter(tokens)); 459 | } 460 | Some(ident) if ident == "value" => { 461 | result = Some(FieldAttr::Value(tokens)); 462 | } 463 | _ => panic!( 464 | "Invalid #[new] attribute: #[new({} = ..)]", 465 | path_to_string(&kv.path) 466 | ), 467 | } 468 | } else { 469 | panic!("Non-string literal value in #[new] attribute"); 470 | } 471 | } 472 | syn::Meta::List(l) => { 473 | panic!( 474 | "Invalid #[new] attribute: #[new({}(..))]", 475 | path_to_string(&l.path) 476 | ); 477 | } 478 | } 479 | } 480 | } 481 | result 482 | } 483 | } 484 | 485 | struct FieldExt<'a> { 486 | ty: &'a syn::Type, 487 | attr: Option, 488 | ident: syn::Ident, 489 | named: bool, 490 | } 491 | 492 | impl<'a> FieldExt<'a> { 493 | pub fn new(field: &'a syn::Field, idx: usize, named: bool) -> FieldExt<'a> { 494 | FieldExt { 495 | ty: &field.ty, 496 | attr: FieldAttr::parse(&field.attrs), 497 | ident: if named { 498 | field.ident.clone().unwrap() 499 | } else { 500 | syn::Ident::new(&format!("f{}", idx), proc_macro2::Span::call_site()) 501 | }, 502 | named, 503 | } 504 | } 505 | 506 | pub fn is_phantom_data(&self) -> bool { 507 | match *self.ty { 508 | syn::Type::Path(syn::TypePath { 509 | qself: None, 510 | ref path, 511 | }) => path 512 | .segments 513 | .last() 514 | .map(|x| x.ident == "PhantomData") 515 | .unwrap_or(false), 516 | _ => false, 517 | } 518 | } 519 | 520 | pub fn as_arg(&self) -> Option { 521 | if self.is_phantom_data() { 522 | return None; 523 | } 524 | 525 | let ident = &self.ident; 526 | let ty = &self.ty; 527 | 528 | match self.attr { 529 | Some(FieldAttr::Default) => None, 530 | Some(FieldAttr::Into) => Some(my_quote!(#ident: impl ::core::convert::Into<#ty>)), 531 | Some(FieldAttr::IntoIter(ref s)) => { 532 | Some(my_quote!(#ident: impl ::core::iter::IntoIterator)) 533 | } 534 | Some(FieldAttr::Value(_)) => None, 535 | None => Some(my_quote!(#ident: #ty)), 536 | } 537 | } 538 | 539 | pub fn as_init(&self) -> proc_macro2::TokenStream { 540 | let f_name = &self.ident; 541 | let init = if self.is_phantom_data() { 542 | my_quote!(::core::marker::PhantomData) 543 | } else { 544 | match self.attr { 545 | None => my_quote!(#f_name), 546 | Some(ref attr) => attr.as_tokens(f_name), 547 | } 548 | }; 549 | if self.named { 550 | my_quote!(#f_name: #init) 551 | } else { 552 | my_quote!(#init) 553 | } 554 | } 555 | } 556 | 557 | fn lit_str_to_token_stream(s: &syn::LitStr) -> Result { 558 | let code = s.value(); 559 | let ts: TokenStream2 = code.parse()?; 560 | Ok(set_ts_span_recursive(ts, &s.span())) 561 | } 562 | 563 | fn set_ts_span_recursive(ts: TokenStream2, span: &proc_macro2::Span) -> TokenStream2 { 564 | ts.into_iter() 565 | .map(|mut tt| { 566 | tt.set_span(*span); 567 | if let proc_macro2::TokenTree::Group(group) = &mut tt { 568 | let stream = set_ts_span_recursive(group.stream(), span); 569 | *group = proc_macro2::Group::new(group.delimiter(), stream); 570 | } 571 | tt 572 | }) 573 | .collect() 574 | } 575 | 576 | fn to_snake_case(s: &str) -> String { 577 | let (ch, next, mut acc): (Option, Option, String) = 578 | s.chars() 579 | .fold((None, None, String::new()), |(prev, ch, mut acc), next| { 580 | if let Some(ch) = ch { 581 | if let Some(prev) = prev { 582 | if ch.is_uppercase() 583 | && (prev.is_lowercase() 584 | || prev.is_numeric() 585 | || (prev.is_uppercase() && next.is_lowercase())) 586 | { 587 | acc.push('_'); 588 | } 589 | } 590 | acc.extend(ch.to_lowercase()); 591 | } 592 | (ch, Some(next), acc) 593 | }); 594 | if let Some(next) = next { 595 | if let Some(ch) = ch { 596 | if (ch.is_lowercase() || ch.is_numeric()) && next.is_uppercase() { 597 | acc.push('_'); 598 | } 599 | } 600 | acc.extend(next.to_lowercase()); 601 | } 602 | acc 603 | } 604 | 605 | #[test] 606 | fn test_to_snake_case() { 607 | assert_eq!(to_snake_case(""), ""); 608 | assert_eq!(to_snake_case("a"), "a"); 609 | assert_eq!(to_snake_case("B"), "b"); 610 | assert_eq!(to_snake_case("BC"), "bc"); 611 | assert_eq!(to_snake_case("Bc"), "bc"); 612 | assert_eq!(to_snake_case("bC"), "b_c"); 613 | assert_eq!(to_snake_case("Fred"), "fred"); 614 | assert_eq!(to_snake_case("CARGO"), "cargo"); 615 | assert_eq!(to_snake_case("_Hello"), "_hello"); 616 | assert_eq!(to_snake_case("QuxBaz"), "qux_baz"); 617 | assert_eq!(to_snake_case("FreeBSD"), "free_bsd"); 618 | assert_eq!(to_snake_case("specialK"), "special_k"); 619 | assert_eq!(to_snake_case("hello1World"), "hello1_world"); 620 | assert_eq!(to_snake_case("Keep_underscore"), "keep_underscore"); 621 | assert_eq!(to_snake_case("ThisISNotADrill"), "this_is_not_a_drill"); 622 | } 623 | -------------------------------------------------------------------------------- /testcrate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "testcrate" 3 | version = "0.1.0" 4 | authors = ["Alex Crichton "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | derive-new = { path = ".." } 9 | 10 | [dev-dependencies] 11 | compiletest_rs = { version = "0.11", features = ["stable"] } 12 | 13 | [[test]] 14 | name = "compile-fail" 15 | path = "tests/compile-fail.rs" 16 | harness = false 17 | -------------------------------------------------------------------------------- /testcrate/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | assert_eq!(2 + 2, 4); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail.rs: -------------------------------------------------------------------------------- 1 | extern crate compiletest_rs as compiletest; 2 | 3 | use std::env; 4 | use std::path::PathBuf; 5 | 6 | fn run_mode(mode: &'static str) { 7 | let mut config = compiletest::Config::default(); 8 | config.mode = mode.parse().expect("invalid mode"); 9 | let mut me = env::current_exe().unwrap(); 10 | me.pop(); 11 | config.target_rustcflags = Some(format!("-L {}", me.display())); 12 | let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 13 | config.src_base = src.join("tests").join(mode); 14 | 15 | me.pop(); 16 | me.pop(); 17 | config.build_base = me.join("tests").join(mode); 18 | config.filter = env::args().nth(1); 19 | 20 | compiletest::run_tests(&config); 21 | } 22 | 23 | fn main() { 24 | run_mode("compile-fail"); 25 | } 26 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/enum-with-discriminants.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP #[derive(new)] cannot be implemented for enums with discriminants 7 | enum Enum { 8 | Foo, 9 | Bar = 42, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/enum-zero-variants.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP #[derive(new)] cannot be implemented for enums with zero variants 7 | enum Enum {} 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/multiple-new-attrs.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Expected at most one #[new] attribute 7 | struct Foo { 8 | x: i32, 9 | #[new(default)] 10 | #[new(value = "42")] 11 | y: i32, 12 | } 13 | 14 | fn main() {} 15 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-invalid-attr-type.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Invalid #[new] attribute, expected #[new(..)] 7 | struct Foo { 8 | #[new = "foo"] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-invalid-value.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR produced unparsable tokens 6 | struct Foo { 7 | #[new(value = "hello@world")] 8 | //~^ ERROR expected one of 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-list-invalid.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Invalid #[new] attribute: #[new(foo)] 7 | struct Foo { 8 | #[new(foo)] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-name-value-invalid.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Invalid #[new] attribute: #[new(foo = ..)] 7 | struct Foo { 8 | #[new(foo = "bar")] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-nested-list.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Invalid #[new] attribute: #[new(foo(..))] 7 | struct Foo { 8 | #[new(foo("bar"))] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-nested-literal.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Invalid #[new] attribute: expected identifier 7 | struct Foo { 8 | #[new("foo")] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /testcrate/tests/compile-fail/new-non-literal-value.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate derive_new; 3 | 4 | #[derive(new)] 5 | //~^ ERROR proc-macro derive 6 | //~^^ HELP Non-string literal value in #[new] attribute 7 | struct Foo { 8 | #[new(value = false)] 9 | x: i32, 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #![deny(non_snake_case)] 2 | 3 | #[macro_use] 4 | extern crate derive_new; 5 | 6 | use std::fmt::Debug; 7 | 8 | /// A struct with no fields. 9 | #[derive(new, PartialEq, Debug)] 10 | pub struct Foo {} 11 | 12 | #[test] 13 | fn test_empty_struct() { 14 | let x = Foo::new(); 15 | assert_eq!(x, Foo {}); 16 | } 17 | 18 | /// A unit struct. 19 | #[derive(new, PartialEq, Debug)] 20 | pub struct Baz; 21 | 22 | #[test] 23 | fn test_unit_struct() { 24 | let x = Baz::new(); 25 | assert_eq!(x, Baz); 26 | } 27 | 28 | /// A struct with fields. 29 | #[derive(new, PartialEq, Debug)] 30 | #[new(visibility = "pub(crate)")] 31 | pub struct Bar { 32 | pub x: i32, 33 | pub y: String, 34 | } 35 | 36 | #[test] 37 | fn test_simple_struct() { 38 | let x = Bar::new(42, "Hello".to_owned()); 39 | assert_eq!( 40 | x, 41 | Bar { 42 | x: 42, 43 | y: "Hello".to_owned() 44 | } 45 | ); 46 | } 47 | 48 | /// A struct with a lifetime parameter. 49 | #[derive(new, PartialEq, Debug)] 50 | pub struct Intersection<'scene> { 51 | pub object: &'scene Bar, 52 | pub normal: Foo, 53 | pub point: Foo, 54 | pub t: f64, 55 | } 56 | 57 | #[test] 58 | fn test_struct_with_lifetime() { 59 | let b = Bar::new(42, "Hello".to_owned()); 60 | let x = Intersection::new(&b, Foo::new(), Foo::new(), 42.0); 61 | assert_eq!( 62 | x, 63 | Intersection { 64 | object: &b, 65 | normal: Foo {}, 66 | point: Foo {}, 67 | t: 42.0 68 | } 69 | ); 70 | } 71 | 72 | /// A struct with generics and bounds. 73 | #[derive(new, PartialEq, Debug)] 74 | pub struct Qux { 75 | pub f1: T, 76 | pub f2: Vec, 77 | pub f3: i32, 78 | } 79 | 80 | #[test] 81 | fn test_struct_with_bounds() { 82 | let x = Qux::new("Hello!", Vec::::new(), 42); 83 | assert_eq!( 84 | x, 85 | Qux { 86 | f1: "Hello!", 87 | f2: vec![], 88 | f3: 42 89 | } 90 | ); 91 | 92 | let x: Qux<&'static str, String> = Qux::new("Hello!", Vec::::new(), 42); 93 | assert_eq!( 94 | x, 95 | Qux { 96 | f1: "Hello!", 97 | f2: vec![], 98 | f3: 42 99 | } 100 | ); 101 | 102 | let x = Qux::<_, String>::new("Hello!", vec![], 42); 103 | assert_eq!( 104 | x, 105 | Qux { 106 | f1: "Hello!", 107 | f2: vec![], 108 | f3: 42 109 | } 110 | ); 111 | } 112 | 113 | /// A struct with a lifetime parameter, generics and bounds. 114 | #[derive(new, PartialEq, Debug)] 115 | pub struct FooBar<'a, T, U> 116 | where 117 | T: 'a + PartialEq + Debug, 118 | U: Sized + Send + 'a + PartialEq + Debug, 119 | { 120 | pub f1: Box, 121 | pub f2: Vec<&'a U>, 122 | pub f3: i32, 123 | } 124 | 125 | #[test] 126 | fn test_struct_lifetime_bounds() { 127 | let a = 42; 128 | let x = FooBar::new(Box::new("Hello".to_owned()), vec![&a], 42); 129 | assert_eq!( 130 | x, 131 | FooBar { 132 | f1: Box::new("Hello".to_owned()), 133 | f2: vec![&a], 134 | f3: 42 135 | } 136 | ); 137 | } 138 | 139 | /// A tuple struct. 140 | #[derive(new, PartialEq, Debug)] 141 | pub struct Tuple(pub i32, pub i32); 142 | 143 | #[test] 144 | fn test_simple_tuple_struct() { 145 | let x = Tuple::new(5, 6); 146 | assert_eq!(x, Tuple(5, 6)); 147 | } 148 | 149 | /// A tuple struct with a lifetime parameter. 150 | #[derive(new, PartialEq, Debug)] 151 | pub struct TupleWithLifetime<'a>(pub &'a str); 152 | 153 | #[test] 154 | fn test_tuple_struct_lifetime() { 155 | let x = TupleWithLifetime::new("Hello"); 156 | assert_eq!(x, TupleWithLifetime("Hello")); 157 | } 158 | 159 | #[cfg(feature = "std")] 160 | #[test] 161 | fn test_struct_with_defaults() { 162 | use std::default::Default; 163 | 164 | /// A struct where fields have default values. 165 | #[derive(new, PartialEq, Debug)] 166 | pub struct Waldo { 167 | #[new(default)] 168 | pub x: i32, 169 | pub y: u8, 170 | #[new(default)] 171 | pub z: T, 172 | } 173 | 174 | let x = Waldo::>::new(42); 175 | assert_eq!( 176 | x, 177 | Waldo { 178 | x: 0, 179 | y: 42, 180 | z: vec![] 181 | } 182 | ); 183 | } 184 | 185 | #[cfg(feature = "std")] 186 | #[test] 187 | fn test_struct_with_into() { 188 | #[derive(new, PartialEq, Debug)] 189 | pub struct Foo { 190 | #[new(into)] 191 | pub value: String, 192 | } 193 | 194 | assert_eq!( 195 | Foo::new("bar"), 196 | Foo { 197 | value: "bar".to_string(), 198 | } 199 | ); 200 | } 201 | 202 | #[cfg(feature = "std")] 203 | #[test] 204 | fn test_struct_with_into_iter() { 205 | #[derive(new, PartialEq, Debug)] 206 | pub struct Foo { 207 | #[new(into_iter = "bool")] 208 | pub values: Vec, 209 | } 210 | 211 | assert_eq!( 212 | Foo::new([true, false, true]), 213 | Foo { 214 | values: vec![true, false, true] 215 | } 216 | ); 217 | 218 | assert_eq!( 219 | Foo::new(Some(false)), 220 | Foo { 221 | values: vec![false] 222 | } 223 | ); 224 | 225 | assert_eq!(Foo::new(None), Foo { values: vec![] }); 226 | } 227 | 228 | /// A struct where fields have explicitly provided defaults. 229 | #[derive(new, PartialEq, Debug)] 230 | pub struct Fred { 231 | #[new(value = "1 + 2")] 232 | pub x: i32, 233 | pub y: String, 234 | #[new(value = "vec![-42, 42]")] 235 | pub z: Vec, 236 | } 237 | 238 | #[test] 239 | fn test_struct_with_values() { 240 | let x = Fred::new("Fred".to_owned()); 241 | assert_eq!( 242 | x, 243 | Fred { 244 | x: 3, 245 | y: "Fred".to_owned(), 246 | z: vec![-42, 42] 247 | } 248 | ); 249 | } 250 | 251 | #[cfg(feature = "std")] 252 | #[test] 253 | fn test_struct_mixed_defaults() { 254 | /// A struct with defaults and specified values. 255 | #[derive(new, PartialEq, Debug)] 256 | pub struct Thud { 257 | #[new(value = r#""Thud".to_owned()"#)] 258 | pub x: String, 259 | #[new(default)] 260 | pub y: String, 261 | } 262 | 263 | let x = Thud::new(); 264 | assert_eq!( 265 | x, 266 | Thud { 267 | x: "Thud".to_owned(), 268 | y: String::new() 269 | } 270 | ); 271 | } 272 | 273 | #[cfg(feature = "std")] 274 | #[test] 275 | fn test_struct_phantom_data() { 276 | use std::marker::PhantomData; 277 | 278 | /// A generic struct with PhantomData member. 279 | #[derive(new, PartialEq, Debug)] 280 | pub struct Bob { 281 | pub a: i32, 282 | pub b: PhantomData, 283 | } 284 | let x = Bob::::new(42); 285 | assert_eq!( 286 | x, 287 | Bob { 288 | a: 42, 289 | b: PhantomData 290 | } 291 | ); 292 | } 293 | 294 | #[cfg(feature = "std")] 295 | #[test] 296 | fn test_tuple_with_defaults() { 297 | use std::default::Default; 298 | 299 | /// A tuple struct where fields have default values. 300 | #[derive(new, PartialEq, Debug)] 301 | pub struct Boom( 302 | #[new(default)] pub i32, 303 | pub u8, 304 | #[new(default)] pub T, 305 | ); 306 | 307 | let x = Boom::>::new(42); 308 | assert_eq!(x, Boom(0, 42, vec![])); 309 | } 310 | 311 | /// A tuple struct where fields have explicitly provided defaults. 312 | #[derive(new, PartialEq, Debug)] 313 | pub struct Moog( 314 | #[new(value = "1 + 2")] pub i32, 315 | pub String, 316 | #[new(value = "vec![-42, 42]")] pub Vec, 317 | ); 318 | 319 | #[test] 320 | fn test_tuple_with_values() { 321 | let x = Moog::new("Fred".to_owned()); 322 | assert_eq!(x, Moog(3, "Fred".to_owned(), vec![-42, 42])); 323 | } 324 | 325 | #[cfg(feature = "std")] 326 | #[test] 327 | fn test_tuple_mixed_defaults() { 328 | /// A tuple struct with defaults and specified values. 329 | #[derive(new, PartialEq, Debug)] 330 | pub struct Crab( 331 | #[new(value = r#""Thud".to_owned()"#)] pub String, 332 | #[new(default)] pub String, 333 | ); 334 | 335 | let x = Crab::new(); 336 | assert_eq!(x, Crab("Thud".to_owned(), String::new())); 337 | } 338 | 339 | #[cfg(feature = "std")] 340 | #[test] 341 | fn test_tuple_phantom_data() { 342 | use std::marker::PhantomData; 343 | 344 | /// A generic tuple struct with PhantomData member. 345 | #[derive(new, PartialEq, Debug)] 346 | pub struct Sponge(pub i32, pub PhantomData); 347 | 348 | let x = Sponge::::new(42); 349 | assert_eq!(x, Sponge(42, PhantomData)); 350 | } 351 | 352 | /// An enum with unit variants 353 | #[derive(new, PartialEq, Debug)] 354 | pub enum Fizz { 355 | ThisISNotADrill, 356 | BiteMe, 357 | } 358 | 359 | #[test] 360 | fn test_enum_unit_variants() { 361 | let x = Fizz::new_this_is_not_a_drill(); 362 | assert_eq!(x, Fizz::ThisISNotADrill); 363 | 364 | let x = Fizz::new_bite_me(); 365 | assert_eq!(x, Fizz::BiteMe); 366 | } 367 | 368 | #[cfg(feature = "std")] 369 | #[test] 370 | fn test_more_involved_enum() { 371 | use std::default::Default; 372 | use std::marker::PhantomData; 373 | 374 | /// A more involved enum 375 | #[derive(new, PartialEq, Debug)] 376 | pub enum Enterprise { 377 | Picard, 378 | Data( 379 | #[new(value = "\"fascinating\".to_owned()")] String, 380 | #[new(default)] T, 381 | ), 382 | Spock { 383 | x: PhantomData, 384 | y: i32, 385 | }, 386 | } 387 | 388 | let x = Enterprise::::new_picard(); 389 | assert_eq!(x, Enterprise::Picard); 390 | 391 | let x = Enterprise::::new_data(); 392 | assert_eq!(x, Enterprise::Data("fascinating".to_owned(), 0u8)); 393 | 394 | let x = Enterprise::::new_spock(42); 395 | assert_eq!( 396 | x, 397 | Enterprise::Spock { 398 | x: PhantomData, 399 | y: 42 400 | } 401 | ); 402 | } 403 | 404 | #[allow(non_snake_case)] 405 | #[derive(new, PartialEq, Debug)] 406 | pub struct Upside { 407 | X: i32, 408 | } 409 | 410 | #[cfg_attr(test, allow(non_snake_case))] 411 | #[derive(new, PartialEq, Debug)] 412 | pub struct Down { 413 | X: i32, 414 | } 415 | 416 | #[derive(new, PartialEq, Debug)] 417 | pub struct All { 418 | #[allow(missing_docs)] 419 | pub x: i32, 420 | } 421 | --------------------------------------------------------------------------------