├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── doc ├── parse.md ├── struct_meta.md └── to_tokens.md ├── structmeta-derive ├── Cargo.toml └── src │ ├── lib.rs │ ├── parse.rs │ ├── struct_meta.rs │ ├── syn_utils.rs │ ├── to_tokens.rs │ └── to_tokens_attribute.rs ├── structmeta-tests ├── Cargo.toml ├── examples │ └── readme.rs ├── src │ └── lib.rs └── tests │ ├── compile_fail.rs │ ├── compile_fail │ ├── parse │ │ ├── mismatch_delimiter_enum.rs │ │ ├── mismatch_delimiter_enum.stderr │ │ ├── mismatch_delimiter_struct.rs │ │ ├── mismatch_delimiter_struct.stderr │ │ ├── mismatch_delimiter_tuple.rs │ │ ├── mismatch_delimiter_tuple.stderr │ │ ├── single_variant_fail.rs │ │ └── single_variant_fail.stderr │ ├── struct_meta │ │ ├── duplicate_named_arguments.rs │ │ ├── duplicate_named_arguments.stderr │ │ ├── duplicate_named_arguments_with_rest.rs │ │ ├── duplicate_named_arguments_with_rest.stderr │ │ ├── duplicate_rest_named_argument.rs │ │ ├── duplicate_rest_named_argument.stderr │ │ ├── expected_flag_but_name_args.rs │ │ ├── expected_flag_but_name_args.stderr │ │ ├── expected_flag_but_name_value.rs │ │ ├── expected_flag_but_name_value.stderr │ │ ├── expected_name_args_buf_flag.rs │ │ ├── expected_name_args_buf_flag.stderr │ │ ├── expected_name_args_buf_name_value.rs │ │ ├── expected_name_args_buf_name_value.stderr │ │ ├── expected_name_args_or_flag_buf_name_value.rs │ │ ├── expected_name_args_or_flag_buf_name_value.stderr │ │ ├── expected_name_value_but_flag.rs │ │ ├── expected_name_value_but_flag.stderr │ │ ├── expected_name_value_but_name_args.rs │ │ ├── expected_name_value_but_name_args.stderr │ │ ├── expected_name_value_rest_but_name_args.rs │ │ ├── expected_name_value_rest_but_name_args.stderr │ │ ├── hash_map_flag.rs │ │ ├── hash_map_flag.stderr │ │ ├── missing_named_argument.rs │ │ ├── missing_named_argument.stderr │ │ ├── missing_unnamed_argument.rs │ │ ├── missing_unnamed_argument.stderr │ │ ├── missing_unnamed_argument_inner.rs │ │ ├── missing_unnamed_argument_inner.stderr │ │ ├── not_parse.rs │ │ ├── not_parse.stderr │ │ ├── option_bool.rs │ │ ├── option_bool.stderr │ │ ├── option_flag.rs │ │ ├── option_flag.stderr │ │ ├── option_hash_map.rs │ │ ├── option_hash_map.stderr │ │ ├── option_not_parse.rs │ │ ├── option_not_parse.stderr │ │ ├── similar_name.rs │ │ ├── similar_name.stderr │ │ ├── too_many_unnamed_arguments.rs │ │ ├── too_many_unnamed_arguments.stderr │ │ ├── unexpected_named_argument.rs │ │ ├── unexpected_named_argument.stderr │ │ ├── unexpected_unnamed_argument.rs │ │ ├── unexpected_unnamed_argument.stderr │ │ ├── vec_not_parse.rs │ │ └── vec_not_parse.stderr │ └── to_tokens │ │ ├── invalid_argument.rs │ │ ├── invalid_argument.stderr │ │ ├── mismatch_delimiter.rs │ │ ├── mismatch_delimiter.stderr │ │ ├── not_impl_to_tokens.rs │ │ └── not_impl_to_tokens.stderr │ ├── parse.rs │ ├── struct_meta.rs │ ├── struct_meta_proc_macro.rs │ ├── test_utils.rs │ └── to_tokens.rs └── structmeta ├── Cargo.toml └── src ├── arg_types.rs ├── easy_syntax.rs ├── helpers.rs └── lib.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: [cron: "20 5 * * *"] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Rustup update stable 15 | run: rustup update stable 16 | - name: Show cargo version 17 | run: cargo --version 18 | - name: Build 19 | run: cargo build --verbose 20 | - name: Build tests 21 | run: cargo test --verbose --no-run 22 | - name: Run tests 23 | run: cargo test --verbose 24 | - name: Run compile fail tests 25 | run: cargo test --test compile_fail --verbose -- --ignored 26 | - name: Clippy 27 | run: cargo clippy --all-features --tests --lib -- -W clippy::all 28 | env: 29 | RUSTFLAGS: -D warnings 30 | - name: Clean 31 | run: cargo clean 32 | - name: Run tests structmeta-derive 33 | run: cargo test -p structmeta-derive 34 | - name: Clean 35 | run: cargo clean 36 | - name: Run tests structmeta 37 | run: cargo test -p structmeta 38 | - name: Rustup toolchain install nightly 39 | run: rustup toolchain install nightly 40 | - name: Set minimal versions 41 | run: cargo +nightly update -Z minimal-versions 42 | - name: Build tests (minimal versions) 43 | run: cargo +stable test --verbose --no-run 44 | - name: Run tests (minimal versions) 45 | run: cargo +stable test --verbose 46 | - uses: taiki-e/install-action@cargo-hack 47 | - name: Check msrv 48 | run: cargo hack test --rust-version --workspace --all-targets --ignore-private 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | /*/src/main.rs 4 | /structmeta-tests/tests/example.rs -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug", 11 | "program": "${workspaceFolder}/", 12 | "args": [], 13 | "cwd": "${workspaceFolder}" 14 | } 15 | ] 16 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "rust-analyzer.procMacro.enable": true, 3 | "rust-analyzer.server.extraEnv": { 4 | "CARGO_TARGET_DIR": "./target/debug-ra", 5 | }, 6 | "markdownlint.config": { 7 | "MD041": false, 8 | }, 9 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "shell", 8 | "label": "cargo build", 9 | "command": "cargo", 10 | "args": [ 11 | "build" 12 | ], 13 | "problemMatcher": [ 14 | "$rustc" 15 | ], 16 | "presentation": { 17 | "panel": "dedicated", 18 | "clear": true 19 | } 20 | // "options": { 21 | // "env": { 22 | // "RUST_BACKTRACE": "1", 23 | // } 24 | // } 25 | }, 26 | { 27 | "type": "shell", 28 | "label": "cargo build release", 29 | "command": "cargo", 30 | "args": [ 31 | "build", 32 | "--release" 33 | ], 34 | "problemMatcher": [ 35 | "$rustc" 36 | ], 37 | "presentation": { 38 | "panel": "dedicated", 39 | "clear": true 40 | } 41 | }, 42 | { 43 | "type": "shell", 44 | "label": "cargo test", 45 | "command": "cargo", 46 | "args": [ 47 | "test", 48 | ], 49 | "problemMatcher": [ 50 | "$rustc" 51 | ], 52 | "group": { 53 | "kind": "build", 54 | "isDefault": true 55 | }, 56 | "presentation": { 57 | "panel": "dedicated", 58 | "clear": true 59 | }, 60 | "dependsOn": [ 61 | "rustdoc-include" 62 | ], 63 | }, 64 | { 65 | "type": "shell", 66 | "label": "cargo watch", 67 | "command": "wt", 68 | "args": [ 69 | "new-tab", 70 | "cargo-watch", 71 | "-c", 72 | "-x", 73 | "test" 74 | ], 75 | "problemMatcher": [], 76 | "presentation": { 77 | "panel": "dedicated", 78 | "clear": true 79 | } 80 | }, 81 | { 82 | "type": "shell", 83 | "label": "cargo run exmaple", 84 | "command": "cargo", 85 | "args": [ 86 | "run", 87 | "--example", 88 | "${fileBasenameNoExtension}" 89 | ], 90 | "problemMatcher": [ 91 | "$rustc" 92 | ], 93 | "presentation": { 94 | "panel": "dedicated", 95 | "clear": true 96 | } 97 | }, 98 | { 99 | "type": "shell", 100 | "label": "cargo doc open", 101 | "command": "cargo", 102 | "args": [ 103 | "doc", 104 | "--open", 105 | "--no-deps", 106 | "--all-features" 107 | ], 108 | "problemMatcher": [ 109 | "$rustc" 110 | ], 111 | "presentation": { 112 | "panel": "dedicated", 113 | "clear": true 114 | }, 115 | "dependsOn": [ 116 | "rustdoc-include" 117 | ], 118 | }, 119 | { 120 | "type": "shell", 121 | "label": "cargo clippy", 122 | "command": "cargo", 123 | "args": [ 124 | "clippy", 125 | "--all-features", 126 | "--", 127 | "-W", 128 | "clippy::all" 129 | ], 130 | "problemMatcher": [ 131 | "$rustc" 132 | ], 133 | "presentation": { 134 | "panel": "dedicated", 135 | "clear": true 136 | } 137 | }, 138 | { 139 | "type": "shell", 140 | "label": "cargo fix & fmt", 141 | "command": "cargo fix && cargo fmt", 142 | "problemMatcher": [ 143 | "$rustc" 144 | ], 145 | "presentation": { 146 | "panel": "dedicated", 147 | "clear": true, 148 | } 149 | }, 150 | { 151 | "type": "shell", 152 | "label": "cargo bench", 153 | "command": "cargo", 154 | "args": [ 155 | "bench" 156 | ], 157 | "options": { 158 | "cwd": "${workspaceFolder}/benchmarks" 159 | }, 160 | "problemMatcher": [ 161 | "$rustc" 162 | ], 163 | "presentation": { 164 | "panel": "dedicated", 165 | "clear": true 166 | } 167 | }, 168 | { 169 | "type": "shell", 170 | "label": "cargo update minimal-versions", 171 | "command": "cargo", 172 | "args": [ 173 | "+nightly", 174 | "update", 175 | "-Z", 176 | "minimal-versions" 177 | ], 178 | "problemMatcher": [ 179 | "$rustc" 180 | ], 181 | "presentation": { 182 | "panel": "dedicated", 183 | "clear": true 184 | } 185 | }, 186 | { 187 | "type": "shell", 188 | "label": "update compile error", 189 | "command": "cargo", 190 | "args": [ 191 | "test", 192 | "--test", 193 | "compile_fail", 194 | "--", 195 | "--ignored" 196 | ], 197 | "problemMatcher": [ 198 | "$rustc" 199 | ], 200 | "presentation": { 201 | "panel": "dedicated", 202 | "clear": true 203 | }, 204 | "options": { 205 | "env": { 206 | "TRYBUILD": "overwrite" 207 | } 208 | } 209 | }, 210 | { 211 | "type": "shell", 212 | "label": "rustdoc-include", 213 | "command": "rustdoc-include", 214 | "args": [ 215 | "--root", 216 | "${workspaceFolder}" 217 | ], 218 | "problemMatcher": [ 219 | { 220 | "owner": "rustdoc-include", 221 | "fileLocation": [ 222 | "relative", 223 | "${workspaceFolder}" 224 | ], 225 | "pattern": [ 226 | { 227 | "regexp": "^(error): (.*)$", 228 | "severity": 1, 229 | "message": 2, 230 | }, 231 | { 232 | "regexp": "^--> (.*):(\\d+)\\s*$", 233 | "file": 1, 234 | "line": 2, 235 | "loop": true, 236 | }, 237 | ] 238 | }, 239 | ], 240 | "presentation": { 241 | "panel": "dedicated", 242 | "clear": true 243 | } 244 | }, 245 | { 246 | "type": "shell", 247 | "label": "cargo test macro", 248 | "command": "cargo", 249 | "args": [ 250 | "+nightly", 251 | "test", 252 | "--target-dir", 253 | "./target/macro_detail", 254 | "--all-features" 255 | ], 256 | "problemMatcher": [ 257 | "$rustc" 258 | ], 259 | "presentation": { 260 | "panel": "dedicated", 261 | "clear": true 262 | }, 263 | "options": { 264 | "env": { 265 | "RUSTFLAGS": "-Z macro-backtrace" 266 | } 267 | } 268 | }, 269 | ] 270 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["structmeta", "structmeta-derive", "structmeta-tests"] 4 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 FrozenLib 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 | # StructMeta 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/structmeta.svg)](https://crates.io/crates/structmeta) 4 | [![Docs.rs](https://docs.rs/structmeta/badge.svg)](https://docs.rs/structmeta/) 5 | [![Actions Status](https://github.com/frozenlib/structmeta/workflows/CI/badge.svg)](https://github.com/frozenlib/structmeta/actions) 6 | 7 | Parse Rust's attribute arguments by defining a struct. 8 | 9 | ## Documentation 10 | 11 | See [`#[derive(StructMeta)]` documentation](https://docs.rs/structmeta/latest/structmeta/derive.StructMeta.html) for details. 12 | 13 | ## Install 14 | 15 | Add this to your Cargo.toml: 16 | 17 | ```toml 18 | [dependencies] 19 | structmeta = "0.3.0" 20 | proc-macro2 = "1.0.78" 21 | syn = "2.0.48" 22 | quote = "1.0.35" 23 | ``` 24 | 25 | ## Example 26 | 27 | ```rust 28 | use structmeta::StructMeta; 29 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 30 | 31 | #[derive(StructMeta, Debug)] 32 | struct MyAttr { 33 | x: LitInt, 34 | y: LitStr, 35 | } 36 | let attr: Attribute = parse_quote!(#[my_attr(x = 10, y = "abc")]); 37 | let attr: MyAttr = attr.parse_args().unwrap(); 38 | println!("x = {}, y = {}", attr.x, attr.y.value()); 39 | ``` 40 | 41 | This code outputs: 42 | 43 | ```txt 44 | x = 10, y = abc 45 | ``` 46 | 47 | ## License 48 | 49 | This project is dual licensed under Apache-2.0/MIT. See the two LICENSE-\* files for details. 50 | 51 | ## Contribution 52 | 53 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. 54 | -------------------------------------------------------------------------------- /doc/parse.md: -------------------------------------------------------------------------------- 1 | Derive [`syn::parse::Parse`] for syntax tree node. 2 | 3 | - [Example](#example) 4 | - [Helper attributes](#helper-attributes) 5 | - [`#[to_tokens("[", "]", "(", ")", "{", "}")]`](#to_tokens-----) 6 | - [`#[parse(peek)]`](#parsepeek) 7 | - [`#[parse(any)]`](#parseany) 8 | - [`#[parse(terminated)]`](#parseterminated) 9 | - [`#[parse(dump)]`](#parsedump) 10 | 11 | # Example 12 | 13 | `#[derive(Parse)]` generates an implementation of `Parse` that calls [`Parse::parse`](syn::parse::Parse::parse) for each field. 14 | 15 | ```rust 16 | use syn::{LitInt, LitStr}; 17 | 18 | #[derive(structmeta::Parse)] 19 | struct Example(LitInt, LitStr); 20 | ``` 21 | 22 | Code like this will be generated: 23 | 24 | ```rust 25 | # use syn::{LitInt, LitStr}; 26 | # struct Example(LitInt, LitStr); 27 | impl syn::parse::Parse for Example { 28 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 29 | let _0 = input.parse()?; 30 | let _1 = input.parse()?; 31 | return Ok(Example(_0, _1)); 32 | } 33 | } 34 | ``` 35 | 36 | `#[derive(Parse)]` can also be specified for enum. 37 | 38 | ```rust 39 | use syn::{LitInt, LitStr}; 40 | 41 | #[derive(structmeta::Parse)] 42 | enum Example { 43 | A(LitInt, LitInt), 44 | B(LitStr), 45 | } 46 | ``` 47 | 48 | Code like this will be generated: 49 | 50 | ```rust 51 | # use syn::{LitInt, LitStr}; 52 | # enum Example { 53 | # A(LitInt, LitInt), 54 | # B(LitStr), 55 | # } 56 | use syn::parse::discouraged::Speculative; 57 | impl syn::parse::Parse for Example { 58 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 59 | let fork = input.fork(); 60 | if let Ok(value) = fork.call(|input| Ok(Example::A(input.parse()?, input.parse()?))) { 61 | input.advance_to(&fork); 62 | return Ok(value); 63 | } 64 | 65 | let fork = input.fork(); 66 | if let Ok(value) = fork.call(|input| Ok(Example::B(input.parse()?))) { 67 | input.advance_to(&fork); 68 | return Ok(value); 69 | } 70 | 71 | Err(input.error("parse failed.")) 72 | } 73 | } 74 | ``` 75 | 76 | # Helper attributes 77 | 78 | | | struct | enum | variant | field | 79 | | --------------------------------------------------------------- | ------ | ---- | ------- | ----- | 80 | | [`#[to_tokens("[", "]", "(", ")", "{", "}")]`](#to_tokens-----) | | | | ✔ | 81 | | [`#[parse(peek)]`](#parsepeek) | | | | ✔ | 82 | | [`#[parse(any)]`](#parseany) | | | | ✔ | 83 | | [`#[parse(terminated)]`](#parseterminated) | | | | ✔ | 84 | | [`#[parse(dump)]`](#parsedump) | ✔ | ✔ | | | 85 | 86 | ## `#[to_tokens("[", "]", "(", ")", "{", "}")]` 87 | 88 | By specifying `#[to_tokens("[")]` or `#[to_tokens("(")]` or `#[to_tokens("[")]` , subsequent tokens will be enclosed in `[]` or `()` or `{}`. 89 | 90 | By default, all subsequent fields are enclosed. 91 | To restrict the enclosing fields, specify `#[to_tokens("]")]` or `#[to_tokens(")")]` or `#[to_tokens("}")]` for the field after the end of the enclosure. 92 | 93 | ```rust 94 | use syn::{token, LitInt}; 95 | 96 | #[derive(structmeta::Parse)] 97 | struct Example { 98 | x: LitInt, 99 | #[to_tokens("[")] 100 | bracket_token: token::Bracket, 101 | y: LitInt, 102 | #[to_tokens("]")] 103 | z: LitInt, 104 | } 105 | ``` 106 | 107 | Code like this will be generated: 108 | 109 | ```rust 110 | # use syn::{token, LitInt}; 111 | # 112 | # struct Example { 113 | # x: LitInt, 114 | # bracket_token: token::Bracket, 115 | # y: LitInt, 116 | # z: LitInt, 117 | # } 118 | impl syn::parse::Parse for Example { 119 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 120 | let x = input.parse()?; 121 | let content; 122 | let bracket_token = syn::bracketed!(content in input); 123 | let y = content.parse()?; 124 | let z = input.parse()?; 125 | Ok(Self { 126 | x, 127 | bracket_token, 128 | y, 129 | z, 130 | }) 131 | } 132 | } 133 | ``` 134 | 135 | If the field type is `Bracket` or `Paren` or `Brace`, the symbol corresponding to the token type must be specified. 136 | 137 | If the field type is `MacroDelimiter`, any symbol can be used and there is no difference in behavior. (Three types of parentheses are available, no matter which symbol is specified.) 138 | 139 | | field type | start | end | 140 | | ------------------------------ | ----------------------- | ----------------------- | 141 | | [`struct@syn::token::Bracket`] | `"["` | `"]"` | 142 | | [`struct@syn::token::Paren`] | `"("` | `")"` | 143 | | [`struct@syn::token::Brace`] | `"{"` | `"}"` | 144 | | [`enum@syn::MacroDelimiter`] | `"["` or `"("` or `"{"` | `"]"` or `")"` or `"}"` | 145 | 146 | ## `#[parse(peek)]` 147 | 148 | When parsing an enum, it will peek the field with this attribute set, 149 | and if successful, will parse the variant containing the field. 150 | If the peek succeeds, the subsequent variant will not be parsed even if the parse failed. 151 | 152 | Variant where `#[parse(peek)]` is not specified will fork input and parse. 153 | 154 | If the peek fails or the parsing of the forked input fails, the subsequent variant will be parsed. 155 | 156 | ```rust 157 | use syn::{LitInt, LitStr}; 158 | #[derive(structmeta::Parse)] 159 | enum Example { 160 | A(#[parse(peek)] LitInt, LitInt), 161 | B(LitStr), 162 | } 163 | ``` 164 | 165 | Code like this will be generated: 166 | 167 | ```rust 168 | # use syn::{LitInt, LitStr}; 169 | # enum Example { 170 | # A(LitInt, LitInt), 171 | # B(LitStr), 172 | # } 173 | impl syn::parse::Parse for Example { 174 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 175 | if input.peek(LitInt) { 176 | let a_0 = input.parse()?; 177 | let a_1 = input.parse()?; 178 | return Ok(Example::A(a_0, a_1)); 179 | } 180 | let b_0 = input.parse()?; 181 | Ok(Example::B(b_0)) 182 | } 183 | } 184 | ``` 185 | 186 | `#[parse(peek)]` can be specified on the first three `TokenTree` for each variant. 187 | 188 | ```rust 189 | use syn::{LitInt, LitStr}; 190 | #[derive(structmeta::Parse)] 191 | enum Example { 192 | A(#[parse(peek)] LitInt, #[parse(peek)]LitInt, #[parse(peek)]LitInt), 193 | B(#[parse(peek)] LitStr), 194 | } 195 | ``` 196 | 197 | Since the tokens enclosed by the delimiter is treated as a single token tree, you can also specify `#[parse(peek)]` to the field with `#[to_tokens("]")]`, `#[to_tokens("}")]`, `#[to_tokens(")")]`. 198 | 199 | ```rust 200 | use syn::{token, LitInt, LitStr}; 201 | #[derive(structmeta::Parse)] 202 | enum Example { 203 | A { 204 | #[parse(peek)] 205 | #[to_tokens("{")] 206 | a: token::Brace, 207 | b: LitInt, 208 | c: LitInt, 209 | #[to_tokens("}")] 210 | #[parse(peek)] 211 | d: LitInt, 212 | }, 213 | } 214 | ``` 215 | 216 | To use `#[parse(peek)]` for a field that type is `Ident`, use `syn::Ident` instead of `proc_macro2::Ident`. 217 | 218 | ```compile_fail 219 | #[derive(structmeta::Parse)] 220 | enum ExampleNg { 221 | A(#[parse(peek)] proc_macro2::Ident), 222 | } 223 | ``` 224 | 225 | ```rust 226 | #[derive(structmeta::Parse)] 227 | enum ExampleOk { 228 | A(#[parse(peek)] syn::Ident), 229 | } 230 | ``` 231 | 232 | ## `#[parse(any)]` 233 | 234 | When parsing `Ident`, allow values that cannot be used as identifiers, such as keywords. 235 | 236 | In other words, `Ident::parse_any` and `Ident::peek_any` was generated instead of `Ident::parse` and `Ident::peek`. 237 | 238 | ```rust 239 | use quote::quote; 240 | use structmeta::Parse; 241 | use syn::{parse2, Ident}; 242 | 243 | #[derive(Parse)] 244 | struct WithAny(#[parse(any)] Ident); 245 | 246 | #[derive(Parse)] 247 | struct WithoutAny(Ident); 248 | 249 | assert_eq!(parse2::(quote!(self)).is_ok(), true); 250 | assert_eq!(parse2::(quote!(self)).is_ok(), false); 251 | ``` 252 | 253 | ## `#[parse(terminated)]` 254 | 255 | Use [`Punctuated::parse_terminated`](syn::punctuated::Punctuated::parse_terminated) to parse. 256 | 257 | ```rust 258 | use quote::quote; 259 | use structmeta::Parse; 260 | use syn::{parse2, punctuated::Punctuated, Ident, Token}; 261 | #[derive(Parse)] 262 | struct Example(#[parse(terminated)] Punctuated); 263 | assert_eq!(parse2::(quote!(a, b, c)).is_ok(), true); 264 | ``` 265 | 266 | `terminated` can also be used with `any`. 267 | 268 | ```rust 269 | use quote::quote; 270 | use structmeta::Parse; 271 | use syn::{parse2, punctuated::Punctuated, Ident, Token}; 272 | 273 | #[derive(Parse)] 274 | struct WithAny(#[parse(terminated, any)] Punctuated); 275 | 276 | #[derive(Parse)] 277 | struct WithoutAny(#[parse(terminated)] Punctuated); 278 | 279 | assert_eq!(parse2::(quote!(self, self)).is_ok(), true); 280 | assert_eq!(parse2::(quote!(self, self)).is_ok(), false); 281 | ``` 282 | 283 | ## `#[parse(dump)]` 284 | 285 | Causes a compile error and outputs the code generated by `#[derive(Parse)]` as an error message. 286 | -------------------------------------------------------------------------------- /doc/struct_meta.md: -------------------------------------------------------------------------------- 1 | Derive [`syn::parse::Parse`] for parsing attribute arguments. 2 | 3 | - [Example](#example) 4 | - [Named parameter](#named-parameter) 5 | - [Supported field types for named parameter](#supported-field-types-for-named-parameter) 6 | - [Flag style](#flag-style) 7 | - [NameValue style](#namevalue-style) 8 | - [NameValue or Flag style](#namevalue-or-flag-style) 9 | - [NameArgs style](#nameargs-style) 10 | - [NameArgs or Flag style](#nameargs-or-flag-style) 11 | - [NameArgList style](#namearglist-style) 12 | - [NameArgList or Flag style](#namearglist-or-flag-style) 13 | - [Optional named parameter](#optional-named-parameter) 14 | - [Rest named parameter](#rest-named-parameter) 15 | - [Unnamed parameter](#unnamed-parameter) 16 | - [Required unnamed parameter](#required-unnamed-parameter) 17 | - [Optional unnamed parameter](#optional-unnamed-parameter) 18 | - [Variadic unnamed parameter](#variadic-unnamed-parameter) 19 | - [Parameter order](#parameter-order) 20 | - [Helper attribute `#[struct_meta(...)]`](#helper-attribute-struct_meta) 21 | - [Uses with `#[proc_macro_derive]`](#uses-with-proc_macro_derive) 22 | - [Uses with `#[proc_macro_attribute]`](#uses-with-proc_macro_attribute) 23 | - [Parsing ambiguous arguments](#parsing-ambiguous-arguments) 24 | - [`#[struct_meta(name_filter = "...")]`](#struct_metaname_filter--) 25 | 26 | # Example 27 | 28 | ```rust 29 | use structmeta::StructMeta; 30 | use syn::{parse_quote, Attribute}; 31 | use syn::{LitInt, LitStr}; 32 | 33 | #[derive(StructMeta)] 34 | struct Args { 35 | #[struct_meta(unnamed)] 36 | a: LitStr, 37 | b: LitInt, 38 | c: Option, 39 | } 40 | 41 | let attr: Attribute = parse_quote!(#[attr("xyz", b = 10)]); 42 | let args: Args = attr.parse_args()?; 43 | assert_eq!(args.a.value(), "xyz"); 44 | assert_eq!(args.b.base10_parse::()?, 10); 45 | assert!(args.c.is_none()); 46 | # syn::Result::Ok(()) 47 | ``` 48 | 49 | # Named parameter 50 | 51 | The following field will be "Named parameter". 52 | 53 | - field in record struct. 54 | - field with `#[struct_meta(name = "...")]` in tuple struct. 55 | - However, fields that meet the following conditions are excluded 56 | - field with `#[struct_meta(unnamed)]` 57 | - field with type `HashMap` 58 | 59 | "Named parameter" is a parameter that specifies with a name, such as `#[attr(flag, x = 10, y(1, 2, 3))]`. 60 | 61 | ## Supported field types for named parameter 62 | 63 | "Named parameter" has the following four styles, and the style is determined by the type of the field. 64 | 65 | - Flag style : `name` 66 | - NameValue style : `name = value` 67 | - NameArgs style : `name(args)` 68 | - NameArgList style : `name(arg, arg, ...)` 69 | 70 | | field type | field type (with span) | style | example | 71 | | ---------- | ---------------------------- | ------------------------------------------------- | ------------------------------- | 72 | | `bool` | [`Flag`] | [Flag](#flag-style) | `name` | 73 | | `T` | [`NameValue`] | [NameValue](#namevalue-style) | `name = value` | 74 | | | [`NameValue>`] | [NameValue or Flag](#namevalue-or-flag-style) | `name = value` or `name` | 75 | | | [`NameArgs`] | [NameArgs](#nameargs-or-flag-style) | `name(args)` | 76 | | | [`NameArgs>`] | [NameArgs or Flag](#nameargs-or-flag-style) | `name(args)` or `name` | 77 | | `Vec` | [`NameArgs>`] | [NameArgList](#namearglist-style) | `name(arg, arg, ...)` | 78 | | | [`NameArgs>>`] | [NameArgList or Flag](#namearglist-or-flag-style) | `name(arg, arg, ...)` or `name` | 79 | 80 | Note: the type `T` in the table above needs to implement [`syn::parse::Parse`]. 81 | 82 | With the above type as P (`bool` and `Flag` are excluded), you can also use the following types. 83 | 84 | | field type | effect | 85 | | -------------------- | ----------------------------------------------- | 86 | | `Option

` | [optional parameter](#optional-named-parameter) | 87 | | `HashMap` | [rest parameter](#rest-named-parameter) | 88 | 89 | ## Flag style 90 | 91 | A field with the type `bool` will be a parameter that specifies only its name. 92 | 93 | ```rust 94 | use structmeta::StructMeta; 95 | use syn::{parse_quote, Attribute}; 96 | 97 | #[derive(StructMeta)] 98 | struct Args { 99 | a: bool, 100 | b: bool, 101 | } 102 | 103 | let attr: Attribute = parse_quote!(#[attr(a)]); 104 | let args: Args = attr.parse_args()?; 105 | assert_eq!(args.a, true); 106 | assert_eq!(args.b, false); 107 | 108 | let attr: Attribute = parse_quote!(#[attr(a, b)]); 109 | let args: Args = attr.parse_args()?; 110 | assert_eq!(args.a, true); 111 | assert_eq!(args.b, true); 112 | # syn::Result::Ok(()) 113 | ``` 114 | 115 | If you use `Flag` instead of `bool`, you will get its `Span` when the argument is specified. 116 | 117 | ```rust 118 | use structmeta::{Flag, StructMeta}; 119 | use syn::{parse_quote, Attribute}; 120 | 121 | #[derive(StructMeta)] 122 | struct Args { 123 | a: Flag, 124 | } 125 | 126 | let attr: Attribute = parse_quote!(#[attr(a)]); 127 | let args: Args = attr.parse_args()?; 128 | if let Some(_span) = args.a.span { 129 | // Use span. 130 | } 131 | # syn::Result::Ok(()) 132 | ``` 133 | 134 | ## NameValue style 135 | 136 | A field with type `T` or `NameValue` will be `name = value` style parameter. 137 | 138 | ```rust 139 | use structmeta::{NameValue, StructMeta}; 140 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 141 | 142 | #[derive(StructMeta)] 143 | struct Args { 144 | a: LitStr, 145 | b: NameValue, 146 | } 147 | 148 | let attr: Attribute = parse_quote!(#[attr(a = "abc", b = 10)]); 149 | let args: Args = attr.parse_args()?; 150 | assert_eq!(args.a.value(), "abc"); 151 | assert_eq!(args.b.value.base10_parse::()?, 10); 152 | # syn::Result::Ok(()) 153 | ``` 154 | 155 | ## NameValue or Flag style 156 | 157 | A field with type `NameArgs>` will be `name = value` or `name` style parameter. 158 | 159 | ```rust 160 | use structmeta::{NameValue, StructMeta}; 161 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 162 | 163 | #[derive(StructMeta)] 164 | struct Args { 165 | a: NameValue>, 166 | b: NameValue>, 167 | } 168 | 169 | let attr: Attribute = parse_quote!(#[attr(a, b = 10)]); 170 | let args: Args = attr.parse_args()?; 171 | assert!(args.a.value.is_none()); 172 | assert_eq!(args.b.value.unwrap().base10_parse::()?, 10); 173 | # syn::Result::Ok(()) 174 | ``` 175 | 176 | ## NameArgs style 177 | 178 | A field with type `NameArgs` will be `name(args)` style parameter. 179 | 180 | ```rust 181 | use structmeta::{NameArgs, StructMeta}; 182 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 183 | 184 | #[derive(StructMeta)] 185 | struct Args { 186 | a: NameArgs, 187 | b: NameArgs, 188 | } 189 | 190 | let attr: Attribute = parse_quote!(#[attr(a("abc"), b(10))]); 191 | let args: Args = attr.parse_args()?; 192 | assert_eq!(args.a.args.value(), "abc"); 193 | assert_eq!(args.b.args.base10_parse::()?, 10); 194 | # syn::Result::Ok(()) 195 | ``` 196 | 197 | ## NameArgs or Flag style 198 | 199 | A field with type `NameArgs>` will be `name(args)` or `name` style parameter. 200 | 201 | ```rust 202 | use structmeta::{NameArgs, StructMeta}; 203 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 204 | 205 | #[derive(StructMeta)] 206 | struct Args { 207 | a: NameArgs>, 208 | b: NameArgs>, 209 | } 210 | 211 | let attr: Attribute = parse_quote!(#[attr(a, b(10))]); 212 | let args: Args = attr.parse_args()?; 213 | assert!(args.a.args.is_none()); 214 | assert_eq!(args.b.args.unwrap().base10_parse::()?, 10); 215 | # syn::Result::Ok(()) 216 | ``` 217 | 218 | ## NameArgList style 219 | 220 | A field with type `NameArgs>` will be `name(arg, arg, ...)` style parameter. 221 | 222 | ```rust 223 | use structmeta::{NameArgs, StructMeta}; 224 | use syn::{parse_quote, Attribute, LitStr}; 225 | 226 | #[derive(StructMeta)] 227 | struct Args { 228 | a: NameArgs>, 229 | } 230 | 231 | let attr: Attribute = parse_quote!(#[attr(a())]); 232 | let args: Args = attr.parse_args()?; 233 | assert_eq!(args.a.args.len(), 0); 234 | 235 | let attr: Attribute = parse_quote!(#[attr(a("x"))]); 236 | let args: Args = attr.parse_args()?; 237 | assert_eq!(args.a.args.len(), 1); 238 | 239 | let attr: Attribute = parse_quote!(#[attr(a("x", "y"))]); 240 | let args: Args = attr.parse_args()?; 241 | assert_eq!(args.a.args.len(), 2); 242 | # syn::Result::Ok(()) 243 | ``` 244 | 245 | ## NameArgList or Flag style 246 | 247 | A field with type `NameArgs>>` will be `name(arg, arg, ...)` or `name` style parameter. 248 | 249 | ```rust 250 | use structmeta::{NameArgs, StructMeta}; 251 | use syn::{parse_quote, Attribute, LitStr}; 252 | 253 | #[derive(StructMeta)] 254 | struct Args { 255 | abc: NameArgs>>, 256 | } 257 | 258 | let attr: Attribute = parse_quote!(#[attr(abc)]); 259 | let args: Args = attr.parse_args()?; 260 | assert_eq!(args.abc.args.is_none(), true); 261 | 262 | let attr: Attribute = parse_quote!(#[attr(abc())]); 263 | let args: Args = attr.parse_args()?; 264 | assert_eq!(args.abc.args.unwrap().len(), 0); 265 | 266 | let attr: Attribute = parse_quote!(#[attr(abc("x"))]); 267 | let args: Args = attr.parse_args()?; 268 | assert_eq!(args.abc.args.unwrap().len(), 1); 269 | 270 | let attr: Attribute = parse_quote!(#[attr(abc("x", "y"))]); 271 | let args: Args = attr.parse_args()?; 272 | assert_eq!(args.abc.args.unwrap().len(), 2); 273 | # syn::Result::Ok(()) 274 | ``` 275 | 276 | ## Optional named parameter 277 | 278 | If you use `Option` for the field type, it becomes an optional parameter. 279 | 280 | ```rust 281 | use structmeta::{NameValue, StructMeta}; 282 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 283 | 284 | #[derive(StructMeta)] 285 | struct Args { 286 | a: Option, 287 | b: Option>, 288 | } 289 | 290 | let attr: Attribute = parse_quote!(#[attr(a = "abc")]); 291 | let args: Args = attr.parse_args()?; 292 | assert!(args.a.is_some()); 293 | assert!(args.b.is_none()); 294 | 295 | let attr: Attribute = parse_quote!(#[attr(b = 10)]); 296 | let args: Args = attr.parse_args()?; 297 | assert!(args.a.is_none()); 298 | assert!(args.b.is_some()); 299 | # syn::Result::Ok(()) 300 | ``` 301 | 302 | ## Rest named parameter 303 | 304 | If `HashMap` is used for the field type, the field will contain named arguments that are not associated with the field. 305 | 306 | ```rust 307 | use std::collections::HashMap; 308 | use structmeta::StructMeta; 309 | use syn::{parse_quote, Attribute, LitInt}; 310 | 311 | #[derive(StructMeta)] 312 | struct Args { 313 | a: Option, 314 | rest: HashMap, 315 | } 316 | 317 | let attr: Attribute = parse_quote!(#[attr(a = 10, b = 20, c = 30)]); 318 | let args: Args = attr.parse_args()?; 319 | assert!(args.a.is_some()); 320 | let mut keys: Vec<_> = args.rest.keys().collect(); 321 | keys.sort(); 322 | assert_eq!(keys, vec!["b", "c"]); 323 | # syn::Result::Ok(()) 324 | ``` 325 | 326 | # Unnamed parameter 327 | 328 | The following field will be "Unnamed parameter". 329 | 330 | - field in tuple struct. 331 | - field with `#[struct_meta(unnamed)]` in record struct. 332 | - However, fields that meet the following conditions are excluded 333 | - field with `#[struct_meta(name = "...")]` 334 | - field with type `HashMap` 335 | 336 | "Unnamed parameter" is a value-only parameter, such as `#[attr("abc", 10, 20)]`. 337 | 338 | | field type | effect | 339 | | ----------- | ------------------------------------------------- | 340 | | `T` | [required parameter](#required-unnamed-parameter) | 341 | | `Option` | [optional parameter](#optional-unnamed-parameter) | 342 | | `Vec` | [variadic parameter](#variadic-unnamed-parameter) | 343 | 344 | The type `T` in the table above needs to implement [`syn::parse::Parse`]. 345 | 346 | ## Required unnamed parameter 347 | 348 | Fields of the type that implement [`syn::parse::Parse`] will be required parameters. 349 | 350 | ```rust 351 | use structmeta::StructMeta; 352 | use syn::{parse_quote, Attribute, LitStr, Result}; 353 | 354 | #[derive(StructMeta)] 355 | struct Args(LitStr); 356 | 357 | let attr: Attribute = parse_quote!(#[attr()]); 358 | let args: Result = attr.parse_args(); 359 | assert!(args.is_err()); 360 | 361 | let attr: Attribute = parse_quote!(#[attr("a")]); 362 | let args: Args = attr.parse_args()?; 363 | assert_eq!(args.0.value(), "a"); 364 | # syn::Result::Ok(()) 365 | ``` 366 | 367 | ## Optional unnamed parameter 368 | 369 | Fields of type `Option` will be optional parameters. 370 | 371 | ```rust 372 | use structmeta::StructMeta; 373 | use syn::{parse_quote, Attribute, LitStr}; 374 | 375 | #[derive(StructMeta)] 376 | struct Args(Option); 377 | 378 | let attr: Attribute = parse_quote!(#[attr()]); 379 | let args: Args = attr.parse_args()?; 380 | assert!(args.0.is_none()); 381 | 382 | let attr: Attribute = parse_quote!(#[attr("a")]); 383 | let args: Args = attr.parse_args()?; 384 | assert_eq!(args.0.unwrap().value(), "a"); 385 | # syn::Result::Ok(()) 386 | ``` 387 | 388 | ## Variadic unnamed parameter 389 | 390 | If you use `Vec` as the field type, multiple arguments can be stored in a single field. 391 | 392 | ```rust 393 | use structmeta::StructMeta; 394 | use syn::{parse_quote, Attribute, LitStr}; 395 | 396 | #[derive(StructMeta)] 397 | struct Args(Vec); 398 | 399 | let attr: Attribute = parse_quote!(#[attr()]); 400 | let args: Args = attr.parse_args()?; 401 | assert_eq!(args.0.len(), 0); 402 | 403 | let attr: Attribute = parse_quote!(#[attr("a")]); 404 | let args: Args = attr.parse_args()?; 405 | assert_eq!(args.0.len(), 1); 406 | assert_eq!(args.0[0].value(), "a"); 407 | 408 | let attr: Attribute = parse_quote!(#[attr("a", "b")]); 409 | let args: Args = attr.parse_args()?; 410 | assert_eq!(args.0.len(), 2); 411 | assert_eq!(args.0[0].value(), "a"); 412 | assert_eq!(args.0[1].value(), "b"); 413 | # syn::Result::Ok(()) 414 | ``` 415 | 416 | # Parameter order 417 | 418 | The parameters must be in the following order. 419 | 420 | - Unnamed 421 | - Required 422 | - Optional 423 | - Variadic 424 | - Named 425 | 426 | # Helper attribute `#[struct_meta(...)]` 427 | 428 | | argument | struct | field | effect | 429 | | -------------------------------------------------- | ------ | ----- | ---------------------------------------------------------------------------------------- | 430 | | `dump` | ✔ | | Causes a compile error and outputs the automatically generated code as an error message. | 431 | | [`name_filter = "..."`](#struct_metaname_filter--) | ✔ | | Specify how to distinguish between a parameter name and a value of an unnamed parameter. | 432 | | `name = "..."` | | ✔ | Specify a parameter name. | 433 | | `unnamed` | | ✔ | Make the field be treated as an unnamed parameter. | 434 | 435 | # Uses with `#[proc_macro_derive]` 436 | 437 | A type with `#[derive(StructMeta)]` can be used with [`syn::Attribute::parse_args`]. 438 | 439 | ```rust 440 | # extern crate proc_macro; 441 | use proc_macro::TokenStream; 442 | use quote::quote; 443 | use structmeta::StructMeta; 444 | use syn::{parse, parse_macro_input, DeriveInput, LitStr}; 445 | 446 | #[derive(StructMeta)] 447 | struct MyAttr { 448 | msg: LitStr, 449 | } 450 | 451 | # const IGNORE_TOKENS: &str = stringify! { 452 | #[proc_macro_derive(MyMsg, attributes(my_msg))] 453 | # }; 454 | pub fn derive_my_msg(input: TokenStream) -> TokenStream { 455 | let input = parse_macro_input!(input as DeriveInput); 456 | let mut msg = String::new(); 457 | for attr in input.attrs { 458 | if attr.path().is_ident("my_msg") { 459 | let attr = attr.parse_args::().unwrap(); 460 | msg = attr.msg.value(); 461 | } 462 | } 463 | quote!(const MSG: &str = #msg;).into() 464 | } 465 | ``` 466 | 467 | ```ignore 468 | #[derive(MyMsg)] 469 | #[my_msg(msg = "abc")] 470 | struct TestType; 471 | 472 | assert_eq!(MSG, "abc"); 473 | ``` 474 | 475 | # Uses with `#[proc_macro_attribute]` 476 | 477 | A type with `#[derive(StructMeta)]` can be used with `attr` parameter in attribute proc macro. 478 | 479 | ```rust 480 | # extern crate proc_macro; 481 | use proc_macro::TokenStream; 482 | use quote::quote; 483 | use structmeta::StructMeta; 484 | use syn::{parse, parse_macro_input, DeriveInput, LitStr}; 485 | 486 | #[derive(StructMeta)] 487 | struct MyAttr { 488 | msg: LitStr, 489 | } 490 | # const IGNORE_TOKENS: &str = stringify! { 491 | #[proc_macro_attribute] 492 | # }; 493 | pub fn my_attr(attr: TokenStream, _item: TokenStream) -> TokenStream { 494 | let attr = parse::(attr).unwrap(); 495 | let msg = attr.msg.value(); 496 | quote!(const MSG: &str = #msg;).into() 497 | } 498 | ``` 499 | 500 | ```ignore 501 | #[my_attr(msg = "xyz")] 502 | struct TestType; 503 | 504 | assert_eq!(MSG, "xyz"); 505 | ``` 506 | 507 | # Parsing ambiguous arguments 508 | 509 | If one or more `name = value` style parameters are defined, arguments beginning with `name =` will be parsed as `name = value` style, 510 | even if the name is different from what it is defined as. 511 | 512 | This specification is intended to prevent `name = value` from being treated as unnamed parameter due to typo. 513 | 514 | ```rust 515 | use structmeta::StructMeta; 516 | use syn::{parse_quote, Attribute, Expr, LitInt, Result}; 517 | 518 | #[derive(StructMeta)] 519 | struct WithNamed { 520 | #[struct_meta(unnamed)] 521 | unnamed: Option, 522 | x: Option, 523 | } 524 | 525 | let attr_x: Attribute = parse_quote!(#[attr(x = 10)]); 526 | let result: WithNamed = attr_x.parse_args().unwrap(); 527 | assert_eq!(result.unnamed.is_some(), false); 528 | assert_eq!(result.x.is_some(), true); 529 | 530 | // `y = 10` is parsed as a wrong named parameter. 531 | let attr_y: Attribute = parse_quote!(#[attr(y = 10)]); 532 | let result: Result = attr_y.parse_args(); 533 | assert!(result.is_err()); 534 | ``` 535 | 536 | If `name = value` style parameter is not defined, it will be parsed as unnamed parameter. 537 | 538 | ```rust 539 | use structmeta::StructMeta; 540 | use syn::{parse_quote, Attribute, Expr, LitInt, Result}; 541 | 542 | #[derive(StructMeta)] 543 | struct WithoutNamed { 544 | #[struct_meta(unnamed)] 545 | unnamed: Option, 546 | } 547 | 548 | // `y = 10` is parsed as a unnamed parameter. 549 | let attr_y: Attribute = parse_quote!(#[attr(y = 10)]); 550 | let result: WithoutNamed = attr_y.parse_args().unwrap(); 551 | assert_eq!(result.unnamed, Some(parse_quote!(y = 10))); 552 | ``` 553 | 554 | Similarly, if one or more `name(args)` style parameters are defined, arguments with `name(args)` will be parsed as `name(args)` style. 555 | If `name(args)` style parameter is not defined, it will be parsed as unnamed parameter. 556 | 557 | The same is true for `name` or `name = value` style parameter. 558 | 559 | ## `#[struct_meta(name_filter = "...")]` 560 | 561 | By attaching `#[struct_meta(name_filter = "...")]` to struct definition, you can restrict the names that can be used as parameter names and treat other identifiers as a value of unnamed parameter. 562 | 563 | The following value can be used. 564 | 565 | - `"snake_case"` 566 | 567 | ```rust 568 | use structmeta::StructMeta; 569 | use syn::{parse_quote, Attribute, Expr, LitInt, Result}; 570 | 571 | let attr_x: Attribute = parse_quote!(#[attr(X)]); 572 | let attr_y: Attribute = parse_quote!(#[attr(Y)]); 573 | 574 | #[derive(StructMeta)] 575 | struct NoFilter { 576 | #[struct_meta(unnamed)] 577 | unnamed: Option, 578 | #[struct_meta(name = "X")] 579 | x: bool, 580 | } 581 | let result: NoFilter = attr_x.parse_args().unwrap(); 582 | assert_eq!(result.unnamed, None); 583 | assert_eq!(result.x, true); // `X` is parsed as a named parameter. 584 | 585 | let result: Result = attr_y.parse_args(); 586 | assert!(result.is_err()); // `Y` is parsed as a wrong named parameter. 587 | 588 | 589 | #[derive(StructMeta)] 590 | #[struct_meta(name_filter = "snake_case")] 591 | struct WithFilter { 592 | #[struct_meta(unnamed)] 593 | unnamed: Option, 594 | #[struct_meta(name = "X")] 595 | x: bool, 596 | } 597 | let result: WithFilter = attr_x.parse_args().unwrap(); 598 | assert_eq!(result.unnamed, Some(parse_quote!(X))); // `X` is parsed as a unnamed parameter. 599 | assert_eq!(result.x, false); 600 | ``` 601 | -------------------------------------------------------------------------------- /doc/to_tokens.md: -------------------------------------------------------------------------------- 1 | Derive [`quote::ToTokens`] for syntax tree node. 2 | 3 | - [Example](#example) 4 | - [Helper attributes](#helper-attributes) 5 | - [`#[to_tokens("[", "]", "(", ")", "{", "}"]`](#to_tokens-----) 6 | - [`#[to_tokens(dump)]`](#to_tokensdump) 7 | 8 | # Example 9 | 10 | `#[derive(ToTokens)]` generates an implementation of `ToTokens` that calls [`ToTokens::to_tokens`](quote::ToTokens::to_tokens) for each field. 11 | 12 | ```rust 13 | use syn::LitInt; 14 | 15 | #[derive(structmeta::ToTokens)] 16 | struct Example(LitInt, LitInt); 17 | ``` 18 | 19 | Code like this will be generated: 20 | 21 | ```rust 22 | # use syn::LitInt; 23 | # struct Example(LitInt, LitInt); 24 | impl quote::ToTokens for Example { 25 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 26 | self.0.to_tokens(tokens); 27 | self.1.to_tokens(tokens); 28 | } 29 | } 30 | ``` 31 | 32 | `#[derive(ToTokens)]` can also be specified for enum. 33 | 34 | ```rust 35 | use syn::{LitInt, LitStr}; 36 | 37 | #[derive(structmeta::ToTokens)] 38 | enum Example { 39 | A(LitInt), 40 | B(LitStr), 41 | } 42 | ``` 43 | 44 | Code like this will be generated: 45 | 46 | ```rust 47 | # use syn::{LitInt, LitStr}; 48 | # enum Example { 49 | # A(LitInt), 50 | # B(LitStr), 51 | # } 52 | impl quote::ToTokens for Example { 53 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 54 | match self { 55 | Self::A(l) => l.to_tokens(tokens), 56 | Self::B(l) => l.to_tokens(tokens), 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | # Helper attributes 63 | 64 | | | struct | enum | variant | field | 65 | | ----------------------------------------------------------- | ------ | ---- | ------- | ----- | 66 | | [`#[to_tokens("[")]`, `#[to_tokens("]")]`](#to_tokens-----) | | | | ✔ | 67 | | [`#[to_tokens("(")]`, `#[to_tokens(")")]`](#to_tokens-----) | | | | ✔ | 68 | | [`#[to_tokens("{")]`, `#[to_tokens("}")]`](#to_tokens-----) | | | | ✔ | 69 | | [`#[to_tokens(dump)]`](#to_tokensdump) | ✔ | ✔ | | | 70 | 71 | ## `#[to_tokens("[", "]", "(", ")", "{", "}"]` 72 | 73 | By specifying `#[to_tokens("[")]` or `#[to_tokens("(")]` or `#[to_tokens("[")]` , subsequent tokens will be enclosed in `[]` or `()` or `{}`. 74 | 75 | By default, all subsequent fields are enclosed. 76 | To restrict the enclosing fields, specify `#[to_tokens("]")]` or `#[to_tokens(")")]` or `#[to_tokens("}")]` for the field after the end of the enclosure. 77 | 78 | ```rust 79 | use syn::{token, LitInt}; 80 | 81 | #[derive(structmeta::ToTokens)] 82 | struct Example { 83 | x: LitInt, 84 | #[to_tokens("[")] 85 | bracket_token: token::Bracket, 86 | y: LitInt, 87 | #[to_tokens("]")] 88 | z: LitInt, 89 | } 90 | ``` 91 | 92 | Code like this will be generated: 93 | 94 | ```rust 95 | # use syn::{token, LitInt}; 96 | # 97 | # struct Example { 98 | # x: LitInt, 99 | # bracket_token: token::Bracket, 100 | # y: LitInt, 101 | # z: LitInt, 102 | # } 103 | impl quote::ToTokens for Example { 104 | fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { 105 | self.x.to_tokens(tokens); 106 | token::Bracket::surround(&self.bracket_token, tokens, |tokens| { 107 | self.y.to_tokens(tokens); 108 | }); 109 | self.z.to_tokens(tokens); 110 | } 111 | } 112 | ``` 113 | 114 | If the field type is `Bracket` or `Paren` or `Brace`, the symbol corresponding to the token type must be specified. 115 | 116 | If the field type is `MacroDelimiter`, any symbol can be used and there is no difference in behavior. (Three types of parentheses are available, no matter which symbol is specified.) 117 | 118 | | field type | start | end | 119 | | ------------------------------ | ----------------------- | ----------------------- | 120 | | [`struct@syn::token::Bracket`] | `"["` | `"]"` | 121 | | [`struct@syn::token::Paren`] | `"("` | `")"` | 122 | | [`struct@syn::token::Brace`] | `"{"` | `"}"` | 123 | | [`enum@syn::MacroDelimiter`] | `"["` or `"("` or `"{"` | `"]"` or `")"` or `"}"` | 124 | 125 | ## `#[to_tokens(dump)]` 126 | 127 | Causes a compile error and outputs the code generated by `#[derive(ToTokens)]` as an error message. 128 | -------------------------------------------------------------------------------- /structmeta-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structmeta-derive" 3 | version = "0.3.0" 4 | authors = ["frozenlib"] 5 | license = "MIT OR Apache-2.0" 6 | readme = "../README.md" 7 | repository = "https://github.com/frozenlib/structmeta" 8 | documentation = "https://docs.rs/structmeta/" 9 | keywords = ["derive", "parse", "attribute", "syn", "totokens"] 10 | categories = ["development-tools::procedural-macro-helpers"] 11 | description = "derive macro for structmeta crate." 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [lib] 19 | proc-macro = true 20 | 21 | [dependencies] 22 | proc-macro2 = "1.0.78" 23 | syn = "2.0.48" 24 | quote = "1.0.35" 25 | 26 | [dev-dependencies] 27 | syn = { version = "2.0.48", features = ["extra-traits"] } 28 | -------------------------------------------------------------------------------- /structmeta-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! The documentation for this crate is found in the structmeta crate. 2 | 3 | extern crate proc_macro; 4 | 5 | #[macro_use] 6 | mod syn_utils; 7 | mod parse; 8 | mod struct_meta; 9 | mod to_tokens; 10 | mod to_tokens_attribute; 11 | 12 | use syn::{parse_macro_input, DeriveInput}; 13 | use syn_utils::*; 14 | 15 | #[proc_macro_derive(ToTokens, attributes(to_tokens))] 16 | pub fn derive_to_tokens(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 17 | let input = parse_macro_input!(input as DeriveInput); 18 | into_macro_output(to_tokens::derive_to_tokens(input)) 19 | } 20 | 21 | #[proc_macro_derive(Parse, attributes(to_tokens, parse))] 22 | pub fn derive_parse(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 23 | let input = parse_macro_input!(input as DeriveInput); 24 | into_macro_output(parse::derive_parse(input)) 25 | } 26 | 27 | #[proc_macro_derive(StructMeta, attributes(struct_meta))] 28 | pub fn derive_struct_meta(input: proc_macro::TokenStream) -> proc_macro::TokenStream { 29 | let input = parse_macro_input!(input as DeriveInput); 30 | into_macro_output(struct_meta::derive_struct_meta(input)) 31 | } 32 | -------------------------------------------------------------------------------- /structmeta-derive/src/parse.rs: -------------------------------------------------------------------------------- 1 | use crate::{syn_utils::*, to_tokens_attribute::*}; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote, quote_spanned}; 4 | use std::unreachable; 5 | use syn::{ 6 | parse::{Parse, ParseStream}, 7 | parse_quote, 8 | spanned::Spanned, 9 | Data, DataEnum, DataStruct, DeriveInput, Fields, Ident, Result, Token, 10 | }; 11 | 12 | pub fn derive_parse(input: DeriveInput) -> Result { 13 | let mut dump = false; 14 | for attr in &input.attrs { 15 | if attr.path().is_ident("parse") { 16 | let attr: ParseAttribute = attr.parse_args()?; 17 | dump = dump || attr.dump.is_some(); 18 | } 19 | } 20 | 21 | let ts = match &input.data { 22 | Data::Struct(data) => code_from_struct(data)?, 23 | Data::Enum(data) => code_from_enum(&input.ident, data)?, 24 | Data::Union(_) => { 25 | bail!(Span::call_site(), "Not supported for union.") 26 | } 27 | }; 28 | let ts = quote! { 29 | fn parse(input: ::structmeta::helpers::exports::syn::parse::ParseStream<'_>) -> ::structmeta::helpers::exports::syn::Result { 30 | #ts 31 | } 32 | }; 33 | let ts = impl_trait_result( 34 | &input, 35 | &parse_quote!(::structmeta::helpers::exports::syn::parse::Parse), 36 | &[], 37 | ts, 38 | dump, 39 | )?; 40 | Ok(ts) 41 | } 42 | 43 | fn code_from_struct(data: &DataStruct) -> Result { 44 | code_from_fields(quote!(Self), &data.fields, None) 45 | } 46 | fn code_from_enum(self_ident: &Ident, data: &DataEnum) -> Result { 47 | let mut ts = TokenStream::new(); 48 | ts.extend(quote!( 49 | use syn::ext::*; 50 | )); 51 | let mut input_is_forked = false; 52 | let mut input_is_moved = false; 53 | for index in 0..data.variants.len() { 54 | let variant = &data.variants[index]; 55 | let variant_ident = &variant.ident; 56 | let is_last = index == data.variants.len() - 1; 57 | let fn_ident = format_ident!("_parse_{}", &variant.ident); 58 | let mut peeks = Vec::new(); 59 | let fn_expr = code_from_fields( 60 | quote!(#self_ident::#variant_ident), 61 | &variant.fields, 62 | Some(&mut peeks), 63 | )?; 64 | let fn_def = quote! { 65 | #[allow(non_snake_case)] 66 | fn #fn_ident(input: ::structmeta::helpers::exports::syn::parse::ParseStream<'_>) -> ::structmeta::helpers::exports::syn::Result<#self_ident> { 67 | #fn_expr 68 | } 69 | }; 70 | let code = if peeks.is_empty() { 71 | if is_last && !input_is_forked { 72 | input_is_moved = true; 73 | quote! { 74 | #fn_ident(input) 75 | } 76 | } else { 77 | input_is_forked = true; 78 | quote! { 79 | let fork = input.fork(); 80 | if let Ok(value) = #fn_ident(&fork) { 81 | ::structmeta::helpers::exports::syn::parse::discouraged::Speculative::advance_to(input, &fork); 82 | return Ok(value); 83 | } 84 | } 85 | } 86 | } else { 87 | let mut preds = Vec::new(); 88 | for (index, peek) in peeks.into_iter().enumerate() { 89 | preds.push(to_predicate(index, &peek)?); 90 | } 91 | quote! { 92 | if #(#preds )&&* { 93 | return #fn_ident(&input); 94 | } 95 | } 96 | }; 97 | ts.extend(quote! { 98 | #fn_def 99 | #code 100 | }); 101 | } 102 | if !input_is_moved { 103 | ts.extend(quote! { 104 | Err(input.error("parse failed.")) 105 | }); 106 | } 107 | Ok(ts) 108 | } 109 | fn to_predicate(index: usize, peek: &PeekItem) -> Result { 110 | let peek_ident: Ident = match index { 111 | 0 => parse_quote!(peek), 112 | 1 => parse_quote!(peek2), 113 | 2 => parse_quote!(peek3), 114 | _ => bail!(peek.span, "more than three `#[parse(peek)]` was specified."), 115 | }; 116 | let peek_arg = &peek.arg; 117 | Ok(quote!(input.#peek_ident(#peek_arg))) 118 | } 119 | 120 | struct Scope { 121 | input: Ident, 122 | close: Option, 123 | } 124 | struct PeekItem { 125 | span: Span, 126 | arg: TokenStream, 127 | } 128 | fn to_parse_bracket(c: char) -> Ident { 129 | match c { 130 | '(' => parse_quote!(parenthesized), 131 | '[' => parse_quote!(bracketed), 132 | '{' => parse_quote!(braced), 133 | _ => unreachable!(), 134 | } 135 | } 136 | fn code_from_fields( 137 | self_path: TokenStream, 138 | fields: &Fields, 139 | mut peeks: Option<&mut Vec>, 140 | ) -> Result { 141 | let mut scopes = vec![Scope { 142 | input: parse_quote!(input), 143 | close: None, 144 | }]; 145 | let mut ts = TokenStream::new(); 146 | let mut inits = Vec::new(); 147 | let mut non_peek_field = None; 148 | for (index, field) in fields.iter().enumerate() { 149 | let ty = &field.ty; 150 | let var_ident = to_var_ident(index, &field.ident); 151 | let mut use_parse = true; 152 | let mut peek = None; 153 | let mut is_any = false; 154 | let mut is_terminated = false; 155 | let mut is_root = scopes.len() == 1; 156 | for attr in &field.attrs { 157 | if attr.path().is_ident("to_tokens") { 158 | let attr: ToTokensAttribute = attr.parse_args()?; 159 | for token in attr.token { 160 | for c in token.value().chars() { 161 | match c { 162 | '(' | '[' | '{' => { 163 | use_parse = false; 164 | let parse_bracket = if is_macro_delimiter(&field.ty) { 165 | quote!(::structmeta::helpers_parse_macro_delimiter) 166 | } else { 167 | let parse_bracket = to_parse_bracket(c); 168 | quote!(::structmeta::helpers::exports::syn::#parse_bracket) 169 | }; 170 | let input_old = &scopes.last().unwrap().input; 171 | let input = format_ident!("input_{}", index); 172 | let ty = &field.ty; 173 | let code = quote_spanned!(field.span()=> 174 | let #input; 175 | let #var_ident = #parse_bracket!(#input in #input_old); 176 | let #var_ident : #ty = #var_ident; 177 | let #input = &#input; 178 | ); 179 | // We need `let #var_ident : #ty = #var_ident;` to make error messages easier to understand. 180 | // Try remove this line and run compile fail tests for details. 181 | 182 | ts.extend(code); 183 | scopes.push(Scope { 184 | close: Some(to_close(c)), 185 | input, 186 | }); 187 | } 188 | ')' | ']' | '}' => { 189 | if scopes.last().unwrap().close != Some(c) { 190 | bail!(token.span(), "mismatched closing delimiter `{}`.", c); 191 | } 192 | scopes.pop(); 193 | if scopes.len() == 1 { 194 | is_root = true; 195 | } 196 | } 197 | _ => { 198 | bail!( 199 | token.span(), 200 | "expected '(', ')', '[', ']', '{{' or '}}', found `{}`.", 201 | c 202 | ); 203 | } 204 | } 205 | } 206 | } 207 | } 208 | if attr.path().is_ident("parse") { 209 | let attr: ParseAttribute = attr.parse_args()?; 210 | peek = peek.or(attr.peek); 211 | is_any = is_any || attr.any.is_some(); 212 | is_terminated = is_terminated || attr.terminated.is_some(); 213 | } 214 | } 215 | if let Some(peeks) = &mut peeks { 216 | if let Some(peek) = peek { 217 | let span = peek.span(); 218 | if !is_root { 219 | bail!(span, "`#[parse(peek)]` cannot be specified with a field enclosed by `[]`, `()` or `{}`."); 220 | } 221 | if let Some(non_peek_field) = &non_peek_field { 222 | bail!( 223 | span, 224 | "you need to peek all previous tokens. consider specifying `#[parse(peek)]` for field `{}`.", 225 | non_peek_field 226 | ); 227 | } 228 | let arg = if is_any { 229 | quote!(#ty::peek_any) 230 | } else { 231 | quote!(#ty) 232 | }; 233 | peeks.push(PeekItem { span, arg }); 234 | } 235 | } 236 | if is_root && peek.is_none() && non_peek_field.is_none() { 237 | non_peek_field = Some(to_display(index, &field.ident)); 238 | } 239 | if use_parse { 240 | let input = &scopes.last().unwrap().input; 241 | let expr = match (is_terminated, is_any) { 242 | (false, false) => { 243 | quote!(::structmeta::helpers::exports::syn::parse::Parse::parse(#input)) 244 | } 245 | (false, true) => quote!(<#ty>::parse_any(#input)), 246 | (true, false) => { 247 | quote!(<#ty>::parse_terminated(#input)) 248 | } 249 | (true, true) => { 250 | quote!(<#ty>::parse_terminated_with(#input, ::structmeta::helpers::exports::syn::ext::IdentExt::parse_any)) 251 | } 252 | }; 253 | let code = quote_spanned!(field.span()=>let #var_ident = #expr?;); 254 | ts.extend(code); 255 | } 256 | if let Some(field_ident) = &field.ident { 257 | inits.push(quote!(#field_ident : #var_ident)); 258 | } else { 259 | inits.push(quote!(#var_ident)); 260 | } 261 | } 262 | let init = match &fields { 263 | Fields::Named(_) => quote!({#(#inits,)*}), 264 | Fields::Unnamed(_) => quote!((#(#inits,)*)), 265 | Fields::Unit => quote!(), 266 | }; 267 | Ok(quote! { 268 | use syn::ext::*; 269 | #ts 270 | Ok(#self_path #init) 271 | }) 272 | } 273 | 274 | fn to_var_ident(index: usize, ident: &Option) -> Ident { 275 | if let Some(ident) = ident { 276 | format_ident!("_{}", ident) 277 | } else { 278 | format_ident!("_{}", index) 279 | } 280 | } 281 | fn to_display(index: usize, ident: &Option) -> String { 282 | if let Some(ident) = ident { 283 | format!("{ident}") 284 | } else { 285 | format!("{index}") 286 | } 287 | } 288 | 289 | struct ParseAttribute { 290 | any: Option, 291 | peek: Option, 292 | terminated: Option, 293 | dump: Option, 294 | } 295 | impl Parse for ParseAttribute { 296 | fn parse(input: ParseStream) -> Result { 297 | let mut any = None; 298 | let mut peek = None; 299 | let mut terminated = None; 300 | let mut dump = None; 301 | let args = input.parse_terminated(ParseAttributeArg::parse, Token![,])?; 302 | for arg in args.into_iter() { 303 | match arg { 304 | ParseAttributeArg::Any(kw_any) => any = any.or(Some(kw_any)), 305 | ParseAttributeArg::Peek(kw_peek) => peek = peek.or(Some(kw_peek)), 306 | ParseAttributeArg::Terminated(kw_terminated) => { 307 | terminated = terminated.or(Some(kw_terminated)) 308 | } 309 | ParseAttributeArg::Dump(kw_dump) => dump = dump.or(Some(kw_dump)), 310 | } 311 | } 312 | Ok(Self { 313 | any, 314 | peek, 315 | terminated, 316 | dump, 317 | }) 318 | } 319 | } 320 | mod kw { 321 | use syn::custom_keyword; 322 | custom_keyword!(any); 323 | custom_keyword!(peek); 324 | custom_keyword!(terminated); 325 | custom_keyword!(dump); 326 | } 327 | 328 | enum ParseAttributeArg { 329 | Any(kw::any), 330 | Peek(kw::peek), 331 | Terminated(kw::terminated), 332 | Dump(kw::dump), 333 | } 334 | impl Parse for ParseAttributeArg { 335 | fn parse(input: ParseStream) -> Result { 336 | if input.peek(kw::any) { 337 | Ok(Self::Any(input.parse()?)) 338 | } else if input.peek(kw::peek) { 339 | Ok(Self::Peek(input.parse()?)) 340 | } else if input.peek(kw::terminated) { 341 | Ok(Self::Terminated(input.parse()?)) 342 | } else if input.peek(kw::dump) { 343 | Ok(Self::Dump(input.parse()?)) 344 | } else { 345 | Err(input.error("expected `any`, `peek`, `terminated` or `dump`.")) 346 | } 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /structmeta-derive/src/struct_meta.rs: -------------------------------------------------------------------------------- 1 | use crate::syn_utils::*; 2 | use proc_macro2::{Span, TokenStream}; 3 | use quote::{format_ident, quote, quote_spanned}; 4 | use std::collections::BTreeMap; 5 | use syn::{ 6 | ext::IdentExt, 7 | parse::{Parse, ParseStream}, 8 | parse_quote, 9 | punctuated::Punctuated, 10 | spanned::Spanned, 11 | Attribute, Data, DeriveInput, Field, Fields, GenericArgument, Ident, LitStr, PathArguments, 12 | Result, Token, Type, 13 | }; 14 | 15 | pub fn derive_struct_meta(input: DeriveInput) -> Result { 16 | if let Data::Struct(data) = &input.data { 17 | let mut args = ArgsForStruct::default(); 18 | for attr in &input.attrs { 19 | if attr.path().is_ident("struct_meta") { 20 | args.parse_from_attr(attr)?; 21 | } 22 | } 23 | let ps = Params::from_fields(&data.fields, &args)?; 24 | let body = ps.build(); 25 | impl_trait_result( 26 | &input, 27 | &parse_quote!(::structmeta::helpers::exports::syn::parse::Parse), 28 | &[], 29 | quote! { 30 | fn parse(input: ::structmeta::helpers::exports::syn::parse::ParseStream<'_>) -> ::structmeta::helpers::exports::syn::Result { 31 | #body 32 | } 33 | }, 34 | args.dump, 35 | ) 36 | } else { 37 | let span = input.span(); 38 | bail!(span, "`#[derive(StructMeta)]` supports only struct.") 39 | } 40 | } 41 | struct Params<'a> { 42 | fields: &'a Fields, 43 | unnamed_required: Vec>, 44 | unnamed_optional: Vec>, 45 | unnamed_variadic: Option>, 46 | named: BTreeMap>, 47 | rest: Option>, 48 | name_filter: NameFilter, 49 | } 50 | impl<'a> Params<'a> { 51 | fn from_fields(fields: &'a Fields, args: &ArgsForStruct) -> Result { 52 | let mut unnamed_required = Vec::new(); 53 | let mut unnamed_optional = Vec::new(); 54 | let mut unnamed_variadic = None; 55 | let mut named = BTreeMap::new(); 56 | let mut rest = None; 57 | for (index, field) in fields.iter().enumerate() { 58 | let span = field.span(); 59 | match Param::from_field(index, field)? { 60 | Param::Unnamed(p) => { 61 | if unnamed_variadic.is_some() { 62 | bail!( 63 | span, 64 | "cannot use unnamed parameter after variadic parameter." 65 | ) 66 | } 67 | if p.is_vec { 68 | unnamed_variadic = Some(p); 69 | } else if p.is_option { 70 | unnamed_optional.push(p); 71 | } else { 72 | if !unnamed_optional.is_empty() { 73 | bail!( 74 | span, 75 | "cannot use non optional parameter after variadic parameter." 76 | ) 77 | } 78 | unnamed_required.push(p); 79 | } 80 | } 81 | Param::Named(p) => { 82 | if named.contains_key(&p.name) { 83 | bail!(p.name_span, "`{}` is already exists.", p.name); 84 | } 85 | named.insert(p.name.clone(), p); 86 | } 87 | Param::Rest(p) => { 88 | if rest.is_some() { 89 | bail!(span, "cannot use rest parameter twice.") 90 | } 91 | rest = Some(p); 92 | } 93 | } 94 | } 95 | Ok(Self { 96 | fields, 97 | unnamed_required, 98 | unnamed_optional, 99 | unnamed_variadic, 100 | named, 101 | rest, 102 | name_filter: args.name_filter(), 103 | }) 104 | } 105 | fn build(&self) -> TokenStream { 106 | let mut is_next = false; 107 | let mut ts = TokenStream::new(); 108 | let mut ctor_args = vec![TokenStream::new(); self.fields.len()]; 109 | for (index, p) in self.unnamed_required.iter().enumerate() { 110 | if is_next { 111 | let msg = format!( 112 | "expected least {} arguments but {} argument was supplied", 113 | self.unnamed_required.len(), 114 | index, 115 | ); 116 | ts.extend(quote! { 117 | if input.is_empty () { 118 | return Err(::structmeta::helpers::exports::syn::Error::new(input.span(), #msg)); 119 | } 120 | input.parse::<::structmeta::helpers::exports::syn::Token![,]>()?; 121 | }); 122 | } 123 | is_next = true; 124 | ts.extend(p.info.build_let_parse()); 125 | p.build_ctor_arg(false, &mut ctor_args); 126 | } 127 | 128 | let mut arms_unnamed = Vec::new(); 129 | for (index, p) in self.unnamed_optional.iter().enumerate() { 130 | ts.extend(p.info.build_let_none()); 131 | arms_unnamed.push(p.build_arm_parse_value(index)); 132 | p.build_ctor_arg(true, &mut ctor_args); 133 | } 134 | if let Some(p) = &self.unnamed_variadic { 135 | ts.extend(p.info.build_let_vec_new()); 136 | arms_unnamed.push(p.build_arm_parse_vec_item()); 137 | p.build_ctor_arg(false, &mut ctor_args); 138 | } else { 139 | arms_unnamed.push(quote! { 140 | _ => { return Err(input.error("too many unnamed parameter")); } 141 | }); 142 | } 143 | for p in self.named.values() { 144 | ts.extend(p.build_let()); 145 | p.build_ctor_arg(&mut ctor_args); 146 | } 147 | let (flag_ps, flag_rest) = self.named_ps(|p| p.is_flag()); 148 | let (name_value_ps, name_value_rest) = self.named_ps(|p| p.is_name_value()); 149 | let (name_args_ps, name_args_rest) = self.named_ps(|p| p.is_name_args()); 150 | 151 | let mut arms_named = Vec::new(); 152 | for (index, p) in flag_ps.iter().enumerate() { 153 | arms_named.push(p.build_arm_parse(index, ArgKind::Flag)); 154 | } 155 | for (index, p) in name_value_ps.iter().enumerate() { 156 | arms_named.push(p.build_arm_parse(index, ArgKind::NameValue)); 157 | } 158 | for (index, p) in name_args_ps.iter().enumerate() { 159 | arms_named.push(p.build_arm_parse(index, ArgKind::NameArgs)); 160 | } 161 | if let Some(p) = &self.rest { 162 | ts.extend(p.build_let()); 163 | p.build_ctor_arg(&mut ctor_args); 164 | if flag_rest { 165 | arms_named.push(p.build_arm_parse(ArgKind::Flag)); 166 | } 167 | if name_value_rest { 168 | arms_named.push(p.build_arm_parse(ArgKind::NameValue)); 169 | } 170 | if name_args_rest { 171 | arms_named.push(p.build_arm_parse(ArgKind::NameArgs)); 172 | } 173 | } 174 | 175 | let flag_names = NamedParam::names(&flag_ps); 176 | let name_value_names = NamedParam::names(&name_value_ps); 177 | let name_args_names = NamedParam::names(&name_args_ps); 178 | let no_unnamed = self.unnamed_optional.is_empty() && self.unnamed_variadic.is_none(); 179 | let ctor_args = match &self.fields { 180 | Fields::Named(_) => { 181 | quote!({ #(#ctor_args,)*}) 182 | } 183 | Fields::Unnamed(_) => { 184 | quote!(( #(#ctor_args,)*)) 185 | } 186 | Fields::Unit => { 187 | quote!() 188 | } 189 | }; 190 | 191 | let ts_parse_unnamed = if !self.unnamed_optional.is_empty() 192 | || self.unnamed_variadic.is_some() 193 | { 194 | quote! { 195 | if named_used { 196 | return Err(input.error("cannot use unnamed parameter after named parameter")); 197 | } 198 | match unnamed_index { 199 | #(#arms_unnamed)* 200 | } 201 | unnamed_index += 1; 202 | } 203 | } else { 204 | quote! { 205 | return Err(input.error("cannot use unnamed parameter")); 206 | } 207 | }; 208 | let name_filter = self.name_filter.to_code(); 209 | 210 | ts.extend(quote! { 211 | let mut is_next = #is_next; 212 | let mut unnamed_index = 0; 213 | let mut named_used = false; 214 | while !input.is_empty() { 215 | if is_next { 216 | input.parse::<::structmeta::helpers::exports::syn::Token![,]>()?; 217 | if input.is_empty() { 218 | break; 219 | } 220 | } 221 | is_next = true; 222 | if let Some((index, span)) = ::structmeta::helpers::try_parse_name(input, 223 | &[#(#flag_names,)*], 224 | #flag_rest, 225 | &[#(#name_value_names,)*], 226 | #name_value_rest, 227 | &[#(#name_args_names,)*], 228 | #name_args_rest, 229 | #no_unnamed, 230 | #name_filter)? 231 | { 232 | named_used = true; 233 | match index { 234 | #(#arms_named)* 235 | _ => unreachable!() 236 | } 237 | 238 | } else { 239 | #ts_parse_unnamed 240 | } 241 | } 242 | Ok(Self #ctor_args) 243 | }); 244 | 245 | ts 246 | } 247 | fn named_ps(&self, f: impl Fn(&NamedParamType<'a>) -> bool) -> (Vec<&NamedParam<'a>>, bool) { 248 | ( 249 | self.named.values().filter(|p| f(&p.ty)).collect(), 250 | if let Some(p) = &self.rest { 251 | f(&p.ty) 252 | } else { 253 | false 254 | }, 255 | ) 256 | } 257 | } 258 | 259 | enum Param<'a> { 260 | Unnamed(UnnamedParam<'a>), 261 | Named(NamedParam<'a>), 262 | Rest(RestParam<'a>), 263 | } 264 | 265 | impl<'a> Param<'a> { 266 | fn from_field(index: usize, field: &'a Field) -> Result { 267 | let mut name = None; 268 | let mut name_specified = false; 269 | let mut unnamed = false; 270 | for attr in &field.attrs { 271 | if attr.path().is_ident("struct_meta") { 272 | let a = attr.parse_args::()?; 273 | if let Some(a_name) = a.name { 274 | name = Some((a_name.value(), a_name.span())); 275 | name_specified = true; 276 | } 277 | if a.unnamed { 278 | unnamed = true; 279 | } 280 | } 281 | } 282 | if name.is_none() { 283 | if let Some(ident) = &field.ident { 284 | name = Some((ident.unraw().to_string(), ident.span())); 285 | } 286 | } 287 | if unnamed { 288 | name = None; 289 | } 290 | 291 | let mut is_map = false; 292 | let mut is_option = false; 293 | 294 | let ty = if let (false, Some(ty)) = (name_specified, get_hash_map_string_element(&field.ty)) 295 | { 296 | is_map = true; 297 | ty 298 | } else if let Some(ty) = get_option_element(&field.ty) { 299 | is_option = true; 300 | ty 301 | } else { 302 | &field.ty 303 | }; 304 | 305 | let info = ParamInfo::new(index, field, ty); 306 | let ty = NamedParamType::from_type(ty, !is_map && !is_option); 307 | let this = if is_map { 308 | Param::Rest(RestParam { info, ty }) 309 | } else if let Some((name, name_span)) = name { 310 | Param::Named(NamedParam { 311 | info, 312 | name, 313 | name_span, 314 | ty, 315 | is_option, 316 | }) 317 | } else if let NamedParamType::Value { ty, is_vec } = ty { 318 | Param::Unnamed(UnnamedParam { 319 | info, 320 | ty, 321 | is_option, 322 | is_vec, 323 | }) 324 | } else { 325 | bail!( 326 | info.span(), 327 | "this field type cannot be used as unnamed parameter." 328 | ) 329 | }; 330 | Ok(this) 331 | } 332 | } 333 | 334 | struct ParamInfo<'a> { 335 | index: usize, 336 | field: &'a Field, 337 | ty: &'a Type, 338 | temp_ident: Ident, 339 | } 340 | impl<'a> ParamInfo<'a> { 341 | fn new(index: usize, field: &'a Field, ty: &'a Type) -> Self { 342 | let temp_ident = format_ident!("_value_{}", index); 343 | Self { 344 | index, 345 | field, 346 | ty, 347 | temp_ident, 348 | } 349 | } 350 | fn span(&self) -> Span { 351 | self.field.span() 352 | } 353 | fn build_let_none(&self) -> TokenStream { 354 | let temp_ident = &self.temp_ident; 355 | let ty = &self.ty; 356 | quote!(let mut #temp_ident : Option<#ty> = None;) 357 | } 358 | fn build_let_vec_new(&self) -> TokenStream { 359 | let temp_ident = &self.temp_ident; 360 | let ty = &self.ty; 361 | quote!(let mut #temp_ident = <#ty>::new();) 362 | } 363 | fn build_let_parse(&self) -> TokenStream { 364 | let temp_ident = &self.temp_ident; 365 | let ty = &self.field.ty; 366 | quote_spanned!(self.span()=> let #temp_ident = input.parse::<#ty>()?;) 367 | } 368 | } 369 | 370 | struct RestParam<'a> { 371 | info: ParamInfo<'a>, 372 | ty: NamedParamType<'a>, 373 | } 374 | 375 | struct NamedParam<'a> { 376 | info: ParamInfo<'a>, 377 | name: String, 378 | name_span: Span, 379 | ty: NamedParamType<'a>, 380 | is_option: bool, 381 | } 382 | 383 | struct UnnamedParam<'a> { 384 | info: ParamInfo<'a>, 385 | ty: &'a Type, 386 | is_option: bool, 387 | is_vec: bool, 388 | } 389 | impl NamedParam<'_> { 390 | fn build_let(&self) -> TokenStream { 391 | let temp_ident = &self.info.temp_ident; 392 | quote!(let mut #temp_ident = None;) 393 | } 394 | fn build_arm_parse(&self, index: usize, kind: ArgKind) -> TokenStream { 395 | let temp_ident = &self.info.temp_ident; 396 | let msg = format!("parameter `{}` specified more than once", self.name); 397 | let span = self.info.field.span(); 398 | let expr = self.ty.build_parse_expr(kind, span); 399 | let var = kind.to_helper_name_index_variant(); 400 | quote_spanned! { span=> 401 | ::structmeta::helpers::NameIndex::#var(Ok(#index)) => { 402 | if #temp_ident.is_some() { 403 | return Err(::structmeta::helpers::exports::syn::Error::new(span, #msg)); 404 | } 405 | #temp_ident = Some(#expr); 406 | } 407 | } 408 | } 409 | fn names<'b>(ps: &[&'b Self]) -> Vec<&'b str> { 410 | ps.iter().map(|x| x.name.as_str()).collect() 411 | } 412 | fn build_ctor_arg(&self, ctor_args: &mut [TokenStream]) { 413 | let temp_ident = &self.info.temp_ident; 414 | let value = if self.is_option { 415 | quote!(#temp_ident) 416 | } else { 417 | match self.ty { 418 | NamedParamType::Flag => quote!(::structmeta::Flag { span: #temp_ident }), 419 | NamedParamType::Bool => quote!(#temp_ident.is_some()), 420 | NamedParamType::Value { .. } | NamedParamType::NameValue { .. } => { 421 | let msg = format!("missing argument `{} = ...`", self.name); 422 | quote!(#temp_ident.ok_or_else(|| ::structmeta::helpers::exports::syn::Error::new(::structmeta::helpers::exports::proc_macro2::Span::call_site(), #msg))?) 423 | } 424 | NamedParamType::NameArgs { .. } => { 425 | let msg = format!("missing argument `{}(...)`", self.name); 426 | quote!(#temp_ident.ok_or_else(|| ::structmeta::helpers::exports::syn::Error::new(::structmeta::helpers::exports::proc_macro2::Span::call_site(), #msg))?) 427 | } 428 | } 429 | }; 430 | build_ctor_arg(&self.info, value, ctor_args) 431 | } 432 | } 433 | impl RestParam<'_> { 434 | fn build_let(&self) -> TokenStream { 435 | let temp_ident = &self.info.temp_ident; 436 | quote!(let mut #temp_ident = ::std::collections::HashMap::new();) 437 | } 438 | fn build_arm_parse(&self, kind: ArgKind) -> TokenStream { 439 | let temp_ident = &self.info.temp_ident; 440 | let span = self.info.field.span(); 441 | let expr = self.ty.build_parse_expr(kind, span); 442 | let var = kind.to_helper_name_index_variant(); 443 | quote_spanned! { span=> 444 | ::structmeta::helpers::NameIndex::#var(Err(name)) => { 445 | if #temp_ident.insert(name.to_string(), #expr).is_some() { 446 | return Err(::structmeta::helpers::exports::syn::Error::new(span, format!("parameter `{}` specified more than once", name))); 447 | } 448 | } 449 | } 450 | } 451 | fn build_ctor_arg(&self, ctor_args: &mut [TokenStream]) { 452 | let temp_ident = &self.info.temp_ident; 453 | build_ctor_arg(&self.info, quote!(#temp_ident), ctor_args) 454 | } 455 | } 456 | impl UnnamedParam<'_> { 457 | fn build_arm_parse_value(&self, index: usize) -> TokenStream { 458 | let temp_ident = &self.info.temp_ident; 459 | let span = self.info.field.span(); 460 | let expr = build_parse_expr(self.ty, span); 461 | quote_spanned! { span=> 462 | #index => { 463 | #temp_ident = Some(#expr); 464 | } 465 | } 466 | } 467 | fn build_arm_parse_vec_item(&self) -> TokenStream { 468 | let temp_ident = &self.info.temp_ident; 469 | let span = self.info.field.span(); 470 | let expr = build_parse_expr(self.ty, span); 471 | quote_spanned! { self.info.field.span()=> 472 | _ => { 473 | #temp_ident.push(#expr); 474 | } 475 | } 476 | } 477 | fn build_ctor_arg(&self, var_is_option: bool, ctor_args: &mut [TokenStream]) { 478 | let temp_ident = &self.info.temp_ident; 479 | let value = match (var_is_option, self.is_option) { 480 | (false, false) | (true, true) => { 481 | quote!(#temp_ident) 482 | } 483 | (true, false) => { 484 | quote!(#temp_ident.unwrap()) 485 | } 486 | _ => { 487 | unreachable!() 488 | } 489 | }; 490 | build_ctor_arg(&self.info, value, ctor_args) 491 | } 492 | } 493 | fn build_ctor_arg(info: &ParamInfo, value: TokenStream, ctor_args: &mut [TokenStream]) { 494 | let value = if let Some(ident) = &info.field.ident { 495 | quote!(#ident : #value) 496 | } else { 497 | value 498 | }; 499 | ctor_args[info.index] = value; 500 | } 501 | 502 | mod kw { 503 | use syn::custom_keyword; 504 | 505 | custom_keyword!(dump); 506 | custom_keyword!(name_filter); 507 | custom_keyword!(name); 508 | custom_keyword!(unnamed); 509 | } 510 | 511 | #[derive(Debug, Clone, Copy)] 512 | enum NameFilter { 513 | None, 514 | SnakeCase, 515 | } 516 | impl NameFilter { 517 | fn to_code(self) -> TokenStream { 518 | match self { 519 | NameFilter::None => quote!(&|_| true), 520 | NameFilter::SnakeCase => quote!(&::structmeta::helpers::is_snake_case), 521 | } 522 | } 523 | } 524 | 525 | #[derive(Debug, Default, Clone, Copy)] 526 | struct ArgsForStruct { 527 | dump: bool, 528 | name_filter: Option, 529 | } 530 | impl ArgsForStruct { 531 | fn parse_from_attr(&mut self, attr: &Attribute) -> Result<()> { 532 | let args = attr.parse_args_with(Punctuated::::parse_terminated)?; 533 | for arg in args.into_iter() { 534 | match arg { 535 | ArgForStruct::Dump(_) => self.dump = true, 536 | ArgForStruct::NameFilter { span, value } => { 537 | if self.name_filter.is_some() { 538 | bail!(span, "`name_filter` cannot be specified twice"); 539 | } 540 | self.name_filter = Some(value); 541 | } 542 | } 543 | } 544 | Ok(()) 545 | } 546 | fn name_filter(&self) -> NameFilter { 547 | self.name_filter.unwrap_or(NameFilter::None) 548 | } 549 | } 550 | 551 | enum ArgForStruct { 552 | Dump(#[allow(dead_code)] kw::dump), 553 | NameFilter { span: Span, value: NameFilter }, 554 | } 555 | impl Parse for ArgForStruct { 556 | fn parse(input: ParseStream) -> Result { 557 | if input.peek(kw::dump) { 558 | return Ok(Self::Dump(input.parse()?)); 559 | } 560 | if input.peek(kw::name_filter) { 561 | let kw_name_filter: kw::name_filter = input.parse()?; 562 | let _eq: Token![=] = input.parse()?; 563 | let s: LitStr = input.parse()?; 564 | let value = match s.value().as_str() { 565 | "snake_case" => NameFilter::SnakeCase, 566 | _ => { 567 | bail!(s.span(), "expected \"snake_case\"") 568 | } 569 | }; 570 | return Ok(Self::NameFilter { 571 | span: kw_name_filter.span, 572 | value, 573 | }); 574 | } 575 | Err(input.error("usage : #[struct_meta(dump)]")) 576 | } 577 | } 578 | 579 | struct ArgsForField { 580 | name: Option, 581 | unnamed: bool, 582 | } 583 | impl Parse for ArgsForField { 584 | fn parse(input: ParseStream) -> Result { 585 | let mut name = None; 586 | let mut unnamed = false; 587 | for p in Punctuated::<_, Token![,]>::parse_terminated(input)?.into_iter() { 588 | match p { 589 | ArgForField::Name { value, .. } => name = Some(value), 590 | ArgForField::Unnamed { .. } => unnamed = true, 591 | } 592 | } 593 | Ok(Self { name, unnamed }) 594 | } 595 | } 596 | 597 | enum ArgForField { 598 | Name { 599 | _name_token: kw::name, 600 | _eq_token: Token![=], 601 | value: LitStr, 602 | }, 603 | Unnamed { 604 | _unnamed_token: kw::unnamed, 605 | }, 606 | } 607 | impl Parse for ArgForField { 608 | fn parse(input: ParseStream) -> Result { 609 | if input.peek(kw::name) && input.peek2(Token![=]) { 610 | let name_token = input.parse()?; 611 | let eq_token = input.parse()?; 612 | let value = input.parse()?; 613 | Ok(Self::Name { 614 | _name_token: name_token, 615 | _eq_token: eq_token, 616 | value, 617 | }) 618 | } else if input.peek(kw::unnamed) { 619 | Ok(Self::Unnamed { 620 | _unnamed_token: input.parse()?, 621 | }) 622 | } else { 623 | Err(input.error("expected `name = \"...\"` or `unnamed`.")) 624 | } 625 | } 626 | } 627 | 628 | enum NamedParamType<'a> { 629 | Bool, 630 | Flag, 631 | Value { 632 | ty: &'a Type, 633 | is_vec: bool, 634 | }, 635 | NameValue { 636 | ty: &'a Type, 637 | is_option: bool, 638 | }, 639 | NameArgs { 640 | ty: &'a Type, 641 | is_option: bool, 642 | is_vec: bool, 643 | }, 644 | } 645 | 646 | impl<'a> NamedParamType<'a> { 647 | fn from_type(ty: &'a Type, may_flag: bool) -> Self { 648 | if may_flag && is_bool(ty) { 649 | Self::Bool 650 | } else if may_flag && is_flag(ty) { 651 | Self::Flag 652 | } else if let Some(mut ty) = get_name_value_element(ty) { 653 | let mut is_option = false; 654 | if let Some(e) = get_option_element(ty) { 655 | is_option = true; 656 | ty = e; 657 | } 658 | Self::NameValue { ty, is_option } 659 | } else if let Some(mut ty) = get_name_args_element(ty) { 660 | let mut is_option = false; 661 | if let Some(e) = get_option_element(ty) { 662 | is_option = true; 663 | ty = e; 664 | } 665 | let mut is_vec = false; 666 | if let Some(e) = get_vec_element(ty) { 667 | is_vec = true; 668 | ty = e; 669 | } 670 | Self::NameArgs { 671 | ty, 672 | is_option, 673 | is_vec, 674 | } 675 | } else { 676 | let mut ty = ty; 677 | let mut is_vec = false; 678 | if let Some(e) = get_vec_element(ty) { 679 | is_vec = true; 680 | ty = e; 681 | } 682 | Self::Value { ty, is_vec } 683 | } 684 | } 685 | fn is_flag(&self) -> bool { 686 | match self { 687 | NamedParamType::Bool | NamedParamType::Flag => true, 688 | NamedParamType::Value { .. } => false, 689 | NamedParamType::NameValue { is_option, .. } 690 | | NamedParamType::NameArgs { is_option, .. } => *is_option, 691 | } 692 | } 693 | fn is_name_value(&self) -> bool { 694 | match self { 695 | NamedParamType::Bool | NamedParamType::Flag => false, 696 | NamedParamType::Value { is_vec, .. } => !is_vec, 697 | NamedParamType::NameValue { .. } => true, 698 | NamedParamType::NameArgs { .. } => false, 699 | } 700 | } 701 | fn is_name_args(&self) -> bool { 702 | match self { 703 | NamedParamType::Bool | NamedParamType::Flag => false, 704 | NamedParamType::Value { is_vec, .. } => *is_vec, 705 | NamedParamType::NameValue { .. } => false, 706 | NamedParamType::NameArgs { .. } => true, 707 | } 708 | } 709 | fn build_parse_expr(&self, kind: ArgKind, span: Span) -> TokenStream { 710 | match self { 711 | NamedParamType::Bool | NamedParamType::Flag => quote!(span), 712 | NamedParamType::Value { ty, is_vec } => { 713 | if *is_vec { 714 | build_parse_expr_name_args(ty, *is_vec, span) 715 | } else { 716 | build_parse_expr(ty, span) 717 | } 718 | } 719 | NamedParamType::NameValue { ty, is_option } => { 720 | let value = if kind == ArgKind::Flag && *is_option { 721 | quote!(None) 722 | } else { 723 | let value = build_parse_expr(ty, span); 724 | if *is_option { 725 | quote!(Some(#value)) 726 | } else { 727 | value 728 | } 729 | }; 730 | quote!(::structmeta::NameValue { name_span : span, value: #value }) 731 | } 732 | NamedParamType::NameArgs { 733 | ty, 734 | is_option, 735 | is_vec, 736 | } => { 737 | let args = if kind == ArgKind::Flag && *is_option { 738 | quote!(None) 739 | } else { 740 | let args = build_parse_expr_name_args(ty, *is_vec, span); 741 | if *is_option { 742 | quote!(Some(#args)) 743 | } else { 744 | args 745 | } 746 | }; 747 | quote!(structmeta::NameArgs { name_span : span, args: #args }) 748 | } 749 | } 750 | } 751 | } 752 | 753 | fn build_parse_expr(ty: &Type, span: Span) -> TokenStream { 754 | quote_spanned!(span=> input.parse::<#ty>()?) 755 | } 756 | fn build_parse_expr_name_args(ty: &Type, is_vec: bool, span: Span) -> TokenStream { 757 | let value = if is_vec { 758 | quote_spanned!(span=> ::structmeta::helpers::exports::syn::punctuated::Punctuated::<#ty, ::structmeta::helpers::exports::syn::Token![,]>::parse_terminated(&content)?.into_iter().collect()) 759 | } else { 760 | quote_spanned!(span=> content.parse::<#ty>()?) 761 | }; 762 | quote! { 763 | { 764 | let content; 765 | ::structmeta::helpers::exports::syn::parenthesized!(content in input); 766 | #value 767 | } 768 | } 769 | } 770 | 771 | #[derive(Eq, PartialEq, Debug, Copy, Clone)] 772 | enum ArgKind { 773 | Flag, 774 | NameValue, 775 | NameArgs, 776 | } 777 | impl ArgKind { 778 | fn to_helper_name_index_variant(self) -> TokenStream { 779 | match self { 780 | Self::Flag => quote!(Flag), 781 | Self::NameValue => quote!(NameValue), 782 | Self::NameArgs => quote!(NameArgs), 783 | } 784 | } 785 | } 786 | 787 | fn get_option_element(ty: &Type) -> Option<&Type> { 788 | get_element(ty, &[&["std", "option"], &["core", "option"]], "Option") 789 | } 790 | fn get_vec_element(ty: &Type) -> Option<&Type> { 791 | get_element(ty, &[&["std", "vec"], &["alloc", "vec"]], "Vec") 792 | } 793 | fn get_name_value_element(ty: &Type) -> Option<&Type> { 794 | get_element(ty, NS_STRUCTMETA, "NameValue") 795 | } 796 | fn get_name_args_element(ty: &Type) -> Option<&Type> { 797 | get_element(ty, NS_STRUCTMETA, "NameArgs") 798 | } 799 | fn get_hash_map_element(ty: &Type) -> Option<(&Type, &Type)> { 800 | get_element2( 801 | ty, 802 | &[&["std", "collections"], &["std", "collections", "hash_map"]], 803 | "HashMap", 804 | ) 805 | } 806 | fn get_hash_map_string_element(ty: &Type) -> Option<&Type> { 807 | let (ty_key, ty_value) = get_hash_map_element(ty)?; 808 | if is_string(ty_key) { 809 | Some(ty_value) 810 | } else { 811 | None 812 | } 813 | } 814 | 815 | fn is_bool(ty: &Type) -> bool { 816 | is_type(ty, NS_PRIMITIVE, "bool") 817 | } 818 | fn is_flag(ty: &Type) -> bool { 819 | is_type(ty, NS_STRUCTMETA, "Flag") 820 | } 821 | fn is_string(ty: &Type) -> bool { 822 | is_type(ty, &[&["std", "string"], &["alloc", "string"]], "String") 823 | } 824 | 825 | fn get_element<'a>(ty: &'a Type, ns: &[&[&str]], name: &str) -> Option<&'a Type> { 826 | if let PathArguments::AngleBracketed(args) = get_arguments_of(ty, ns, name)? { 827 | if args.args.len() == 1 { 828 | if let GenericArgument::Type(ty) = &args.args[0] { 829 | return Some(ty); 830 | } 831 | } 832 | } 833 | None 834 | } 835 | fn get_element2<'a>(ty: &'a Type, ns: &[&[&str]], name: &str) -> Option<(&'a Type, &'a Type)> { 836 | if let PathArguments::AngleBracketed(args) = get_arguments_of(ty, ns, name)? { 837 | if args.args.len() == 2 { 838 | if let (GenericArgument::Type(ty0), GenericArgument::Type(ty1)) = 839 | (&args.args[0], &args.args[1]) 840 | { 841 | return Some((ty0, ty1)); 842 | } 843 | } 844 | } 845 | None 846 | } 847 | 848 | const NS_STRUCTMETA: &[&[&str]] = &[&["structmeta"]]; 849 | const NS_PRIMITIVE: &[&[&str]] = &[&["std", "primitive"], &["core", "primitive"]]; 850 | 851 | #[cfg(test)] 852 | mod tests { 853 | use super::*; 854 | #[test] 855 | fn test_is_option() { 856 | assert_eq!( 857 | get_option_element(&parse_quote!(Option)), 858 | Some(&parse_quote!(u8)) 859 | ); 860 | } 861 | #[test] 862 | fn test_is_option_mod() { 863 | assert_eq!( 864 | get_option_element(&parse_quote!(option::Option)), 865 | Some(&parse_quote!(u8)) 866 | ); 867 | } 868 | #[test] 869 | fn test_is_option_core() { 870 | assert_eq!( 871 | get_option_element(&parse_quote!(core::option::Option)), 872 | Some(&parse_quote!(u8)) 873 | ); 874 | } 875 | #[test] 876 | fn test_is_option_std() { 877 | assert_eq!( 878 | get_option_element(&parse_quote!(std::option::Option)), 879 | Some(&parse_quote!(u8)) 880 | ); 881 | } 882 | } 883 | -------------------------------------------------------------------------------- /structmeta-derive/src/syn_utils.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::{ 4 | punctuated::Punctuated, DeriveInput, Path, PathArguments, PathSegment, Result, Token, Type, 5 | WherePredicate, 6 | }; 7 | 8 | macro_rules! bail { 9 | ($span:expr, $message:literal $(,)?) => { 10 | return std::result::Result::Err(syn::Error::new($span, $message)) 11 | }; 12 | ($span:expr, $err:expr $(,)?) => { 13 | return std::result::Result::Err(syn::Error::new($span, $err)) 14 | }; 15 | ($span:expr, $fmt:expr, $($arg:tt)*) => { 16 | return std::result::Result::Err(syn::Error::new($span, std::format!($fmt, $($arg)*))) 17 | }; 18 | } 19 | 20 | pub fn into_macro_output(input: Result) -> proc_macro::TokenStream { 21 | match input { 22 | Ok(s) => s, 23 | Err(e) => e.to_compile_error(), 24 | } 25 | .into() 26 | } 27 | 28 | pub fn impl_trait( 29 | input: &DeriveInput, 30 | trait_path: &Path, 31 | wheres: &[WherePredicate], 32 | contents: TokenStream, 33 | ) -> TokenStream { 34 | let ty = &input.ident; 35 | let (impl_g, ty_g, where_clause) = input.generics.split_for_impl(); 36 | let mut wheres = wheres.to_vec(); 37 | if let Some(where_clause) = where_clause { 38 | wheres.extend(where_clause.predicates.iter().cloned()); 39 | } 40 | let where_clause = if wheres.is_empty() { 41 | quote! {} 42 | } else { 43 | quote! { where #(#wheres,)*} 44 | }; 45 | quote! { 46 | #[automatically_derived] 47 | impl #impl_g #trait_path for #ty #ty_g #where_clause { 48 | #contents 49 | } 50 | } 51 | } 52 | pub fn impl_trait_result( 53 | input: &DeriveInput, 54 | trait_path: &Path, 55 | wheres: &[WherePredicate], 56 | contents: TokenStream, 57 | dump: bool, 58 | ) -> Result { 59 | let ts = impl_trait(input, trait_path, wheres, contents); 60 | if dump { 61 | panic!("macro result: \n{ts}"); 62 | } 63 | Ok(ts) 64 | } 65 | 66 | pub fn is_type(ty: &Type, ns: &[&[&str]], name: &str) -> bool { 67 | if let Some(a) = get_arguments_of(ty, ns, name) { 68 | a.is_empty() 69 | } else { 70 | false 71 | } 72 | } 73 | pub fn get_arguments_of<'a>(ty: &'a Type, ns: &[&[&str]], name: &str) -> Option<&'a PathArguments> { 74 | if let Type::Path(ty) = ty { 75 | if ty.qself.is_some() { 76 | return None; 77 | } 78 | let ss = &ty.path.segments; 79 | if let Some(last) = ty.path.segments.last() { 80 | if last.ident != name { 81 | return None; 82 | } 83 | return if ns.iter().any(|ns| is_match_ns(ss, ns)) { 84 | Some(&last.arguments) 85 | } else { 86 | None 87 | }; 88 | } 89 | } 90 | None 91 | } 92 | pub fn is_match_ns(ss: &Punctuated, ns: &[&str]) -> bool { 93 | let mut i_ss = ss.len() - 1; 94 | let mut i_ns = ns.len(); 95 | while i_ss > 0 && i_ns > 0 { 96 | i_ns -= 1; 97 | i_ss -= 1; 98 | let s = &ss[i_ss]; 99 | if s.ident != ns[i_ns] || !s.arguments.is_empty() { 100 | return false; 101 | } 102 | } 103 | i_ss == 0 104 | } 105 | pub const NS_SYN: &[&[&str]] = &[&["syn"]]; 106 | 107 | pub fn is_macro_delimiter(ty: &Type) -> bool { 108 | is_type(ty, NS_SYN, "MacroDelimiter") 109 | } 110 | -------------------------------------------------------------------------------- /structmeta-derive/src/to_tokens.rs: -------------------------------------------------------------------------------- 1 | use crate::{syn_utils::*, to_tokens_attribute::*}; 2 | use proc_macro2::{Delimiter, Ident, Span, TokenStream}; 3 | use quote::{format_ident, quote, quote_spanned}; 4 | use std::unreachable; 5 | use syn::{ 6 | parse_quote, spanned::Spanned, Data, DataEnum, DataStruct, DeriveInput, Field, Fields, Result, 7 | }; 8 | 9 | pub fn derive_to_tokens(input: DeriveInput) -> Result { 10 | let mut dump = false; 11 | for attr in &input.attrs { 12 | if attr.path().is_ident("to_tokens") { 13 | let attr: ToTokensAttribute = attr.parse_args()?; 14 | dump = dump || attr.dump.is_some(); 15 | } 16 | } 17 | 18 | let ts = match &input.data { 19 | Data::Struct(data) => code_from_struct(data)?, 20 | Data::Enum(data) => code_from_enum(data)?, 21 | Data::Union(_) => { 22 | bail!(Span::call_site(), "Not supported for union.") 23 | } 24 | }; 25 | let ts = quote! { 26 | fn to_tokens(&self, tokens: &mut ::structmeta::helpers::exports::proc_macro2::TokenStream) { 27 | #ts 28 | } 29 | }; 30 | let ts = impl_trait_result( 31 | &input, 32 | &parse_quote!(::structmeta::helpers::exports::quote::ToTokens), 33 | &[], 34 | ts, 35 | dump, 36 | )?; 37 | Ok(ts) 38 | } 39 | fn code_from_struct(data: &DataStruct) -> Result { 40 | let p = to_pattern(quote!(Self), &data.fields); 41 | let ts = code_from_fields(&data.fields)?; 42 | let ts = quote! { 43 | let #p = self; 44 | #ts 45 | }; 46 | Ok(ts) 47 | } 48 | fn code_from_enum(data: &DataEnum) -> Result { 49 | let mut arms = Vec::new(); 50 | for variant in &data.variants { 51 | let ident = &variant.ident; 52 | let p = to_pattern(quote!(Self::#ident), &variant.fields); 53 | let code = code_from_fields(&variant.fields)?; 54 | arms.push(quote! { 55 | #p => { 56 | #code 57 | } 58 | }); 59 | } 60 | Ok(quote! { 61 | match self { 62 | #(#arms)* 63 | } 64 | }) 65 | } 66 | fn to_pattern(self_path: TokenStream, fields: &Fields) -> TokenStream { 67 | let mut vars = Vec::new(); 68 | match fields { 69 | Fields::Unit => self_path, 70 | Fields::Unnamed(_) => { 71 | for (index, field) in fields.iter().enumerate() { 72 | let var_ident = to_var_ident(Some(index), &field.ident); 73 | vars.push(quote!(#var_ident)); 74 | } 75 | quote!( #self_path( #(#vars,)*)) 76 | } 77 | Fields::Named(_) => { 78 | for field in fields.iter() { 79 | let field_ident = &field.ident; 80 | let var_ident = to_var_ident(None, field_ident); 81 | vars.push(quote!(#field_ident : #var_ident)); 82 | } 83 | quote!( #self_path { #(#vars,)* } ) 84 | } 85 | } 86 | } 87 | struct Scope<'a> { 88 | ts: TokenStream, 89 | surround: Option>, 90 | } 91 | struct Surround<'a> { 92 | ident: Ident, 93 | field: &'a Field, 94 | delimiter: Delimiter, 95 | } 96 | 97 | fn delimiter_from_open_char(value: char) -> Option { 98 | match value { 99 | '[' => Some(Delimiter::Bracket), 100 | '{' => Some(Delimiter::Brace), 101 | '(' => Some(Delimiter::Parenthesis), 102 | _ => None, 103 | } 104 | } 105 | fn delimiter_from_close_char(value: char) -> Option { 106 | match value { 107 | ']' => Some(Delimiter::Bracket), 108 | '}' => Some(Delimiter::Brace), 109 | ')' => Some(Delimiter::Parenthesis), 110 | _ => None, 111 | } 112 | } 113 | 114 | impl<'a> Scope<'a> { 115 | fn new(surround: Option>) -> Self { 116 | Self { 117 | ts: TokenStream::new(), 118 | surround, 119 | } 120 | } 121 | } 122 | fn close_char_of(delimiter: Delimiter) -> char { 123 | match delimiter { 124 | Delimiter::Bracket => ']', 125 | Delimiter::Brace => '}', 126 | Delimiter::Parenthesis => ')', 127 | _ => unreachable!("unsupported delimiter"), 128 | } 129 | } 130 | impl Surround<'_> { 131 | fn token_type_ident(&self) -> Ident { 132 | match self.delimiter { 133 | Delimiter::Bracket => parse_quote!(Bracket), 134 | Delimiter::Brace => parse_quote!(Brace), 135 | Delimiter::Parenthesis => parse_quote!(Paren), 136 | _ => unreachable!("unsupported delimiter"), 137 | } 138 | } 139 | } 140 | 141 | impl Scope<'_> { 142 | fn into_code(self, delimiter: Option, span: Span) -> Result { 143 | if let Some(s) = self.surround { 144 | if let Some(delimiter) = delimiter { 145 | if s.delimiter != delimiter { 146 | bail!( 147 | span, 148 | "mismatched closing delimiter expected `{}`, found `{}`.", 149 | close_char_of(s.delimiter), 150 | close_char_of(delimiter), 151 | ) 152 | } 153 | } 154 | let ident = &s.ident; 155 | let ts = self.ts; 156 | let ty = &s.field.ty; 157 | let span = s.field.span(); 158 | let func = if is_macro_delimiter(ty) { 159 | quote_spanned!(span=> ::structmeta::helpers::surround_macro_delimiter) 160 | } else { 161 | let ty = s.token_type_ident(); 162 | quote_spanned!(span=> ::structmeta::helpers::exports::syn::token::#ty::surround) 163 | }; 164 | let code = quote_spanned!(span=> #func(#ident, tokens, |tokens| { #ts });); 165 | return Ok(code); 166 | } 167 | Ok(quote!()) 168 | } 169 | } 170 | fn code_from_fields(fields: &Fields) -> Result { 171 | let mut scopes = vec![Scope::new(None)]; 172 | for (index, field) in fields.iter().enumerate() { 173 | let ident = to_var_ident(Some(index), &field.ident); 174 | let mut field_to_tokens = true; 175 | for attr in &field.attrs { 176 | if attr.path().is_ident("to_tokens") { 177 | let attr: ToTokensAttribute = attr.parse_args()?; 178 | for token in &attr.token { 179 | for c in token.value().chars() { 180 | if let Some(delimiter) = delimiter_from_open_char(c) { 181 | scopes.push(Scope::new(Some(Surround { 182 | ident: ident.clone(), 183 | field, 184 | delimiter, 185 | }))); 186 | field_to_tokens = false; 187 | } else if let Some(delimiter) = delimiter_from_close_char(c) { 188 | let scope = scopes.pop().unwrap(); 189 | scopes 190 | .last_mut() 191 | .unwrap() 192 | .ts 193 | .extend(scope.into_code(Some(delimiter), token.span())?); 194 | } else { 195 | bail!( 196 | token.span(), 197 | "expected '(', ')', '[', ']', '{{' or '}}', found `{}`.", 198 | c 199 | ); 200 | } 201 | } 202 | } 203 | } 204 | } 205 | if field_to_tokens { 206 | let code = quote_spanned!(field.span()=> ::structmeta::helpers::exports::quote::ToTokens::to_tokens(#ident, tokens);); 207 | scopes.last_mut().unwrap().ts.extend(code); 208 | } 209 | } 210 | while let Some(scope) = scopes.pop() { 211 | if scopes.is_empty() { 212 | return Ok(scope.ts); 213 | } 214 | scopes 215 | .last_mut() 216 | .unwrap() 217 | .ts 218 | .extend(scope.into_code(None, Span::call_site())?); 219 | } 220 | unreachable!() 221 | } 222 | fn to_var_ident(index: Option, ident: &Option) -> Ident { 223 | if let Some(ident) = ident { 224 | format_ident!("_{}", ident) 225 | } else { 226 | format_ident!("_{}", index.unwrap()) 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /structmeta-derive/src/to_tokens_attribute.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use syn::{parse::Parse, spanned::Spanned, LitStr, Result, Token}; 3 | 4 | pub struct ToTokensAttribute { 5 | pub dump: Option, 6 | pub token: Vec, 7 | } 8 | impl Parse for ToTokensAttribute { 9 | fn parse(input: syn::parse::ParseStream) -> Result { 10 | let args = input.parse_terminated(ToTokensAttributeArg::parse, Token![,])?; 11 | let mut token = Vec::new(); 12 | let mut dump = None; 13 | for arg in args.into_iter() { 14 | match arg { 15 | ToTokensAttributeArg::Token(token_value) => { 16 | token.push(token_value); 17 | } 18 | ToTokensAttributeArg::Dump(kw_dump) => { 19 | if dump.is_none() { 20 | dump = Some(kw_dump.span()); 21 | } 22 | } 23 | } 24 | } 25 | Ok(Self { dump, token }) 26 | } 27 | } 28 | 29 | mod kw { 30 | use syn::custom_keyword; 31 | custom_keyword!(dump); 32 | } 33 | 34 | enum ToTokensAttributeArg { 35 | Token(LitStr), 36 | Dump(kw::dump), 37 | } 38 | impl Parse for ToTokensAttributeArg { 39 | fn parse(input: syn::parse::ParseStream) -> Result { 40 | if input.peek(LitStr) { 41 | Ok(Self::Token(input.parse()?)) 42 | } else if input.peek(kw::dump) { 43 | Ok(Self::Dump(input.parse()?)) 44 | } else { 45 | Err(input.error("expected string literal.")) 46 | } 47 | } 48 | } 49 | 50 | pub fn to_close(c: char) -> char { 51 | match c { 52 | '(' => ')', 53 | '[' => ']', 54 | '{' => '}', 55 | _ => panic!("not found closing delimiter for {c}"), 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /structmeta-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structmeta-tests" 3 | version = "0.0.0" 4 | authors = ["frozenlib "] 5 | edition = "2021" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [lib] 11 | proc-macro = true 12 | 13 | [dependencies] 14 | structmeta = { path = "../structmeta" } 15 | proc-macro2 = "1.0.78" 16 | syn = { version = "2.0.48", features = ["extra-traits", "full"] } 17 | quote = "1.0.35" 18 | trybuild = "1.0.89" 19 | toml = "0.8.8" 20 | serde = { version = "1.0.196", features = ["derive"] } 21 | anyhow = "1.0.79" 22 | ignore = "0.4.22" 23 | regex = "1.10.3" 24 | -------------------------------------------------------------------------------- /structmeta-tests/examples/readme.rs: -------------------------------------------------------------------------------- 1 | use structmeta::StructMeta; 2 | use syn::{parse_quote, Attribute, LitInt, LitStr}; 3 | 4 | fn main() { 5 | #[derive(StructMeta, Debug)] 6 | struct MyAttr { 7 | x: LitInt, 8 | y: LitStr, 9 | } 10 | let attr: Attribute = parse_quote!(#[my_attr(x = 10, y = "abc")]); 11 | let attr: MyAttr = attr.parse_args().unwrap(); 12 | println!("x = {}, y = {}", attr.x, attr.y.value()); 13 | } 14 | -------------------------------------------------------------------------------- /structmeta-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use structmeta::{NameArgs, NameValue, Parse, StructMeta}; 6 | use syn::{parse, parse2, parse_macro_input, DeriveInput, LitInt, LitStr}; 7 | 8 | #[derive(StructMeta)] 9 | struct MyAttr { 10 | msg: LitStr, 11 | } 12 | 13 | #[proc_macro_derive(MyMsg, attributes(my_msg))] 14 | pub fn derive_my_msg(input: TokenStream) -> TokenStream { 15 | let input = parse_macro_input!(input as DeriveInput); 16 | let mut msg = String::new(); 17 | for attr in input.attrs { 18 | if attr.path().is_ident("my_msg") { 19 | let attr = attr.parse_args::().unwrap(); 20 | msg = attr.msg.value(); 21 | } 22 | } 23 | quote!(const MSG: &str = #msg;).into() 24 | } 25 | 26 | #[proc_macro_attribute] 27 | pub fn my_attr(attr: TokenStream, _item: TokenStream) -> TokenStream { 28 | let attr = parse::(attr).unwrap(); 29 | let msg = attr.msg.value(); 30 | quote!(const MSG: &str = #msg;).into() 31 | } 32 | 33 | #[derive(Parse)] 34 | enum SingleVariant { 35 | A(#[allow(dead_code)] LitInt, #[allow(dead_code)] LitStr), 36 | } 37 | 38 | #[proc_macro] 39 | pub fn parse_single_variant(input: TokenStream) -> TokenStream { 40 | match parse2::(input.into()) { 41 | Ok(_) => quote!(), 42 | Err(e) => e.into_compile_error(), 43 | } 44 | .into() 45 | } 46 | 47 | #[derive(StructMeta)] 48 | struct RequiredUnnamed2(#[allow(dead_code)] LitInt, #[allow(dead_code)] LitInt); 49 | 50 | #[proc_macro_attribute] 51 | pub fn attr_required_unnamed2(attr: TokenStream, item: TokenStream) -> TokenStream { 52 | parse_attr::(attr, item) 53 | } 54 | 55 | #[allow(dead_code)] 56 | #[derive(StructMeta)] 57 | struct RequiredUnnamed2Inner { 58 | value: NameArgs, 59 | after: LitInt, 60 | } 61 | 62 | #[proc_macro_attribute] 63 | pub fn attr_required_unnamed2_inner(attr: TokenStream, item: TokenStream) -> TokenStream { 64 | parse_attr::(attr, item) 65 | } 66 | 67 | #[allow(dead_code)] 68 | #[derive(StructMeta)] 69 | struct OptionalFlag { 70 | value: bool, 71 | } 72 | 73 | #[proc_macro_attribute] 74 | pub fn attr_flag(attr: TokenStream, item: TokenStream) -> TokenStream { 75 | parse_attr::(attr, item) 76 | } 77 | 78 | #[allow(dead_code)] 79 | #[derive(StructMeta)] 80 | struct RequiredNameValue { 81 | value: NameValue, 82 | } 83 | 84 | #[proc_macro_attribute] 85 | pub fn attr_required_name_value(attr: TokenStream, item: TokenStream) -> TokenStream { 86 | parse_attr::(attr, item) 87 | } 88 | 89 | #[allow(dead_code)] 90 | #[derive(StructMeta)] 91 | struct OptionalNameValue { 92 | value: Option>, 93 | } 94 | #[proc_macro_attribute] 95 | pub fn attr_optional_name_value(attr: TokenStream, item: TokenStream) -> TokenStream { 96 | parse_attr::(attr, item) 97 | } 98 | 99 | #[allow(dead_code)] 100 | #[derive(StructMeta)] 101 | struct RestNameValue { 102 | m: HashMap>, 103 | } 104 | #[proc_macro_attribute] 105 | pub fn attr_rest_name_value(attr: TokenStream, item: TokenStream) -> TokenStream { 106 | parse_attr::(attr, item) 107 | } 108 | 109 | #[allow(dead_code)] 110 | #[derive(StructMeta)] 111 | struct RequiredAndRestNameValue { 112 | value: NameValue, 113 | m: HashMap>, 114 | } 115 | #[proc_macro_attribute] 116 | pub fn attr_required_and_rest_name_value(attr: TokenStream, item: TokenStream) -> TokenStream { 117 | parse_attr::(attr, item) 118 | } 119 | 120 | #[allow(dead_code)] 121 | #[derive(StructMeta)] 122 | struct RequiredNameArgs { 123 | value: NameArgs, 124 | } 125 | 126 | #[proc_macro_attribute] 127 | pub fn attr_required_name_args(attr: TokenStream, item: TokenStream) -> TokenStream { 128 | parse_attr::(attr, item) 129 | } 130 | 131 | #[allow(dead_code)] 132 | #[derive(StructMeta)] 133 | struct OptionalNameArgs { 134 | value: Option>, 135 | } 136 | 137 | #[proc_macro_attribute] 138 | pub fn attr_optional_name_args(attr: TokenStream, item: TokenStream) -> TokenStream { 139 | parse_attr::(attr, item) 140 | } 141 | 142 | #[allow(dead_code)] 143 | #[derive(StructMeta)] 144 | struct RequiredNameArgsOrFlag { 145 | value: NameArgs>, 146 | } 147 | 148 | #[proc_macro_attribute] 149 | pub fn attr_required_name_args_or_flag(attr: TokenStream, item: TokenStream) -> TokenStream { 150 | parse_attr::(attr, item) 151 | } 152 | 153 | fn parse_attr(attr: TokenStream, item: TokenStream) -> TokenStream { 154 | match parse::(attr) { 155 | Ok(_) => item, 156 | Err(e) => e.into_compile_error().into(), 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ignore::Walk; 3 | use regex::Regex; 4 | use serde::Deserialize; 5 | use std::{ 6 | env::var, 7 | ffi::OsStr, 8 | fs::{self}, 9 | path::Path, 10 | str, 11 | }; 12 | 13 | #[test] 14 | #[ignore] 15 | fn compile_fail() { 16 | update_stderr_files("./tests/compile_fail").unwrap(); 17 | trybuild::TestCases::new().compile_fail("tests/compile_fail/*/*.rs") 18 | } 19 | 20 | #[derive(Deserialize)] 21 | struct CargoLockRoot { 22 | package: Vec, 23 | } 24 | 25 | impl CargoLockRoot { 26 | fn get_version_of(&self, package_name: &str) -> &str { 27 | for p in &self.package { 28 | if p.name == package_name { 29 | return &p.version; 30 | } 31 | } 32 | panic!("package {package_name} not found."); 33 | } 34 | } 35 | 36 | #[derive(Deserialize)] 37 | struct CargoLockPackage { 38 | name: String, 39 | version: String, 40 | } 41 | 42 | fn update_stderr_files(path: &str) -> Result<()> { 43 | let manifest_dir = var("CARGO_MANIFEST_DIR")?; 44 | let manifest_dir = Path::new(&manifest_dir); 45 | let root_dir = manifest_dir.parent().unwrap(); 46 | let cargo_lock: CargoLockRoot = 47 | toml::from_str(str::from_utf8(&fs::read(root_dir.join("Cargo.lock"))?)?)?; 48 | let syn_version = cargo_lock.get_version_of("syn"); 49 | 50 | let path = manifest_dir.join(path); 51 | for i in Walk::new(path) { 52 | let i = i?; 53 | if let Some(file_type) = i.file_type() { 54 | if file_type.is_file() && i.path().extension() == Some(OsStr::new("stderr")) { 55 | fix_file(i.path(), syn_version)?; 56 | } 57 | } 58 | } 59 | Ok(()) 60 | } 61 | fn fix_file(path: &Path, syn_version: &str) -> Result<()> { 62 | let re = Regex::new(r"--> \$CARGO/syn-([0-9]+.[0-9]+.[0-9]+)/")?; 63 | let b = fs::read(path)?; 64 | let s0 = str::from_utf8(&b)?; 65 | let s1 = re.replace_all(s0, format!(r"--> $$CARGO/syn-{syn_version}/")); 66 | if s0 != s1 { 67 | fs::write(path, &*s1)?; 68 | } 69 | Ok(()) 70 | } 71 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_enum.rs: -------------------------------------------------------------------------------- 1 | use structmeta::Parse; 2 | use syn::LitStr; 3 | #[derive(Parse)] 4 | enum TestType { 5 | A { 6 | #[to_tokens("(")] 7 | bracket_token: syn::token::Bracket, 8 | str: LitStr, 9 | }, 10 | B(LitStr), 11 | } 12 | 13 | fn main() {} 14 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_enum.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/parse/mismatch_delimiter_enum.rs:7:9 3 | | 4 | 7 | bracket_token: syn::token::Bracket, 5 | | ^^^^^^^^^^^^^ ------------------- expected due to this 6 | | | 7 | | expected `Bracket`, found `Paren` 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_struct.rs: -------------------------------------------------------------------------------- 1 | use structmeta::Parse; 2 | use syn::LitStr; 3 | #[derive(Parse)] 4 | struct TestType { 5 | #[to_tokens("(")] 6 | bracket_token: syn::token::Bracket, 7 | str: LitStr, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_struct.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/parse/mismatch_delimiter_struct.rs:6:5 3 | | 4 | 6 | bracket_token: syn::token::Bracket, 5 | | ^^^^^^^^^^^^^ ------------------- expected due to this 6 | | | 7 | | expected `Bracket`, found `Paren` 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_tuple.rs: -------------------------------------------------------------------------------- 1 | use structmeta::Parse; 2 | use syn::LitStr; 3 | #[derive(Parse)] 4 | struct TestType(#[to_tokens("(")] syn::token::Bracket, LitStr); 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/mismatch_delimiter_tuple.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/parse/mismatch_delimiter_tuple.rs:3:10 3 | | 4 | 3 | #[derive(Parse)] 5 | | ^^^^^ expected `Bracket`, found `Paren` 6 | 4 | struct TestType(#[to_tokens("(")] syn::token::Bracket, LitStr); 7 | | ------------------- expected due to this 8 | | 9 | = note: this error originates in the derive macro `Parse` (in Nightly builds, run with -Z macro-backtrace for more info) 10 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/single_variant_fail.rs: -------------------------------------------------------------------------------- 1 | structmeta_tests::parse_single_variant!(10 10); 2 | 3 | fn main() {} 4 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/parse/single_variant_fail.stderr: -------------------------------------------------------------------------------- 1 | error: expected string literal 2 | --> $DIR/single_variant_fail.rs:1:44 3 | | 4 | 1 | structmeta_tests::parse_single_variant!(10 10); 5 | | ^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_named_arguments.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_value(value = 10, value = 20)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_named_arguments.stderr: -------------------------------------------------------------------------------- 1 | error: parameter `value` specified more than once 2 | --> $DIR/duplicate_named_arguments.rs:1:58 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_value(value = 10, value = 20)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_named_arguments_with_rest.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_and_rest_name_value(value = 10, value = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_named_arguments_with_rest.stderr: -------------------------------------------------------------------------------- 1 | error: parameter `value` specified more than once 2 | --> $DIR/duplicate_named_arguments_with_rest.rs:1:67 3 | | 4 | 1 | #[structmeta_tests::attr_required_and_rest_name_value(value = 10, value = 10)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_rest_named_argument.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_rest_name_value(a = 10, a = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/duplicate_rest_named_argument.stderr: -------------------------------------------------------------------------------- 1 | error: parameter `a` specified more than once 2 | --> $DIR/duplicate_rest_named_argument.rs:1:50 3 | | 4 | 1 | #[structmeta_tests::attr_rest_name_value(a = 10, a = 10)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_flag_but_name_args.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_flag(value(10))] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_flag_but_name_args.stderr: -------------------------------------------------------------------------------- 1 | error: expected flag `value`, found `value`(...) 2 | --> $DIR/expected_flag_but_name_args.rs:1:31 3 | | 4 | 1 | #[structmeta_tests::attr_flag(value(10))] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_flag_but_name_value.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_flag(value = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_flag_but_name_value.stderr: -------------------------------------------------------------------------------- 1 | error: expected flag `value`, found `value = ...` 2 | --> $DIR/expected_flag_but_name_value.rs:1:31 3 | | 4 | 1 | #[structmeta_tests::attr_flag(value = 10)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_buf_flag.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_args(value)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_buf_flag.stderr: -------------------------------------------------------------------------------- 1 | error: expected `value(...)`, found `value` 2 | --> $DIR/expected_name_args_buf_flag.rs:1:45 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_args(value)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_buf_name_value.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_args(value = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_buf_name_value.stderr: -------------------------------------------------------------------------------- 1 | error: expected `value(...)`, found `value = ...` 2 | --> $DIR/expected_name_args_buf_name_value.rs:1:45 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_args(value = 10)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_or_flag_buf_name_value.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_args_or_flag(value = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_args_or_flag_buf_name_value.stderr: -------------------------------------------------------------------------------- 1 | error: expected flag `value` or `value(...)`, found `value = ...` 2 | --> $DIR/expected_name_args_or_flag_buf_name_value.rs:1:53 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_args_or_flag(value = 10)] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_but_flag.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_name_value(value)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_but_flag.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `attr_name_value` in `structmeta_tests` 2 | --> $DIR/expected_name_value_but_flag.rs:1:21 3 | | 4 | 1 | #[structmeta_tests::attr_name_value(value)] 5 | | ^^^^^^^^^^^^^^^ could not find `attr_name_value` in `structmeta_tests` 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_but_name_args.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_name_value(value(10))] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_but_name_args.stderr: -------------------------------------------------------------------------------- 1 | error[E0433]: failed to resolve: could not find `attr_name_value` in `structmeta_tests` 2 | --> $DIR/expected_name_value_but_name_args.rs:1:21 3 | | 4 | 1 | #[structmeta_tests::attr_name_value(value(10))] 5 | | ^^^^^^^^^^^^^^^ could not find `attr_name_value` in `structmeta_tests` 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_rest_but_name_args.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_rest_name_value(value(10))] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/expected_name_value_rest_but_name_args.stderr: -------------------------------------------------------------------------------- 1 | error: expected `value = ...`, found `value`(...) 2 | --> $DIR/expected_name_value_rest_but_name_args.rs:1:42 3 | | 4 | 1 | #[structmeta_tests::attr_rest_name_value(value(10))] 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/hash_map_flag.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use structmeta::Flag; 3 | 4 | #[derive(structmeta::StructMeta)] 5 | struct Example { 6 | rest: HashMap, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/hash_map_flag.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `structmeta::Flag: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/hash_map_flag.rs:6:27 3 | | 4 | 6 | rest: HashMap, 5 | | ---- ^^^^ the trait `Parse` is not implemented for `structmeta::Flag` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_named_argument.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_value()] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_named_argument.stderr: -------------------------------------------------------------------------------- 1 | error: missing argument `value = ...` 2 | --> $DIR/missing_named_argument.rs:1:1 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_value()] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `structmeta_tests::attr_required_name_value` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_unnamed_argument.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_unnamed2(10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_unnamed_argument.stderr: -------------------------------------------------------------------------------- 1 | error: expected least 2 arguments but 1 argument was supplied 2 | --> $DIR/missing_unnamed_argument.rs:1:1 3 | | 4 | 1 | #[structmeta_tests::attr_required_unnamed2(10)] 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `structmeta_tests::attr_required_unnamed2` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_unnamed_argument_inner.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_unnamed2_inner(value(10), after = 20)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/missing_unnamed_argument_inner.stderr: -------------------------------------------------------------------------------- 1 | error: expected least 2 arguments but 1 argument was supplied 2 | --> tests/compile_fail/struct_meta/missing_unnamed_argument_inner.rs:1:58 3 | | 4 | 1 | #[structmeta_tests::attr_required_unnamed2_inner(value(10), after = 20)] 5 | | ^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/not_parse.rs: -------------------------------------------------------------------------------- 1 | #[derive(structmeta::StructMeta)] 2 | struct Example { 3 | not_parse: NotParse, 4 | } 5 | struct NotParse; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/not_parse.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `NotParse: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/not_parse.rs:3:16 3 | | 4 | 3 | not_parse: NotParse, 5 | | --------- ^^^^^^^^ the trait `Parse` is not implemented for `NotParse` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_bool.rs: -------------------------------------------------------------------------------- 1 | #[derive(structmeta::StructMeta)] 2 | struct Example { 3 | flag: Option, 4 | } 5 | 6 | fn main() {} 7 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_bool.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `bool: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/option_bool.rs:3:18 3 | | 4 | 3 | flag: Option, 5 | | ---- ^^^^ the trait `Parse` is not implemented for `bool` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_flag.rs: -------------------------------------------------------------------------------- 1 | use structmeta::Flag; 2 | 3 | #[derive(structmeta::StructMeta)] 4 | struct Example { 5 | flag: Option, 6 | } 7 | 8 | fn main() {} 9 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_flag.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `structmeta::Flag: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/option_flag.rs:5:18 3 | | 4 | 5 | flag: Option, 5 | | ---- ^^^^ the trait `Parse` is not implemented for `structmeta::Flag` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_hash_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use syn::LitInt; 3 | 4 | #[derive(structmeta::StructMeta)] 5 | struct Example { 6 | rest: Option>, 7 | } 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_hash_map.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `HashMap: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/option_hash_map.rs:6:18 3 | | 4 | 6 | rest: Option>, 5 | | ---- ^^^^^^^^^^^^^^^^^^^^^^^ the trait `Parse` is not implemented for `HashMap` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_not_parse.rs: -------------------------------------------------------------------------------- 1 | #[derive(structmeta::StructMeta)] 2 | struct Example { 3 | not_parse: Option, 4 | } 5 | struct NotParse; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/option_not_parse.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `NotParse: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/option_not_parse.rs:3:23 3 | | 4 | 3 | not_parse: Option, 5 | | --------- ^^^^^^^^ the trait `Parse` is not implemented for `NotParse` 6 | | | 7 | | required by a bound introduced by this call 8 | | 9 | = help: the following other types implement trait `Parse`: 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | At 17 | Auto 18 | and $N others 19 | note: required by a bound in `ParseBuffer::<'a>::parse` 20 | --> $CARGO/syn-2.0.92/src/parse.rs 21 | | 22 | | pub fn parse(&self) -> Result { 23 | | ^^^^^ required by this bound in `ParseBuffer::<'a>::parse` 24 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/similar_name.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_value(vaue = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/similar_name.stderr: -------------------------------------------------------------------------------- 1 | error: cannot find parameter `vaue` in this scope (help: a parameter with a similar name exists: `value`) 2 | --> tests/compile_fail/struct_meta/similar_name.rs:1:46 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_value(vaue = 10)] 5 | | ^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/too_many_unnamed_arguments.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_unnamed2(10, 20, 30)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/too_many_unnamed_arguments.stderr: -------------------------------------------------------------------------------- 1 | error: too many arguments. 2 | --> $DIR/too_many_unnamed_arguments.rs:1:52 3 | | 4 | 1 | #[structmeta_tests::attr_required_unnamed2(10, 20, 30)] 5 | | ^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/unexpected_named_argument.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_required_name_value(value_x = 10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/unexpected_named_argument.stderr: -------------------------------------------------------------------------------- 1 | error: cannot find parameter `value_x` in this scope 2 | --> $DIR/unexpected_named_argument.rs:1:46 3 | | 4 | 1 | #[structmeta_tests::attr_required_name_value(value_x = 10)] 5 | | ^^^^^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/unexpected_unnamed_argument.rs: -------------------------------------------------------------------------------- 1 | #[structmeta_tests::attr_optional_name_value(10)] 2 | fn my_func() {} 3 | 4 | fn main() {} 5 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/unexpected_unnamed_argument.stderr: -------------------------------------------------------------------------------- 1 | error: too many unnamed arguments. 2 | --> $DIR/unexpected_unnamed_argument.rs:1:46 3 | | 4 | 1 | #[structmeta_tests::attr_optional_name_value(10)] 5 | | ^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/vec_not_parse.rs: -------------------------------------------------------------------------------- 1 | #[derive(structmeta::StructMeta)] 2 | struct Example { 3 | x: Vec, 4 | } 5 | struct NotParse; 6 | 7 | fn main() {} 8 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/struct_meta/vec_not_parse.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `NotParse: Parse` is not satisfied 2 | --> tests/compile_fail/struct_meta/vec_not_parse.rs:3:5 3 | | 4 | 3 | x: Vec, 5 | | ^ the trait `Parse` is not implemented for `NotParse` 6 | | 7 | = help: the following other types implement trait `Parse`: 8 | Abstract 9 | AndAnd 10 | AndEq 11 | AngleBracketedGenericArguments 12 | Arm 13 | As 14 | At 15 | Auto 16 | and $N others 17 | note: required by a bound in `punctuated::Punctuated::::parse_terminated` 18 | --> $CARGO/syn-2.0.92/src/punctuated.rs 19 | | 20 | | pub fn parse_terminated(input: ParseStream) -> Result 21 | | ---------------- required by a bound in this associated function 22 | | where 23 | | T: Parse, 24 | | ^^^^^ required by this bound in `Punctuated::::parse_terminated` 25 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/invalid_argument.rs: -------------------------------------------------------------------------------- 1 | use structmeta::ToTokens; 2 | use syn::LitStr; 3 | #[derive(ToTokens)] 4 | struct TestType { 5 | #[to_tokens(xxx = 123)] 6 | bracket_token: syn::token::Bracket, 7 | str: LitStr, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/invalid_argument.stderr: -------------------------------------------------------------------------------- 1 | error: expected string literal. 2 | --> $DIR/invalid_argument.rs:5:17 3 | | 4 | 5 | #[to_tokens(xxx = 123)] 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/mismatch_delimiter.rs: -------------------------------------------------------------------------------- 1 | use structmeta::ToTokens; 2 | use syn::LitStr; 3 | #[derive(ToTokens)] 4 | struct TestType { 5 | #[to_tokens("(")] 6 | bracket_token: syn::token::Bracket, 7 | str: LitStr, 8 | } 9 | 10 | fn main() {} 11 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/mismatch_delimiter.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/compile_fail/to_tokens/mismatch_delimiter.rs:6:5 3 | | 4 | 5 | #[to_tokens("(")] 5 | | - arguments to this function are incorrect 6 | 6 | bracket_token: syn::token::Bracket, 7 | | ^^^^^^^^^^^^^ expected `&Paren`, found `&Bracket` 8 | | 9 | = note: expected reference `&syn::token::Paren` 10 | found reference `&syn::token::Bracket` 11 | note: method defined here 12 | --> $CARGO/syn-2.0.92/src/token.rs 13 | | 14 | | / define_delimiters! { 15 | | | Brace pub struct Brace /// `{`…`}` 16 | | | Bracket pub struct Bracket /// `[`…`]` 17 | | | Parenthesis pub struct Paren /// `(`…`)` 18 | | | } 19 | | |_^ 20 | = note: this error originates in the macro `define_delimiters` (in Nightly builds, run with -Z macro-backtrace for more info) 21 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/not_impl_to_tokens.rs: -------------------------------------------------------------------------------- 1 | use structmeta::ToTokens; 2 | #[derive(ToTokens)] 3 | struct TestType { 4 | x: X, 5 | } 6 | 7 | struct X; 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /structmeta-tests/tests/compile_fail/to_tokens/not_impl_to_tokens.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `X: ToTokens` is not satisfied 2 | --> tests/compile_fail/to_tokens/not_impl_to_tokens.rs:4:5 3 | | 4 | 4 | x: X, 5 | | ^ the trait `ToTokens` is not implemented for `X` 6 | | 7 | = help: the following other types implement trait `ToTokens`: 8 | &T 9 | &mut T 10 | Abstract 11 | AndAnd 12 | AndEq 13 | AngleBracketedGenericArguments 14 | Arm 15 | As 16 | and $N others 17 | -------------------------------------------------------------------------------- /structmeta-tests/tests/parse.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{quote, ToTokens}; 5 | use structmeta::{Parse, ToTokens}; 6 | use syn::{parse::Parse, punctuated::Punctuated, token, Expr, LitInt, LitStr, Token}; 7 | use syn::{Ident, MacroDelimiter}; 8 | use test_utils::*; 9 | 10 | #[test] 11 | fn for_tuple() { 12 | #[derive(Parse, ToTokens)] 13 | struct TestTuple(LitInt, LitStr); 14 | assert_parse::(quote!(10 "abc")); 15 | } 16 | 17 | #[test] 18 | fn for_struct() { 19 | #[derive(Parse, ToTokens)] 20 | struct TestStruct { 21 | name: Ident, 22 | eq_token: Token![=], 23 | value: Expr, 24 | } 25 | assert_parse::(quote!(xxx = 1 + 2)); 26 | } 27 | 28 | #[test] 29 | fn for_enum() { 30 | #[allow(clippy::large_enum_variant)] 31 | #[derive(ToTokens, Parse)] 32 | enum TestEnum { 33 | A(Token![=], LitInt, LitInt), 34 | B { plus_token: Token![+], value: Expr }, 35 | C, 36 | } 37 | assert_parse::(quote!(= 1 2)); 38 | assert_parse::(quote!(+ 1 + 2)); 39 | assert_parse::(quote!()); 40 | 41 | assert_parse_fail::(quote!(= 1)); 42 | } 43 | 44 | #[test] 45 | fn brace_all() { 46 | #[derive(Parse, ToTokens)] 47 | struct TestStruct { 48 | #[to_tokens("{")] 49 | brace_token: token::Brace, 50 | key: syn::LitStr, 51 | eq_token: Token![=], 52 | value: Expr, 53 | } 54 | 55 | assert_parse::(quote!({ "abc" = 1 + 2 })); 56 | } 57 | 58 | #[test] 59 | fn brace_close() { 60 | #[derive(Parse, ToTokens)] 61 | struct TestStruct { 62 | #[to_tokens("{")] 63 | brace_token: token::Brace, 64 | key: syn::LitStr, 65 | eq_token: Token![=], 66 | #[to_tokens("}")] 67 | value: Expr, 68 | } 69 | assert_parse::(quote!({ "abc" = } 1 + 2)); 70 | } 71 | 72 | #[test] 73 | fn paren_all() { 74 | #[derive(Parse, ToTokens)] 75 | struct TestStruct { 76 | #[to_tokens("(")] 77 | paren_token: token::Paren, 78 | key: syn::LitStr, 79 | eq_token: Token![=], 80 | value: Expr, 81 | } 82 | assert_parse::(quote!(("abc" = 1 + 2))); 83 | } 84 | 85 | #[test] 86 | fn paren_close() { 87 | #[derive(Parse, ToTokens)] 88 | struct TestStruct { 89 | #[to_tokens("(")] 90 | brace_token: token::Paren, 91 | key: syn::LitStr, 92 | eq_token: Token![=], 93 | #[to_tokens(")")] 94 | value: Expr, 95 | } 96 | assert_parse::(quote!(("abc" = ) 1 + 2 )); 97 | } 98 | 99 | #[test] 100 | fn paren_nested() { 101 | #[derive(Parse, ToTokens)] 102 | struct TestStruct { 103 | #[to_tokens("(")] 104 | brace_token1: token::Paren, 105 | key: syn::LitStr, 106 | 107 | #[to_tokens("(")] 108 | brace_token2: token::Paren, 109 | 110 | eq_token: Token![=], 111 | #[to_tokens(")")] 112 | value: Expr, 113 | } 114 | assert_parse::(quote!(("abc" ( = ) 1 + 2 ))); 115 | } 116 | 117 | #[test] 118 | fn paren_close_many() { 119 | #[derive(Parse, ToTokens)] 120 | struct TestStruct { 121 | #[to_tokens("(")] 122 | brace_token1: token::Paren, 123 | key: syn::LitStr, 124 | 125 | #[to_tokens("(")] 126 | brace_token2: token::Paren, 127 | 128 | eq_token: Token![=], 129 | #[to_tokens("))")] 130 | value: Expr, 131 | } 132 | assert_parse::(quote!(("abc" ( = )) 1 + 2 )); 133 | } 134 | 135 | #[test] 136 | fn paren_close_open() { 137 | #[derive(Parse, ToTokens)] 138 | struct TestStruct { 139 | #[to_tokens("(")] 140 | brace_token1: token::Paren, 141 | key: syn::LitStr, 142 | 143 | #[to_tokens(")(")] 144 | brace_token2: token::Paren, 145 | 146 | eq_token: Token![=], 147 | value: Expr, 148 | } 149 | assert_parse::(quote!(("abc")( = 1 + 2 ))); 150 | } 151 | 152 | #[test] 153 | fn bracket_all() { 154 | #[derive(Parse, ToTokens)] 155 | struct TestStruct { 156 | #[to_tokens("[")] 157 | paren_token: token::Bracket, 158 | key: syn::LitStr, 159 | eq_token: Token![=], 160 | value: Expr, 161 | } 162 | assert_parse::(quote!(["abc" = 1 + 2])); 163 | } 164 | 165 | #[test] 166 | fn bracket_close() { 167 | #[derive(Parse, ToTokens)] 168 | struct TestStruct { 169 | #[to_tokens("[")] 170 | brace_token: token::Bracket, 171 | key: syn::LitStr, 172 | eq_token: Token![=], 173 | #[to_tokens("]")] 174 | value: Expr, 175 | } 176 | assert_parse::(quote!(["abc" = ] 1 + 2 )); 177 | } 178 | 179 | #[test] 180 | fn macro_delimiter_all() { 181 | #[derive(Parse, ToTokens)] 182 | struct TestStruct { 183 | #[to_tokens("(")] 184 | paren_token: MacroDelimiter, 185 | key: syn::LitStr, 186 | eq_token: Token![=], 187 | value: Expr, 188 | } 189 | assert_parse::(quote!(["abc" = 1 + 2])); 190 | assert_parse::(quote!(("abc" = 1 + 2))); 191 | assert_parse::(quote!({ "abc" = 1 + 2 })); 192 | } 193 | 194 | #[test] 195 | fn macro_delimiter_close() { 196 | #[derive(Parse, ToTokens)] 197 | struct TestStruct { 198 | #[to_tokens("(")] 199 | brace_token: MacroDelimiter, 200 | key: syn::LitStr, 201 | eq_token: Token![=], 202 | #[to_tokens(")")] 203 | value: Expr, 204 | } 205 | assert_parse::(quote!(["abc" = ] 1 + 2 )); 206 | assert_parse::(quote!(("abc" = ) 1 + 2 )); 207 | assert_parse::(quote!({"abc" = } 1 + 2 )); 208 | } 209 | 210 | #[test] 211 | fn peek() { 212 | #[derive(Parse, ToTokens)] 213 | enum TestEnum { 214 | A { 215 | #[parse(peek)] 216 | eq_token: Token![=], 217 | }, 218 | B { 219 | #[parse(peek)] 220 | plus_token: Token![+], 221 | }, 222 | } 223 | 224 | assert_parse::(quote!(=)); 225 | assert_parse::(quote!(+)); 226 | } 227 | 228 | #[test] 229 | fn peek_no_fall_through() { 230 | #[derive(Parse, ToTokens)] 231 | enum TestEnum { 232 | A(#[parse(peek)] Token![=], LitStr), 233 | B(Token![=], LitInt), 234 | } 235 | 236 | assert_parse::(quote!(= "abc")); 237 | assert_parse_fail::(quote!(= 1)); 238 | } 239 | 240 | #[test] 241 | fn peek2() { 242 | #[derive(Parse, ToTokens)] 243 | enum TestEnum { 244 | A { 245 | #[parse(peek)] 246 | key: Ident, 247 | #[parse(peek)] 248 | eq_token: Token![=], 249 | }, 250 | B { 251 | #[parse(peek)] 252 | key: Ident, 253 | #[parse(peek)] 254 | plus_token: Token![+], 255 | }, 256 | } 257 | 258 | assert_parse::(quote!(a=)); 259 | assert_parse::(quote!(a+)); 260 | } 261 | 262 | #[test] 263 | fn peek3() { 264 | #[derive(Parse, ToTokens)] 265 | enum TestEnum { 266 | A { 267 | #[parse(peek)] 268 | key: Ident, 269 | #[parse(peek)] 270 | eq_token: Token![=], 271 | #[parse(peek)] 272 | value: Ident, 273 | }, 274 | B { 275 | #[parse(peek)] 276 | key: Ident, 277 | #[parse(peek)] 278 | plus_token: Token![+], 279 | #[parse(peek)] 280 | value: Ident, 281 | }, 282 | } 283 | 284 | assert_parse::(quote!(a = x)); 285 | assert_parse::(quote!(a + y)); 286 | } 287 | 288 | #[test] 289 | fn parse_any() { 290 | #[derive(Parse, ToTokens)] 291 | struct TestStruct { 292 | #[parse(any)] 293 | key: Ident, 294 | eq_token: Token![=], 295 | } 296 | assert_parse::(quote!(struct =)); 297 | } 298 | 299 | #[test] 300 | fn peek_bracket() { 301 | #[derive(Parse, ToTokens)] 302 | enum TestEnum { 303 | A { 304 | #[parse(peek)] 305 | #[to_tokens("[")] 306 | bracket_token: token::Bracket, 307 | eq_token: Token![=], 308 | #[to_tokens("]")] 309 | #[parse(peek)] 310 | name: Ident, 311 | }, 312 | } 313 | assert_parse::(quote!([=] abc)); 314 | } 315 | 316 | #[test] 317 | fn peek_any() { 318 | #[derive(Parse, ToTokens)] 319 | enum TestEnum { 320 | A { 321 | #[parse(peek, any)] 322 | key: Ident, 323 | #[parse(peek)] 324 | eq_token: Token![=], 325 | }, 326 | } 327 | assert_parse::(quote!(struct =)); 328 | } 329 | 330 | #[test] 331 | fn parse_terminated() { 332 | #[derive(Parse, ToTokens)] 333 | struct TestStruct { 334 | #[parse(terminated)] 335 | key: Punctuated, 336 | } 337 | assert_parse::(quote!("a")); 338 | assert_parse::(quote!("a",)); 339 | assert_parse::(quote!("a", "b")); 340 | } 341 | 342 | #[test] 343 | fn parse_terminated_any() { 344 | #[derive(Parse, ToTokens)] 345 | struct TestStruct { 346 | #[parse(terminated, any)] 347 | key: Punctuated, 348 | } 349 | assert_parse::(quote!(a)); 350 | assert_parse::(quote!(a,)); 351 | assert_parse::(quote!(a, b, struct)); 352 | } 353 | 354 | #[track_caller] 355 | fn assert_parse(ts: TokenStream) { 356 | let value: T = syn::parse2(ts.clone()).expect("syn::parse2 failed."); 357 | assert_eq_ts(value, ts); 358 | } 359 | 360 | #[track_caller] 361 | fn assert_parse_fail(ts: TokenStream) { 362 | let value: syn::Result = syn::parse2(ts); 363 | if value.is_ok() { 364 | panic!("expect parse failed, but parse succeeded."); 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /structmeta-tests/tests/struct_meta.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Span; 2 | use std::{collections::HashMap, fmt::Debug, iter::FromIterator}; 3 | use structmeta::*; 4 | use syn::{parse::Parse, parse_quote, Attribute, Expr, LitInt, LitStr}; 5 | 6 | macro_rules! pq { 7 | ($($tt:tt)*) => { parse_quote!($($tt)*) } 8 | } 9 | 10 | #[test] 11 | fn test_unit() { 12 | #[derive(StructMeta, PartialEq, Debug)] 13 | struct Attr; 14 | check(pq!(#[attr()]), Attr); 15 | } 16 | 17 | #[test] 18 | fn test_tuple_field1() { 19 | #[derive(StructMeta, PartialEq, Debug)] 20 | struct Attr(LitStr); 21 | check(pq!(#[attr("abc")]), Attr(pq!("abc"))); 22 | } 23 | 24 | #[test] 25 | fn test_tuple_field2() { 26 | #[derive(StructMeta, PartialEq, Debug)] 27 | struct Attr(LitStr, LitInt); 28 | check(pq!(#[attr("abc", 12)]), Attr(pq!("abc"), pq!(12))); 29 | } 30 | 31 | #[test] 32 | fn test_tuple_optional_1() { 33 | #[derive(StructMeta, PartialEq, Debug)] 34 | struct Attr(Option); 35 | check(pq!(#[attr("abc")]), Attr(Some(pq!("abc")))); 36 | check(pq!(#[attr()]), Attr(None)); 37 | } 38 | 39 | #[test] 40 | fn test_tuple_optional_2() { 41 | #[derive(StructMeta, PartialEq, Debug)] 42 | struct Attr(Option, Option); 43 | check_msg( 44 | pq!(#[attr("abc", 12)]), 45 | Attr(Some(pq!("abc")), Some(pq!(12))), 46 | "args 2", 47 | ); 48 | check_msg(pq!(#[attr("abc")]), Attr(Some(pq!("abc")), None), "args 1"); 49 | check_msg(pq!(#[attr()]), Attr(None, None), "args_0"); 50 | } 51 | 52 | #[test] 53 | fn test_tuple_required_and_optional() { 54 | #[derive(StructMeta, PartialEq, Debug)] 55 | struct Attr(LitStr, Option); 56 | check_msg( 57 | pq!(#[attr("abc", 12)]), 58 | Attr(pq!("abc"), Some(pq!(12))), 59 | "args 2", 60 | ); 61 | check_msg(pq!(#[attr("abc")]), Attr(pq!("abc"), None), "args 1"); 62 | } 63 | #[test] 64 | fn test_tuple_variadic() { 65 | #[derive(StructMeta, PartialEq, Debug)] 66 | struct Attr(Vec); 67 | 68 | check_msg(pq!(#[attr()]), Attr(vec![]), "args 0"); 69 | check_msg(pq!(#[attr(0)]), Attr(vec![pq!(0)]), "args 1"); 70 | check_msg(pq!(#[attr(0, 1)]), Attr(vec![pq!(0), pq!(1)]), "args 2"); 71 | } 72 | 73 | #[test] 74 | fn test_tuple_required_and_variadic() { 75 | #[derive(StructMeta, PartialEq, Debug)] 76 | struct Attr(LitStr, Vec); 77 | 78 | check_msg(pq!(#[attr("abc")]), Attr(pq!("abc"), vec![]), "args 1"); 79 | check_msg( 80 | pq!(#[attr("abc", 0)]), 81 | Attr(pq!("abc"), vec![pq!(0)]), 82 | "args 2", 83 | ); 84 | check_msg( 85 | pq!(#[attr("abc", 0, 1)]), 86 | Attr(pq!("abc"), vec![pq!(0), pq!(1)]), 87 | "args 3", 88 | ); 89 | } 90 | 91 | #[test] 92 | fn test_tuple_optional_and_variadic() { 93 | #[derive(StructMeta, PartialEq, Debug)] 94 | struct Attr(Option, Vec); 95 | 96 | check_msg(pq!(#[attr()]), Attr(None, vec![]), "args 0"); 97 | check_msg( 98 | pq!(#[attr("abc")]), 99 | Attr(Some(pq!("abc")), vec![]), 100 | "args 1", 101 | ); 102 | check_msg( 103 | pq!(#[attr("abc", 0)]), 104 | Attr(Some(pq!("abc")), vec![pq!(0)]), 105 | "args 2", 106 | ); 107 | check_msg( 108 | pq!(#[attr("abc", 0, 1)]), 109 | Attr(Some(pq!("abc")), vec![pq!(0), pq!(1)]), 110 | "args 3", 111 | ); 112 | } 113 | #[test] 114 | fn test_tuple_required_named() { 115 | #[derive(StructMeta, PartialEq, Debug)] 116 | struct Attr(#[struct_meta(name = "abc")] LitStr); 117 | check(pq!(#[attr(abc = "def")]), Attr(pq!("def"))); 118 | } 119 | 120 | #[test] 121 | fn test_tuple_required_named_2() { 122 | #[derive(StructMeta, PartialEq, Debug)] 123 | struct Attr( 124 | #[struct_meta(name = "a")] LitStr, 125 | #[struct_meta(name = "b")] LitInt, 126 | ); 127 | check_msg(pq!(#[attr(a = "x", b = 12)]), Attr(pq!("x"), pq!(12)), "ab"); 128 | } 129 | 130 | #[test] 131 | fn test_tuple_optional_named() { 132 | #[derive(StructMeta, PartialEq, Debug)] 133 | struct Attr(#[struct_meta(name = "abc")] Option); 134 | check_msg(pq!(#[attr()]), Attr(None), "args 0"); 135 | check_msg(pq!(#[attr(abc = "def")]), Attr(Some(pq!("def"))), "args 1"); 136 | } 137 | 138 | #[test] 139 | fn test_struct_value() { 140 | #[derive(StructMeta, PartialEq, Debug)] 141 | struct Attr { 142 | abc: LitStr, 143 | } 144 | check(pq!(#[attr(abc = "def")]), Attr { abc: pq!("def") }); 145 | } 146 | 147 | #[test] 148 | fn test_struct_option_value() { 149 | #[derive(StructMeta, PartialEq, Debug)] 150 | struct Attr { 151 | abc: Option, 152 | } 153 | check(pq!(#[attr()]), Attr { abc: None }); 154 | check( 155 | pq!(#[attr(abc = "def")]), 156 | Attr { 157 | abc: Some(pq!("def")), 158 | }, 159 | ); 160 | } 161 | 162 | #[test] 163 | fn test_struct_value_raw() { 164 | #[derive(StructMeta, PartialEq, Debug)] 165 | struct Attr { 166 | r#abc: LitStr, 167 | } 168 | check(pq!(#[attr(abc = "def")]), Attr { abc: pq!("def") }); 169 | } 170 | #[test] 171 | fn test_struct_value_raw_keyword() { 172 | #[derive(StructMeta, PartialEq, Debug)] 173 | struct Attr { 174 | r#if: LitStr, 175 | } 176 | check(pq!(#[attr(if = "def")]), Attr { r#if: pq!("def") }); 177 | } 178 | #[test] 179 | fn test_struct_value_name() { 180 | #[derive(StructMeta, PartialEq, Debug)] 181 | struct Attr { 182 | #[struct_meta(name = "xxx")] 183 | abc: LitStr, 184 | } 185 | check(pq!(#[attr(xxx = "def")]), Attr { abc: pq!("def") }); 186 | } 187 | 188 | #[test] 189 | fn test_struct_value_name_keyword() { 190 | #[derive(StructMeta, PartialEq, Debug)] 191 | struct Attr { 192 | #[struct_meta(name = "if")] 193 | abc: LitStr, 194 | } 195 | check(pq!(#[attr(if = "def")]), Attr { abc: pq!("def") }); 196 | } 197 | 198 | #[test] 199 | fn test_struct_value_name_self() { 200 | #[derive(StructMeta, PartialEq, Debug)] 201 | struct Attr { 202 | #[struct_meta(name = "self")] 203 | abc: LitStr, 204 | } 205 | check(pq!(#[attr(self = "def")]), Attr { abc: pq!("def") }); 206 | } 207 | 208 | #[test] 209 | fn test_struct_value_unnamed() { 210 | #[derive(StructMeta, PartialEq, Debug)] 211 | struct Attr { 212 | #[struct_meta(unnamed)] 213 | abc: LitStr, 214 | } 215 | check(pq!(#[attr("def")]), Attr { abc: pq!("def") }); 216 | } 217 | 218 | #[test] 219 | fn test_struct_vec() { 220 | #[derive(StructMeta, PartialEq, Debug)] 221 | struct Attr { 222 | abc: Vec, 223 | } 224 | check_msg(pq!(#[attr(abc())]), Attr { abc: vec![] }, "args 0"); 225 | check_msg( 226 | pq!(#[attr(abc("item1"))]), 227 | Attr { 228 | abc: vec![pq!("item1")], 229 | }, 230 | "args 1", 231 | ); 232 | check_msg( 233 | pq!(#[attr(abc("item1", "item2"))]), 234 | Attr { 235 | abc: vec![pq!("item1"), pq!("item2")], 236 | }, 237 | "args 2", 238 | ); 239 | } 240 | #[test] 241 | fn test_struct_option_vec() { 242 | #[derive(StructMeta, PartialEq, Debug)] 243 | struct Attr { 244 | abc: Option>, 245 | } 246 | check_msg(pq!(#[attr()]), Attr { abc: None }, "args none"); 247 | check_msg(pq!(#[attr(abc())]), Attr { abc: Some(vec![]) }, "args 0"); 248 | check_msg( 249 | pq!(#[attr(abc("item1"))]), 250 | Attr { 251 | abc: Some(vec![pq!("item1")]), 252 | }, 253 | "args 1", 254 | ); 255 | check_msg( 256 | pq!(#[attr(abc("item1", "item2"))]), 257 | Attr { 258 | abc: Some(vec![pq!("item1"), pq!("item2")]), 259 | }, 260 | "args 2", 261 | ); 262 | } 263 | 264 | #[test] 265 | fn test_struct_vec_unnamed() { 266 | #[derive(StructMeta, PartialEq, Debug)] 267 | struct Attr { 268 | #[struct_meta(unnamed)] 269 | abc: Vec, 270 | } 271 | check_msg(pq!(#[attr()]), Attr { abc: vec![] }, "args 0"); 272 | check_msg( 273 | pq!(#[attr("item1")]), 274 | Attr { 275 | abc: vec![pq!("item1")], 276 | }, 277 | "args 1", 278 | ); 279 | check_msg( 280 | pq!(#[attr("item1", "item2")]), 281 | Attr { 282 | abc: vec![pq!("item1"), pq!("item2")], 283 | }, 284 | "args 2", 285 | ); 286 | } 287 | 288 | #[test] 289 | fn test_struct_flag() { 290 | #[derive(StructMeta, PartialEq, Debug)] 291 | struct Attr { 292 | is_enable: Flag, 293 | } 294 | check( 295 | pq!(#[attr(is_enable)]), 296 | Attr { 297 | is_enable: true.into(), 298 | }, 299 | ); 300 | check( 301 | pq!(#[attr()]), 302 | Attr { 303 | is_enable: false.into(), 304 | }, 305 | ); 306 | } 307 | #[test] 308 | fn test_struct_bool() { 309 | #[derive(StructMeta, PartialEq, Debug)] 310 | struct Attr { 311 | is_enable: bool, 312 | } 313 | check(pq!(#[attr(is_enable)]), Attr { is_enable: true }); 314 | check(pq!(#[attr()]), Attr { is_enable: false }); 315 | } 316 | 317 | #[test] 318 | fn test_struct_name_value() { 319 | #[derive(StructMeta, PartialEq, Debug)] 320 | struct Attr { 321 | abc: NameValue, 322 | } 323 | check( 324 | pq!(#[attr(abc = "xyz")]), 325 | Attr { 326 | abc: NameValue { 327 | name_span: Span::call_site(), 328 | value: pq!("xyz"), 329 | }, 330 | }, 331 | ); 332 | } 333 | #[test] 334 | fn test_struct_name_args() { 335 | #[derive(StructMeta, PartialEq, Debug)] 336 | struct Attr { 337 | abc: NameArgs, 338 | } 339 | check( 340 | pq!(#[attr(abc("xyz"))]), 341 | Attr { 342 | abc: NameArgs { 343 | name_span: Span::call_site(), 344 | args: pq!("xyz"), 345 | }, 346 | }, 347 | ); 348 | } 349 | 350 | #[test] 351 | fn test_struct_name_args_or_flag() { 352 | #[derive(StructMeta, PartialEq, Debug)] 353 | struct Attr { 354 | a: NameArgs>, 355 | } 356 | check(pq!(#[attr(a)]), Attr { a: name_args(None) }); 357 | check( 358 | pq!(#[attr(a(1))]), 359 | Attr { 360 | a: name_args(Some(pq!(1))), 361 | }, 362 | ); 363 | } 364 | 365 | #[test] 366 | fn test_struct_name_value_or_flag() { 367 | #[derive(StructMeta, PartialEq, Debug)] 368 | struct Attr { 369 | a: NameValue>, 370 | } 371 | check( 372 | pq!(#[attr(a)]), 373 | Attr { 374 | a: name_value(None), 375 | }, 376 | ); 377 | check( 378 | pq!(#[attr(a = 1)]), 379 | Attr { 380 | a: name_value(Some(pq!(1))), 381 | }, 382 | ); 383 | } 384 | 385 | #[test] 386 | fn test_struct_map() { 387 | #[derive(StructMeta, PartialEq, Debug)] 388 | struct Attr { 389 | m: HashMap, 390 | } 391 | check_msg( 392 | pq!(#[attr()]), 393 | Attr { 394 | m: HashMap::from_iter(vec![]), 395 | }, 396 | "args 0", 397 | ); 398 | check_msg( 399 | pq!(#[attr(abc = "xyz")]), 400 | Attr { 401 | m: HashMap::from_iter(vec![("abc".into(), pq!("xyz"))]), 402 | }, 403 | "args 1", 404 | ); 405 | check_msg( 406 | pq!(#[attr(abc = "xyz", def = "123")]), 407 | Attr { 408 | m: HashMap::from_iter(vec![("abc".into(), pq!("xyz")), ("def".into(), pq!("123"))]), 409 | }, 410 | "args 2", 411 | ); 412 | } 413 | 414 | #[test] 415 | fn test_expr_or_flag() { 416 | #[derive(StructMeta, PartialEq, Debug)] 417 | struct Attr { 418 | #[struct_meta(unnamed)] 419 | expr: Option, 420 | x: bool, 421 | } 422 | check( 423 | pq!(#[attr(x)]), 424 | Attr { 425 | expr: None, 426 | x: true, 427 | }, 428 | ); 429 | check_err::(pq!(#[attr(y)])); 430 | check_err::(pq!(#[attr(Y)])); 431 | } 432 | 433 | #[test] 434 | fn name_filter() { 435 | #[derive(StructMeta, PartialEq, Debug)] 436 | #[struct_meta(name_filter = "snake_case")] 437 | struct Attr { 438 | #[struct_meta(unnamed)] 439 | expr: Option, 440 | x: bool, 441 | } 442 | 443 | check( 444 | pq!(#[attr(x)]), 445 | Attr { 446 | expr: None, 447 | x: true, 448 | }, 449 | ); 450 | check_err::(pq!(#[attr(y)])); 451 | check( 452 | pq!(#[attr(Y)]), 453 | Attr { 454 | expr: Some(pq!(Y)), 455 | x: false, 456 | }, 457 | ); 458 | } 459 | 460 | #[test] 461 | fn test_expr_or_name_value() { 462 | #[derive(StructMeta, PartialEq, Debug)] 463 | struct Attr { 464 | #[struct_meta(unnamed)] 465 | expr: Option, 466 | x: Option>, 467 | } 468 | check( 469 | pq!(#[attr(func(1))]), 470 | Attr { 471 | expr: Some(pq!(func(1))), 472 | x: None, 473 | }, 474 | ); 475 | } 476 | 477 | #[test] 478 | fn test_expr_or_name_value_similar_1() { 479 | #[derive(StructMeta, PartialEq, Debug)] 480 | struct Attr { 481 | #[struct_meta(unnamed)] 482 | expr: Option, 483 | x: Option>, 484 | } 485 | check( 486 | pq!(#[attr(x == y)]), 487 | Attr { 488 | expr: Some(pq!(x == y)), 489 | x: None, 490 | }, 491 | ); 492 | } 493 | #[test] 494 | fn test_expr_or_name_value_similar_2() { 495 | #[derive(StructMeta, PartialEq, Debug)] 496 | struct Attr { 497 | #[struct_meta(unnamed)] 498 | expr: Option, 499 | x: Option>, 500 | } 501 | check( 502 | pq!(#[attr(x = 1)]), 503 | Attr { 504 | expr: None, 505 | x: Some(name_value(pq!(1))), 506 | }, 507 | ); 508 | } 509 | 510 | #[test] 511 | fn test_expr_or_name_args_similar() { 512 | #[derive(StructMeta, PartialEq, Debug)] 513 | struct Attr { 514 | #[struct_meta(unnamed)] 515 | expr: Option, 516 | x: Option>, 517 | } 518 | check( 519 | pq!(#[attr(x(1))]), 520 | Attr { 521 | expr: None, 522 | x: Some(name_args(pq!(1))), 523 | }, 524 | ); 525 | } 526 | #[test] 527 | fn test_unnamed_similar_name_value() { 528 | use syn::parse_quote; 529 | use syn::Expr; 530 | 531 | #[derive(StructMeta, PartialEq, Debug)] 532 | struct Attr(Option); 533 | check(pq!(#[attr(a = 10)]), Attr(Some(pq!(a = 10)))); 534 | } 535 | 536 | #[test] 537 | fn test_required_unnamed_then_name_value_similar() { 538 | #[derive(StructMeta, PartialEq, Debug)] 539 | struct Attr { 540 | #[struct_meta(unnamed)] 541 | expr: Expr, 542 | x: Option>, 543 | } 544 | check( 545 | pq!(#[attr(x = 1)]), 546 | Attr { 547 | expr: pq!(x = 1), 548 | x: None, 549 | }, 550 | ); 551 | } 552 | 553 | fn name_value(value: T) -> NameValue { 554 | NameValue { 555 | value, 556 | name_span: Span::call_site(), 557 | } 558 | } 559 | fn name_args(args: T) -> NameArgs { 560 | NameArgs { 561 | args, 562 | name_span: Span::call_site(), 563 | } 564 | } 565 | 566 | #[track_caller] 567 | fn check(input: Attribute, expected: T) { 568 | check_msg(input, expected, "") 569 | } 570 | #[track_caller] 571 | fn check_msg(input: Attribute, expected: T, msg: &str) { 572 | match input.parse_args::() { 573 | Ok(value) => { 574 | assert_eq!(value, expected, "{msg}"); 575 | } 576 | Err(e) => { 577 | panic!("{msg} : parse failed. \n{e}") 578 | } 579 | } 580 | } 581 | 582 | #[track_caller] 583 | fn check_err(input: Attribute) { 584 | if let Ok(value) = input.parse_args::() { 585 | panic!("the parsing did not fail. \ninput : {input:?}\n value : {value:?}"); 586 | } 587 | } 588 | -------------------------------------------------------------------------------- /structmeta-tests/tests/struct_meta_proc_macro.rs: -------------------------------------------------------------------------------- 1 | use structmeta_tests::*; 2 | #[allow(dead_code)] 3 | #[test] 4 | fn test_proc_macro_derive() { 5 | #[derive(MyMsg)] 6 | #[my_msg(msg = "abc")] 7 | struct TestType; 8 | 9 | assert_eq!(MSG, "abc"); 10 | } 11 | 12 | #[test] 13 | fn test_proc_macro_attr() { 14 | #[my_attr(msg = "xyz")] 15 | struct TestType; 16 | 17 | assert_eq!(MSG, "xyz"); 18 | } 19 | -------------------------------------------------------------------------------- /structmeta-tests/tests/test_utils.rs: -------------------------------------------------------------------------------- 1 | #[track_caller] 2 | pub fn assert_eq_ts(s: impl quote::ToTokens, ts: proc_macro2::TokenStream) { 3 | let ts0 = s.to_token_stream().to_string(); 4 | let ts1 = ts.to_string(); 5 | assert_eq!(ts0, ts1); 6 | } 7 | -------------------------------------------------------------------------------- /structmeta-tests/tests/to_tokens.rs: -------------------------------------------------------------------------------- 1 | mod test_utils; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::quote; 5 | use structmeta::ToTokens; 6 | use syn::parse_quote; 7 | use test_utils::*; 8 | 9 | #[test] 10 | fn for_struct() { 11 | #[derive(ToTokens)] 12 | struct TestStruct { 13 | name: syn::Ident, 14 | eq_token: syn::Token![=], 15 | value: syn::Expr, 16 | } 17 | 18 | let s = TestStruct { 19 | name: parse_quote!(abc), 20 | eq_token: parse_quote!(=), 21 | value: parse_quote!(1 + 2), 22 | }; 23 | let ts = quote!(abc = 1 + 2); 24 | assert_eq_ts(s, ts); 25 | } 26 | #[test] 27 | fn for_tuple_struct() { 28 | #[derive(ToTokens)] 29 | struct TestStruct(syn::Ident, syn::Token![=], syn::Expr); 30 | 31 | let s = TestStruct(parse_quote!(abc), parse_quote!(=), parse_quote!(1 + 2)); 32 | let ts = quote!(abc = 1 + 2); 33 | assert_eq_ts(s, ts); 34 | } 35 | 36 | #[test] 37 | fn for_unit_struct() { 38 | #[derive(ToTokens)] 39 | struct TestStruct; 40 | 41 | let s = TestStruct; 42 | let ts = quote!(); 43 | assert_eq_ts(s, ts); 44 | } 45 | 46 | #[test] 47 | fn for_enum() { 48 | #[derive(ToTokens)] 49 | enum TestEnum { 50 | A { 51 | name: syn::Ident, 52 | eq_token: syn::Token![=], 53 | value: syn::Expr, 54 | }, 55 | B(syn::Ident, syn::Token![=>], syn::Expr), 56 | C, 57 | } 58 | 59 | let s = TestEnum::A { 60 | name: parse_quote!(abc), 61 | eq_token: parse_quote!(=), 62 | value: parse_quote!(1 + 2), 63 | }; 64 | let ts = quote!(abc = 1 + 2); 65 | assert_eq_ts(s, ts); 66 | 67 | let s = TestEnum::B(parse_quote!(abc), parse_quote!(=>), parse_quote!(1 + 2)); 68 | let ts = quote!(abc => 1 + 2); 69 | assert_eq_ts(s, ts); 70 | 71 | let s = TestEnum::C; 72 | let ts = quote!(); 73 | assert_eq_ts(s, ts); 74 | } 75 | 76 | #[test] 77 | fn struct_raw_keyword_field() { 78 | #[derive(ToTokens)] 79 | struct TestStruct { 80 | r#mut: syn::Ident, 81 | eq_token: syn::Token![=], 82 | value: syn::Expr, 83 | } 84 | 85 | let s = TestStruct { 86 | r#mut: parse_quote!(abc), 87 | eq_token: parse_quote!(=), 88 | value: parse_quote!(1 + 2), 89 | }; 90 | let ts = quote!(abc = 1 + 2); 91 | assert_eq_ts(s, ts); 92 | } 93 | 94 | #[test] 95 | fn enum_raw_keyword_field() { 96 | #[derive(ToTokens)] 97 | enum TestEnum { 98 | A { 99 | r#mut: syn::Ident, 100 | eq_token: syn::Token![=], 101 | value: syn::Expr, 102 | }, 103 | } 104 | 105 | let s = TestEnum::A { 106 | r#mut: parse_quote!(abc), 107 | eq_token: parse_quote!(=), 108 | value: parse_quote!(1 + 2), 109 | }; 110 | let ts = quote!(abc = 1 + 2); 111 | assert_eq_ts(s, ts); 112 | } 113 | 114 | #[test] 115 | fn brace_all() { 116 | #[derive(ToTokens)] 117 | struct TestStruct { 118 | #[to_tokens("{")] 119 | brace_token: syn::token::Brace, 120 | key: syn::LitStr, 121 | eq_token: syn::Token![=], 122 | value: syn::Expr, 123 | } 124 | 125 | let s = TestStruct { 126 | brace_token: syn::token::Brace::default(), 127 | key: parse_quote!("abc"), 128 | eq_token: parse_quote!(=), 129 | value: parse_quote!(1 + 2), 130 | }; 131 | let ts = quote!({ "abc" = 1 + 2 }); 132 | assert_eq_ts(s, ts); 133 | } 134 | 135 | #[test] 136 | fn brace_close() { 137 | #[derive(ToTokens)] 138 | struct TestStruct { 139 | #[to_tokens("{")] 140 | brace_token: syn::token::Brace, 141 | key: syn::LitStr, 142 | eq_token: syn::Token![=], 143 | #[to_tokens("}")] 144 | value: syn::Expr, 145 | } 146 | 147 | let s = TestStruct { 148 | brace_token: Default::default(), 149 | key: parse_quote!("abc"), 150 | eq_token: parse_quote!(=), 151 | value: parse_quote!(1 + 2), 152 | }; 153 | let ts = quote!({ "abc" = } 1 + 2 ); 154 | assert_eq_ts(s, ts); 155 | } 156 | 157 | #[test] 158 | fn paren_all() { 159 | #[derive(ToTokens)] 160 | struct TestStruct { 161 | #[to_tokens("(")] 162 | paren_token: syn::token::Paren, 163 | key: syn::LitStr, 164 | eq_token: syn::Token![=], 165 | value: syn::Expr, 166 | } 167 | 168 | let s = TestStruct { 169 | paren_token: Default::default(), 170 | key: parse_quote!("abc"), 171 | eq_token: parse_quote!(=), 172 | value: parse_quote!(1 + 2), 173 | }; 174 | let ts = quote!(("abc" = 1 + 2)); 175 | assert_eq_ts(s, ts); 176 | } 177 | 178 | #[test] 179 | fn paren_close() { 180 | #[derive(ToTokens)] 181 | struct TestStruct { 182 | #[to_tokens("(")] 183 | paren_token: syn::token::Paren, 184 | key: syn::LitStr, 185 | eq_token: syn::Token![=], 186 | #[to_tokens(")")] 187 | value: syn::Expr, 188 | } 189 | 190 | let s = TestStruct { 191 | paren_token: Default::default(), 192 | key: parse_quote!("abc"), 193 | eq_token: parse_quote!(=), 194 | value: parse_quote!(1 + 2), 195 | }; 196 | let ts = quote!(("abc" = ) 1 + 2 ); 197 | assert_eq_ts(s, ts); 198 | } 199 | 200 | #[test] 201 | fn paren_nested() { 202 | #[derive(ToTokens)] 203 | struct TestStruct { 204 | #[to_tokens("(")] 205 | paren_token1: syn::token::Paren, 206 | key: syn::LitStr, 207 | 208 | #[to_tokens("(")] 209 | paren_token2: syn::token::Paren, 210 | 211 | eq_token: syn::Token![=], 212 | #[to_tokens(")")] 213 | value: syn::Expr, 214 | } 215 | 216 | let s = TestStruct { 217 | paren_token1: Default::default(), 218 | key: parse_quote!("abc"), 219 | paren_token2: Default::default(), 220 | eq_token: parse_quote!(=), 221 | value: parse_quote!(1 + 2), 222 | }; 223 | let ts = quote!(("abc" ( = ) 1 + 2 )); 224 | assert_eq_ts(s, ts); 225 | } 226 | 227 | #[test] 228 | fn paren_close_many() { 229 | #[derive(ToTokens)] 230 | struct TestStruct { 231 | #[to_tokens("(")] 232 | paren_token1: syn::token::Paren, 233 | key: syn::LitStr, 234 | 235 | #[to_tokens("(")] 236 | paren_token2: syn::token::Paren, 237 | 238 | eq_token: syn::Token![=], 239 | #[to_tokens("))")] 240 | value: syn::Expr, 241 | } 242 | 243 | let s = TestStruct { 244 | paren_token1: Default::default(), 245 | key: parse_quote!("abc"), 246 | paren_token2: Default::default(), 247 | eq_token: parse_quote!(=), 248 | value: parse_quote!(1 + 2), 249 | }; 250 | let ts = quote!(("abc" ( = )) 1 + 2 ); 251 | assert_eq_ts(s, ts); 252 | } 253 | 254 | #[test] 255 | fn paren_close_open() { 256 | #[derive(ToTokens)] 257 | struct TestStruct { 258 | #[to_tokens("(")] 259 | paren_token1: syn::token::Paren, 260 | key: syn::LitStr, 261 | 262 | #[to_tokens(")(")] 263 | paren_token2: syn::token::Paren, 264 | 265 | eq_token: syn::Token![=], 266 | value: syn::Expr, 267 | } 268 | 269 | let s = TestStruct { 270 | paren_token1: Default::default(), 271 | key: parse_quote!("abc"), 272 | paren_token2: Default::default(), 273 | eq_token: parse_quote!(=), 274 | value: parse_quote!(1 + 2), 275 | }; 276 | let ts = quote!(("abc")( = 1 + 2 )); 277 | assert_eq_ts(s, ts); 278 | } 279 | 280 | #[test] 281 | fn bracket_all() { 282 | #[derive(ToTokens)] 283 | struct TestStruct { 284 | #[to_tokens("[")] 285 | bracket_token: syn::token::Bracket, 286 | key: syn::LitStr, 287 | eq_token: syn::Token![=], 288 | value: syn::Expr, 289 | } 290 | 291 | let s = TestStruct { 292 | bracket_token: Default::default(), 293 | key: parse_quote!("abc"), 294 | eq_token: parse_quote!(=), 295 | value: parse_quote!(1 + 2), 296 | }; 297 | let ts = quote!(["abc" = 1 + 2]); 298 | assert_eq_ts(s, ts); 299 | } 300 | 301 | #[test] 302 | fn bracket_close() { 303 | #[derive(ToTokens)] 304 | struct TestStruct { 305 | #[to_tokens("[")] 306 | bracket_token: syn::token::Bracket, 307 | key: syn::LitStr, 308 | eq_token: syn::Token![=], 309 | #[to_tokens("]")] 310 | value: syn::Expr, 311 | } 312 | 313 | let s = TestStruct { 314 | bracket_token: Default::default(), 315 | key: parse_quote!("abc"), 316 | eq_token: parse_quote!(=), 317 | value: parse_quote!(1 + 2), 318 | }; 319 | let ts = quote!(["abc" = ] 1 + 2 ); 320 | assert_eq_ts(s, ts); 321 | } 322 | 323 | #[test] 324 | fn macro_delimiter_all() { 325 | #[derive(ToTokens)] 326 | struct TestStructParen { 327 | #[to_tokens("(")] 328 | delimiter: syn::MacroDelimiter, 329 | key: syn::LitStr, 330 | eq_token: syn::Token![=], 331 | value: syn::Expr, 332 | } 333 | #[derive(ToTokens)] 334 | struct TestStructBrace { 335 | #[to_tokens("{")] 336 | delimiter: syn::MacroDelimiter, 337 | key: syn::LitStr, 338 | eq_token: syn::Token![=], 339 | value: syn::Expr, 340 | } 341 | #[derive(ToTokens)] 342 | struct TestStructBracket { 343 | #[to_tokens("[")] 344 | delimiter: syn::MacroDelimiter, 345 | key: syn::LitStr, 346 | eq_token: syn::Token![=], 347 | value: syn::Expr, 348 | } 349 | 350 | fn check(delimiter: syn::MacroDelimiter, ts: TokenStream) { 351 | let s = TestStructParen { 352 | delimiter: delimiter.clone(), 353 | key: parse_quote!("abc"), 354 | eq_token: parse_quote!(=), 355 | value: parse_quote!(1 + 2), 356 | }; 357 | assert_eq_ts(s, ts.clone()); 358 | 359 | let s = TestStructBrace { 360 | delimiter: delimiter.clone(), 361 | key: parse_quote!("abc"), 362 | eq_token: parse_quote!(=), 363 | value: parse_quote!(1 + 2), 364 | }; 365 | assert_eq_ts(s, ts.clone()); 366 | 367 | let s = TestStructBracket { 368 | delimiter, 369 | key: parse_quote!("abc"), 370 | eq_token: parse_quote!(=), 371 | value: parse_quote!(1 + 2), 372 | }; 373 | assert_eq_ts(s, ts); 374 | } 375 | check( 376 | syn::MacroDelimiter::Paren(Default::default()), 377 | quote!(("abc" = 1 + 2)), 378 | ); 379 | check( 380 | syn::MacroDelimiter::Brace(Default::default()), 381 | quote!({ "abc" = 1 + 2 }), 382 | ); 383 | check( 384 | syn::MacroDelimiter::Bracket(Default::default()), 385 | quote!(["abc" = 1 + 2]), 386 | ); 387 | } 388 | 389 | #[test] 390 | fn macro_delimiter_close() { 391 | #[derive(ToTokens)] 392 | struct TestStructParen { 393 | #[to_tokens("(")] 394 | delimiter: syn::MacroDelimiter, 395 | key: syn::LitStr, 396 | eq_token: syn::Token![=], 397 | #[to_tokens(")")] 398 | value: syn::Expr, 399 | } 400 | #[derive(ToTokens)] 401 | struct TestStructBrace { 402 | #[to_tokens("{")] 403 | delimiter: syn::MacroDelimiter, 404 | key: syn::LitStr, 405 | eq_token: syn::Token![=], 406 | #[to_tokens("}")] 407 | value: syn::Expr, 408 | } 409 | #[derive(ToTokens)] 410 | struct TestStructBracket { 411 | #[to_tokens("[")] 412 | delimiter: syn::MacroDelimiter, 413 | key: syn::LitStr, 414 | eq_token: syn::Token![=], 415 | #[to_tokens("]")] 416 | value: syn::Expr, 417 | } 418 | fn check(delimiter: syn::MacroDelimiter, ts: TokenStream) { 419 | let s = TestStructParen { 420 | delimiter: delimiter.clone(), 421 | key: parse_quote!("abc"), 422 | eq_token: parse_quote!(=), 423 | value: parse_quote!(1 + 2), 424 | }; 425 | assert_eq_ts(s, ts.clone()); 426 | 427 | let s = TestStructBrace { 428 | delimiter: delimiter.clone(), 429 | key: parse_quote!("abc"), 430 | eq_token: parse_quote!(=), 431 | value: parse_quote!(1 + 2), 432 | }; 433 | assert_eq_ts(s, ts.clone()); 434 | 435 | let s = TestStructBracket { 436 | delimiter, 437 | key: parse_quote!("abc"), 438 | eq_token: parse_quote!(=), 439 | value: parse_quote!(1 + 2), 440 | }; 441 | assert_eq_ts(s, ts); 442 | } 443 | check( 444 | syn::MacroDelimiter::Paren(Default::default()), 445 | quote!(("abc" = ) 1 + 2), 446 | ); 447 | check( 448 | syn::MacroDelimiter::Brace(Default::default()), 449 | quote!({ "abc" = } 1 + 2), 450 | ); 451 | check( 452 | syn::MacroDelimiter::Bracket(Default::default()), 453 | quote!(["abc" = ] 1 + 2), 454 | ); 455 | } 456 | -------------------------------------------------------------------------------- /structmeta/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "structmeta" 3 | version = "0.3.0" 4 | authors = ["frozenlib"] 5 | license = "MIT OR Apache-2.0" 6 | readme = "../README.md" 7 | repository = "https://github.com/frozenlib/structmeta" 8 | documentation = "https://docs.rs/structmeta/" 9 | keywords = ["derive", "parse", "attribute", "syn", "totokens"] 10 | categories = ["development-tools::procedural-macro-helpers"] 11 | description = "Parse Rust's attribute arguments by defining a struct." 12 | edition = "2021" 13 | rust-version = "1.70.0" 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | structmeta-derive = { version = "=0.3.0", path = "../structmeta-derive" } 19 | proc-macro2 = "1.0.78" 20 | syn = "2.0.48" 21 | quote = "1.0.35" 22 | 23 | 24 | [dev-dependencies] 25 | syn = { version = "2.0.48", features = ["extra-traits", "full"] } 26 | -------------------------------------------------------------------------------- /structmeta/src/arg_types.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{BitOr, BitOrAssign}; 2 | 3 | use proc_macro2::Span; 4 | /// `name` style attribute argument. 5 | /// 6 | /// See [`#[derive(StructMeta)]`](macro@crate::StructMeta) documentation for details. 7 | #[derive(Clone, Debug, Default)] 8 | pub struct Flag { 9 | pub span: Option, 10 | } 11 | impl Flag { 12 | pub const NONE: Flag = Flag { span: None }; 13 | pub fn value(&self) -> bool { 14 | self.span.is_some() 15 | } 16 | } 17 | impl PartialEq for Flag { 18 | fn eq(&self, other: &Self) -> bool { 19 | self.value() == other.value() 20 | } 21 | } 22 | impl From for Flag { 23 | fn from(value: bool) -> Self { 24 | Self { 25 | span: if value { Some(Span::call_site()) } else { None }, 26 | } 27 | } 28 | } 29 | impl BitOr for Flag { 30 | type Output = Self; 31 | fn bitor(self, rhs: Self) -> Self::Output { 32 | Self { 33 | span: self.span.or(rhs.span), 34 | } 35 | } 36 | } 37 | impl BitOrAssign for Flag { 38 | fn bitor_assign(&mut self, rhs: Self) { 39 | self.span = self.span.or(rhs.span); 40 | } 41 | } 42 | 43 | /// `name = value` style attribute argument. 44 | /// 45 | /// See [`#[derive(StructMeta)]`](macro@crate::StructMeta) documentation for details. 46 | #[derive(Copy, Clone, Debug)] 47 | pub struct NameValue { 48 | pub name_span: Span, 49 | pub value: T, 50 | } 51 | impl PartialEq for NameValue { 52 | fn eq(&self, other: &Self) -> bool { 53 | self.value == other.value 54 | } 55 | } 56 | 57 | /// `name(value)` style attribute argument. 58 | /// 59 | /// See [`#[derive(StructMeta)]`](macro@crate::StructMeta) documentation for details. 60 | #[derive(Copy, Clone, Debug)] 61 | pub struct NameArgs { 62 | pub name_span: Span, 63 | pub args: T, 64 | } 65 | impl PartialEq for NameArgs { 66 | fn eq(&self, other: &Self) -> bool { 67 | self.args == other.args 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /structmeta/src/easy_syntax.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Span}; 2 | use std::{fmt::Display, str::FromStr}; 3 | use syn::{ 4 | ext::IdentExt, 5 | parse::{Parse, ParseStream}, 6 | Error, LitFloat, LitInt, LitStr, Result, 7 | }; 8 | 9 | pub struct Keyword { 10 | pub span: Span, 11 | pub value: T, 12 | } 13 | impl Parse for Keyword 14 | where 15 | T::Err: Display, 16 | { 17 | fn parse(input: ParseStream) -> Result { 18 | let name = Ident::parse_any(input)?; 19 | let span = name.span(); 20 | match name.to_string().parse() { 21 | Ok(value) => Ok(Keyword { span, value }), 22 | Err(e) => Err(Error::new(span, e)), 23 | } 24 | } 25 | } 26 | 27 | #[derive(Copy, Clone, Debug)] 28 | pub struct LitValue { 29 | span: Span, 30 | value: T, 31 | } 32 | impl PartialEq for LitValue { 33 | fn eq(&self, other: &Self) -> bool { 34 | self.value == other.value 35 | } 36 | } 37 | impl PartialEq for LitValue { 38 | fn eq(&self, other: &T) -> bool { 39 | self.value.eq(other) 40 | } 41 | } 42 | 43 | macro_rules! impl_parse_lit_value_int { 44 | ($($ty:ty),*) => { 45 | $( 46 | impl Parse for LitValue<$ty> { 47 | fn parse(input: ParseStream) -> Result { 48 | let lit = input.parse::()?; 49 | Ok(Self { 50 | span: lit.span(), 51 | value: lit.base10_parse()?, 52 | }) 53 | } 54 | } 55 | )* 56 | }; 57 | } 58 | 59 | impl_parse_lit_value_int!(u8, u16, u32, u64, u128); 60 | impl_parse_lit_value_int!(i8, i16, i32, i64, i128); 61 | 62 | impl Parse for LitValue { 63 | fn parse(input: ParseStream) -> Result { 64 | let lit = input.parse::()?; 65 | Ok(LitValue { 66 | span: lit.span(), 67 | value: lit.value(), 68 | }) 69 | } 70 | } 71 | 72 | impl Parse for LitValue { 73 | fn parse(input: ParseStream) -> Result { 74 | let lit = input.parse::()?; 75 | Ok(LitValue { 76 | span: lit.span(), 77 | value: lit.base10_parse()?, 78 | }) 79 | } 80 | } 81 | impl Parse for LitValue { 82 | fn parse(input: ParseStream) -> Result { 83 | let lit = input.parse::()?; 84 | Ok(LitValue { 85 | span: lit.span(), 86 | value: lit.base10_parse()?, 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /structmeta/src/helpers.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::{Ident, Spacing, Span, TokenStream}; 2 | use syn::{ 3 | braced, bracketed, 4 | ext::IdentExt, 5 | parenthesized, 6 | parse::{discouraged::Speculative, ParseBuffer, ParseStream}, 7 | token::{self}, 8 | MacroDelimiter, Result, Token, 9 | }; 10 | 11 | pub mod exports { 12 | pub use proc_macro2; 13 | pub use quote; 14 | pub use syn; 15 | } 16 | 17 | pub enum NameIndex { 18 | Flag(std::result::Result), 19 | NameValue(std::result::Result), 20 | NameArgs(std::result::Result), 21 | } 22 | 23 | #[allow(clippy::too_many_arguments)] 24 | pub fn try_parse_name( 25 | input: ParseStream, 26 | flag_names: &[&str], 27 | flag_rest: bool, 28 | name_value_names: &[&str], 29 | name_value_rest: bool, 30 | name_args_names: &[&str], 31 | name_args_rest: bool, 32 | no_unnamed: bool, 33 | name_filter: &dyn Fn(&str) -> bool, 34 | ) -> Result> { 35 | let may_flag = !flag_names.is_empty() || flag_rest; 36 | let may_name_value = !name_value_names.is_empty() || name_value_rest; 37 | let may_name_args = !name_args_names.is_empty() || name_args_rest; 38 | let fork = input.fork(); 39 | if let Ok(ident) = Ident::parse_any(&fork) { 40 | if name_filter(&ident.to_string()) { 41 | let span = ident.span(); 42 | let mut kind = None; 43 | if (no_unnamed || may_flag) && (fork.peek(Token![,]) || fork.is_empty()) { 44 | if let Some(i) = name_index_of(flag_names, flag_rest, &ident) { 45 | input.advance_to(&fork); 46 | return Ok(Some((NameIndex::Flag(i), span))); 47 | } 48 | kind = Some(ArgKind::Flag); 49 | } else if (no_unnamed || may_name_value) && peek_eq_op(&fork) { 50 | if let Some(i) = name_index_of(name_value_names, name_value_rest, &ident) { 51 | fork.parse::()?; 52 | input.advance_to(&fork); 53 | return Ok(Some((NameIndex::NameValue(i), span))); 54 | } 55 | kind = Some(ArgKind::NameValue); 56 | } else if (no_unnamed || may_name_args) && fork.peek(token::Paren) { 57 | if let Some(i) = name_index_of(name_args_names, name_args_rest, &ident) { 58 | input.advance_to(&fork); 59 | return Ok(Some((NameIndex::NameArgs(i), span))); 60 | } 61 | kind = Some(ArgKind::NameArgs); 62 | }; 63 | 64 | if kind.is_some() || no_unnamed { 65 | let mut expected = Vec::new(); 66 | if let Some(name) = name_of(flag_names, flag_rest, &ident) { 67 | expected.push(format!("flag `{name}`")); 68 | } 69 | if let Some(name) = name_of(name_value_names, name_value_rest, &ident) { 70 | expected.push(format!("`{name} = ...`")); 71 | } 72 | if let Some(name) = name_of(name_args_names, name_args_rest, &ident) { 73 | expected.push(format!("`{name}(...)`")); 74 | } 75 | if !expected.is_empty() { 76 | return Err(input.error(msg( 77 | &expected, 78 | kind.map(|kind| Arg { 79 | kind, 80 | ident: &ident, 81 | }), 82 | ))); 83 | } 84 | let help = if let Some(similar_name) = 85 | find_similar_name(&[flag_names, name_value_names, name_args_names], &ident) 86 | { 87 | format!(" (help: a parameter with a similar name exists: `{similar_name}`)",) 88 | } else { 89 | "".into() 90 | }; 91 | return Err(input.error(format!( 92 | "cannot find parameter `{ident}` in this scope{help}" 93 | ))); 94 | } 95 | } 96 | } 97 | if no_unnamed { 98 | let message = if may_flag || may_name_value || may_name_args { 99 | "too many unnamed arguments." 100 | } else { 101 | "too many arguments." 102 | }; 103 | return Err(input.error(message)); 104 | } 105 | Ok(None) 106 | } 107 | fn peek_eq_op(input: ParseStream) -> bool { 108 | if let Some((p, _)) = input.cursor().punct() { 109 | p.as_char() == '=' && p.spacing() == Spacing::Alone 110 | } else { 111 | false 112 | } 113 | } 114 | fn name_index_of( 115 | names: &[&str], 116 | rest: bool, 117 | ident: &Ident, 118 | ) -> Option> { 119 | if let Some(index) = find(names, ident) { 120 | Some(Ok(index)) 121 | } else if rest { 122 | Some(Err(ident.clone())) 123 | } else { 124 | None 125 | } 126 | } 127 | fn name_of(names: &[&str], rest: bool, ident: &Ident) -> Option { 128 | if rest { 129 | Some(ident.to_string()) 130 | } else { 131 | find(names, ident).map(|i| names[i].to_string()) 132 | } 133 | } 134 | fn find(names: &[&str], ident: &Ident) -> Option { 135 | names.iter().position(|name| ident == name) 136 | } 137 | fn msg(expected: &[String], found: Option) -> String { 138 | if expected.is_empty() { 139 | return "unexpected token.".into(); 140 | } 141 | let mut m = String::new(); 142 | m.push_str("expected "); 143 | for i in 0..expected.len() { 144 | if i != 0 { 145 | let sep = if i == expected.len() - 1 { 146 | " or " 147 | } else { 148 | ", " 149 | }; 150 | m.push_str(sep); 151 | } 152 | m.push_str(&expected[i]); 153 | } 154 | if let Some(arg) = found { 155 | m.push_str(", found "); 156 | m.push_str(&match arg.kind { 157 | ArgKind::Flag => format!("`{}`", arg.ident), 158 | ArgKind::NameValue => format!("`{} = ...`", arg.ident), 159 | ArgKind::NameArgs => format!("`{}`(...)", arg.ident), 160 | }); 161 | } 162 | m 163 | } 164 | fn find_similar_name<'a>(names: &[&[&'a str]], ident: &Ident) -> Option<&'a str> { 165 | let c0: Vec<_> = ident.to_string().chars().collect(); 166 | let mut c1 = Vec::new(); 167 | let mut r = None; 168 | let mut r_d = usize::MAX; 169 | for &names in names { 170 | for &name in names { 171 | c1.clear(); 172 | c1.extend(name.chars()); 173 | if let Some(d) = distance(&c0, &c1) { 174 | if d < r_d { 175 | r_d = d; 176 | r = Some(name); 177 | } 178 | if d == r_d && Some(name) != r { 179 | return None; 180 | } 181 | } 182 | } 183 | } 184 | r 185 | } 186 | 187 | fn distance(s0: &[char], s1: &[char]) -> Option { 188 | if s0.len() > s1.len() { 189 | return distance(s1, s0); 190 | } 191 | if s0.len() + 1 < s1.len() { 192 | return None; 193 | } 194 | let mut start = 0; 195 | while start < s0.len() && start < s1.len() && s0[start] == s1[start] { 196 | start += 1; 197 | } 198 | let mut end = 0; 199 | while start + end < s0.len() 200 | && start + end < s1.len() 201 | && s0[s0.len() - end - 1] == s1[s1.len() - end - 1] 202 | { 203 | end += 1; 204 | } 205 | if s0.len() == s1.len() { 206 | if start + end == s0.len() { 207 | return Some(0); 208 | } 209 | if start + end + 1 == s0.len() { 210 | return Some(1); 211 | } 212 | if start + end + 2 == s0.len() && s0[start] == s1[start + 1] && s0[start + 1] == s1[start] { 213 | return Some(2); 214 | } 215 | } else if start + end == s0.len() { 216 | return Some(1); 217 | } 218 | 219 | None 220 | } 221 | 222 | pub fn is_snake_case(s: &str) -> bool { 223 | s.chars() 224 | .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_') 225 | } 226 | 227 | pub fn surround_macro_delimiter(this: &MacroDelimiter, tokens: &mut TokenStream, f: F) 228 | where 229 | F: FnOnce(&mut TokenStream), 230 | { 231 | match this { 232 | MacroDelimiter::Paren(p) => p.surround(tokens, f), 233 | MacroDelimiter::Bracket(b) => b.surround(tokens, f), 234 | MacroDelimiter::Brace(b) => b.surround(tokens, f), 235 | } 236 | } 237 | 238 | pub fn parse_macro_delimiter<'a>( 239 | input: &ParseBuffer<'a>, 240 | ) -> Result<(MacroDelimiter, ParseBuffer<'a>)> { 241 | let content; 242 | let token = if input.peek(token::Paren) { 243 | MacroDelimiter::Paren(parenthesized!(content in input)) 244 | } else if input.peek(token::Bracket) { 245 | MacroDelimiter::Bracket(bracketed!(content in input)) 246 | } else if input.peek(token::Brace) { 247 | MacroDelimiter::Brace(braced!(content in input)) 248 | } else { 249 | return Err(input.error("expected `(`, `[` or `{`")); 250 | }; 251 | Ok((token, content)) 252 | } 253 | 254 | #[doc(hidden)] 255 | #[macro_export] 256 | macro_rules! helpers_parse_macro_delimiter { 257 | ($content:ident in $input:ident) => { 258 | match $crate::helpers::parse_macro_delimiter($input) { 259 | Ok((token, content)) => { 260 | $content = content; 261 | token 262 | } 263 | Err(e) => return Err(e), 264 | } 265 | }; 266 | } 267 | 268 | #[test] 269 | fn test_is_near() { 270 | fn check(s0: &str, s1: &str, e: Option) { 271 | let c0: Vec<_> = s0.chars().collect(); 272 | let c1: Vec<_> = s1.chars().collect(); 273 | assert_eq!(distance(&c0, &c1), e, "{s0} , {s1}") 274 | } 275 | check("a", "a", Some(0)); 276 | check("a", "b", Some(1)); 277 | check("a", "ab", Some(1)); 278 | check("ab", "a", Some(1)); 279 | check("a", "aa", Some(1)); 280 | check("ab", "ba", Some(2)); 281 | } 282 | 283 | enum ArgKind { 284 | Flag, 285 | NameValue, 286 | NameArgs, 287 | } 288 | struct Arg<'a> { 289 | kind: ArgKind, 290 | ident: &'a Ident, 291 | } 292 | --------------------------------------------------------------------------------