├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── src ├── attributes.rs ├── discriminant.rs ├── fish.rs ├── lib.rs ├── special_data.rs ├── tests │ ├── mod.rs │ └── test_attributes │ │ ├── attribute_modifier.rs │ │ └── mod.rs ├── ty.rs ├── unpack.rs └── unpack_context.rs └── tests ├── examples.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .idea 4 | **/*.rs.bk -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nestify" 3 | version = "0.3.3" 4 | edition = "2021" 5 | authors = ["Patrick Unick "] 6 | license = "MIT" 7 | keywords = ["macro", "nested", "syntax", "struct", "serde"] 8 | repository = "https://github.com/snowfoxsh/nestify" 9 | description = "Nestify offers a macro to simplify and beautify nested struct definitions in Rust, enabling cleaner, more readable code structures with less verbosity. It's especially valuable for handling API responses." 10 | 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = { version = "1.0.69", features = ["span-locations"] } 17 | syn = { version = "2.0.39", features = ["extra-traits"] } 18 | quote = "1.0.33" 19 | proc-macro-error = "1.0.4" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person obtaining a copy 2 | of this software and associated documentation files (the "Software"), to deal 3 | in the Software without restriction, including without limitation the rights 4 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | copies of the Software, and to permit persons to whom the Software is 6 | furnished to do so, subject to the following conditions: 7 | 8 | The above copyright notice and this permission notice shall be included in 9 | all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 17 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nestify 2 | 3 | Nestify is a Rust library offering a powerful macro to streamline the definition of nested structs and enums. 4 | Designed to improve code readability and maintainability 5 | 6 | [crates.io](https://crates.io/crates/nestify) 7 | [github](https://github.com/snowfoxsh/nestify) 8 | [License](LICENSE) 9 | 10 | 11 | ## Abstract 12 | 13 | Nestify re-imagines Rust struct and enum definitions with its "Type is Definition" approach, 14 | streamlining the way you handle nested structures. 15 | Gone are the days of flipping back and forth between type definitions—Nestify 16 | Unifies your codebase, making your code cleaner and far more readable. 17 | 18 | Nestify is crafted for ease of learning, with its syntax tailored to be comfortable for Rust developers. The aim is for anyone, even those unfamiliar with the Nest macro, to quickly grasp its concept upon first glance. 19 | 20 | ## Features 21 | 22 | - Simplify nested struct and enum definitions in Rust. 23 | - Make your codebase more readable and less verbose. 24 | - Ideal for modeling complex API responses. 25 | - Advanced attribute modifiers. 26 | - Works well with Serde. 27 | - Intuitive syntax 28 | 29 | ## Installation 30 | 31 | Add this to your `Cargo.toml`: 32 | 33 | ```toml 34 | [dependencies] 35 | nestify = "0.3.3" 36 | ``` 37 | 38 | Then use the macro: 39 | 40 | ```rust 41 | use nestify::nest; 42 | ``` 43 | 44 | > [!NOTE] 45 | > A nightly toolchain might provide better error diagnostics 46 | 47 | ## Quick Examples 48 | 49 | ### Simple Nested Structures 50 | 51 | Here's a quick example to show how Nestify simplifies nested struct definitions: 52 | 53 | ```rust 54 | // Define a user profile with nested address and preferences structures 55 | nest! { 56 | struct UserProfile { 57 | name: String, 58 | address: struct Address { 59 | street: String, 60 | city: String, 61 | }, 62 | preferences: struct Preferences { 63 | newsletter: bool, 64 | }, 65 | } 66 | } 67 | ``` 68 | 69 | 70 |
71 | 72 | Expand 73 | 74 |
75 | 76 | ```rust 77 | struct UserProfile { 78 | name: String, 79 | address: Address, 80 | preferences: Preferences, 81 | } 82 | 83 | struct Address { 84 | street: String, 85 | city: String, 86 | } 87 | 88 | struct Preferences { 89 | newsletter: bool, 90 | } 91 | ``` 92 | 93 |
94 | 95 | ### Simple Nested Enums 96 | ```rust 97 | // Define a task with a nested status enum 98 | nest! { 99 | struct Task { 100 | id: i32, 101 | description: String, 102 | status: enum Status { 103 | Pending, 104 | InProgress, 105 | Completed, 106 | }, 107 | } 108 | } 109 | ``` 110 | 111 |
112 | 113 | Expand 114 | 115 |
116 | 117 | ```rust 118 | struct Task { 119 | id: i32, 120 | description: String, 121 | status: Status, 122 | } 123 | 124 | enum Status { 125 | Pending, 126 | InProgress, 127 | Completed, 128 | } 129 | ``` 130 | 131 |
132 | 133 | 134 | ## Supported definitions 135 | 136 | Nestify supports both [structs](https://doc.rust-lang.org/reference/expressions/struct-expr.html) and [enums](https://doc.rust-lang.org/reference/items/enumerations.html). 137 | 138 | ```rust 139 | // field structs (named) 140 | nest! { 141 | struct Named { 142 | f: struct Nested {} 143 | } 144 | } 145 | 146 | // tuple structs (unnamed) 147 | nest! { 148 | struct Unnamed(struct Nested()) 149 | } 150 | 151 | // unit structs 152 | nest! { 153 | struct Unit { 154 | unit: struct UnitStruct 155 | } 156 | } 157 | 158 | 159 | // enums 160 | nest! { 161 | enum EnumVariants { 162 | Unit, 163 | Tuple(i32, struct TupleNested), 164 | Struct { 165 | f1: i32, 166 | 167 | } 168 | DiscriminantVariant = 1, 169 | } 170 | } 171 | // note: any variant can have a discriminant 172 | // just as in normal rust 173 | ``` 174 | 175 |
176 | 177 | Expand 178 | 179 |
180 | 181 | 182 | ```rust 183 | // field structs (named) 184 | struct Named { 185 | f: Nested, 186 | } 187 | struct Nested {} 188 | 189 | // tuple structs (unnamed) 190 | struct Unnamed(Nested,); 191 | struct Nested(); 192 | 193 | // unit structs 194 | struct Unit { 195 | unit: UnitStruct, 196 | } 197 | struct UnitStruct; 198 | 199 | 200 | // enums 201 | enum EnumVariants { 202 | Unit, 203 | Tuple(i32, TupleNested), 204 | Struct { 205 | f1: i32 206 | }, 207 | DiscriminantVariant = 1, 208 | } 209 | struct TupleNested; 210 | ``` 211 | 212 |
213 | 214 | ## Generics 215 | Nestify fully supports Rust's generic parameters. This compatibility ensures that you can incorporate both lifetime and type parameters within your nested struct definitions, just as you would in standard Rust code. 216 | 217 | ```rust 218 | nest! { 219 | struct Example<'a, T> { 220 | s: &'a str, 221 | t: T 222 | } 223 | } 224 | ``` 225 |
226 | 227 | Expand 228 | 229 |
230 | 231 | 232 | ```rust 233 | struct Example<'a, T> { 234 | s: &'a str, 235 | t: T, 236 | } 237 | ``` 238 |
239 | 240 | ### Nested Generics 241 | 242 | When defining nested generics, you need to add generics to types. Enter "FishHook" syntax. 243 | To define generics on the field use `||<...>`. This will let you specify the nested generic types. 244 | It also works with lifetimes if needed. 245 | 246 | ```rust 247 | nest! { 248 | struct Parent<'a> { 249 | child : struct Child<'c, C> { 250 | s: &'c str, 251 | f: C 252 | } ||<'a, i32> 253 | } 254 | } 255 | ``` 256 | 257 |
258 | 259 | Expand 260 | 261 |
262 | 263 | ```rust 264 | struct Parent<'a> { 265 | child: Child<'a, i32>, 266 | // ^^^^^^^^ FishHook expands to this part 267 | } 268 | 269 | struct Child<'c, C> { 270 | s: &'c str, 271 | f: C, 272 | } 273 | ``` 274 | 275 |
276 | 277 | ## Attributes 278 | 279 | You can apply attributes just like you would with a normal struct. 280 | 281 | ```rust 282 | nest! { 283 | #[derive(Clone)] 284 | struct CloneMe {} 285 | } 286 | 287 | let x = CloneMe {}; 288 | let cl = x.clone(); 289 | ``` 290 | 291 | 292 | ### Recursive Attributes **`#[meta]*`** 293 | 294 | Using `*` syntax you can inherit attributes to child structures easily. The attribute 295 | will propagate to each nested structure or enum. 296 | 297 | ```rust 298 | nest! { 299 | #[apply_all]* 300 | struct One { 301 | two: struct Two { 302 | three: struct Three { 303 | payload: () 304 | } 305 | } 306 | } 307 | } 308 | ``` 309 | 310 |
311 | 312 | Expand 313 | 314 |
315 | 316 | 317 | ```rust 318 | #[apply_all] 319 | struct One { 320 | two: Tow, 321 | } 322 | 323 | #[apply_all] 324 | struct Two { 325 | three: Three, 326 | } 327 | 328 | #[apply_all] 329 | struct Three { 330 | payload: (), 331 | } 332 | ``` 333 | 334 |
335 | 336 | ### Removal Syntax 337 | 338 | #### Disable Propagation **`#[meta]/`** 339 | 340 | You can end the recursion of an attribute with a `/` attribute modifier. 341 | It will remove a recursive attribute from the current structure and all nested structures 342 | 343 | ```rust 344 | nest! { 345 | #[nest]* 346 | struct One { 347 | two: struct Two { 348 | three: #[nest]/ 349 | struct Three { 350 | four: struct Four { } 351 | } 352 | } 353 | } 354 | } 355 | ``` 356 | 357 |
358 | 359 | Expand 360 | 361 |
362 | 363 | 364 | ```rust 365 | #[nest] 366 | struct One { 367 | two: Two, 368 | } 369 | 370 | #[nest] 371 | struct Two { 372 | three: Three, 373 | } 374 | 375 | struct Three { 376 | four: Four, 377 | } 378 | 379 | struct Four {} 380 | ``` 381 | 382 |
383 | 384 | #### Disable Single **`#[meta]-`** 385 | 386 | Using the `-` modifier will remove a recursive attribute from a single structure 387 | To use the previous example using `-` instead of `/`: 388 | 389 | ```rust 390 | nest! { 391 | #[nest]* 392 | struct One { 393 | two: struct Two { 394 | three: #[nest]- 395 | struct Three { 396 | four: struct Four { } 397 | } 398 | } 399 | } 400 | } 401 | ``` 402 | 403 |
404 | 405 | Expand 406 | 407 |
408 | 409 | 410 | ```rust 411 | #[nest] 412 | struct One { 413 | two: Two, 414 | } 415 | 416 | #[nest] 417 | struct Two { 418 | three: Three, 419 | } 420 | 421 | struct Three { 422 | four: Four, 423 | } 424 | 425 | #[nest] 426 | struct Four {} 427 | ``` 428 | 429 |
430 | 431 | ### Field Attributes **`#>[meta]`** 432 | 433 | If you structure has many defined attributes, it can become awkward to define attributes before the nested structure. To combat this, you can define attributes that apply to nested objects before fields and enum variants. This can be accomplished by using `#>[meta]` syntax. `#>` will apply the attribute to the next struct. 434 | 435 | ```rust 436 | nest! { 437 | struct MyStruct { 438 | #>[derive(Debug)] 439 | f: struct DebugableStruct { } 440 | // equivlent to: 441 | // f: #[derive(Debug)] 442 | // struct DebugableStruct { } 443 | } 444 | } 445 | ``` 446 | 447 | 448 |
449 | 450 | Expand 451 | 452 |
453 | 454 | ```rust 455 | struct MyStruct { 456 | f: DebugableStruct, 457 | } 458 | 459 | #[derive(Debug)] 460 | // ^^^^^ applied to structure and not field `f` 461 | struct DebugableStruct {} 462 | ``` 463 | 464 |
465 | 466 | #### Enum Variant Attributes 467 | Field attributes can also be applied to an enum variant. If there are multiple items defined in a single variant then the attribute will be applied to each. 468 | 469 | ```rust 470 | nest! { 471 | enum MyEnum { 472 | #>[derive(Debug)] 473 | Variant { 474 | // #[derive(Debug) 475 | one: struct One, 476 | // #[derive(Debug) 477 | two: struct Two 478 | } 479 | } 480 | } 481 | ``` 482 | 483 |
484 | 485 | Expand 486 | 487 |
488 | 489 | ```rust 490 | enum MyEnum { 491 | Variant { 492 | one: One, 493 | two: Two, 494 | } 495 | } 496 | 497 | #[derive(Debug)] 498 | struct One; 499 | 500 | #[derive(Debug)] 501 | struct Two; 502 | ``` 503 | 504 |
505 | 506 | ## Semicolons 507 | 508 | Rust mandates semicolons to mark the end of tuple struct and unit struct declarations. Nestify, however, introduces flexibility by making this semicolon optional. 509 | 510 | ### Rust Standard 511 | 512 | - Tuple struct: `struct MyTuple(i32, String);` 513 | - Unit struct: `struct MyUnit;` 514 | 515 | ### Nestify Flexibility 516 | 517 | With Nestify, you can omit the semicolon without any impact: 518 | 519 | ```rust 520 | // Unit struct without a semicolon 521 | nest! { 522 | struct MyUnit 523 | } 524 | 525 | // Tuple struct without a semicolon 526 | nest! { 527 | struct MyTuple(i32, String) 528 | } 529 | ``` 530 |
531 | 532 | Expand 533 | 534 |
535 | 536 | ```rust 537 | struct MyUnit; 538 | // ^ automaticly added 539 | 540 | struct MyTuple(i32, String); 541 | // ^ automaticly added 542 | ``` 543 | 544 |
545 |
546 | 547 | This adjustment simplifies syntax, particularly in the context of defining nested structures, aligning with Nestify's goal of enhancing code readability and maintenance. Whether you include the semicolon or not, Nestify processes the definitions correctly, thanks to its domain-specific optimizations. 548 | 549 | ## Visibility 550 | 551 | Visibility can be altered in both parent and nested structures. It exhibits the following behavior 552 | 553 | ### Named Field Visibility 554 | When using named fields, you must specify the desired visibility before *both* the field and the definition. 555 | 556 | ```rust 557 | nest! { 558 | pub struct One { 559 | pub two: pub struct Two 560 | //| ^^^ visibility applied to definition (b) 561 | //|> visibility applied to field (a) 562 | } 563 | } 564 | ``` 565 | 566 |
567 | 568 | Expand 569 | 570 |
571 | 572 | ```rust 573 | pub struct One { 574 | pub two: Two, 575 | //^ (a) 576 | } 577 | 578 | pub struct Two; 579 | //^ (b) 580 | ``` 581 | 582 |
583 | 584 | ### Unnamed Field Visibility 585 | Unnamed fields apply visibility to both the field and the item. 586 | 587 | ```rust 588 | nest! { 589 | pub struct One(pub struct Two) 590 | // ^^^ visibility applied to both field and struct 591 | } 592 | ``` 593 | 594 | 595 |
596 | 597 | Expand 598 | 599 |
600 | 601 | ```rust 602 | pub struct One(pub Two); 603 | // ^^^ applied here 604 | 605 | pub struct Two; 606 | //^ and here 607 | ``` 608 | 609 |
610 | 611 | ### Enum Variants 612 | Enum variants apply visibility just to the structure. 613 | This is because variants inherit the base visibility of the enum. 614 | See [E0449](https://doc.rust-lang.org/error_codes/E0449.html) for more details. 615 | 616 | ```rust 617 | nest! { 618 | pub enum One { 619 | Two(pub struct Two) 620 | // ^^^ will apply to the structure 621 | } 622 | } 623 | ``` 624 | 625 |
626 | 627 | Expand 628 | 629 |
630 | 631 | ```rust 632 | pub enum One { 633 | Two(Two) 634 | } 635 | 636 | pub struct Two; 637 | //^ applied to structure 638 | ``` 639 | 640 |
641 | 642 | ## Generic containers 643 | 644 | Nestify also supports defining nested structures inside generic containers like `Vec`, `Option`, or `Result`. 645 | 646 | ```rust 647 | struct One(Vec); 648 | ``` 649 |
650 | 651 | Expand 652 | 653 |
654 | 655 | ```rust 656 | struct One(Vec); 657 | 658 | struct Two { 659 | field: i32, 660 | } 661 | ``` 662 | 663 |
664 | 665 | Here, `struct Two` is being defined directly within the generic parameter of `Vec`. 666 | 667 | ### Another Example 668 | 669 | To further illustrate, consider a scenario where you want to include an optional configuration struct within another struct: 670 | 671 | ```rust 672 | struct AppConfig(Option); 673 | ``` 674 | 675 |
676 | 677 | Expand 678 | 679 |
680 | 681 | ```rust 682 | struct AppConfig(Option); 683 | 684 | struct DatabaseConfig { 685 | url: String, 686 | } 687 | ``` 688 | 689 |
690 | 691 | In this example, `struct DatabaseConfig` is defined directly within the `Option` generic type in the declaration of `AppConfig`. 692 | 693 | ### Limitations 694 | 695 | Other kinds of indirections are not supported, such as after a `&` or `&mut`, inside a fixed size array `[_, N]` or dynamic size array `[_]`, tuples and probably many others. If you need such indirections feel free to contribute to add support for them. 696 | 697 | --- 698 | 699 | ## Contributing 700 | I love contributors! Plus, 701 | I'm a bad writer, so I would love community support to improve this guide as well. 702 | 703 | > [!IMPORTANT] 704 | > While not required, 705 | > It is *strongly recommended* that you either [submit an issue](https://github.com/snowfoxsh/nestify/issues) 706 | > or [contact me](#contact-me) before implementing a feature for the best chance of being accepted 707 | 708 | To make code changes: 709 | 710 | 1. Fork the repository. 711 | 2. Create a new branch for your features or bug fixes. 712 | 3. Write tests for your changes. 713 | 4. Make sure all tests pass. 714 | 5. Submit a pull request. 715 | 716 | Standard stuff! 717 | 718 | ## License 719 | 720 | This project is licensed under the MIT License. If you need it under a different license *Contact Me*. 721 | MIT license support will always be maintained. Don't fear! 722 | 723 | ## Contact me 724 | 725 | Check GitHub for information @snowfoxsh 726 | 727 | ## Credits 728 | These wonderful people made major contributions. Check them out 729 | 730 | - @nanocryk: Implemented nested definitions in generics 731 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - [x] HIGH: fix publicity modifiers [ISSUE](https://github.com/snowfoxsh/nestify/issues/1) 4 | - [x] Enums 5 | - [x] Confirm structures 6 | - [ ] Generic Nesting 7 | - [x] Add support for container generic nesting 8 | - [ ] Add support for tuple slice + more types generic nesting 9 | - [x] add nested attribute `#>[meta]` support 10 | - [x] fix where clause 11 | - [ ] improve documentation 12 | - [x] write README.md 13 | - [x] add recursive generic support 14 | - [ ] fix bug where `_ : struct Name {}` is valid when it should not be 15 | - [x] rework fish support 16 | - [x] rename fish to FishHook 17 | - [ ] fix spans 18 | - [ ] fix issue where `struct { };` "unexpected `;`" error is not spanned correctly 19 | - [ ] better errors 20 | - [ ] add diagnostic warnings and possibly errors behind a feature flag for nightly users 21 | - [ ] add warning to put `#>[meta]` after `#[meta]` 22 | - [ ] update errors to be more descriptive 23 | - [ ] switch errors from proc-macro-error abort! to syn 24 | - [ ] write more tests -------------------------------------------------------------------------------- /src/attributes.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{bracketed, token, Meta, Token}; 5 | 6 | /// ```ignore 7 | /// // In structs 8 | /// struct MyStruct { 9 | /// #[field_attribute] 10 | /// field: () 11 | /// } 12 | /// 13 | /// // In enums 14 | /// enum MyEnum { 15 | /// #[field_attribute] 16 | /// MyNamed { 17 | /// #[field_attribute] 18 | /// field: () 19 | /// }, 20 | /// } 21 | /// ``` 22 | pub enum FieldAttribute { 23 | Nested(NestedAttribute), 24 | Field(Attribute), 25 | } 26 | 27 | #[derive(Clone, PartialEq, Eq)] 28 | pub struct Attribute { 29 | pub pound_token: Token![#], 30 | pub bracket_token: token::Bracket, 31 | pub meta: Meta, 32 | } 33 | 34 | pub struct NestedAttribute { 35 | pub pound_token: Token![#], 36 | pub ident_token: Token![>], 37 | pub bracket_token: token::Bracket, 38 | pub meta: Meta, 39 | pub modifier: Option, 40 | } 41 | 42 | #[derive(Clone)] 43 | pub struct CompositeAttribute { 44 | pub pound_token: Token![#], 45 | pub bracket_token: token::Bracket, 46 | pub meta: Meta, 47 | pub modifier: Option, 48 | } 49 | 50 | #[derive(Clone, Copy)] 51 | pub enum AttributeModifier { 52 | Star(Token![*]), 53 | Slash(Token![/]), 54 | Minus(Token![-]), 55 | } 56 | 57 | pub trait ParseAttribute: Sized { 58 | fn parse_outer(input: ParseStream) -> syn::Result> { 59 | let mut attrs = vec![]; 60 | while input.peek(Token![#]) { 61 | attrs.push(input.call(Self::parse_single_outer)?) 62 | } 63 | Ok(attrs) 64 | } 65 | fn parse_single_outer(input: ParseStream) -> syn::Result; 66 | } 67 | 68 | impl Parse for AttributeModifier { 69 | fn parse(input: ParseStream) -> syn::Result { 70 | let lookahead = input.lookahead1(); 71 | if lookahead.peek(Token![*]) { 72 | input.parse().map(Self::Star) 73 | } else if lookahead.peek(Token![/]) { 74 | input.parse().map(Self::Slash) 75 | } else if lookahead.peek(Token![-]) { 76 | input.parse().map(Self::Minus) 77 | } else { 78 | Err(lookahead.error()) 79 | } 80 | } 81 | } 82 | 83 | impl ParseAttribute for Attribute { 84 | fn parse_single_outer(input: ParseStream) -> syn::Result { 85 | let content; 86 | Ok(Self { 87 | pound_token: input.parse()?, 88 | bracket_token: bracketed!(content in input), 89 | meta: content.parse()?, 90 | }) 91 | } 92 | } 93 | 94 | impl ParseAttribute for FieldAttribute { 95 | fn parse_single_outer(input: ParseStream) -> syn::Result { 96 | if input.peek(Token![#]) && input.peek2(Token![>]) { 97 | Ok(Self::Nested(input.call(NestedAttribute::parse_single_outer)?)) 98 | } else { 99 | Ok(Self::Field(input.call(Attribute::parse_single_outer)?)) 100 | } 101 | } 102 | } 103 | 104 | impl ParseAttribute for NestedAttribute { 105 | fn parse_single_outer(mut input: ParseStream) -> syn::Result { 106 | let content; 107 | let pound_token = input.parse()?; 108 | let ident_token = input.parse()?; 109 | let bracket_token = bracketed!(content in input); 110 | let meta = content.parse()?; 111 | 112 | let modifier = handle_attribute_modifier(&mut input)?; 113 | 114 | Ok(Self { 115 | pound_token, 116 | ident_token, 117 | bracket_token, 118 | meta, 119 | modifier, 120 | }) 121 | } 122 | } 123 | 124 | impl ParseAttribute for CompositeAttribute { 125 | fn parse_single_outer(mut input: ParseStream) -> syn::Result { 126 | let content; 127 | let pound_token = input.parse()?; 128 | let bracket_token = bracketed!(content in input); 129 | let meta = content.parse()?; 130 | 131 | let modifier = handle_attribute_modifier(&mut input)?; 132 | 133 | Ok(Self { 134 | pound_token, 135 | bracket_token, 136 | meta, 137 | modifier, 138 | }) 139 | } 140 | } 141 | 142 | fn handle_attribute_modifier(input: &mut ParseStream) -> syn::Result> { 143 | if input.peek(Token![*]) 144 | || input.peek(Token![/]) 145 | || input.peek(Token![-]) 146 | || input.peek(Token![+]) 147 | { 148 | Ok(Some(input.parse()?)) 149 | } else { 150 | Ok(None) 151 | } 152 | } 153 | 154 | impl ToTokens for Attribute { 155 | fn to_tokens(&self, tokens: &mut TokenStream) { 156 | self.pound_token.to_tokens(tokens); 157 | 158 | self.bracket_token 159 | .surround(tokens, |meta_tokens| self.meta.to_tokens(meta_tokens)) 160 | } 161 | } 162 | 163 | impl ToTokens for CompositeAttribute { 164 | fn to_tokens(&self, tokens: &mut TokenStream) { 165 | self.pound_token.to_tokens(tokens); 166 | 167 | self.bracket_token 168 | .surround(tokens, |meta_tokens| self.meta.to_tokens(meta_tokens)) 169 | } 170 | } 171 | 172 | impl ToTokens for NestedAttribute { 173 | fn to_tokens(&self, tokens: &mut TokenStream) { 174 | self.pound_token.to_tokens(tokens); 175 | 176 | self.bracket_token 177 | .surround(tokens, |meta_tokens| self.meta.to_tokens(meta_tokens)) 178 | } 179 | } 180 | 181 | impl ToTokens for FieldAttribute { 182 | fn to_tokens(&self, tokens: &mut TokenStream) { 183 | match self { 184 | FieldAttribute::Nested(a) => a.to_tokens(tokens), 185 | FieldAttribute::Field(a) => a.to_tokens(tokens), 186 | } 187 | } 188 | } 189 | 190 | impl From for Attribute { 191 | fn from(nested: NestedAttribute) -> Self { 192 | Attribute { 193 | pound_token: nested.pound_token, 194 | bracket_token: nested.bracket_token, 195 | meta: nested.meta, 196 | } 197 | } 198 | } 199 | 200 | impl From for Attribute { 201 | fn from(composite: CompositeAttribute) -> Self { 202 | Attribute { 203 | pound_token: composite.pound_token, 204 | bracket_token: composite.bracket_token, 205 | meta: composite.meta, 206 | } 207 | } 208 | } 209 | 210 | impl From for CompositeAttribute { 211 | fn from(nested: NestedAttribute) -> Self { 212 | CompositeAttribute { 213 | pound_token: nested.pound_token, 214 | bracket_token: nested.bracket_token, 215 | meta: nested.meta, 216 | modifier: nested.modifier, 217 | } 218 | } 219 | } 220 | 221 | impl PartialEq for Attribute { 222 | fn eq(&self, other: &NestedAttribute) -> bool { 223 | self.meta == other.meta 224 | } 225 | } 226 | 227 | impl PartialEq for NestedAttribute { 228 | fn eq(&self, other: &Attribute) -> bool { 229 | self.meta == other.meta 230 | } 231 | } 232 | 233 | impl PartialEq for Attribute { 234 | fn eq(&self, other: &CompositeAttribute) -> bool { 235 | self.meta == other.meta 236 | } 237 | } 238 | 239 | impl PartialEq for CompositeAttribute { 240 | fn eq(&self, other: &Attribute) -> bool { 241 | self.meta == other.meta 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/discriminant.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::ToTokens; 3 | use syn::{Expr, Token}; 4 | 5 | pub struct Discriminant { 6 | pub eq_token: Token![=], 7 | pub expr: Expr, 8 | } 9 | 10 | impl ToTokens for Discriminant { 11 | fn to_tokens(&self, tokens: &mut TokenStream) { 12 | self.eq_token.to_tokens(tokens); 13 | self.expr.to_tokens(tokens); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/fish.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Span, TokenStream}; 2 | use quote::{ToTokens}; 3 | use syn::parse::{Parse, ParseStream}; 4 | use syn::{Token, AngleBracketedGenericArguments}; 5 | use syn::spanned::Spanned; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct FishHook { 9 | pub prefix: Token![||], 10 | pub generics: AngleBracketedGenericArguments, 11 | } 12 | 13 | impl FishHook { 14 | pub fn span(&self) -> Span { 15 | // will provide better span on a nightly compiler 16 | self.prefix.span().join(self.generics.span()).unwrap_or_else(|| self.prefix.span()) 17 | } 18 | } 19 | 20 | impl Parse for FishHook { 21 | fn parse(input: ParseStream) -> syn::Result { 22 | let fish = Self { 23 | prefix: input.parse()?, 24 | generics: input.parse()?, 25 | }; 26 | 27 | if let Some(tokens) = fish.generics.colon2_token { 28 | return Err(syn::Error::new(tokens.span(), ":: are not allowed in FishHook syntax")); 29 | } 30 | 31 | if fish.generics.args.iter().count() == 0 { 32 | return Err(syn::Error::new(fish.span(), "FishHook should not have empty or no generics")); 33 | } 34 | Ok(fish) 35 | } 36 | } 37 | 38 | impl ToTokens for FishHook { 39 | fn to_tokens(&self, tokens: &mut TokenStream) { 40 | self.generics.to_tokens(tokens); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::special_data::Special; 2 | use crate::unpack::Unpack; 3 | use crate::unpack_context::UnpackContext; 4 | use proc_macro_error::{abort_call_site, proc_macro_error}; 5 | use syn::parse_macro_input; 6 | 7 | #[cfg(test)] 8 | mod tests; 9 | pub(crate) mod attributes; 10 | pub(crate) mod discriminant; 11 | pub(crate) mod fish; 12 | pub(crate) mod special_data; 13 | pub(crate) mod ty; 14 | pub(crate) mod unpack_context; 15 | 16 | /// Provides functionality for unpacking special data structures. 17 | /// 18 | /// This module defines traits and implementations for recursively unpacking 19 | /// data structures annotated with custom attributes, facilitating a form of 20 | /// metaprogramming within Rust macros. 21 | pub(crate) mod unpack; 22 | 23 | 24 | /// Allows for the expansion of "nested" items 25 | /// 26 | /// # Learn 27 | /// See [Guide](https://github.com/snowfoxsh/nestify) 28 | /// 29 | /// # Examples 30 | /// 31 | /// Define a user profile with nested address and preferences structures 32 | /// ``` 33 | /// nestify::nest! { 34 | /// struct UserProfile { 35 | /// name: String, 36 | /// address: struct Address { 37 | /// street: String, 38 | /// city: String, 39 | /// }, 40 | /// preferences: struct Preferences { 41 | /// newsletter: bool, 42 | /// }, 43 | /// } 44 | /// } 45 | /// ``` 46 | /// 47 | /// Define a task with a nested status enum 48 | /// ``` 49 | /// nestify::nest! { 50 | /// struct Task { 51 | /// id: i32, 52 | /// description: String, 53 | /// status: enum Status { 54 | /// Pending, 55 | /// InProgress, 56 | /// Completed, 57 | /// }, 58 | /// } 59 | /// } 60 | /// ``` 61 | #[proc_macro] 62 | #[proc_macro_error] 63 | pub fn nest(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 64 | if input.is_empty() { 65 | abort_call_site!( 66 | "Nest! macro expansion failed: The input is empty."; 67 | note = "The nest! macro expects a non-empty `struct` or `enum` definition to function properly."; 68 | help = "Please ensure that you are using the nest! macro with a valid `struct` or `enum`.\ 69 | Refer to documentation for information on how to use this macro and more examples"; 70 | ); 71 | } 72 | 73 | let def = parse_macro_input!(input as Special); 74 | 75 | def.unpack(UnpackContext::default(), Vec::default(), None, false).into() 76 | } 77 | -------------------------------------------------------------------------------- /src/special_data.rs: -------------------------------------------------------------------------------- 1 | use crate::attributes::{CompositeAttribute, FieldAttribute, ParseAttribute}; 2 | use crate::discriminant::Discriminant; 3 | use crate::fish::FishHook; 4 | use crate::ty::SpecialType; 5 | use proc_macro_error::abort; 6 | use syn::ext::IdentExt; 7 | use syn::parse::{Parse, ParseStream}; 8 | use syn::punctuated::Punctuated; 9 | use syn::{ 10 | braced, parenthesized, token, FieldMutability, Generics, Ident, Token, Visibility, WhereClause, 11 | }; 12 | 13 | // some comments are based on the `syn` crate documentation 14 | 15 | /// The base type definition. 16 | /// It allows recursive definition expansions, therefore, 17 | /// it is *Special* 18 | pub struct Special { 19 | pub attrs: Vec, // used to be RecAttribute 20 | pub vis: Visibility, 21 | pub ident: Ident, 22 | pub generics: Generics, 23 | pub body: Body, 24 | } 25 | 26 | /// The body of a definition; Contains the data for the item 27 | pub enum Body { 28 | Struct(BodyStruct), 29 | Enum(BodyEnum), 30 | // Union(BodyUnion), todo 31 | } 32 | 33 | /// Structure Body aka Data in syn 34 | pub struct BodyStruct { 35 | struct_token: Token![struct], 36 | pub fields: SpecialFields, 37 | semi_token: Option, 38 | } 39 | 40 | /// Enumeration Body aka Data in syn 41 | pub struct BodyEnum { 42 | enum_token: Token![enum], 43 | brace_token: token::Brace, 44 | pub variants: Punctuated, 45 | } 46 | 47 | // struct BodyUnion { 48 | // union_token: Token![union], 49 | // } 50 | 51 | /// An enum variant 52 | pub struct SpecialVariant { 53 | /// Attributes belonging to variant: 54 | /// ```txt 55 | /// #[...]* // standard attribute application 56 | /// #>[...]* // applied to type definitions in variant 57 | /// Variant 58 | /// ``` 59 | pub attrs: Vec, // field attribute 60 | 61 | /// Name of the variant. 62 | pub ident: Ident, 63 | 64 | /// Content stored in the variant. 65 | pub fields: SpecialFields, 66 | 67 | /// Explicit discriminant: `Variant = 1` 68 | pub discriminant: Option, 69 | } 70 | 71 | /// Data stored in an enum variant or structure 72 | pub enum SpecialFields { 73 | /// Named fields of a struct or struct variant such as 74 | /// `Point { 75 | /// x: f64, 76 | /// y: f64 77 | /// }` 78 | Named(FieldsNamed), // 79 | 80 | /// Unnamed fields of a tuple struct or tuple variant such as 81 | /// `Some(T)`. 82 | Unnamed(FieldsUnnamed), 83 | 84 | /// Unit struct or unit variant such as `None`. 85 | Unit, 86 | } 87 | 88 | /// Named fields of a struct or struct variant such as 89 | /// `Point { 90 | /// x: f64, 91 | /// y: f64 92 | /// }` 93 | pub struct FieldsNamed { 94 | pub brace_token: token::Brace, 95 | pub named: Punctuated, 96 | } 97 | 98 | /// Unnamed fields of a tuple struct or tuple variant such as `Some(T)`. 99 | pub struct FieldsUnnamed { 100 | pub paren_token: token::Paren, 101 | pub unnamed: Punctuated, 102 | } 103 | 104 | // note: refactor to a new file eventually 105 | 106 | /// A field of a struct or enum variant. 107 | pub struct SpecialField { 108 | pub attrs: Vec, 109 | pub vis: Visibility, 110 | pub mutability: FieldMutability, 111 | /// Name of the field if any 112 | pub ident: Option, 113 | pub colon_token: Option, 114 | pub ty: SpecialType, 115 | pub fish: Option, 116 | } 117 | 118 | impl Parse for Special { 119 | fn parse(input: ParseStream) -> syn::Result { 120 | let attrs = input.call(CompositeAttribute::parse_outer)?; 121 | let vis = input.parse::()?; 122 | 123 | let lookahead = input.lookahead1(); 124 | if lookahead.peek(Token![struct]) { 125 | let struct_token = input.parse::()?; 126 | let ident = input.parse::()?; 127 | let generics = input.parse::()?; 128 | let (where_clause, fields, semi) = parse_data_struct(input)?; 129 | Ok(Special { 130 | attrs, 131 | vis, 132 | ident, 133 | generics: Generics { 134 | where_clause, 135 | ..generics 136 | }, 137 | body: Body::Struct(BodyStruct { 138 | struct_token, 139 | fields, 140 | semi_token: semi, 141 | }), 142 | }) 143 | } else if lookahead.peek(Token![enum]) { 144 | let enum_token = input.parse::()?; 145 | let ident = input.parse::()?; 146 | let generics = input.parse::()?; 147 | let (where_clause, brace, variants) = parse_data_enum(input)?; 148 | Ok(Special { 149 | attrs, 150 | vis, 151 | ident, 152 | generics: Generics { 153 | where_clause, 154 | ..generics 155 | }, 156 | body: Body::Enum(BodyEnum { 157 | enum_token, 158 | brace_token: brace, 159 | variants, 160 | }), 161 | }) 162 | } else if lookahead.peek(Token![union]) { 163 | Err(input.error("Unions remain unimplemented")) //todo: improve this message 164 | } else { 165 | Err(lookahead.error()) 166 | } 167 | } 168 | } 169 | 170 | fn parse_data_struct( 171 | input: ParseStream, 172 | ) -> syn::Result<(Option, SpecialFields, Option)> { 173 | let mut lookahead = input.lookahead1(); 174 | let mut where_clause: Option = None; 175 | if lookahead.peek(Token![where]) { 176 | where_clause = Some(input.parse()?); 177 | lookahead = input.lookahead1(); 178 | } 179 | 180 | if where_clause.is_none() && lookahead.peek(token::Paren) { 181 | let fields: FieldsUnnamed = input.parse()?; 182 | 183 | lookahead = input.lookahead1(); 184 | if lookahead.peek(Token![where]) { 185 | where_clause = Some(input.parse()?); 186 | lookahead = input.lookahead1(); 187 | } 188 | 189 | // parse an optional semi 190 | if lookahead.peek(Token![;]) { 191 | let semi = input.parse()?; 192 | Ok((where_clause, SpecialFields::Unnamed(fields), Some(semi))) 193 | } else { 194 | Ok((where_clause, SpecialFields::Unnamed(fields), None)) 195 | } 196 | } else if lookahead.peek(token::Brace) { 197 | let fields: FieldsNamed = input.parse()?; 198 | 199 | Ok((where_clause, SpecialFields::Named(fields), None)) 200 | } else if lookahead.peek(Token![;]) { 201 | let semi = input.parse()?; 202 | Ok((where_clause, SpecialFields::Unit, Some(semi))) 203 | } else { 204 | Ok((where_clause, SpecialFields::Unit, None)) 205 | } 206 | } 207 | 208 | fn parse_data_enum( 209 | input: ParseStream, 210 | ) -> syn::Result<( 211 | Option, 212 | token::Brace, 213 | Punctuated, 214 | )> { 215 | let where_clause = input.parse()?; 216 | 217 | let content; 218 | let brace = braced!(content in input); 219 | let variants = content.parse_terminated(SpecialVariant::parse, Token![,])?; 220 | 221 | Ok((where_clause, brace, variants)) 222 | } 223 | 224 | impl Parse for SpecialVariant { 225 | fn parse(input: ParseStream) -> syn::Result { 226 | let attrs = input.call(FieldAttribute::parse_outer)?; 227 | let _vis: Visibility = input.parse()?; // left in for compatibility 228 | let ident: Ident = input.parse()?; 229 | let fields = if input.peek(token::Brace) { 230 | SpecialFields::Named(input.parse()?) 231 | } else if input.peek(token::Paren) { 232 | SpecialFields::Unnamed(input.parse()?) 233 | } else { 234 | SpecialFields::Unit 235 | }; 236 | let discriminant = if input.peek(Token![=]) { 237 | Some(Discriminant { 238 | eq_token: input.parse()?, 239 | expr: input.parse()?, 240 | }) 241 | } else { 242 | None 243 | }; 244 | Ok(SpecialVariant { 245 | attrs, 246 | ident, 247 | fields, 248 | discriminant, 249 | }) 250 | } 251 | } 252 | 253 | impl Parse for FieldsNamed { 254 | fn parse(input: ParseStream) -> syn::Result { 255 | let content; 256 | Ok(FieldsNamed { 257 | brace_token: braced!(content in input), 258 | named: content.parse_terminated(SpecialField::parse_named, Token![,])?, 259 | }) 260 | } 261 | } 262 | 263 | impl Parse for FieldsUnnamed { 264 | fn parse(input: ParseStream) -> syn::Result { 265 | let content; 266 | Ok(FieldsUnnamed { 267 | paren_token: parenthesized!(content in input), 268 | unnamed: content.parse_terminated(SpecialField::parse_unnamed, Token![,])?, 269 | }) 270 | } 271 | } 272 | 273 | impl SpecialField { 274 | pub fn parse_named(mut input: ParseStream) -> syn::Result { 275 | let attrs = input.call(FieldAttribute::parse_outer)?; 276 | let vis: Visibility = input.parse()?; 277 | 278 | // note: has cfg!(feature = "full") | data.rs 279 | let unnamed_field = input.peek(Token![_]); 280 | 281 | let ident = if unnamed_field { 282 | input.call(Ident::parse_any) 283 | } else { 284 | input.parse() 285 | }?; 286 | 287 | let colon_token: Token![:] = input.parse()?; 288 | 289 | let ty: SpecialType = if unnamed_field 290 | && (input.peek(Token![struct]) 291 | || input.peek(Token![union]) && input.peek2(token::Brace)) 292 | { 293 | let span = input.span(); 294 | abort!( 295 | span, 296 | "Not implemented Yet"; 297 | note = "Requires a rewrite of the syn::verbatim module" 298 | ); // todo 299 | } else { 300 | input.parse()? 301 | }; 302 | 303 | // handle FishHook 304 | let fish = handle_fish_hook(&mut input, &ty)?; 305 | 306 | Ok(SpecialField { 307 | attrs, 308 | vis, 309 | mutability: FieldMutability::None, 310 | ident: Some(ident), 311 | fish, 312 | colon_token: Some(colon_token), 313 | ty, 314 | }) 315 | } 316 | 317 | pub fn parse_unnamed(mut input: ParseStream) -> syn::Result { 318 | let attrs = input.call(FieldAttribute::parse_outer)?; 319 | let vis = input.parse()?; 320 | let ty = input.parse()?; 321 | 322 | // handle FishHook 323 | let fish = handle_fish_hook(&mut input, &ty)?; 324 | 325 | Ok(SpecialField { 326 | attrs, 327 | vis, 328 | mutability: FieldMutability::None, 329 | ident: None, 330 | colon_token: None, 331 | ty, 332 | fish, 333 | }) 334 | } 335 | } 336 | 337 | pub(crate) fn handle_fish_hook(input: &mut ParseStream, ty: &SpecialType) -> syn::Result> { 338 | if input.peek(Token![||]) { 339 | // only allow FishHook syntax after a nested type definition 340 | let fishhook = input.parse::()?; 341 | if matches!(ty, SpecialType::Type(_)) { 342 | // we have run into a FishHook in an invalid location 343 | return Err(syn::Error::new( 344 | fishhook.span(), 345 | "FishHook should only come after nested type") 346 | ) 347 | } 348 | Ok(Some(fishhook)) 349 | } else { 350 | Ok(None) 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | mod test_attributes; -------------------------------------------------------------------------------- /src/tests/test_attributes/attribute_modifier.rs: -------------------------------------------------------------------------------- 1 | use syn::parse_str; 2 | use quote::quote; 3 | use crate::attributes::AttributeModifier; 4 | 5 | #[test] 6 | fn parse_star_modifier() { 7 | let input = quote!{*}; 8 | let parsed = parse_str::<>(&input.to_string()); 9 | 10 | assert!(matches!(parsed, Ok(AttributeModifier::Star(_)))); 11 | } 12 | 13 | #[test] 14 | fn parse_slash_modifier() { 15 | let input = quote!{/}; 16 | let parsed = parse_str::(&input.to_string()); 17 | 18 | assert!(matches!(parsed, Ok(AttributeModifier::Slash(_)))); 19 | } 20 | 21 | #[test] 22 | fn parse_minus_modifier() { 23 | let input = quote!{-}; 24 | let parsed = parse_str::(&input.to_string()); 25 | 26 | assert!(matches!(parsed, Ok(AttributeModifier::Minus(_)))); 27 | } 28 | -------------------------------------------------------------------------------- /src/tests/test_attributes/mod.rs: -------------------------------------------------------------------------------- 1 | mod attribute_modifier; -------------------------------------------------------------------------------- /src/ty.rs: -------------------------------------------------------------------------------- 1 | use { 2 | crate::special_data::Special, 3 | syn::{ 4 | parse::{Parse, ParseStream}, 5 | Type, 6 | }, 7 | }; 8 | 9 | /// Can either be a normal type, or a type definition 10 | pub enum SpecialType { 11 | /// Our curstom `struct`/`enum` syntax 12 | Def(Special), 13 | /// A normal Rust type with custom parsing to support nested special types 14 | /// (for exemple in generics). 15 | Augmented(augmented::Type), 16 | /// A normal Rust type 17 | Type(Type), 18 | } 19 | 20 | impl Parse for SpecialType { 21 | fn parse(input: ParseStream) -> syn::Result { 22 | if let Some(ty) = augmented::Type::maybe_parse(input)? { 23 | Ok(SpecialType::Augmented(ty)) 24 | } else if let Ok(ty) = input.parse::() { 25 | Ok(SpecialType::Type(ty)) 26 | } else { 27 | Ok(SpecialType::Def(input.parse::()?)) 28 | } 29 | } 30 | } 31 | 32 | /// Re-implementation of syn types to support nested custom syntax. 33 | pub(crate) mod augmented { 34 | use { 35 | syn::{ 36 | ext::IdentExt, 37 | parse::{Parse, ParseStream}, 38 | Result, Token, 39 | spanned::Spanned, 40 | }, 41 | crate::{Unpack, UnpackContext, attributes::CompositeAttribute, fish::FishHook, special_data::handle_fish_hook}, 42 | proc_macro2::TokenStream, 43 | }; 44 | 45 | pub enum Type { 46 | Path(TypePath), 47 | } 48 | 49 | pub struct TypePath { 50 | pub qself: Option, 51 | pub path: Path, 52 | } 53 | 54 | pub struct Path { 55 | pub leading_colon: Option, 56 | pub segments: syn::punctuated::Punctuated, 57 | } 58 | 59 | pub struct PathSegment { 60 | pub ident: syn::Ident, 61 | pub arguments: PathArguments, 62 | } 63 | 64 | pub enum PathArguments { 65 | None, 66 | AngleBracketed(AngleBracketedGenericArguments), 67 | // todo: support custom types inside `Fn(A, B) -> C` ? 68 | // Parenthesized(syn::ParenthesizedGenericArguments), 69 | } 70 | 71 | pub struct AngleBracketedGenericArguments { 72 | pub colon2_token: Option, 73 | pub lt_token: syn::token::Lt, 74 | pub args: syn::punctuated::Punctuated, 75 | pub gt_token: syn::token::Gt, 76 | } 77 | 78 | pub enum GenericArgument { 79 | Lifetime(syn::Lifetime), 80 | // here we replace `syn::Type` by `super::SpecialType` ! 81 | Type(super::SpecialType, Option), 82 | Const(syn::Expr), 83 | AssocType(syn::AssocType), 84 | AssocConst(syn::AssocConst), 85 | Constraint(syn::Constraint), 86 | } 87 | 88 | impl Type { 89 | /// Returns Ok(None) if it doesn't match 90 | pub fn maybe_parse(input: ParseStream) -> Result> { 91 | let lookahead = input.lookahead1(); 92 | 93 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/ty.rs#L490 94 | if lookahead.peek(syn::Ident) 95 | || input.peek(Token![super]) 96 | || input.peek(Token![self]) 97 | || input.peek(Token![Self]) 98 | || input.peek(Token![crate]) 99 | || lookahead.peek(Token![::]) 100 | || lookahead.peek(Token![<]) 101 | { 102 | let ty: TypePath = input.parse()?; 103 | if ty.qself.is_some() { 104 | return Ok(Some(Type::Path(ty))); 105 | } 106 | 107 | // we ignore macros 108 | if input.peek(Token![!]) && !input.peek(Token![!=]) && ty.path.is_mod_style() { 109 | return Ok(None); 110 | } 111 | 112 | return Ok(Some(Type::Path(ty))); 113 | } 114 | 115 | // not a augmented type, fallback to `syn::Type` 116 | Ok(None) 117 | } 118 | } 119 | 120 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/ty.rs#L760 121 | impl Parse for TypePath { 122 | fn parse(input: ParseStream) -> Result { 123 | let expr_style = false; 124 | let (qself, path) = parse_qpath(input, expr_style)?; 125 | Ok(TypePath { qself, path }) 126 | } 127 | } 128 | 129 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L636 130 | fn parse_qpath(input: ParseStream, expr_style: bool) -> Result<(Option, Path)> { 131 | if input.peek(Token![<]) { 132 | let lt_token: Token![<] = input.parse()?; 133 | let this: syn::Type = input.parse()?; 134 | let path = if input.peek(Token![as]) { 135 | let as_token: Token![as] = input.parse()?; 136 | let path: Path = input.parse()?; 137 | Some((as_token, path)) 138 | } else { 139 | None 140 | }; 141 | let gt_token: Token![>] = input.parse()?; 142 | let colon2_token: Token![::] = input.parse()?; 143 | let mut rest = syn::punctuated::Punctuated::new(); 144 | loop { 145 | let path = PathSegment::parse_helper(input, expr_style)?; 146 | rest.push_value(path); 147 | if !input.peek(Token![::]) { 148 | break; 149 | } 150 | let punct: Token![::] = input.parse()?; 151 | rest.push_punct(punct); 152 | } 153 | let (position, as_token, path) = match path { 154 | Some((as_token, mut path)) => { 155 | let pos = path.segments.len(); 156 | path.segments.push_punct(colon2_token); 157 | path.segments.extend(rest.into_pairs()); 158 | (pos, Some(as_token), path) 159 | } 160 | None => { 161 | let path = Path { 162 | leading_colon: Some(colon2_token), 163 | segments: rest, 164 | }; 165 | (0, None, path) 166 | } 167 | }; 168 | let qself = syn::QSelf { 169 | lt_token, 170 | ty: Box::new(this), 171 | position, 172 | as_token, 173 | gt_token, 174 | }; 175 | Ok((Some(qself), path)) 176 | } else { 177 | let path = Path::parse_helper(input, expr_style)?; 178 | Ok((None, path)) 179 | } 180 | } 181 | 182 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L629 183 | impl Path { 184 | fn is_mod_style(&self) -> bool { 185 | self.segments 186 | .iter() 187 | .all(|segment| segment.arguments.is_none()) 188 | } 189 | 190 | fn parse_helper(input: ParseStream, expr_style: bool) -> Result { 191 | let mut path = Path { 192 | leading_colon: input.parse()?, 193 | segments: { 194 | let mut segments = syn::punctuated::Punctuated::new(); 195 | let value = PathSegment::parse_helper(input, expr_style)?; 196 | segments.push_value(value); 197 | segments 198 | }, 199 | }; 200 | Path::parse_rest(input, &mut path, expr_style)?; 201 | Ok(path) 202 | } 203 | 204 | fn parse_rest( 205 | input: ParseStream, 206 | path: &mut Self, 207 | expr_style: bool, 208 | ) -> Result<()> { 209 | while input.peek(Token![::]) && !input.peek3(syn::token::Paren) { 210 | let punct: Token![::] = input.parse()?; 211 | path.segments.push_punct(punct); 212 | let value = PathSegment::parse_helper(input, expr_style)?; 213 | path.segments.push_value(value); 214 | } 215 | Ok(()) 216 | } 217 | } 218 | 219 | impl Parse for Path { 220 | fn parse(input: ParseStream) -> Result { 221 | Self::parse_helper(input, false) 222 | } 223 | } 224 | 225 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L163 226 | impl PathArguments { 227 | pub fn is_none(&self) -> bool { 228 | match self { 229 | PathArguments::None => true, 230 | PathArguments::AngleBracketed(_) => false, 231 | } 232 | } 233 | } 234 | 235 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L501 236 | impl Parse for PathSegment { 237 | fn parse(input: ParseStream) -> Result { 238 | Self::parse_helper(input, false) 239 | } 240 | } 241 | 242 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L508 243 | impl PathSegment { 244 | fn parse_helper(input: ParseStream, expr_style: bool) -> Result { 245 | if input.peek(Token![super]) 246 | || input.peek(Token![self]) 247 | || input.peek(Token![crate]) 248 | || cfg!(feature = "full") && input.peek(Token![try]) 249 | { 250 | let ident = input.call(syn::Ident::parse_any)?; 251 | return Ok(PathSegment::from(ident)); 252 | } 253 | 254 | let ident = if input.peek(Token![Self]) { 255 | input.call(syn::Ident::parse_any)? 256 | } else { 257 | input.parse()? 258 | }; 259 | 260 | if !expr_style && input.peek(Token![<]) && !input.peek(Token![<=]) 261 | || input.peek(Token![::]) && input.peek3(Token![<]) 262 | { 263 | Ok(PathSegment { 264 | ident, 265 | arguments: PathArguments::AngleBracketed(input.parse()?), 266 | }) 267 | } else { 268 | Ok(PathSegment::from(ident)) 269 | } 270 | } 271 | } 272 | 273 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L116 274 | impl From for PathSegment 275 | where 276 | T: Into, 277 | { 278 | fn from(ident: T) -> Self { 279 | PathSegment { 280 | ident: ident.into(), 281 | arguments: PathArguments::None, 282 | } 283 | } 284 | } 285 | 286 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L481 287 | impl Parse for AngleBracketedGenericArguments { 288 | fn parse(input: ParseStream) -> Result { 289 | let colon2_token: Option = input.parse()?; 290 | Self::do_parse(colon2_token, input) 291 | } 292 | } 293 | 294 | impl AngleBracketedGenericArguments { 295 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L452 296 | fn do_parse(colon2_token: Option, input: ParseStream) -> Result { 297 | Ok(AngleBracketedGenericArguments { 298 | colon2_token, 299 | lt_token: input.parse()?, 300 | args: { 301 | let mut args = syn::punctuated::Punctuated::new(); 302 | loop { 303 | if input.peek(Token![>]) { 304 | break; 305 | } 306 | let value: GenericArgument = input.parse()?; 307 | args.push_value(value); 308 | if input.peek(Token![>]) { 309 | break; 310 | } 311 | let punct: Token![,] = input.parse()?; 312 | args.push_punct(punct); 313 | } 314 | args 315 | }, 316 | gt_token: input.parse()?, 317 | }) 318 | } 319 | } 320 | 321 | // Copied from: https://github.com/dtolnay/syn/blob/c7f734d8c1c288ea5fc48391a419c23ade441cac/src/path.rs#L316 322 | impl Parse for GenericArgument { 323 | fn parse(mut input: ParseStream) -> Result { 324 | if input.peek(syn::Lifetime) && !input.peek2(Token![+]) { 325 | return Ok(GenericArgument::Lifetime(input.parse()?)); 326 | } 327 | 328 | if input.peek(syn::Lit) || input.peek(syn::token::Brace) { 329 | // we can't call `const_argument` like in syn's source and it is 330 | // hard to copy. We instead fall back on `syn::GenericArgument` and convert it. 331 | 332 | match input.parse()? { 333 | syn::GenericArgument::Const(c) => return Ok(GenericArgument::Const(c)), 334 | _ => unreachable!() 335 | } 336 | } 337 | 338 | let mut argument: super::SpecialType = input.parse()?; 339 | let fish = handle_fish_hook(&mut input, &argument)?; 340 | 341 | match argument { 342 | super::SpecialType::Type(syn::Type::Path(mut ty)) 343 | if ty.qself.is_none() 344 | && ty.path.leading_colon.is_none() 345 | && ty.path.segments.len() == 1 346 | && match &ty.path.segments[0].arguments { 347 | syn::PathArguments::None | syn::PathArguments::AngleBracketed(_) => { 348 | true 349 | } 350 | syn::PathArguments::Parenthesized(_) => false, 351 | } => 352 | { 353 | if let Some(eq_token) = input.parse::>()? { 354 | let segment = ty.path.segments.pop().unwrap().into_value(); 355 | let ident = segment.ident; 356 | let generics = match segment.arguments { 357 | syn::PathArguments::None => None, 358 | syn::PathArguments::AngleBracketed(arguments) => Some(arguments), 359 | syn::PathArguments::Parenthesized(_) => unreachable!(), 360 | }; 361 | return if input.peek(syn::Lit) || input.peek(syn::token::Brace) { 362 | Ok(GenericArgument::AssocConst(syn::AssocConst { 363 | ident, 364 | generics, 365 | eq_token, 366 | // value: const_argument(input)?, 367 | // 368 | // still can't call `const_argument`, so we use 369 | // `syn::GenericArgument` and return an error if it doesn't match a 370 | // Const 371 | value: match input.parse()? { 372 | syn::GenericArgument::Const(c) => c, 373 | arg => return Err(syn::Error::new(arg.span(), "Expected const argument")), 374 | } 375 | })) 376 | } else { 377 | // TODO: Add custom syntax here to support 378 | // `impl Future` and alike? 379 | Ok(GenericArgument::AssocType(syn::AssocType { 380 | ident, 381 | generics, 382 | eq_token, 383 | ty: input.parse()?, 384 | })) 385 | }; 386 | } 387 | 388 | if let Some(colon_token) = input.parse::>()? { 389 | let segment = ty.path.segments.pop().unwrap().into_value(); 390 | return Ok(GenericArgument::Constraint(syn::Constraint { 391 | ident: segment.ident, 392 | generics: match segment.arguments { 393 | syn::PathArguments::None => None, 394 | syn::PathArguments::AngleBracketed(arguments) => Some(arguments), 395 | syn::PathArguments::Parenthesized(_) => unreachable!(), 396 | }, 397 | colon_token, 398 | bounds: { 399 | let mut bounds = syn::punctuated::Punctuated::new(); 400 | loop { 401 | if input.peek(Token![,]) || input.peek(Token![>]) { 402 | break; 403 | } 404 | let value: syn::TypeParamBound = input.parse()?; 405 | bounds.push_value(value); 406 | if !input.peek(Token![+]) { 407 | break; 408 | } 409 | let punct: Token![+] = input.parse()?; 410 | bounds.push_punct(punct); 411 | } 412 | bounds 413 | }, 414 | })); 415 | } 416 | 417 | argument = super::SpecialType::Type(syn::Type::Path(ty)); 418 | } 419 | _ => {} 420 | } 421 | 422 | Ok(GenericArgument::Type(argument, fish)) 423 | } 424 | } 425 | 426 | // unpack 427 | impl Unpack for Type { 428 | type Output = (syn::Type, Vec); 429 | 430 | fn unpack( 431 | self, 432 | unpack_context: UnpackContext, 433 | from_variant: Vec, 434 | override_public: Option, 435 | enum_context: bool, 436 | ) -> Self::Output { 437 | match self { 438 | Self::Path(ty) => { 439 | let (ty, definitions) = ty.unpack(unpack_context, from_variant, override_public, enum_context); 440 | (syn::Type::Path(ty), definitions) 441 | } 442 | } 443 | } 444 | } 445 | 446 | impl Unpack for TypePath { 447 | type Output = (syn::TypePath, Vec); 448 | 449 | fn unpack( 450 | self, 451 | unpack_context: UnpackContext, 452 | from_variant: Vec, 453 | override_public: Option, 454 | enum_context: bool, 455 | ) -> Self::Output { 456 | let Self { qself, path } = self; 457 | let (path, definitions) = path.unpack(unpack_context, from_variant, override_public, enum_context); 458 | (syn::TypePath { qself, path }, definitions ) 459 | } 460 | } 461 | 462 | impl Unpack for Path { 463 | type Output = (syn::Path, Vec); 464 | 465 | fn unpack( 466 | self, 467 | unpack_context: UnpackContext, 468 | from_variant: Vec, 469 | override_public: Option, 470 | enum_context: bool, 471 | ) -> Self::Output { 472 | let Self { leading_colon, segments } = self; 473 | let mut definitions = vec![]; 474 | let segments = segments.into_pairs().map(|seg| { 475 | let (seg, punct) = seg.into_tuple(); 476 | let (seg, mut defs) = seg.unpack(unpack_context.clone(), from_variant.clone(), override_public.clone(), enum_context); 477 | definitions.append(&mut defs); 478 | syn::punctuated::Pair::new(seg, punct) 479 | }).collect(); 480 | 481 | (syn::Path { leading_colon, segments}, definitions) 482 | 483 | } 484 | } 485 | 486 | impl Unpack for PathSegment { 487 | type Output = (syn::PathSegment, Vec); 488 | 489 | fn unpack( 490 | self, 491 | unpack_context: UnpackContext, 492 | from_variant: Vec, 493 | override_public: Option, 494 | enum_context: bool, 495 | ) -> Self::Output { 496 | let Self { ident, arguments } = self; 497 | let (arguments, definitions) = arguments.unpack(unpack_context, from_variant, override_public, enum_context); 498 | (syn::PathSegment { ident, arguments }, definitions ) 499 | } 500 | } 501 | 502 | impl Unpack for PathArguments { 503 | type Output = (syn::PathArguments, Vec); 504 | 505 | fn unpack( 506 | self, 507 | unpack_context: UnpackContext, 508 | from_variant: Vec, 509 | override_public: Option, 510 | enum_context: bool, 511 | ) -> Self::Output { 512 | match self { 513 | PathArguments::None => (syn::PathArguments::None, vec![]), 514 | PathArguments::AngleBracketed(a) => { 515 | let (a, defs) = a.unpack(unpack_context, from_variant, override_public, enum_context); 516 | (syn::PathArguments::AngleBracketed(a), defs) 517 | } 518 | } 519 | } 520 | } 521 | 522 | impl Unpack for AngleBracketedGenericArguments { 523 | type Output = (syn::AngleBracketedGenericArguments, Vec); 524 | 525 | fn unpack( 526 | self, 527 | unpack_context: UnpackContext, 528 | from_variant: Vec, 529 | override_public: Option, 530 | enum_context: bool, 531 | ) -> Self::Output { 532 | let Self { colon2_token, lt_token, args, gt_token } = self; 533 | let mut definitions = vec![]; 534 | let args = args.into_pairs().map(|arg| { 535 | let (arg, punct) = arg.into_tuple(); 536 | let (arg, mut defs) = arg.unpack(unpack_context.clone(), from_variant.clone(), override_public.clone(), enum_context); 537 | definitions.append(&mut defs); 538 | syn::punctuated::Pair::new(arg, punct) 539 | }).collect(); 540 | (syn::AngleBracketedGenericArguments { colon2_token, lt_token, args, gt_token }, definitions ) 541 | } 542 | } 543 | 544 | impl Unpack for GenericArgument { 545 | type Output = (syn::GenericArgument, Vec); 546 | 547 | fn unpack( 548 | self, 549 | unpack_context: UnpackContext, 550 | from_variant: Vec, 551 | override_public: Option, 552 | enum_context: bool, 553 | ) -> Self::Output { 554 | match self { 555 | GenericArgument::Lifetime(v) => (syn::GenericArgument::Lifetime(v), vec![]), 556 | GenericArgument::Const(v) => (syn::GenericArgument::Const(v), vec![]), 557 | GenericArgument::AssocType(v) => (syn::GenericArgument::AssocType(v), vec![]), 558 | GenericArgument::AssocConst(v) => (syn::GenericArgument::AssocConst(v), vec![]), 559 | GenericArgument::Constraint(v) => (syn::GenericArgument::Constraint(v), vec![]), 560 | GenericArgument::Type(super::SpecialType::Type(ty), _fish ) => { 561 | (syn::GenericArgument::Type(ty), vec![]) 562 | } 563 | GenericArgument::Type(super::SpecialType::Augmented(ty), _fish) => { 564 | let (ty, defs) = ty.unpack(unpack_context, from_variant, override_public, enum_context); 565 | (syn::GenericArgument::Type(ty), defs) 566 | } 567 | GenericArgument::Type(super::SpecialType::Def(special), fish) => { 568 | let ty = type_from_ident_and_fish(special.ident.clone(), fish); 569 | 570 | let defs = special.unpack(unpack_context.clone(), from_variant, override_public, enum_context); 571 | (syn::GenericArgument::Type(ty), vec![defs]) 572 | 573 | } 574 | } 575 | } 576 | } 577 | 578 | fn type_from_ident_and_fish(ident: syn::Ident, fish: Option) -> syn::Type { 579 | let args = match fish { 580 | None => syn::PathArguments::None, 581 | Some(fish) => { 582 | let syn::AngleBracketedGenericArguments { lt_token, args, gt_token, ..} = fish.generics; 583 | let args = syn::AngleBracketedGenericArguments { lt_token, args, gt_token, colon2_token: None }; 584 | syn::PathArguments::AngleBracketed(args) 585 | } 586 | }; 587 | 588 | let segment = syn::PathSegment { ident, arguments: args }; 589 | let mut segments = syn::punctuated::Punctuated::new(); 590 | segments.push(segment); 591 | let path = syn::Path { leading_colon: None, segments }; 592 | let ty = syn::TypePath { qself: None, path }; 593 | syn::Type::Path(ty) 594 | } 595 | } -------------------------------------------------------------------------------- /src/unpack.rs: -------------------------------------------------------------------------------- 1 | use crate::special_data::{Body, FieldsNamed, FieldsUnnamed, Special, SpecialFields}; 2 | use crate::ty::SpecialType; 3 | use crate::unpack_context::UnpackContext; 4 | use proc_macro2::TokenStream; 5 | use quote::quote; 6 | use syn::Visibility; 7 | use crate::attributes::CompositeAttribute; 8 | 9 | /// A trait for types that can be unpacked within the context of custom attribute processing. 10 | /// 11 | /// Implementors of this trait can be "unpacked" to generate Rust code (as `TokenStream`) 12 | /// based on their structure and annotations, potentially including modifications 13 | /// influenced by a provided `UnpackContext`. 14 | pub(crate) trait Unpack { 15 | type Output; 16 | 17 | /// Unpacks the current structure into a Rust `TokenStream`, taking into account 18 | /// modifications from the given `UnpackContext` and any additional attributes. 19 | /// 20 | /// # Parameters 21 | /// - `self`: The instance of the implementor to unpack. 22 | /// - `context`: The unpacking context carrying information about inherited attributes 23 | /// and possibly influencing how unpacking is performed. 24 | /// - `next`: A collection of `CompositeAttribute` that may modify the behavior of 25 | /// unpacking or influence the generated output. 26 | /// - `override_public`: A modifier that is used to force the next 27 | /// definition to be specific publicity. 28 | /// - `enum_context`: A boolean stating if an unnamed field is in an enum variant or not 29 | /// 30 | /// # Returns 31 | /// `Self::Output`: The generated Rust code as a `TokenStream`. 32 | fn unpack(self, context: UnpackContext, next: Vec, override_public: Option, enum_context: bool) -> Self::Output; 33 | } 34 | 35 | impl Unpack for Special { 36 | type Output = TokenStream; 37 | 38 | /// Performs unpacking for `Special` structures, handling struct and enum definitions 39 | /// uniquely based on their form and attributes. 40 | /// 41 | /// This function combines current and inherited attributes, applies any context-specific 42 | /// modifications, and generates a `TokenStream` representing the Rust code structure of 43 | /// the unpacked `Special` instance. 44 | /// 45 | /// # Parameters 46 | /// - `self`: The `Special` instance to be unpacked. 47 | /// - `unpack_context`: The context that may influence how unpacking is performed, including 48 | /// attribute modifications. 49 | /// - `Next`: Additional attributes that may come from higher-level structures or previous 50 | /// unpacking stages, to be considered in the current unpacking process. 51 | /// 52 | /// # Returns 53 | /// A `TokenStream` representing the generated Rust code after unpacking. 54 | fn unpack(self, mut unpack_context: UnpackContext, next: Vec, override_public: Option, _enum_context: bool) -> Self::Output { 55 | // combine the attributes from the current and previous 56 | let attrs = [self.attrs, next].concat(); 57 | let attrs = unpack_context.modify_composite(attrs); 58 | 59 | let visibility = override_public.unwrap_or_else(|| self.vis); 60 | let ident = self.ident; // the definition name/type 61 | let generics = self.generics; 62 | let where_clause = &generics.where_clause; 63 | // based on the type of the Special type [struct | enum | union?] 64 | // then determine the expansion 65 | match self.body { 66 | Body::Struct(body_struct) => match body_struct.fields { 67 | SpecialFields::Named(named) => { 68 | let (body, definitions) = named.unpack(unpack_context, Vec::default(), None, false); 69 | 70 | // define our current ctx struct 71 | // - define attributes 72 | // - define ident and specify generics 73 | // - insert our previous definitions behind the struct 74 | quote!( 75 | #(#attrs)* 76 | #visibility struct #ident #generics #where_clause #body 77 | 78 | #(#definitions)* 79 | ) 80 | } 81 | SpecialFields::Unnamed(unnamed) => { 82 | // unpack our unnamed structure body, also collecting the recursive definitions 83 | let (body, definitions) = unnamed.unpack(unpack_context, Vec::default(), None, false); 84 | 85 | quote!( 86 | #(#attrs)* 87 | #visibility struct #ident #generics #body #where_clause; 88 | 89 | #(#definitions)* 90 | ) 91 | } 92 | SpecialFields::Unit => { 93 | // no unpacking required here, since there are no types 94 | // in other words, this branch is always a leaf 95 | 96 | quote!( 97 | #(#attrs)* 98 | #visibility struct #ident #generics; 99 | ) 100 | } 101 | }, 102 | Body::Enum(body_enum) => { 103 | let mut accumulated_definitions = vec![]; 104 | let mut variants = vec![]; 105 | 106 | for variant in body_enum.variants { 107 | let (attrs, next) = UnpackContext::filter_field_nested(variant.attrs); // todo: handle this 108 | let ident = variant.ident; 109 | let (field_body, mut definitions) = 110 | variant.fields.unpack(unpack_context.clone(), next, None, true); 111 | accumulated_definitions.append(&mut definitions); 112 | // todo: get variant working 113 | let discriminant = variant.discriminant; 114 | 115 | let variant = quote!( 116 | #(#attrs)* 117 | #ident #field_body 118 | #discriminant 119 | ); 120 | variants.push(variant); 121 | } 122 | 123 | quote!( 124 | #(#attrs)* 125 | #visibility enum #ident #generics #where_clause { 126 | #( #variants ),* 127 | } 128 | 129 | #(#accumulated_definitions)* 130 | ) 131 | } 132 | } 133 | } 134 | } 135 | 136 | impl Unpack for SpecialFields { 137 | type Output = (TokenStream, Vec); 138 | // ^body ^definitions 139 | fn unpack(self, unpack_context: UnpackContext, next: Vec, _override_public: Option, enum_context: bool) -> Self::Output { 140 | match self { 141 | // Delegates to the `unpack` implementation of `FieldsNamed`, which handles the 142 | // unpacking of named fields, 143 | // including generating the necessary code and collecting 144 | // any additional definitions. 145 | SpecialFields::Named(named) => named.unpack(unpack_context, next, None, enum_context), 146 | 147 | // Similarly, for unnamed fields (tuples), it delegates to `FieldsUnnamed`'s 148 | // `unpack` method, which is specialized in handling tuple-like structures. 149 | SpecialFields::Unnamed(unnamed) => unnamed.unpack(unpack_context, next, None, enum_context), 150 | 151 | // For unit types, which have no fields, the function returns a default (empty) 152 | // `TokenStream` along with an empty vector for definitions, 153 | // as there's no additional 154 | // code needed to represent a unit type in Rust. 155 | SpecialFields::Unit => (TokenStream::default(), Vec::::default()), 156 | } 157 | } 158 | } 159 | 160 | impl Unpack for FieldsNamed { 161 | type Output = (TokenStream, Vec); 162 | // ^body ^definitions 163 | fn unpack(self, unpack_context: UnpackContext, from_variant: Vec, _override_public: Option, enum_context: bool) -> Self::Output { 164 | // fields buffer load each 165 | let mut fields = vec![]; 166 | let mut definitions = vec![]; 167 | 168 | // iterate through the fields 169 | for field in self.named { 170 | // filter the attributes, passing the #> to the next iteration, 171 | // we need to filter the attributes so that we can determine which are normal 172 | // or which should be passed on 173 | let (attrs, next) = UnpackContext::filter_field_nested(field.attrs); 174 | let vis = field.vis; 175 | // unused field mutability see syn doc for FieldMutability 176 | let _mutability = field.mutability; 177 | // this is a named type, so there should always be an ident 178 | // if there is no ident then there should be a parsing bug 179 | let ident = field.ident.unwrap_or_else(|| { 180 | panic!( 181 | "Internal Macro Error. This is a bug. \ 182 | Please Consider opening an issue with steps to reproduce the bug \ 183 | Provide this information: Error from line {}", 184 | { line!() } 185 | ) 186 | }); 187 | 188 | let fish = field.fish; 189 | 190 | // branch off the type depending on if leaf is reached 191 | match field.ty { 192 | // leaf node aka a non-special type => don't recurse 193 | // `SpecialType::Type` 194 | // doesn't need fish because it will always be None 195 | SpecialType::Type(ty) => { 196 | let field = quote!( 197 | #(#attrs)* 198 | #vis #ident : #ty 199 | ); 200 | fields.push(field); 201 | } 202 | SpecialType::Augmented(augmented) => { 203 | // combine attributes possibly inherited from an enum variant with field attrs 204 | let next = [next, from_variant.clone()].concat(); 205 | 206 | let (ty, mut aug_definitions) = augmented.unpack(unpack_context.clone(), next, None, enum_context); 207 | definitions.append(&mut aug_definitions); 208 | 209 | let field = quote!( 210 | #(#attrs)* 211 | #vis #ident : #ty 212 | ); 213 | fields.push(field); 214 | } 215 | // recuse down the parse stack 216 | SpecialType::Def(special) => { 217 | // trust that ty will be a definition step 218 | let ty = &special.ident; // don't move so no clone! 219 | 220 | // todo: add fish syntax 221 | let field = quote!( 222 | #(#attrs)* 223 | #vis #ident : #ty #fish 224 | ); 225 | fields.push(field); 226 | 227 | // combine attributes possibly inherited from an enum variant with field attrs 228 | let next = [next, from_variant.clone()].concat(); 229 | 230 | // unpack the definition of the type 231 | // then add it to the definition buffer 232 | // this could be one or more definition 233 | // we don't care 234 | let definition = special.unpack(unpack_context.clone(), next, None, enum_context); 235 | definitions.push(definition); 236 | } 237 | } 238 | } 239 | 240 | let body = quote!( 241 | { #(#fields),* } 242 | ); 243 | 244 | (body, definitions) 245 | } 246 | } 247 | 248 | impl Unpack for FieldsUnnamed { 249 | type Output = (TokenStream, Vec); 250 | // ^body ^definitions 251 | fn unpack(self, unpack_context: UnpackContext, from_variant: Vec, _override_public: Option, enum_context: bool) -> Self::Output { 252 | let mut fields = vec![]; 253 | let mut definitions = vec![]; 254 | 255 | // iterate through types 256 | for field in self.unnamed { 257 | // filter the attributes, passing the #> to the next iteration 258 | let (attrs, next) = UnpackContext::filter_field_nested(field.attrs); 259 | 260 | // let vis = field.vis; 261 | // if we are in an enum variant then don't show the visibility to the field 262 | let move_vis = field.vis; 263 | let vis = if enum_context { 264 | None 265 | } else { 266 | Some(&move_vis) 267 | }; 268 | 269 | // unused field mutability see syn doc for FieldMutability 270 | let _mutability = field.mutability; 271 | 272 | // this is an unnamed variant so there should never Some(T) 273 | let _ident = field.ident; // todo: warn if this is not none 274 | 275 | let fish = field.fish; 276 | 277 | 278 | // branch off based on if a type is defined or should be defined 279 | match field.ty { 280 | SpecialType::Type(ty) => { 281 | let field = quote!( 282 | #(#attrs)* 283 | #vis #ty 284 | ); 285 | fields.push(field); 286 | } 287 | SpecialType::Augmented(augmented) => { 288 | // combine attributes possibly inherited from an enum variant with field attrs 289 | let next = [next, from_variant.clone()].concat(); 290 | 291 | // if it is an unnamed field, then the definition visibility must be overridden 292 | let override_publicity = Some(move_vis.clone()); 293 | 294 | // if field is unnamed the field publicity should be applied to the definition 295 | 296 | let (ty, mut aug_definitions) = augmented.unpack(unpack_context.clone(), next, override_publicity, enum_context); 297 | definitions.append(&mut aug_definitions); 298 | 299 | let field = quote!( 300 | #(#attrs)* 301 | #vis #ty 302 | ); 303 | fields.push(field); 304 | } 305 | SpecialType::Def(special) => { 306 | let ty = &special.ident; 307 | 308 | let field = quote!( 309 | #(#attrs)* 310 | #vis #ty #fish 311 | ); 312 | fields.push(field); 313 | 314 | // combine attributes possibly inherited from an enum variant with field attrs 315 | let next = [next, from_variant.clone()].concat(); 316 | 317 | // if it is an unnamed field, then the definition visibility must be overridden 318 | let override_publicity = Some(move_vis); 319 | 320 | // if field is unnamed the field publicity should be applied to the definition 321 | 322 | let definition = special.unpack(unpack_context.clone(), next, override_publicity, enum_context); 323 | definitions.push(definition); 324 | } 325 | } 326 | } 327 | 328 | let body = quote!( 329 | ( #(#fields),* ) 330 | ); 331 | 332 | (body, definitions) 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /src/unpack_context.rs: -------------------------------------------------------------------------------- 1 | use crate::attributes::{Attribute, AttributeModifier, CompositeAttribute, FieldAttribute}; 2 | 3 | #[derive(Clone, Default)] 4 | pub(crate) struct UnpackContext { 5 | inherited: Vec, 6 | } 7 | 8 | impl UnpackContext { 9 | pub fn modify_composite(&mut self, attributes: Vec) -> Vec { 10 | let mut freeze = self.inherited.clone(); 11 | 12 | let current = attributes 13 | .into_iter() 14 | .filter_map(|ca| { 15 | // handle standard attribute 16 | let Some(modifier) = &ca.modifier else { 17 | let a: Attribute = ca.into(); 18 | 19 | if freeze.contains(&a) { 20 | panic!("already in the stack"); 21 | //todo: improve this error message 22 | // already defined so do something 23 | } 24 | 25 | return Some(a); 26 | }; 27 | 28 | // handle attribute with modifier 29 | match modifier { 30 | AttributeModifier::Star(_) => { 31 | let a: Attribute = ca.into(); 32 | 33 | // already defined so lets error 34 | if freeze.contains(&a) { 35 | panic!("already in the stack (*)") 36 | // todo: improve error msg 37 | } 38 | 39 | // add attribute to the downstream stack 40 | self.inherited.push(a.clone()); 41 | 42 | // return the attribute 43 | Some(a) 44 | } 45 | AttributeModifier::Slash(_) => { 46 | let a: Attribute = ca.into(); 47 | 48 | // already defined so lets error 49 | if !freeze.contains(&a) { 50 | panic!("not in the stack, dont need to remove (/)"); 51 | } 52 | 53 | // remove from the future 54 | self.inherited.retain(|attr| attr != &a); 55 | 56 | // remove from freeze 57 | freeze.retain(|attr| attr != &a); 58 | 59 | // remove it from the current 60 | None 61 | } 62 | AttributeModifier::Minus(_) => { 63 | let a: Attribute = ca.into(); 64 | 65 | // not in the stack so cant remove, lets error 66 | if !freeze.contains(&a) { 67 | panic!("not in the stack, dont need to remove (-)"); 68 | } 69 | 70 | // don't remove it from the future 71 | 72 | // remove from freeze 73 | freeze.retain(|attr| attr != &a); 74 | 75 | // remove it from the current 76 | None 77 | } 78 | } 79 | }) 80 | .collect(); 81 | 82 | [freeze, current].concat() 83 | } 84 | 85 | pub(crate) fn filter_field_nested(field_attributes: Vec) -> (Vec, Vec) { 86 | let mut field_applied_now = vec![]; 87 | let mut composite = vec![]; 88 | 89 | field_attributes.into_iter().for_each(|attr| { 90 | match attr { 91 | FieldAttribute::Field(fa) => field_applied_now.push(fa), 92 | FieldAttribute::Nested(na) => composite.push(na.into()) 93 | } 94 | }); 95 | 96 | (field_applied_now, composite) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /tests/examples.rs: -------------------------------------------------------------------------------- 1 | //! Contains examples from the guide in README.md 2 | 3 | #![allow(dead_code, unused_variables)] 4 | 5 | use nestify::nest; 6 | 7 | 8 | mod quick_examples { 9 | use super::*; 10 | 11 | #[test] 12 | fn simple_nested_structures() { 13 | nest! { 14 | struct UserProfile { 15 | name: String, 16 | address: struct Address { 17 | street: String, 18 | city: String, 19 | }, 20 | preferences: struct Preferences { 21 | newsletter: bool, 22 | }, 23 | } 24 | } 25 | } 26 | 27 | #[test] 28 | fn simple_nested_enums() { 29 | nest! { 30 | struct Task { 31 | id: i32, 32 | description: String, 33 | status: enum Status { 34 | Pending, 35 | InProgress, 36 | Completed, 37 | }, 38 | } 39 | } 40 | } 41 | } 42 | 43 | mod supported_definitions { 44 | use super::*; 45 | 46 | #[test] 47 | fn field_structs() { 48 | nest! { 49 | struct Named { 50 | f: struct Nested {} 51 | } 52 | } 53 | } 54 | 55 | #[test] 56 | fn tuple_structs() { 57 | nest! { 58 | struct Unnamed(struct Nested()) 59 | } 60 | } 61 | 62 | #[test] 63 | fn unit_structs() { 64 | nest! { 65 | struct Unit { 66 | unit: struct UnitStruct 67 | } 68 | } 69 | } 70 | 71 | #[test] 72 | fn enum_variants() { 73 | nest! { 74 | enum EnumVariants { 75 | Unit, 76 | Tuple(i32, struct TupleNested), 77 | Struct { 78 | f1: i32, 79 | 80 | }, 81 | // DiscriminantVariant = 1, 82 | } 83 | } 84 | } 85 | } 86 | 87 | mod generics { 88 | use super::*; 89 | 90 | #[test] 91 | fn generic() { 92 | nest! { 93 | struct Example<'a, T> { 94 | s: &'a str, 95 | t: T 96 | } 97 | } 98 | } 99 | 100 | fn nested_generics() { 101 | nest! { 102 | struct Parent<'a> { 103 | child : struct Child<'c, C> { 104 | s: &'c str, 105 | f: C 106 | } ||<'a, i32> 107 | } 108 | } 109 | } 110 | } 111 | 112 | mod field_attributes { 113 | use super::*; 114 | 115 | #[test] 116 | fn enum_variants() { 117 | nest! { 118 | enum MyEnum { 119 | #>[derive(Debug)] 120 | Variant { 121 | // #[derive(Debug) 122 | one: struct One, 123 | // #[derive(Debug) 124 | two: struct Two 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | mod visibility { 132 | use super::*; 133 | 134 | #[test] 135 | fn named_fields() { 136 | nest! { 137 | pub struct One { 138 | pub two: pub struct Two 139 | //| ^^^ visibility applied to definition (2) 140 | //|> visibility applied to field (1) 141 | } 142 | } 143 | } 144 | 145 | #[test] 146 | fn unnamed_fields() { 147 | nest! { 148 | pub struct One(pub struct Two) 149 | // ^^^ visibility applied to both field and struct 150 | } 151 | } 152 | 153 | #[test] 154 | fn enum_variants() { 155 | nest! { 156 | pub enum One { 157 | Two(pub struct Two) 158 | } 159 | } 160 | } 161 | } 162 | 163 | mod generic_definitions { 164 | use super::*; 165 | fn vec_example() { 166 | nest! { 167 | struct One(Vec); 168 | } 169 | } 170 | 171 | fn option_example() { 172 | nest! { 173 | struct AppConfig(Option); 174 | } 175 | } 176 | } 177 | 178 | // enum EnumVariants { 179 | // Unit, 180 | // Tuple(i32, TupleNested), 181 | // Struct { 182 | // f1: i32 183 | // }, 184 | // // DiscriminantVariant = 1, 185 | // } 186 | // struct TupleNested; 187 | -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | #![allow(dead_code, unused_variables)] 4 | 5 | use nestify::nest; 6 | 7 | #[test] 8 | fn es() {} 9 | 10 | #[test] 11 | fn please_work() { 12 | let s = 32; 13 | nest!( 14 | struct Hello { 15 | pub o: 16 | struct Another { 17 | s: struct A { 18 | a: struct B { 19 | asdfs: struct BB { 20 | 21 | }, 22 | }, 23 | c: i32, 24 | d: struct D();, 25 | asd: Option, 26 | }, 27 | }, 28 | pub unmaed: struct Unamed;, 29 | } 30 | ); 31 | } 32 | 33 | #[test] 34 | fn enum_test() { 35 | nest! { 36 | enum Outside { 37 | Empty, 38 | Tuple(Option, i32, enum Nested1 { }), 39 | #>[derive(Debug)]* 40 | Named { 41 | a: (), 42 | b: Option, 43 | c: enum Nested2 { 44 | Tuple2(enum Nested3 {}) 45 | }, 46 | d: struct Another {} 47 | } 48 | } 49 | } 50 | } 51 | 52 | #[test] 53 | fn attribute_test() { 54 | nest! { 55 | #[derive(Default)] 56 | enum AnEnum { 57 | #[default] 58 | One = 1, 59 | Default, 60 | Two, 61 | } 62 | } 63 | 64 | // nest! { 65 | // struct Outside { 66 | // f : struct Inside { 67 | // gf: G 68 | // }, 69 | // } 70 | // } 71 | 72 | let v = vec![1, 2, 3]; 73 | 74 | let v2 = v.into_iter().collect::>(); 75 | 76 | nest! { 77 | struct A(T) where T: Clone 78 | } 79 | } 80 | 81 | #[test] 82 | fn test_semis() { 83 | // nest! { 84 | // struct Two(:: struct One(T)) 85 | // } 86 | 87 | nest! { 88 | struct Outside ( 89 | enum E {Se(T)} ||, 90 | ) 91 | } 92 | } 93 | 94 | struct One1 (T) 95 | ; 96 | struct Two1( 97 | One1) 98 | ; 99 | 100 | #[derive(Default)] 101 | enum AnEnum { 102 | One = 1, 103 | #[default] 104 | Default, 105 | Two, 106 | } 107 | 108 | 109 | struct NeedsLife<'a> { 110 | string: &'a str 111 | } 112 | #[test] 113 | fn test_fish_errors() { 114 | nest! { 115 | struct Base( 116 | struct NoError { 117 | tf: T 118 | } ||) 119 | } 120 | } 121 | 122 | 123 | 124 | #[test] 125 | fn augmented_types() { 126 | { 127 | nest! { 128 | struct Foo { 129 | field: Option, 132 | } 133 | }; 134 | 135 | let foo = Foo { field: Some(Bar { foo: 42 })}; 136 | } 137 | 138 | { 139 | nest! { 140 | struct Foo( 141 | Option, 144 | ) 145 | }; 146 | 147 | let foo = Foo (Some(Bar { foo: 42 })); 148 | } 149 | 150 | { 151 | nest! { 152 | enum Foo { 153 | Variant { 154 | field: Option<#[derive(Debug)] struct Bar { 155 | foo: u32 156 | }>, 157 | } 158 | 159 | } 160 | }; 161 | 162 | let foo = Foo::Variant { field: Some(Bar { foo: 42 })}; 163 | } 164 | 165 | { 166 | nest! { 167 | #[derive(Debug)]* 168 | enum Foo { 169 | Variant( 170 | Option, 173 | ) 174 | } 175 | }; 176 | 177 | let foo = Foo::Variant (Some(Bar { foo: 42 })); 178 | println!("{foo:?}"); 179 | } 180 | 181 | { 182 | nest! { 183 | struct Foo { 184 | field: Option(T) ||>, 185 | } 186 | }; 187 | 188 | let foo = Foo { field: Some(Bar(42)) }; 189 | // check it is actually a generic struct 190 | let bar: Bar = Bar("test".to_string()); 191 | } 192 | 193 | } --------------------------------------------------------------------------------