├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .template ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── progress.rs ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── bitfield ├── Cargo.toml ├── impl │ ├── Cargo.toml │ └── src │ │ ├── bit.rs │ │ ├── gen.rs │ │ ├── lib.rs │ │ └── spe.rs ├── src │ ├── lib.rs │ └── pos │ │ ├── mod.rs │ │ ├── tests.rs │ │ ├── u16.rs │ │ ├── u32.rs │ │ ├── u64.rs │ │ └── u8.rs └── tests │ ├── 01-specifier-types.rs │ ├── 02-storage.rs │ ├── 03-accessors.rs │ ├── 03-accessors2.rs │ ├── 04-multiple-of-8bits.rs │ ├── 04-multiple-of-8bits.stderr │ ├── 05-accessor-signatures.rs │ ├── 06-enums.rs │ ├── 07-optional-discriminant.rs │ ├── 08-non-power-of-two.rs │ ├── 08-non-power-of-two.stderr │ ├── 09-variant-out-of-range.rs │ ├── 09-variant-out-of-range.stderr │ ├── 09-variant-out-of-range2.rs │ ├── 09-variant-out-of-range2.stderr │ ├── 10-bits-attribute.rs │ ├── 11-bits-attribute-wrong.rs │ ├── 11-bits-attribute-wrong.stderr │ ├── 12-accessors-edge.rs │ ├── bits_pos.rs │ ├── progress.rs │ └── tmp.rs ├── builder ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── 01-parse.rs │ ├── 02-create-builder.rs │ ├── 03-call-setters.rs │ ├── 04-call-build.rs │ ├── 05-method-chaining.rs │ ├── 06-optional-field.rs │ ├── 07-repeated-field-same-name.rs │ ├── 07-repeated-field.rs │ ├── 08-unrecognized-attribute.rs │ ├── 08-unrecognized-attribute.stderr │ ├── 09-redefined-prelude-types.rs │ └── progress.rs ├── debug ├── Cargo.toml ├── src │ ├── bound.rs │ ├── generics.rs │ └── lib.rs └── tests │ ├── 01-parse.rs │ ├── 02-impl-debug.rs │ ├── 03-custom-format.rs │ ├── 04-type-parameter-debug.rs │ ├── 04-type-parameter.rs │ ├── 05-phantom-data.rs │ ├── 06-bound-trouble.rs │ ├── 07-associated-type-direct.rs │ ├── 07-associated-type-multi.rs │ ├── 07-associated-type.rs │ ├── 08-escape-hatch-multi.rs │ ├── 08-escape-hatch.rs │ └── progress.rs ├── main.rs ├── rustfmt.toml ├── seq ├── Cargo.toml ├── src │ ├── lib.rs │ ├── range.rs │ ├── repeat.rs │ └── replace.rs └── tests │ ├── 01-parse-header.rs │ ├── 02-parse-body.rs │ ├── 03-expand-four-errors.rs │ ├── 03-expand-four-errors.stderr │ ├── 04-paste-ident.rs │ ├── 04-paste-ident2.rs │ ├── 05-repeat-section.rs │ ├── 05-repeat-section2.rs │ ├── 06-init-array.rs │ ├── 07-inclusive-range.rs │ ├── 08-ident-span.rs │ ├── 08-ident-span.stderr │ ├── 09-interaction-with-macrorules.rs │ ├── 09-interaction-with-macrorules2.rs │ ├── 09-interaction-with-macrorules2.stderr │ ├── 10-literal-range-parse-err.rs │ ├── 10-literal-range-parse-err.stderr │ ├── dcl-seq.rs │ └── progress.rs └── sorted ├── Cargo.toml ├── src ├── _enum.rs ├── _fn.rs └── lib.rs └── tests ├── 01-parse-enum.rs ├── 02-not-enum.rs ├── 02-not-enum.stderr ├── 03-out-of-order.rs ├── 03-out-of-order.stderr ├── 04-variants-with-data.rs ├── 04-variants-with-data.stderr ├── 05-match-expr.rs ├── 05-match-expr.stderr ├── 06-pattern-path.rs ├── 06-pattern-path.stderr ├── 07-unrecognized-pattern.rs ├── 07-unrecognized-pattern.stderr ├── 08-underscore-failed.rs ├── 08-underscore-failed.stderr ├── 08-underscore.rs └── progress.rs /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: [cron: "40 1 * * *"] 7 | 8 | env: 9 | RUSTFLAGS: '-Dwarnings' 10 | 11 | jobs: 12 | test: 13 | name: Rust ${{matrix.rust}} 14 | runs-on: ubuntu-latest 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | rust: [nightly, beta, stable] 19 | steps: 20 | - uses: actions/checkout@v2 21 | - uses: dtolnay/rust-toolchain@master 22 | with: 23 | toolchain: ${{matrix.rust}} 24 | - run: cargo check 25 | 26 | solution: 27 | name: Project ${{matrix.project}} 28 | runs-on: ubuntu-latest 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | project: [builder, debug, seq, sorted, bitfield] 33 | env: 34 | GIT_COMMITTER_EMAIL: proc-macro-workshop@dtolnay.github.io 35 | GIT_COMMITTER_NAME: proc-macro-workshop CI 36 | steps: 37 | - uses: actions/checkout@v2 38 | - uses: dtolnay/rust-toolchain@nightly 39 | - run: git fetch origin --unshallow refs/solution/${{matrix.project}} HEAD 40 | - run: git rebase HEAD FETCH_HEAD 41 | - run: cargo test 42 | working-directory: ${{matrix.project}} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | wip 4 | -------------------------------------------------------------------------------- /.template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | 18 | [dependencies] 19 | # TODO 20 | -------------------------------------------------------------------------------- /.template/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | -------------------------------------------------------------------------------- /.template/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | 5 | // TODO: add tests 6 | // 7 | // t.pass("tests/01-something-that-works.rs"); 8 | // t.compile_fail("tests/02-some-compiler-error.rs"); 9 | } 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-workshop" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [workspace] 8 | 9 | [[bin]] 10 | name = "workshop" 11 | path = "main.rs" 12 | 13 | [dependencies] 14 | bitfield = { path = "bitfield" } 15 | derive_builder = { path = "builder" } 16 | derive_debug = { path = "debug" } 17 | seq = { path = "seq" } 18 | sorted = { path = "sorted" } 19 | -------------------------------------------------------------------------------- /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 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /bitfield/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitfield" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [[test]] 9 | name = "tests" 10 | path = "tests/progress.rs" 11 | 12 | [[test]] 13 | name = "bits_pos" 14 | path = "./tests/bits_pos.rs" 15 | 16 | [dev-dependencies] 17 | trybuild = { version = "1.0.49", features = ["diff"] } 18 | 19 | [dependencies] 20 | bitfield-impl = { path = "impl" } 21 | -------------------------------------------------------------------------------- /bitfield/impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitfield-impl" 3 | version = "0.0.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | quote = "1" 12 | syn = {version = "1", features = ["extra-traits", "full"]} 13 | proc-macro2 = "1" 14 | -------------------------------------------------------------------------------- /bitfield/impl/src/bit.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream as TokenStream2; 2 | use quote::{format_ident, quote}; 3 | 4 | pub fn expand(input: syn::Item) -> TokenStream2 { 5 | match input { 6 | syn::Item::Struct(syn::ItemStruct { 7 | attrs, 8 | vis, 9 | ident, 10 | generics, 11 | fields: syn::Fields::Named(fields), 12 | .. 13 | }) => { 14 | let id = fields.named.iter().filter_map(|f| f.ident.as_ref()); 15 | let getter = id.clone().map(|i| format_ident!("get_{}", i)); 16 | let setter = id.clone().map(|i| format_ident!("set_{}", i)); 17 | 18 | let ty = fields.named.iter().map(|f| &f.ty); 19 | let ty2 = ty.clone(); 20 | let width = { 21 | let ty = ty.clone(); 22 | quote! { [#( <#ty as ::bitfield::Specifier>::BITS ),*] } 23 | }; 24 | let len = fields.named.len(); 25 | 26 | // related to test 04 27 | let total_bits = { 28 | let ty = ty.clone(); 29 | quote! { #( <#ty as ::bitfield::Specifier>::BITS )+* } 30 | }; 31 | 32 | let sig_ty = ty 33 | .clone() 34 | .map(|t| quote! { <#t as ::bitfield::Specifier>::T }); 35 | let size = quote! { #( <#ty as ::bitfield::Specifier>::BITS as usize )+* }; 36 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 37 | 38 | let range = 0..len; 39 | let acc_name = id.clone().map(|i| format_ident!("acc_{}", i)); 40 | let acc_name2 = acc_name.clone(); 41 | let acc_val = range.map(|n| { 42 | let idx = 0..n; 43 | quote! { 0 #( + WIDTH[#idx] )* } 44 | }); 45 | 46 | // related to test 10 and 11 47 | let check_bits = fields.named.iter().filter_map(check_bits); 48 | 49 | // 把原字段内容完全替换成 `data: [u8; SIZE]` 50 | quote! { 51 | #(#attrs)* 52 | #[repr(C)] 53 | #vis struct #ident #impl_generics #where_clause { 54 | data: [u8; #size >> 3 + ((#size) % 8 != 0) as usize], 55 | } 56 | 57 | const _ : () = { 58 | const WIDTH: [usize; #len] = #width; 59 | const SIZE: usize = #size >> 3 + ((#size) % 8 != 0) as usize; 60 | #( 61 | #[allow(non_upper_case_globals)] 62 | const #acc_name : usize = #acc_val; 63 | )* 64 | impl #impl_generics #ident #ty_generics #where_clause{ 65 | #( 66 | #vis fn #getter (&self) -> #sig_ty { 67 | // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=7e8b096e345dc86054814b095c9e3884 68 | <#ty2 as ::bitfield::Specifier>::get::<#acc_name2, SIZE>(&self.data) 69 | } 70 | 71 | #vis fn #setter (&mut self, #id : #sig_ty) { 72 | <#ty2 as ::bitfield::Specifier>::set::<#acc_name2, SIZE>(&mut self.data, #id) 73 | } 74 | )* 75 | 76 | #vis fn new() -> Self { 77 | Self { data: ::std::default::Default::default() } 78 | } 79 | } 80 | 81 | // multiple of 8 bits 82 | if (#total_bits) % 8 != 0 { panic!("should be a multiple of 8 bits") } 83 | }; 84 | 85 | #( #check_bits )* 86 | } 87 | } 88 | _ => unimplemented!(), 89 | } 90 | } 91 | 92 | fn check_bits(f: &syn::Field) -> Option { 93 | fn meta_bits(attr: &syn::Attribute) -> Option { 94 | match attr.parse_meta().ok()? { 95 | syn::Meta::NameValue(syn::MetaNameValue { lit, path, .. }) if path.is_ident("bits") => { 96 | Some(lit) 97 | } 98 | _ => None, 99 | } 100 | } 101 | 102 | f.attrs.iter().find_map(meta_bits).map(|lit| { 103 | let e = &f.ty; 104 | quote::quote_spanned! { 105 | lit.span()=> 106 | const _ : [(); #lit] = [(); <#e as ::bitfield::Specifier>::BITS]; 107 | } 108 | }) 109 | } 110 | -------------------------------------------------------------------------------- /bitfield/impl/src/gen.rs: -------------------------------------------------------------------------------- 1 | //! 生成 B1 ~ B64(只在 lib 内部使用) 2 | 3 | use quote::format_ident; 4 | 5 | pub fn generate() -> proc_macro2::TokenStream { 6 | let range = 1..=64; 7 | let ident = range.clone().map(|n| format_ident!("B{}", n)); 8 | let (u_ident, bits_u): (Vec<_>, Vec<_>) = range.clone().map(u_).unzip(); 9 | quote::quote! { 10 | #( 11 | pub struct #ident; 12 | impl Specifier for #ident { 13 | const BITS: usize = #range; 14 | type T = ::core::primitive::#u_ident; 15 | 16 | fn set(arr: &mut [u8], num: ::T) { 17 | <#bits_u <#range, ACC, SIZE> as SetGet>::SET(arr, num) 18 | } 19 | fn get(arr: &[u8]) -> ::T { 20 | <#bits_u <#range, ACC, SIZE> as SetGet>::GET(arr) 21 | } 22 | } 23 | )* 24 | } 25 | } 26 | 27 | // 1*8 => u8; 2*8 => u16; 4*8 => u32; 8*8 => u64 28 | // 可以通过位运算优化这里的分支判断 29 | fn u_(bits: usize) -> (proc_macro2::Ident, proc_macro2::Ident) { 30 | let u = if bits > 64 { 31 | unreachable!() 32 | } else if bits > 32 { 33 | 64u8 34 | } else if bits > 16 { 35 | 32 36 | } else if bits > 8 { 37 | 16 38 | } else { 39 | 8 40 | }; 41 | (format_ident!("u{}", u), format_ident!("BitsU{}", u)) 42 | } 43 | -------------------------------------------------------------------------------- /bitfield/impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_attribute] 4 | pub fn bitfield(_: TokenStream, input: TokenStream) -> TokenStream { 5 | let input = syn::parse_macro_input!(input as syn::Item); 6 | TokenStream::from(bit::expand(input)) 7 | } 8 | 9 | mod bit; 10 | mod gen; 11 | mod spe; 12 | 13 | #[proc_macro] 14 | pub fn gen(_: TokenStream) -> TokenStream { 15 | TokenStream::from(gen::generate()) 16 | } 17 | 18 | #[proc_macro_derive(BitfieldSpecifier)] 19 | pub fn derive_bitfield_specifier(input: TokenStream) -> TokenStream { 20 | let input = syn::parse_macro_input!(input as syn::ItemEnum); 21 | TokenStream::from(spe::derive_bitfield_specifier_for_enum(input)) 22 | } 23 | -------------------------------------------------------------------------------- /bitfield/impl/src/spe.rs: -------------------------------------------------------------------------------- 1 | pub fn derive_bitfield_specifier_for_enum(input: syn::ItemEnum) -> proc_macro2::TokenStream { 2 | let enum_name = &input.ident; 3 | let vars = input.variants.iter().map(|v| &v.ident); 4 | 5 | let len = input.variants.len() as u32; 6 | // 保证成员个数为 2 的幂(只为了通过测试 08) 7 | // 如果实际应用中不必严格保证成员个数为 2 的幂,那么使用 log2 函数。 8 | let bits = if let Some(bits) = log2_exact(len) { 9 | bits 10 | } else { 11 | return syn::Error::new( 12 | proc_macro2::Span::call_site(), 13 | "BitfieldSpecifier expected a number of variants which is a power of 2", 14 | ) 15 | .to_compile_error(); 16 | }; 17 | 18 | let ty = quote::format_ident!("B{}", bits); 19 | let ty_u = quote::format_ident!("__{}", input.ident); 20 | let ty_equiv = quote::format_ident!("__{}Equiv", input.ident); 21 | let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); 22 | 23 | // 测试 discriminant 必须小于成员个数 24 | // 受 [static_assertions::const_assert](https://docs.rs/static_assertions) 启发 25 | let check_discriminant = vars.clone().map(|v| { 26 | quote::quote_spanned! { 27 | v.span()=> 28 | const _ : #ty_u = (#len as #ty_u) - (#v as #ty_u) - 1; 29 | } 30 | }); 31 | 32 | // derive 宏无需返回被定义的 item,因此无需加上 #input 33 | // 34 | // 这里的技巧:在局部作用域中实现 trait,因为 impl 35 | // 是全局静态的,无关代码是否执行,也无论是否跨作用域。 36 | // 受 https://users.rust-lang.org/t/conditional-trait-implementations/11850/4 启发。 37 | // 38 | // 此外, `const _` 利用了 39 | // [Unnamed constant 技巧](https://doc.rust-lang.org/nightly/reference/items/constant-items.html#unnamed-constant) 40 | // 因为 `{ ... }` 无法单独当作 item ,从而其内部可视为局部作用域,在里面定义的类型别名不会影响源代码。 41 | quote::quote! { 42 | const _ : () = { 43 | type #ty_equiv = ::bitfield::#ty; 44 | type #ty_u = <#ty_equiv as ::bitfield::Specifier>::T; 45 | 46 | use #enum_name::*; 47 | #( #check_discriminant )* 48 | 49 | impl #impl_generics ::bitfield::Specifier for #enum_name #ty_generics #where_clause { 50 | type T = #enum_name; 51 | const BITS: usize = <#ty_equiv as ::bitfield::Specifier>::BITS; 52 | fn set(arr: &mut [u8], num: ::T) { 53 | <#ty_equiv as ::bitfield::Specifier>::set::(arr, num as #ty_u) 54 | } 55 | fn get(arr: &[u8]) -> ::T { 56 | // https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bed314b000b163a027a7a5312c94e74b 57 | fn __from_integer(num: #ty_u) -> #enum_name { 58 | // The variant must be found in this case. 59 | [#( (#vars as #ty_u, #vars) ),*].into_iter() 60 | .find_map(|(u, e)| if u == num { Some(e) } else { None }).unwrap() 61 | } 62 | __from_integer(<#ty_equiv as ::bitfield::Specifier>::get::(arr)) 63 | } 64 | } 65 | }; 66 | } 67 | } 68 | 69 | // 改进自 https://users.rust-lang.org/t/logarithm-of-integers/8506/5 70 | // 这个函数可以根据 enum 的成员数计算最小 bits(大于或等于成员个数的最小的 2 的对数) 71 | // 这个函数与测试无关,暂时不需要。 72 | #[allow(dead_code)] 73 | const fn log2(n: u32) -> u32 { 74 | u32::BITS - n.leading_zeros() - 1 + (n.count_ones() != 1) as u32 75 | } 76 | 77 | // 严格的 2 的对数(即成员个数的 2 的对数) 78 | const fn log2_exact(n: u32) -> Option { 79 | if n.count_ones() == 1 { 80 | Some(u32::BITS - n.leading_zeros() - 1) 81 | } else { 82 | None 83 | } 84 | } 85 | 86 | #[cfg(test)] 87 | mod tests_log2 { 88 | use super::{log2, log2_exact}; 89 | 90 | #[test] 91 | fn test_log2() { 92 | assert_eq!(log2(1), 0); 93 | assert_eq!(log2(2), 1); 94 | assert_eq!(log2(3), 2); 95 | assert_eq!(log2(4), 2); 96 | assert_eq!(log2(8), 3); 97 | assert_eq!(log2(10), 4); 98 | assert_eq!(log2(63), 6); 99 | assert_eq!(log2(64), 6); 100 | assert_eq!(log2(1024), 10); 101 | assert_eq!(log2(1025), 11); 102 | assert_eq!(log2(u32::MAX), 32); 103 | } 104 | 105 | #[test] 106 | #[should_panic] 107 | fn test_log2_0() { 108 | log2(0); 109 | } 110 | 111 | #[test] 112 | fn test_log2_exact() { 113 | assert_eq!(log2_exact(1), Some(0)); 114 | assert_eq!(log2_exact(2), Some(1)); 115 | assert_eq!(log2_exact(3), None); 116 | assert_eq!(log2_exact(4), Some(2)); 117 | assert_eq!(log2_exact(8), Some(3)); 118 | assert_eq!(log2_exact(10), None); 119 | assert_eq!(log2_exact(63), None); 120 | assert_eq!(log2_exact(64), Some(6)); 121 | assert_eq!(log2_exact(1024), Some(10)); 122 | assert_eq!(log2_exact(1025), None); 123 | assert_eq!(log2_exact(u32::MAX), None); 124 | } 125 | 126 | #[test] 127 | fn test_log2_0_extact() { 128 | assert_eq!(log2_exact(0), None); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /bitfield/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Crates that have the "proc-macro" crate type are only allowed to export 2 | // procedural macros. So we cannot have one crate that defines procedural macros 3 | // alongside other types of public APIs like traits and structs. 4 | // 5 | // For this project we are going to need a #[bitfield] macro but also a trait 6 | // and some structs. We solve this by defining the trait and structs in this 7 | // crate, defining the attribute macro in a separate bitfield-impl crate, and 8 | // then re-exporting the macro from this crate so that users only have one crate 9 | // that they need to import. 10 | // 11 | // From the perspective of a user of this crate, they get all the necessary APIs 12 | // (macro, trait, struct) through the one bitfield crate. 13 | pub use bitfield_impl::bitfield; 14 | pub use bitfield_impl::BitfieldSpecifier; 15 | 16 | pub trait Specifier { 17 | const BITS: usize; 18 | 19 | type T: Sized; 20 | 21 | fn set(arr: &mut [u8], num: ::T); 22 | fn get(arr: &[u8]) -> ::T; 23 | } 24 | 25 | bitfield_impl::gen! {} 26 | 27 | impl Specifier for bool { 28 | type T = bool; 29 | 30 | const BITS: usize = 1; 31 | 32 | fn set(arr: &mut [u8], num: bool) { 33 | B1::set::(arr, num as u8) 34 | } 35 | 36 | fn get(arr: &[u8]) -> bool { 37 | B1::get::(arr).eq(&1) 38 | } 39 | } 40 | 41 | mod pos; 42 | pub use pos::{u16::BitsU16, u32::BitsU32, u64::BitsU64, u8::BitsU8, Basic, SetGet}; 43 | -------------------------------------------------------------------------------- /bitfield/src/pos/mod.rs: -------------------------------------------------------------------------------- 1 | const ERR: &str = "slice 转化 array 时失败"; 2 | type Range = std::ops::RangeInclusive; 3 | 4 | pub trait Basic { 5 | const OFFSET: usize = ACC % 8; 6 | const OFFSET_END: usize = WIDTH - Self::OFFSET_END_; 7 | const OFFSET_END_: usize = (ACC + WIDTH) % 8; 8 | const RANGE: Range = Self::RANGE_LHS..=Self::RANGE_RHS; 9 | const RANGE_BITS: u32 = (Self::RANGE_LEN * 8) as u32; 10 | const RANGE_LEN: usize = Self::RANGE_RHS - Self::RANGE_LHS + 1; 11 | const RANGE_LHS: usize = ACC / 8; 12 | const RANGE_RHS: usize = (WIDTH + ACC - 1) / 8; 13 | } 14 | 15 | pub trait SetGet { 16 | type Target; 17 | const ACROSS: bool; 18 | const GET: fn(&[u8]) -> Self::Target; 19 | const LIMIT: Self::Target; 20 | const RANGE_ALT: Range; 21 | const RANGE_RHS2: Range; 22 | const RANGE_ACROSS: Range; 23 | const SET: fn(&mut [u8], Self::Target); 24 | const U8_MAX_OFFSET: u8; 25 | fn set_across(arr: &mut [u8], num: Self::Target); 26 | fn set_no_across(arr: &mut [u8], num: Self::Target); 27 | fn get_across(arr: &[u8]) -> Self::Target; 28 | fn get_no_across(arr: &[u8]) -> Self::Target; 29 | fn across_end(arr: &mut [u8], num_end: u8) { 30 | let p = &mut arr[Self::RANGE_RHS2]; 31 | let num_old = u8::from_ne_bytes(p.try_into().expect(ERR)); 32 | let num_new = num_old & Self::U8_MAX_OFFSET | num_end; 33 | p.copy_from_slice(&num_new.to_ne_bytes()); 34 | } 35 | fn get_across_helper<'a, T: TryFrom<&'a [u8]>>(arr: &'a [u8]) -> (T, u8) 36 | where 37 | >::Error: std::fmt::Debug, 38 | { 39 | ( 40 | T::try_from(&arr[Self::RANGE_ACROSS]).expect(ERR), 41 | u8::from_ne_bytes(arr[Self::RANGE_RHS2].try_into().expect(ERR)), 42 | ) 43 | } 44 | } 45 | 46 | pub mod u16; 47 | pub mod u32; 48 | pub mod u64; 49 | pub mod u8; 50 | 51 | #[cfg(test)] 52 | mod tests; 53 | -------------------------------------------------------------------------------- /bitfield/src/pos/tests.rs: -------------------------------------------------------------------------------- 1 | //! 此测试应与 tests/bits_pos.rs 保持一致 2 | 3 | use crate::{Basic, BitsU16, BitsU32, BitsU64, BitsU8, SetGet}; 4 | 5 | #[test] 6 | fn test_56_18_26_14_8() { 7 | // [56, 18, 26, 14, 8] 8 | const SIZE: usize = (56 + 18 + 26 + 14 + 8) / 8 + ((56 + 18 + 26 + 14 + 8) % 8 != 0) as usize; 9 | type Bit56 = BitsU64<56, 0, SIZE>; 10 | type Bit18 = BitsU32<18, 56, SIZE>; 11 | type Bit26 = BitsU32<26, 74, SIZE>; 12 | type Bit14 = BitsU16<14, 100, SIZE>; 13 | type Bit08 = BitsU8<8, 114, SIZE>; 14 | 15 | assert_eq!(Bit56::RANGE, 0..=6); 16 | assert_eq!(Bit18::RANGE, 7..=9); 17 | assert_eq!(Bit26::RANGE, 9..=12); 18 | assert_eq!(Bit14::RANGE, 12..=14); 19 | assert_eq!(Bit08::RANGE, 14..=15); 20 | 21 | assert_eq!(Bit56::OFFSET, 0); 22 | assert_eq!(Bit18::OFFSET, 0); 23 | assert_eq!(Bit26::OFFSET, 2); 24 | assert_eq!(Bit14::OFFSET, 4); 25 | assert_eq!(Bit08::OFFSET, 2); 26 | 27 | let mut arr = [0; SIZE]; 28 | 29 | Bit08::SET(&mut arr, 250); 30 | assert_eq!(Bit08::GET(&arr), 250); 31 | Bit08::SET(&mut arr, 115); 32 | assert_eq!(Bit08::GET(&arr), 115); 33 | Bit08::SET(&mut arr, 10); 34 | assert_eq!(Bit08::GET(&arr), 10); 35 | 36 | Bit14::SET(&mut arr, 0b10_1111_0011_1001); 37 | assert_eq!(Bit14::GET(&arr), 0b10_1111_0011_1001); 38 | 39 | Bit18::SET(&mut arr, 0b10_1111_0011_1001); 40 | assert_eq!(Bit18::GET(&arr), 0b10_1111_0011_1001); 41 | Bit18::SET(&mut arr, 0b10_0001_1111_0011_1001); 42 | assert_eq!(Bit18::GET(&arr), 0b10_0001_1111_0011_1001); 43 | 44 | Bit26::SET(&mut arr, 0b10_0001_1111_0011_1001); 45 | assert_eq!(Bit26::GET(&arr), 0b10_0001_1111_0011_1001); 46 | Bit26::SET(&mut arr, 0b10_1111_0011_1001_1111_0011_1001); 47 | assert_eq!(Bit26::GET(&arr), 0b10_1111_0011_1001_1111_0011_1001); 48 | Bit26::SET(&mut arr, u32::MAX); 49 | assert_eq!(Bit26::GET(&arr), u32::MAX >> (32 - 26)); 50 | 51 | Bit56::SET(&mut arr, 0b10_0001_1111_0011_1001); 52 | assert_eq!(Bit56::GET(&arr), 0b10_0001_1111_0011_1001); 53 | Bit56::SET(&mut arr, u64::MAX); 54 | assert_eq!(Bit56::GET(&arr), u64::MAX >> (64 - 56)); 55 | 56 | Bit56::SET(&mut arr, 0); 57 | Bit18::SET(&mut arr, 0); 58 | Bit26::SET(&mut arr, 0); 59 | Bit14::SET(&mut arr, 0); 60 | Bit08::SET(&mut arr, 0); 61 | assert_eq!(arr.into_iter().sum::(), 0); 62 | } 63 | 64 | #[test] 65 | fn test_1_32_64() { 66 | const SIZE: usize = (1 + 32 + 64) / 8 + ((1 + 32 + 64) % 8 != 0) as usize; 67 | type Bit01 = BitsU8<1, 0, SIZE>; 68 | type Bit32 = BitsU32<32, 1, SIZE>; 69 | type Bit64 = BitsU64<64, 33, SIZE>; 70 | 71 | let mut arr = [0; SIZE]; 72 | 73 | Bit01::SET(&mut arr, 1); 74 | assert_eq!(Bit01::GET(&arr), 1); 75 | Bit32::SET(&mut arr, u32::MAX); 76 | assert_eq!(Bit32::GET(&arr), u32::MAX); 77 | Bit64::SET(&mut arr, u64::MAX); 78 | assert_eq!(Bit64::GET(&arr), u64::MAX); 79 | } 80 | 81 | #[test] 82 | fn test_16_32_64() { 83 | const SIZE: usize = (16 + 32 + 64) / 8 + ((16 + 32 + 64) % 8 != 0) as usize; 84 | type Bit16 = BitsU16<16, 0, SIZE>; 85 | type Bit32 = BitsU32<32, 16, SIZE>; 86 | type Bit64 = BitsU64<64, 48, SIZE>; 87 | 88 | let mut arr = [0; SIZE]; 89 | 90 | Bit16::SET(&mut arr, u16::MAX); 91 | assert_eq!(Bit16::GET(&arr), u16::MAX); 92 | Bit32::SET(&mut arr, u32::MAX); 93 | assert_eq!(Bit32::GET(&arr), u32::MAX); 94 | Bit64::SET(&mut arr, u64::MAX); 95 | assert_eq!(Bit64::GET(&arr), u64::MAX); 96 | } 97 | 98 | #[test] 99 | fn test_1_3_4_24() { 100 | const SIZE: usize = (1 + 3 + 4 + 24) / 8 + ((1 + 3 + 4 + 24) % 8 != 0) as usize; 101 | type Bit1 = BitsU8<1, 0, SIZE>; 102 | type Bit3 = BitsU8<3, 1, SIZE>; 103 | type Bit4 = BitsU8<4, 4, SIZE>; 104 | type Bit24 = BitsU32<24, 8, SIZE>; 105 | 106 | assert_eq!(Bit1::RANGE, 0..=0); 107 | assert_eq!(Bit3::RANGE, 0..=0); 108 | assert_eq!(Bit4::RANGE, 0..=0); 109 | assert_eq!(Bit24::RANGE, 1..=3); 110 | 111 | assert_eq!(Bit1::OFFSET, 0); 112 | assert_eq!(Bit3::OFFSET, 1); 113 | assert_eq!(Bit4::OFFSET, 4); 114 | assert_eq!(Bit24::OFFSET, 0); 115 | 116 | assert_eq!(Bit24::RANGE_ALT, 1..=4); 117 | 118 | let mut arr = [0; SIZE]; 119 | 120 | Bit1::SET(&mut arr, u8::MAX); 121 | assert_eq!(Bit1::GET(&arr), u8::MAX >> (8 - 1)); 122 | Bit3::SET(&mut arr, u8::MAX); 123 | assert_eq!(Bit3::GET(&arr), u8::MAX >> (8 - 3)); 124 | Bit4::SET(&mut arr, u8::MAX); 125 | assert_eq!(Bit4::GET(&arr), u8::MAX >> (8 - 4)); 126 | Bit24::SET(&mut arr, u32::MAX); 127 | assert_eq!(Bit24::GET(&arr), u32::MAX >> (32 - 24)); 128 | } 129 | 130 | #[test] 131 | fn test_1_3_4_55_1() { 132 | const SIZE: usize = (1 + 3 + 4 + 55 + 1) / 8 + ((1 + 3 + 4 + 55 + 1) % 8 != 0) as usize; 133 | type Bit1 = BitsU8<1, 0, SIZE>; 134 | type Bit3 = BitsU8<3, 1, SIZE>; 135 | type Bit4 = BitsU8<4, 4, SIZE>; 136 | type Bit55 = BitsU64<55, 8, SIZE>; 137 | type Bit1_ = BitsU8<1, 63, SIZE>; 138 | 139 | assert_eq!(Bit1::RANGE, 0..=0); 140 | assert_eq!(Bit3::RANGE, 0..=0); 141 | assert_eq!(Bit4::RANGE, 0..=0); 142 | assert_eq!(Bit55::RANGE, 1..=7); 143 | assert_eq!(Bit1_::RANGE, 7..=7); 144 | 145 | assert_eq!(Bit1::OFFSET, 0); 146 | assert_eq!(Bit3::OFFSET, 1); 147 | assert_eq!(Bit4::OFFSET, 4); 148 | assert_eq!(Bit55::OFFSET, 0); 149 | assert_eq!(Bit1_::OFFSET, 7); 150 | 151 | let mut arr = [0; SIZE]; 152 | 153 | Bit1::SET(&mut arr, u8::MAX); 154 | assert_eq!(Bit1::GET(&arr), u8::MAX >> (8 - 1)); 155 | Bit3::SET(&mut arr, u8::MAX); 156 | assert_eq!(Bit3::GET(&arr), u8::MAX >> (8 - 3)); 157 | Bit4::SET(&mut arr, u8::MAX); 158 | assert_eq!(Bit4::GET(&arr), u8::MAX >> (8 - 4)); 159 | Bit55::SET(&mut arr, u64::MAX); 160 | assert_eq!(Bit55::GET(&arr), u64::MAX >> (64 - 55)); 161 | Bit1_::SET(&mut arr, u8::MAX); 162 | assert_eq!(Bit1_::GET(&arr), u8::MAX >> (8 - 1)); 163 | 164 | Bit55::SET(&mut arr, u32::MAX as u64); 165 | assert_eq!(Bit55::GET(&arr), u32::MAX as u64); 166 | Bit1_::SET(&mut arr, 0); 167 | assert_eq!(Bit1_::GET(&arr), 0); 168 | 169 | Bit1::SET(&mut arr, 0); 170 | Bit3::SET(&mut arr, 0); 171 | Bit4::SET(&mut arr, 0); 172 | Bit55::SET(&mut arr, 0); 173 | 174 | assert_eq!(arr.iter().copied().sum::(), 0); 175 | 176 | Bit1::SET(&mut arr, u8::MAX); 177 | Bit3::SET(&mut arr, u8::MAX); 178 | Bit4::SET(&mut arr, u8::MAX); 179 | Bit55::SET(&mut arr, u64::MAX); 180 | Bit1_::SET(&mut arr, u8::MAX); 181 | assert_eq!( 182 | arr.iter().map(|&a| a as usize).sum::(), 183 | arr.len() * u8::MAX as usize 184 | ); 185 | } 186 | 187 | // This is on the contrary with test 04-multiple-of-8bits. 188 | #[test] 189 | fn test_1_3_4_23() { 190 | const SIZE: usize = (1 + 3 + 4 + 23) / 8 + ((1 + 3 + 4 + 23) % 8 != 0) as usize; 191 | type Bit1 = BitsU8<1, 0, SIZE>; 192 | type Bit3 = BitsU8<3, 1, SIZE>; 193 | type Bit4 = BitsU8<4, 4, SIZE>; 194 | type Bit23 = BitsU32<23, 8, SIZE>; 195 | 196 | assert_eq!(Bit1::RANGE, 0..=0); 197 | assert_eq!(Bit3::RANGE, 0..=0); 198 | assert_eq!(Bit4::RANGE, 0..=0); 199 | assert_eq!(Bit23::RANGE, 1..=3); 200 | 201 | assert_eq!(Bit1::OFFSET, 0); 202 | assert_eq!(Bit3::OFFSET, 1); 203 | assert_eq!(Bit4::OFFSET, 4); 204 | assert_eq!(Bit23::OFFSET, 0); 205 | 206 | let mut arr = [0; SIZE]; 207 | 208 | Bit1::SET(&mut arr, u8::MAX); 209 | assert_eq!(Bit1::GET(&arr), u8::MAX >> (8 - 1)); 210 | Bit3::SET(&mut arr, u8::MAX); 211 | assert_eq!(Bit3::GET(&arr), u8::MAX >> (8 - 3)); 212 | Bit4::SET(&mut arr, u8::MAX); 213 | assert_eq!(Bit4::GET(&arr), u8::MAX >> (8 - 4)); 214 | Bit23::SET(&mut arr, u32::MAX); 215 | assert_eq!(Bit23::GET(&arr), u32::MAX >> (32 - 23)); 216 | 217 | assert_eq!( 218 | arr.iter().map(|&a| a as usize).sum::(), 219 | arr.len() * u8::MAX as usize 220 | ); 221 | } 222 | -------------------------------------------------------------------------------- /bitfield/src/pos/u16.rs: -------------------------------------------------------------------------------- 1 | use super::{Basic, Range, SetGet, ERR}; 2 | 3 | pub struct BitsU16; 4 | 5 | impl Basic 6 | for BitsU16 7 | { 8 | } 9 | 10 | impl SetGet for BitsU16 { 11 | type Target = u16; 12 | 13 | const ACROSS: bool = Self::RANGE_BITS > u16::BITS; 14 | const GET: fn(&[u8]) -> u16 = if Self::ACROSS { 15 | Self::get_across 16 | } else { 17 | Self::get_no_across 18 | }; 19 | const LIMIT: u16 = (u16::MAX >> (16 - WIDTH)) << Self::OFFSET; 20 | const RANGE_ACROSS: Range = Self::RANGE_LHS..=(Self::RANGE_RHS - 1); 21 | const RANGE_ALT: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 22 | const RANGE_RHS2: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 23 | const SET: fn(&mut [u8], u16) = if Self::ACROSS { 24 | Self::set_across 25 | } else { 26 | Self::set_no_across 27 | }; 28 | const U8_MAX_OFFSET: u8 = !(u8::MAX >> (8 - Self::OFFSET_END_)); 29 | 30 | fn set_across(arr: &mut [u8], num: u16) { 31 | let p = &mut arr[Self::RANGE_ACROSS]; 32 | let num_old = u16::from_ne_bytes(p.try_into().unwrap()); 33 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 34 | p.copy_from_slice(&num_new.to_ne_bytes()); 35 | 36 | let num_end = (num >> (16 - Self::OFFSET)) as u8; 37 | Self::across_end(arr, num_end); 38 | } 39 | 40 | fn set_no_across(arr: &mut [u8], num: u16) { 41 | let p = &mut arr[Self::RANGE]; 42 | let num_old = u16::from_ne_bytes(p.try_into().expect(ERR)); 43 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 44 | p.copy_from_slice(&num_new.to_ne_bytes()); 45 | } 46 | 47 | fn get_across(arr: &[u8]) -> u16 { 48 | let (num_start, num_end) = Self::get_across_helper(arr); 49 | let num_start = (u16::from_ne_bytes(num_start) & Self::LIMIT) >> Self::OFFSET; 50 | let num_end = (num_end as u16 & (u16::MAX >> (16 - Self::OFFSET_END_))) << Self::OFFSET_END; 51 | num_start | num_end 52 | } 53 | 54 | fn get_no_across(arr: &[u8]) -> u16 { 55 | let num = u16::from_ne_bytes(arr[Self::RANGE].try_into().expect(ERR)); 56 | (num & Self::LIMIT) >> Self::OFFSET 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bitfield/src/pos/u32.rs: -------------------------------------------------------------------------------- 1 | use super::{Basic, Range, SetGet, ERR}; 2 | 3 | pub struct BitsU32; 4 | 5 | impl Basic 6 | for BitsU32 7 | { 8 | } 9 | 10 | impl BitsU32 { 11 | const HOLD: bool = !(Self::HOLD_LEN < 4); 12 | const HOLD_LEN: usize = SIZE - Self::RANGE_LHS; 13 | const RANGE_SIZED: Range = Self::RANGE_LHS..=SIZE - 1; 14 | const SET2: fn(&mut [u8], u32) = if Self::HOLD { 15 | Self::can_hold 16 | } else { 17 | Self::not_hold 18 | }; 19 | const U32: fn(&[u8]) -> u32 = if Self::HOLD { 20 | Self::u32_can_hold 21 | } else { 22 | Self::u32_not_hold 23 | }; 24 | 25 | fn can_hold(arr: &mut [u8], num: u32) { 26 | let num_old = Self::u32_can_hold(arr); 27 | let p = &mut arr[Self::RANGE_ALT]; 28 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 29 | p.copy_from_slice(&num_new.to_ne_bytes()); 30 | } 31 | 32 | fn not_hold(arr: &mut [u8], num: u32) { 33 | let num_old = Self::u32_not_hold(arr); 34 | let p = &mut arr[Self::RANGE_SIZED]; 35 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 36 | p.copy_from_slice(&num_new.to_le_bytes()[..Self::HOLD_LEN]); 37 | } 38 | 39 | fn u32_can_hold(arr: &[u8]) -> u32 { 40 | u32::from_ne_bytes(arr[Self::RANGE_ALT].try_into().expect(ERR)) 41 | } 42 | 43 | fn u32_not_hold(arr: &[u8]) -> u32 { 44 | assert_eq!(Self::HOLD_LEN, arr[Self::RANGE_LHS..].len()); 45 | let mut tmp = [0; 4]; 46 | tmp[..Self::HOLD_LEN].copy_from_slice(&arr[Self::RANGE_SIZED]); 47 | u32::from_le_bytes(tmp) 48 | } 49 | } 50 | 51 | impl SetGet for BitsU32 { 52 | type Target = u32; 53 | 54 | const ACROSS: bool = Self::RANGE_BITS > u32::BITS; 55 | const GET: fn(&[u8]) -> u32 = if Self::ACROSS { 56 | Self::get_across 57 | } else { 58 | Self::get_no_across 59 | }; 60 | const LIMIT: u32 = (u32::MAX >> (32 - WIDTH)) << Self::OFFSET; 61 | const RANGE_ACROSS: Range = Self::RANGE_LHS..=(Self::RANGE_RHS - 1); 62 | const RANGE_ALT: Range = if Self::RANGE_LEN == 4 { 63 | Self::RANGE 64 | } else { 65 | Self::RANGE_LHS..=Self::RANGE_LHS + 3 66 | }; 67 | const RANGE_RHS2: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 68 | const SET: fn(&mut [u8], u32) = if Self::ACROSS { 69 | Self::set_across 70 | } else { 71 | Self::set_no_across 72 | }; 73 | const U8_MAX_OFFSET: u8 = !(u8::MAX >> (8 - Self::OFFSET_END_)); 74 | 75 | fn set_across(arr: &mut [u8], num: u32) { 76 | let p = &mut arr[Self::RANGE_ACROSS]; 77 | let num_old = u32::from_ne_bytes(p.try_into().unwrap()); 78 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 79 | p.copy_from_slice(&num_new.to_ne_bytes()); 80 | 81 | let num_end = (num >> (32 - Self::OFFSET)) as u8; 82 | Self::across_end(arr, num_end); 83 | } 84 | 85 | fn set_no_across(arr: &mut [u8], num: u32) { 86 | Self::SET2(arr, num) 87 | } 88 | 89 | fn get_across(arr: &[u8]) -> u32 { 90 | let (num_start, num_end) = Self::get_across_helper(arr); 91 | let num_start = (u32::from_ne_bytes(num_start) & Self::LIMIT) >> Self::OFFSET; 92 | let num_end = (num_end as u32 & (u32::MAX >> (32 - Self::OFFSET_END_))) << Self::OFFSET_END; 93 | num_start | num_end 94 | } 95 | 96 | fn get_no_across(arr: &[u8]) -> u32 { 97 | let num = Self::U32(arr); 98 | (num & Self::LIMIT) >> Self::OFFSET 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /bitfield/src/pos/u64.rs: -------------------------------------------------------------------------------- 1 | use super::{Basic, Range, SetGet, ERR}; 2 | 3 | pub struct BitsU64; 4 | 5 | impl Basic 6 | for BitsU64 7 | { 8 | } 9 | 10 | impl BitsU64 { 11 | const HOLD: bool = !(Self::HOLD_LEN < 8); 12 | const HOLD_LEN: usize = SIZE - Self::RANGE_LHS; 13 | const RANGE_SIZED: Range = Self::RANGE_LHS..=SIZE - 1; 14 | const SET2: fn(&mut [u8], u64) = if Self::HOLD { 15 | Self::can_hold 16 | } else { 17 | Self::not_hold 18 | }; 19 | const U64: fn(&[u8]) -> u64 = if Self::HOLD { 20 | Self::u64_can_hold 21 | } else { 22 | Self::u64_not_hold 23 | }; 24 | 25 | fn can_hold(arr: &mut [u8], num: u64) { 26 | let num_old = Self::u64_can_hold(arr); 27 | let p = &mut arr[Self::RANGE_ALT]; 28 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 29 | p.copy_from_slice(&num_new.to_ne_bytes()); 30 | } 31 | 32 | fn not_hold(arr: &mut [u8], num: u64) { 33 | let num_old = Self::u64_not_hold(arr); 34 | let p = &mut arr[Self::RANGE_SIZED]; 35 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 36 | p.copy_from_slice(&num_new.to_le_bytes()[..Self::HOLD_LEN]); 37 | } 38 | 39 | fn u64_can_hold(arr: &[u8]) -> u64 { 40 | u64::from_ne_bytes(arr[Self::RANGE_ALT].try_into().expect(ERR)) 41 | } 42 | 43 | fn u64_not_hold(arr: &[u8]) -> u64 { 44 | let mut tmp = [0; 8]; 45 | tmp[..Self::HOLD_LEN].copy_from_slice(&arr[Self::RANGE_SIZED]); 46 | u64::from_le_bytes(tmp) 47 | } 48 | } 49 | 50 | impl SetGet for BitsU64 { 51 | type Target = u64; 52 | 53 | const ACROSS: bool = Self::RANGE_BITS > u64::BITS; 54 | const GET: fn(&[u8]) -> u64 = if Self::ACROSS { 55 | Self::get_across 56 | } else { 57 | Self::get_no_across 58 | }; 59 | const LIMIT: u64 = (u64::MAX >> (64 - WIDTH)) << Self::OFFSET; 60 | const RANGE_ACROSS: Range = Self::RANGE_LHS..=(Self::RANGE_RHS - 1); 61 | const RANGE_ALT: Range = if Self::RANGE_LEN == 8 { 62 | Self::RANGE 63 | } else { 64 | Self::RANGE_LHS..=Self::RANGE_LHS + 7 65 | }; 66 | const RANGE_RHS2: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 67 | const SET: fn(&mut [u8], u64) = if Self::ACROSS { 68 | Self::set_across 69 | } else { 70 | Self::set_no_across 71 | }; 72 | const U8_MAX_OFFSET: u8 = !(u8::MAX >> (8 - Self::OFFSET_END_)); 73 | 74 | fn set_across(arr: &mut [u8], num: u64) { 75 | let p = &mut arr[Self::RANGE_ACROSS]; 76 | let num_old = u64::from_ne_bytes(p.try_into().unwrap()); 77 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 78 | p.copy_from_slice(&num_new.to_ne_bytes()); 79 | 80 | let num_end = (num >> (64 - Self::OFFSET)) as u8; 81 | Self::across_end(arr, num_end); 82 | } 83 | 84 | fn set_no_across(arr: &mut [u8], num: u64) { 85 | Self::SET2(arr, num) 86 | } 87 | 88 | fn get_across(arr: &[u8]) -> u64 { 89 | let (num_start, num_end) = Self::get_across_helper(arr); 90 | let num = (u64::from_ne_bytes(num_start) & Self::LIMIT) >> Self::OFFSET; 91 | let num_end = 92 | ((num_end as u64) & (u64::MAX >> (64 - Self::OFFSET_END_))) << Self::OFFSET_END; 93 | num | num_end 94 | } 95 | 96 | fn get_no_across(arr: &[u8]) -> u64 { 97 | let num = Self::U64(arr); 98 | (num & Self::LIMIT) >> Self::OFFSET 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /bitfield/src/pos/u8.rs: -------------------------------------------------------------------------------- 1 | use super::{Basic, Range, SetGet, ERR}; 2 | 3 | pub struct BitsU8; 4 | 5 | impl Basic 6 | for BitsU8 7 | { 8 | } 9 | 10 | impl SetGet for BitsU8 { 11 | type Target = u8; 12 | 13 | const ACROSS: bool = Self::RANGE_BITS > u8::BITS; 14 | const GET: fn(&[u8]) -> u8 = if Self::ACROSS { 15 | Self::get_across 16 | } else { 17 | Self::get_no_across 18 | }; 19 | const LIMIT: u8 = (u8::MAX >> (8 - WIDTH)) << Self::OFFSET; 20 | const RANGE_ACROSS: Range = Self::RANGE_LHS..=(Self::RANGE_RHS - 1); 21 | const RANGE_ALT: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 22 | const RANGE_RHS2: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 23 | const SET: fn(&mut [u8], u8) = if Self::ACROSS { 24 | Self::set_across 25 | } else { 26 | Self::set_no_across 27 | }; 28 | const U8_MAX_OFFSET: u8 = !(u8::MAX >> (8 - Self::OFFSET_END_)); 29 | 30 | fn set_across(arr: &mut [u8], num: u8) { 31 | let p = &mut arr[Self::RANGE_ACROSS]; 32 | let num_old = u8::from_ne_bytes(p.try_into().unwrap()); 33 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 34 | p.copy_from_slice(&num_new.to_ne_bytes()); 35 | 36 | let num_end = (num >> (8 - Self::OFFSET)) as u8; 37 | Self::across_end(arr, num_end); 38 | } 39 | 40 | fn set_no_across(arr: &mut [u8], num: u8) { 41 | let p = &mut arr[Self::RANGE]; 42 | let num_old = u8::from_ne_bytes(p.try_into().expect(ERR)); 43 | let num_new = num_old & !Self::LIMIT | (num << Self::OFFSET); 44 | p.copy_from_slice(&num_new.to_ne_bytes()); 45 | } 46 | 47 | fn get_across(arr: &[u8]) -> u8 { 48 | let (num_start, num_end) = Self::get_across_helper(arr); 49 | let num_start = (u8::from_ne_bytes(num_start) & Self::LIMIT) >> Self::OFFSET; 50 | let num_end = (num_end & (u8::MAX >> (8 - Self::OFFSET_END_))) << Self::OFFSET_END; 51 | num_start | num_end 52 | } 53 | 54 | fn get_no_across(arr: &[u8]) -> u8 { 55 | let num = u8::from_ne_bytes(arr[Self::RANGE].try_into().expect(ERR)); 56 | (num & Self::LIMIT) >> Self::OFFSET 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /bitfield/tests/01-specifier-types.rs: -------------------------------------------------------------------------------- 1 | // Our design for #[bitfield] (see the readme) involves marker types B1 through 2 | // B64 to indicate the bit width of each field. 3 | // 4 | // It would be possible to implement this without having any actual types B1 5 | // through B64 -- the attribute macro could recognize the names "B1" through 6 | // "B64" and deduce the bit width from the number in the name. But this hurts 7 | // composability! Later we'll want to make bitfield members out of other things, 8 | // like enums or type aliases which won't necessarily have a width in their 9 | // name: 10 | // 11 | // #[bitfield] 12 | // struct RedirectionTableEntry { 13 | // vector: B8, 14 | // dest_mode: DestinationMode, 15 | // trigger_mode: TriggerMode, 16 | // destination: Destination, 17 | // } 18 | // 19 | // #[bitfield] 20 | // enum DestinationMode { 21 | // Physical = 0, 22 | // Logical = 1, 23 | // } 24 | // 25 | // #[bitfield] 26 | // enum TriggerMode { 27 | // Edge = 0, 28 | // Level = 1, 29 | // } 30 | // 31 | // #[target_pointer_width = "64"] 32 | // type Destination = B30; 33 | // 34 | // #[target_pointer_width = "32"] 35 | // type Destination = B22; 36 | // 37 | // So instead of parsing a bit width from the type name, the approach we will 38 | // follow will hold bit widths in an associated constant of a trait that is 39 | // implemented for legal bitfield specifier types, including B1 through B64. 40 | // 41 | // Create a trait called bitfield::Specifier with an associated constant BITS, 42 | // and write a function-like procedural macro to define some types B1 through 43 | // B64 with corresponding impls of the Specifier trait. The B* types can be 44 | // anything since we don't need them to carry any meaning outside of a 45 | // #[bitfield] struct definition; an uninhabited enum like `pub enum B1 {}` 46 | // would work best. 47 | // 48 | // Be aware that crates that have the "proc-macro" crate type are not allowed to 49 | // export anything other than procedural macros. The project skeleton for this 50 | // project has been set up with two crates, one for procedural macros and the 51 | // other an ordinary library crate for the Specifier trait and B types which 52 | // also re-exports from the procedural macro crate so that users can get 53 | // everything through one library. 54 | 55 | use bitfield::*; 56 | 57 | //#[bitfield] 58 | pub struct MyFourBytes { 59 | a: B1, 60 | b: B3, 61 | c: B4, 62 | d: B24, 63 | } 64 | 65 | fn main() { 66 | assert_eq!(::BITS, 24); 67 | } 68 | -------------------------------------------------------------------------------- /bitfield/tests/02-storage.rs: -------------------------------------------------------------------------------- 1 | // Write an attribute macro that replaces the struct in its input with a byte 2 | // array representation of the correct size. For example the invocation in the 3 | // test case below might expand to the following where the `size` expression is 4 | // computed by summing the Specifier::BITS constant of each field type. 5 | // 6 | // #[repr(C)] 7 | // pub struct MyFourBytes { 8 | // data: [u8; #size], 9 | // } 10 | // 11 | // Don't worry for now what happens if the total bit size is not a multiple of 12 | // 8 bits. We will come back to that later to make it a compile-time error. 13 | 14 | use bitfield::*; 15 | 16 | #[bitfield] 17 | pub struct MyFourBytes { 18 | a: B1, 19 | b: B3, 20 | c: B4, 21 | d: B24, 22 | } 23 | 24 | fn main() { 25 | assert_eq!(std::mem::size_of::(), 4); 26 | } 27 | -------------------------------------------------------------------------------- /bitfield/tests/03-accessors.rs: -------------------------------------------------------------------------------- 1 | // Generate getters and setters that manipulate the right range of bits 2 | // corresponding to each field. 3 | // 4 | // 5 | // ║ first byte ║ second byte ║ third byte ║ fourth byte ║ 6 | // ╟───────────────╫───────────────╫───────────────╫───────────────╢ 7 | // ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║ 8 | // ╟─╫─────╫───────╫───────────────────────────────────────────────╢ 9 | // ║a║ b ║ c ║ d ║ 10 | // 11 | // 12 | // Depending on your implementation, it's possible that this will require adding 13 | // some associated types, associated constants, or associated functions to your 14 | // bitfield::Specifier trait next to the existing Specifier::BITS constant, but 15 | // it may not. 16 | // 17 | // If it's easier for now, you can use u64 as the argument type for all the 18 | // setters and return type for all the getters. We will follow up with a more 19 | // precise signature in a later test case. 20 | 21 | use bitfield::*; 22 | 23 | #[bitfield] 24 | pub struct MyFourBytes { 25 | a: B1, 26 | b: B3, 27 | c: B4, 28 | d: B24, 29 | } 30 | 31 | fn main() { 32 | let mut bitfield = MyFourBytes::new(); 33 | assert_eq!(0, bitfield.get_a()); 34 | assert_eq!(0, bitfield.get_b()); 35 | assert_eq!(0, bitfield.get_c()); 36 | assert_eq!(0, bitfield.get_d()); 37 | 38 | bitfield.set_c(14); 39 | assert_eq!(0, bitfield.get_a()); 40 | assert_eq!(0, bitfield.get_b()); 41 | assert_eq!(14, bitfield.get_c()); 42 | assert_eq!(0, bitfield.get_d()); 43 | } 44 | -------------------------------------------------------------------------------- /bitfield/tests/03-accessors2.rs: -------------------------------------------------------------------------------- 1 | // Generate getters and setters that manipulate the right range of bits 2 | // corresponding to each field. 3 | // 4 | // 5 | // ║ first byte ║ second byte ║ third byte ║ fourth byte ║ 6 | // ╟───────────────╫───────────────╫───────────────╫───────────────╢ 7 | // ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║ 8 | // ╟─╫─────╫───────╫───────────────────────────────────────────────╢ 9 | // ║a║ b ║ c ║ d ║ 10 | // 11 | // 12 | // Depending on your implementation, it's possible that this will require adding 13 | // some associated types, associated constants, or associated functions to your 14 | // bitfield::Specifier trait next to the existing Specifier::BITS constant, but 15 | // it may not. 16 | // 17 | // If it's easier for now, you can use u64 as the argument type for all the 18 | // setters and return type for all the getters. We will follow up with a more 19 | // precise signature in a later test case. 20 | 21 | use bitfield::*; 22 | 23 | #[bitfield] 24 | pub struct MyFourBytes { 25 | a: B1, 26 | b: B3, 27 | c: B4, 28 | d: B24, 29 | } 30 | 31 | fn main() { 32 | let mut bitfield = MyFourBytes::new(); 33 | assert_eq!(0, bitfield.get_a()); 34 | assert_eq!(0, bitfield.get_b()); 35 | assert_eq!(0, bitfield.get_c()); 36 | assert_eq!(0, bitfield.get_d()); 37 | 38 | // const 从 impl 中被移除,因此此测试作废 39 | assert_eq!([1, 3, 4, 24], MyFourBytes::WIDTH); 40 | assert_eq!(0, MyFourBytes::acc_a); 41 | assert_eq!(1, MyFourBytes::acc_b); 42 | assert_eq!(4, MyFourBytes::acc_c); 43 | assert_eq!(8, MyFourBytes::acc_d); 44 | 45 | bitfield.set_c(14); 46 | assert_eq!(0, bitfield.get_a()); 47 | assert_eq!(0, bitfield.get_b()); 48 | assert_eq!(14, bitfield.get_c()); 49 | assert_eq!(0, bitfield.get_d()); 50 | } 51 | -------------------------------------------------------------------------------- /bitfield/tests/04-multiple-of-8bits.rs: -------------------------------------------------------------------------------- 1 | // Make it so that a bitfield with a size not a multiple of 8 bits will not 2 | // compile. 3 | // 4 | // Aim to make the error message as relevant and free of distractions as you can 5 | // get. The stderr file next to this test case should give some idea as to the 6 | // approach taken by the reference implementation for this project, but feel 7 | // free to overwrite the stderr file to match the implementation you come up 8 | // with. 9 | // 10 | // --- 11 | // Tangent 12 | // 13 | // There is only one profound insight about Rust macro development, and this 14 | // test case begins to touch on it: what makes someone an "expert at macros" 15 | // mostly has nothing to do with how good they are "at macros". 16 | // 17 | // 95% of what enables people to write powerful and user-friendly macro 18 | // libraries is in their mastery of everything else about Rust outside of 19 | // macros, and their creativity to put together ordinary language features in 20 | // interesting ways that may not occur in handwritten code. 21 | // 22 | // You may occasionally come across procedural macros that you feel are really 23 | // advanced or magical. If you ever feel this way, I encourage you to take a 24 | // closer look and you'll discover that as far as the macro implementation 25 | // itself is concerned, none of those libraries are doing anything remotely 26 | // interesting. They always just parse some input in a boring way, crawl some 27 | // syntax trees in a boring way to find out about the input, and paste together 28 | // some output code in a boring way exactly like what you've been doing so far. 29 | // In fact once you've made it this far in the workshop, it's okay to assume you 30 | // basically know everything there is to know about the mechanics of writing 31 | // procedural macros. 32 | // 33 | // To the extent that there are any tricks to macro development, all of them 34 | // revolve around *what* code the macros emit, not *how* the macros emit the 35 | // code. This realization can be surprising to people who entered into macro 36 | // development with a vague notion of procedural macros as a "compiler plugin" 37 | // which they imagine must imply all sorts of complicated APIs for *how* to 38 | // integrate with the rest of the compiler. That's not how it works. The only 39 | // thing macros do is emit code that could have been written by hand. If you 40 | // couldn't have come up with some piece of tricky code from one of those 41 | // magical macros, learning more "about macros" won't change that; but learning 42 | // more about every other part of Rust will. Inversely, once you come up with 43 | // what code you want to generate, writing the macro to generate it is generally 44 | // the easy part. 45 | 46 | use bitfield::*; 47 | 48 | type A = B1; 49 | type B = B3; 50 | type C = B4; 51 | type D = B23; 52 | 53 | #[bitfield] 54 | pub struct NotQuiteFourBytes { 55 | a: A, 56 | b: B, 57 | c: C, 58 | d: D, 59 | } 60 | 61 | fn main() {} 62 | -------------------------------------------------------------------------------- /bitfield/tests/04-multiple-of-8bits.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation of constant value failed 2 | --> tests/04-multiple-of-8bits.rs:53:1 3 | | 4 | 53 | #[bitfield] 5 | | ^^^^^^^^^^^ the evaluated program panicked at 'should be a multiple of 8 bits', $DIR/tests/04-multiple-of-8bits.rs:53:1 6 | | 7 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /bitfield/tests/05-accessor-signatures.rs: -------------------------------------------------------------------------------- 1 | // For getters and setters, we would like for the signature to be in terms of 2 | // the narrowest unsigned integer type that can hold the right number of bits. 3 | // That means the accessors for B1 through B8 would use u8, B9 through B16 would 4 | // use u16 etc. 5 | 6 | use bitfield::*; 7 | use std::mem::size_of_val; 8 | 9 | type A = B1; 10 | type B = B3; 11 | type C = B4; 12 | type D = B24; 13 | 14 | #[bitfield] 15 | pub struct MyFourBytes { 16 | a: A, 17 | b: B, 18 | c: C, 19 | d: D, 20 | } 21 | 22 | fn main() { 23 | let mut x = MyFourBytes::new(); 24 | 25 | // I am testing the signatures in this roundabout way to avoid making it 26 | // possible to pass this test with a generic signature that is inconvenient 27 | // for callers, such as `fn get_a>(&self) -> T`. 28 | 29 | let a = 1; 30 | x.set_a(a); // expect fn(&mut MyFourBytes, u8) 31 | let b = 1; 32 | x.set_b(b); 33 | let c = 1; 34 | x.set_c(c); 35 | let d = 1; 36 | x.set_d(d); // expect fn(&mut MyFourBytes, u32) 37 | 38 | assert_eq!(size_of_val(&a), 1); 39 | assert_eq!(size_of_val(&b), 1); 40 | assert_eq!(size_of_val(&c), 1); 41 | assert_eq!(size_of_val(&d), 4); 42 | 43 | assert_eq!(size_of_val(&x.get_a()), 1); // expect fn(&MyFourBytes) -> u8 44 | assert_eq!(size_of_val(&x.get_b()), 1); 45 | assert_eq!(size_of_val(&x.get_c()), 1); 46 | assert_eq!(size_of_val(&x.get_d()), 4); // expect fn(&MyFourBytes) -> u32 47 | } 48 | -------------------------------------------------------------------------------- /bitfield/tests/06-enums.rs: -------------------------------------------------------------------------------- 1 | // For some bitfield members, working with them as enums will make more sense to 2 | // the user than working with them as integers. We will require enums that have 3 | // a power-of-two number of variants so that they exhaustively cover a fixed 4 | // range of bits. 5 | // 6 | // // Works like B3, but getter and setter signatures will use 7 | // // the enum instead of u8. 8 | // #[derive(BitfieldSpecifier)] 9 | // enum DeliveryMode { 10 | // Fixed = 0b000, 11 | // Lowest = 0b001, 12 | // SMI = 0b010, 13 | // RemoteRead = 0b011, 14 | // NMI = 0b100, 15 | // Init = 0b101, 16 | // Startup = 0b110, 17 | // External = 0b111, 18 | // } 19 | // 20 | // For this test case it is okay to require that every enum variant has an 21 | // explicit discriminant that is an integer literal. We will relax this 22 | // requirement in a later test case. 23 | // 24 | // Optionally if you are interested, come up with a way to support enums with a 25 | // number of variants that is not a power of two, but this is not necessary for 26 | // the test suite. Maybe there could be a #[bits = N] attribute that determines 27 | // the bit width of the specifier, and the getter (only for such enums) would 28 | // return Result with the raw value accessible through the 29 | // error type as u64: 30 | // 31 | // #[derive(BitfieldSpecifier)] 32 | // #[bits = 4] 33 | // enum SmallPrime { 34 | // Two = 0b0010, 35 | // Three = 0b0011, 36 | // Five = 0b0101, 37 | // Seven = 0b0111, 38 | // Eleven = 0b1011, 39 | // Thirteen = 0b1101, 40 | // } 41 | // 42 | // ... 43 | // let mut bitfield = MyBitfield::new(); 44 | // assert_eq!(0, bitfield.small_prime().unwrap_err().raw_value()); 45 | // 46 | // bitfield.set_small_prime(SmallPrime::Seven); 47 | // let p = bitfield.small_prime().unwrap_or(SmallPrime::Two); 48 | 49 | use bitfield::*; 50 | 51 | #[bitfield] 52 | pub struct RedirectionTableEntry { 53 | acknowledged: bool, 54 | trigger_mode: TriggerMode, 55 | delivery_mode: DeliveryMode, 56 | reserved: B3, 57 | } 58 | 59 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 60 | pub enum TriggerMode { 61 | Edge = 0, 62 | Level = 1, 63 | } 64 | 65 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 66 | pub enum DeliveryMode { 67 | Fixed = 0b000, 68 | Lowest = 0b001, 69 | SMI = 0b010, 70 | RemoteRead = 0b011, 71 | NMI = 0b100, 72 | Init = 0b101, 73 | Startup = 0b110, 74 | External = 0b111, 75 | } 76 | 77 | fn main() { 78 | assert_eq!(std::mem::size_of::(), 1); 79 | 80 | // Initialized to all 0 bits. 81 | let mut entry = RedirectionTableEntry::new(); 82 | assert_eq!(entry.get_acknowledged(), false); 83 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge); 84 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Fixed); 85 | 86 | entry.set_acknowledged(true); 87 | entry.set_delivery_mode(DeliveryMode::SMI); 88 | assert_eq!(entry.get_acknowledged(), true); 89 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge); 90 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::SMI); 91 | } 92 | -------------------------------------------------------------------------------- /bitfield/tests/07-optional-discriminant.rs: -------------------------------------------------------------------------------- 1 | // For bitfield use limited to a single binary, such as a space optimization for 2 | // some in-memory data structure, we may not care what exact bit representation 3 | // is used for enums. 4 | // 5 | // Make your BitfieldSpecifier derive macro for enums use the underlying 6 | // discriminant determined by the Rust compiler as the bit representation. Do 7 | // not assume that the compiler uses any particular scheme like PREV+1 for 8 | // implicit discriminants; make sure your implementation respects Rust's choice 9 | // of discriminant regardless of what scheme Rust uses. This is important for 10 | // performance so that the getter and setter both compile down to very simple 11 | // machine code after optimizations. 12 | // 13 | // Do not worry about what happens if discriminants are outside of the range 14 | // 0..2^BITS. We will do a compile-time check in a later test case to ensure 15 | // they are in range. 16 | 17 | use bitfield::*; 18 | 19 | #[bitfield] 20 | pub struct RedirectionTableEntry { 21 | delivery_mode: DeliveryMode, 22 | reserved: B5, 23 | } 24 | 25 | const F: isize = 3; 26 | const G: isize = 0; 27 | 28 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 29 | pub enum DeliveryMode { 30 | Fixed = F, 31 | Lowest, 32 | SMI, 33 | RemoteRead, 34 | NMI, 35 | Init = G, 36 | Startup, 37 | External, 38 | } 39 | 40 | fn main() { 41 | assert_eq!(std::mem::size_of::(), 1); 42 | 43 | // Initialized to all 0 bits. 44 | let mut entry = RedirectionTableEntry::new(); 45 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Init); 46 | 47 | entry.set_delivery_mode(DeliveryMode::Lowest); 48 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Lowest); 49 | } 50 | -------------------------------------------------------------------------------- /bitfield/tests/08-non-power-of-two.rs: -------------------------------------------------------------------------------- 1 | // Bitfield enums with a number of variants other than a power of two should 2 | // fail to compile. 3 | // 4 | // (Or, if you implemented the optional #[bits = N] enum approach mentioned in 5 | // the explanation of test case 06, then enums with non-power-of-two variants 6 | // without a #[bits = N] attribute should fail to compile.) 7 | 8 | use bitfield::*; 9 | 10 | #[derive(BitfieldSpecifier)] 11 | pub enum Bad { 12 | Zero, 13 | One, 14 | Two, 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /bitfield/tests/08-non-power-of-two.stderr: -------------------------------------------------------------------------------- 1 | error: BitfieldSpecifier expected a number of variants which is a power of 2 2 | --> tests/08-non-power-of-two.rs:10:10 3 | | 4 | 10 | #[derive(BitfieldSpecifier)] 5 | | ^^^^^^^^^^^^^^^^^ 6 | | 7 | = note: this error originates in the derive macro `BitfieldSpecifier` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /bitfield/tests/09-variant-out-of-range.rs: -------------------------------------------------------------------------------- 1 | // Bitfield enums with any discriminant (implicit or explicit) outside of the 2 | // range 0..2^BITS should fail to compile. 3 | 4 | use bitfield::*; 5 | 6 | const F: isize = 1; 7 | 8 | #[derive(BitfieldSpecifier)] 9 | pub enum DeliveryMode { 10 | Fixed = F, 11 | Lowest, 12 | SMI, 13 | RemoteRead, 14 | NMI, 15 | Init, 16 | Startup, 17 | External, 18 | } 19 | 20 | fn main() {} 21 | -------------------------------------------------------------------------------- /bitfield/tests/09-variant-out-of-range.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `bitfield::checks::False: bitfield::checks::DiscriminantInRange` is not satisfied 2 | --> tests/09-variant-out-of-range.rs:17:5 3 | | 4 | 17 | External, 5 | | ^^^^^^^^ the trait `bitfield::checks::DiscriminantInRange` is not implemented for `bitfield::checks::False` 6 | -------------------------------------------------------------------------------- /bitfield/tests/09-variant-out-of-range2.rs: -------------------------------------------------------------------------------- 1 | // Bitfield enums with any discriminant (implicit or explicit) outside of the 2 | // range 0..2^BITS should fail to compile. 3 | 4 | use bitfield::*; 5 | 6 | const F: isize = 1; 7 | // const F: isize = 0; 8 | 9 | #[derive(BitfieldSpecifier)] 10 | enum DeliveryMode { 11 | Fixed = F, 12 | Lowest, 13 | SMI, 14 | RemoteRead, 15 | NMI, 16 | Init, 17 | Startup, 18 | External, 19 | } 20 | 21 | // 编译时检查枚举体的 discriminant 是否越界(原 09 test) 22 | // dtolnay 对 bitfield 的具体实现没有过多介绍,有些 failed 类型的测试很难通过 23 | fn main() { } 24 | -------------------------------------------------------------------------------- /bitfield/tests/09-variant-out-of-range2.stderr: -------------------------------------------------------------------------------- 1 | error[E0080]: evaluation of constant value failed 2 | --> tests/09-variant-out-of-range2.rs:18:5 3 | | 4 | 18 | External, 5 | | ^^^^^^^^ attempt to compute `0_u8 - 1_u8`, which would overflow 6 | -------------------------------------------------------------------------------- /bitfield/tests/10-bits-attribute.rs: -------------------------------------------------------------------------------- 1 | // One downside of the way we have implemented enum support so far is that it 2 | // makes it impossible to see from the definition of a bitfield struct how the 3 | // bits are being laid out. In something like the following bitfield, all we 4 | // know from this code is that the total size is a multiple of 8 bits. Maybe 5 | // trigger_mode is 11 bits and delivery_mode is 1 bit. This may tend to make 6 | // maintenance problematic when the types involved are defined across different 7 | // modules or even different crates. 8 | // 9 | // #[bitfield] 10 | // pub struct RedirectionTableEntry { 11 | // trigger_mode: TriggerMode, 12 | // delivery_mode: DeliveryMode, 13 | // reserved: B4, 14 | // } 15 | // 16 | // Introduce an optional #[bits = N] attribute to serve as compile-time checked 17 | // documentation of field size. Ensure that this attribute is entirely optional, 18 | // meaning that the code behaves the same whether or not you write it, but if 19 | // the user does provide the attribute then the program must not compile if 20 | // their value is wrong. 21 | 22 | use bitfield::*; 23 | 24 | #[bitfield] 25 | pub struct RedirectionTableEntry { 26 | #[bits = 1] 27 | trigger_mode: TriggerMode, 28 | #[bits = 3] 29 | delivery_mode: DeliveryMode, 30 | reserved: B4, 31 | } 32 | 33 | #[derive(BitfieldSpecifier, Debug)] 34 | pub enum TriggerMode { 35 | Edge = 0, 36 | Level = 1, 37 | } 38 | 39 | #[derive(BitfieldSpecifier, Debug)] 40 | pub enum DeliveryMode { 41 | Fixed = 0b000, 42 | Lowest = 0b001, 43 | SMI = 0b010, 44 | RemoteRead = 0b011, 45 | NMI = 0b100, 46 | Init = 0b101, 47 | Startup = 0b110, 48 | External = 0b111, 49 | } 50 | 51 | fn main() {} 52 | -------------------------------------------------------------------------------- /bitfield/tests/11-bits-attribute-wrong.rs: -------------------------------------------------------------------------------- 1 | // This is just the compile_fail version of the previous test case, testing what 2 | // error happens if the user has written an incorrect #[bits = N] attribute. 3 | // 4 | // Ensure that the error message points to the incorrect attribute and contains 5 | // the correct number of bits in some form. 6 | 7 | use bitfield::*; 8 | 9 | #[bitfield] 10 | pub struct RedirectionTableEntry { 11 | #[bits = 9] 12 | trigger_mode: TriggerMode, 13 | reserved: B7, 14 | } 15 | 16 | #[derive(BitfieldSpecifier, Debug)] 17 | pub enum TriggerMode { 18 | Edge = 0, 19 | Level = 1, 20 | } 21 | 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /bitfield/tests/11-bits-attribute-wrong.stderr: -------------------------------------------------------------------------------- 1 | error[E0308]: mismatched types 2 | --> tests/11-bits-attribute-wrong.rs:11:14 3 | | 4 | 11 | #[bits = 9] 5 | | ^ 6 | | | 7 | | expected an array with a fixed size of 9 elements, found one with 1 element 8 | | help: consider specifying the actual array length: `1` 9 | -------------------------------------------------------------------------------- /bitfield/tests/12-accessors-edge.rs: -------------------------------------------------------------------------------- 1 | // This test is equivalent to 03-accessors but with some fields spanning across 2 | // byte boundaries. This may or may not already work depending on how your 3 | // implementation has been done so far. 4 | // 5 | // 6 | // ║ first byte ║ second byte ║ third byte ║ fourth byte ║ 7 | // ╟───────────────╫───────────────╫───────────────╫───────────────╢ 8 | // ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║ 9 | // ╟─────────────────╫───────────╫─────────────────────────╫───────╢ 10 | // ║ a ║ b ║ c ║ d ║ 11 | 12 | use bitfield::*; 13 | 14 | #[bitfield] 15 | pub struct EdgeCaseBytes { 16 | a: B9, 17 | b: B6, 18 | c: B13, 19 | d: B4, 20 | } 21 | 22 | fn main() { 23 | let mut bitfield = EdgeCaseBytes::new(); 24 | assert_eq!(0, bitfield.get_a()); 25 | assert_eq!(0, bitfield.get_b()); 26 | assert_eq!(0, bitfield.get_c()); 27 | assert_eq!(0, bitfield.get_d()); 28 | 29 | let a = 0b1100_0011_1; 30 | let b = 0b101_010; 31 | let c = 0x1675; 32 | let d = 0b1110; 33 | 34 | bitfield.set_a(a); 35 | bitfield.set_b(b); 36 | bitfield.set_c(c); 37 | bitfield.set_d(d); 38 | 39 | assert_eq!(a, bitfield.get_a()); 40 | assert_eq!(b, bitfield.get_b()); 41 | assert_eq!(c, bitfield.get_c()); 42 | assert_eq!(d, bitfield.get_d()); 43 | } 44 | -------------------------------------------------------------------------------- /bitfield/tests/bits_pos.rs: -------------------------------------------------------------------------------- 1 | //! 此测试应与 src/pos/tests.rs 保持一致 2 | 3 | // 此结构体为最初的版本,相比于 src/pos 中的设计,存在以下不足: 4 | // 1. 不应有多种展现方式:当 const generics 一旦确定,只需要生成一个目标类型的相关方法(因此改用 5 | // SetGet trait 进行抽象和组织) 6 | // 2. 运行时确定和计算 Self::RANGE_LHS.. ,而这可以优化到编译时计算(因而增加 SIZE 泛型) 7 | pub struct BitsPos; 8 | 9 | type Range = std::ops::RangeInclusive; 10 | 11 | impl BitsPos { 12 | const ACROSS_U08: bool = Self::RANGE_BITS > u8::BITS; 13 | const ACROSS_U16: bool = Self::RANGE_BITS > u16::BITS; 14 | const ACROSS_U32: bool = Self::RANGE_BITS > u32::BITS; 15 | const ACROSS_U64: bool = Self::RANGE_BITS > u64::BITS; 16 | const ERR: &'static str = "slice 转化 array 时失败"; 17 | const LIMIT_U08: u8 = (u8::MAX >> (8 - WIDTH)) << Self::OFFSET; 18 | // const LIMIT_U08_END: u8 = u8::MAX >> (8 - Self::OFFSET_END_); 19 | const LIMIT_U16: u16 = (u16::MAX >> (16 - WIDTH)) << Self::OFFSET; 20 | // const LIMIT_U16_END: u16 = u16::MAX >> (16 - Self::OFFSET_END_); 21 | const LIMIT_U32: u32 = (u32::MAX >> (32 - WIDTH)) << Self::OFFSET; 22 | // const LIMIT_U32_END: u32 = u32::MAX >> (32 - Self::OFFSET_END_); 23 | const LIMIT_U64: u64 = (u64::MAX >> (64 - WIDTH)) << Self::OFFSET; 24 | // const LIMIT_U64_END: u64 = (u64::MAX >> (64 - Self::OFFSET_END_) as u8); 25 | pub const OFFSET: usize = ACC % 8; 26 | // const OFFSET_END: usize = WIDTH - Self::OFFSET_END_; 27 | const OFFSET_END: usize = if WIDTH < Self::OFFSET_END_ { 28 | // 这里其实永远不会使用到:但在字节数过少时可能跳入此情况 29 | WIDTH 30 | } else { 31 | WIDTH - Self::OFFSET_END_ 32 | }; 33 | const OFFSET_END_: usize = (ACC + WIDTH) % 8; 34 | pub const RANGE: Range = Self::RANGE_LHS..=Self::RANGE_RHS; 35 | const RANGE_ACROSS: Range = if Self::RANGE_RHS == 0 { 36 | // 这里其实永远不会使用到:如果为 across,那么一定是第二种情况 37 | Self::RANGE 38 | } else { 39 | Self::RANGE_LHS..=(Self::RANGE_RHS - 1) 40 | }; 41 | const RANGE_BITS: u32 = (Self::RANGE_LEN * 8) as u32; 42 | const RANGE_LEN: usize = Self::RANGE_RHS - Self::RANGE_LHS + 1; 43 | const RANGE_LHS: usize = ACC / 8; 44 | const RANGE_RHS: usize = (WIDTH + ACC - 1) / 8; 45 | const RANGE_RHS2: Range = Self::RANGE_RHS..=Self::RANGE_RHS; 46 | pub const RANGE_U32: Range = if Self::RANGE_LEN == 4 { 47 | Self::RANGE 48 | } else { 49 | Self::RANGE_LHS..=Self::RANGE_LHS + 3 50 | }; 51 | const RANGE_U64: Range = if Self::RANGE_LEN == 8 { 52 | Self::RANGE 53 | } else { 54 | Self::RANGE_LHS..=Self::RANGE_LHS + 7 55 | }; 56 | 57 | pub fn get_u8(arr: &[u8]) -> u8 { 58 | if Self::ACROSS_U08 { 59 | let (num_start, num_end) = Self::get_across(arr); 60 | let num = (u8::from_ne_bytes(num_start) & Self::LIMIT_U08) >> Self::OFFSET; 61 | let num_end = 62 | (num_end as u8 & (u8::MAX >> (8 - Self::OFFSET_END_))) << Self::OFFSET_END; 63 | num | num_end 64 | } else { 65 | let num = u8::from_ne_bytes(Self::get(arr)); 66 | (num & Self::LIMIT_U08) >> Self::OFFSET 67 | } 68 | } 69 | 70 | pub fn get_u16(arr: &[u8]) -> u16 { 71 | if Self::ACROSS_U16 { 72 | let (num_start, num_end) = Self::get_across(arr); 73 | let num = (u16::from_ne_bytes(num_start) & Self::LIMIT_U16) >> Self::OFFSET; 74 | let num_end = 75 | (num_end as u16 & (u16::MAX >> (16 - Self::OFFSET_END_))) << Self::OFFSET_END; 76 | num | num_end 77 | } else { 78 | let num = u16::from_ne_bytes(Self::get(arr)); 79 | (num & Self::LIMIT_U16) >> Self::OFFSET 80 | } 81 | } 82 | 83 | pub fn get_u32(arr: &[u8]) -> u32 { 84 | if Self::ACROSS_U32 { 85 | let (num_start, num_end) = Self::get_across(arr); 86 | let num = (u32::from_ne_bytes(num_start) & Self::LIMIT_U32) >> Self::OFFSET; 87 | let num_end = 88 | (num_end as u32 & (u32::MAX >> (32 - Self::OFFSET_END_))) << Self::OFFSET_END; 89 | num | num_end 90 | } else { 91 | let num = Self::u32(arr).0; 92 | (num & Self::LIMIT_U32) >> Self::OFFSET 93 | } 94 | } 95 | 96 | fn u32(arr: &[u8]) -> (u32, Range, usize) { 97 | let len = arr[Self::RANGE_LHS..].len(); 98 | if len < 4 { 99 | let mut tmp = [0; 4]; 100 | let range = Self::RANGE_LHS..=len; 101 | tmp[..len].copy_from_slice(&arr[range.clone()]); 102 | (u32::from_le_bytes(tmp), range, len) 103 | } else { 104 | ( 105 | u32::from_ne_bytes(arr[Self::RANGE_U32].try_into().expect(Self::ERR)), 106 | Self::RANGE_U32, 107 | 4, 108 | ) 109 | } 110 | } 111 | 112 | fn u64(arr: &[u8]) -> (u64, Range, usize) { 113 | let len = arr[Self::RANGE_LHS..].len(); 114 | if len < 8 { 115 | let mut tmp = [0; 8]; 116 | let range = Self::RANGE_LHS..=len; 117 | tmp[..len].copy_from_slice(&arr[range.clone()]); 118 | (u64::from_le_bytes(tmp), range, len) 119 | } else { 120 | ( 121 | u64::from_ne_bytes(arr[Self::RANGE_U64].try_into().expect(Self::ERR)), 122 | Self::RANGE_U64, 123 | 8, 124 | ) 125 | } 126 | } 127 | 128 | pub fn get_u64(arr: &[u8]) -> u64 { 129 | if Self::ACROSS_U64 { 130 | let (num_start, num_end) = Self::get_across(arr); 131 | let num = (u64::from_ne_bytes(num_start) & Self::LIMIT_U64) >> Self::OFFSET; 132 | let num_end = 133 | ((num_end as u64) & (u64::MAX >> (64 - Self::OFFSET_END_))) << Self::OFFSET_END; 134 | num | num_end 135 | } else { 136 | // let num = u64::from_ne_bytes(arr[Self::RANGE_U64].try_into().expect(Self::ERR)); 137 | let num = Self::u64(arr).0; 138 | (num & Self::LIMIT_U64) >> Self::OFFSET 139 | } 140 | } 141 | 142 | pub fn get<'a, T: TryFrom<&'a [u8]>>(arr: &'a [u8]) -> T 143 | where 144 | >::Error: std::fmt::Debug, 145 | { 146 | T::try_from(&arr[Self::RANGE]).expect(Self::ERR) 147 | } 148 | 149 | pub fn get_across<'a, T: TryFrom<&'a [u8]>>(arr: &'a [u8]) -> (T, u8) 150 | where 151 | >::Error: std::fmt::Debug, 152 | { 153 | ( 154 | T::try_from(&arr[Self::RANGE_ACROSS]).expect(Self::ERR), 155 | u8::from_ne_bytes(arr[Self::RANGE_RHS2].try_into().expect(Self::ERR)), 156 | ) 157 | } 158 | 159 | pub fn across_end(arr: &mut [u8], num_end: u8) { 160 | let p = &mut arr[Self::RANGE_RHS2]; 161 | let num_old = u8::from_ne_bytes(p.try_into().unwrap()); 162 | let num_new = num_old & !(u8::MAX >> (8 - Self::OFFSET_END_)) | num_end; 163 | p.copy_from_slice(&num_new.to_ne_bytes()); 164 | } 165 | 166 | pub fn set_u8(arr: &mut [u8], num: u8) { 167 | if Self::ACROSS_U08 { 168 | let p = &mut arr[Self::RANGE_ACROSS]; 169 | let num_old = u8::from_ne_bytes(p.try_into().unwrap()); 170 | let num_new = num_old & !Self::LIMIT_U08 | (num << Self::OFFSET); 171 | p.copy_from_slice(&num_new.to_ne_bytes()); 172 | 173 | let num_end = (num >> (8 - Self::OFFSET)) as u8; 174 | Self::across_end(arr, num_end); 175 | } else { 176 | let p = &mut arr[Self::RANGE]; 177 | let num_old = u8::from_ne_bytes(p.try_into().expect(Self::ERR)); 178 | let num_new = num_old & !Self::LIMIT_U08 | (num << Self::OFFSET); 179 | p.copy_from_slice(&num_new.to_ne_bytes()); 180 | } 181 | } 182 | 183 | pub fn set_u16(arr: &mut [u8], num: u16) { 184 | if Self::ACROSS_U16 { 185 | let p = &mut arr[Self::RANGE_ACROSS]; 186 | let num_old = u16::from_ne_bytes(p.try_into().unwrap()); 187 | let num_new = num_old & !Self::LIMIT_U16 | (num << Self::OFFSET); 188 | p.copy_from_slice(&num_new.to_ne_bytes()); 189 | 190 | let num_end = (num >> (16 - Self::OFFSET)) as u8; 191 | Self::across_end(arr, num_end); 192 | } else { 193 | let p = &mut arr[Self::RANGE]; 194 | let num_old = u16::from_ne_bytes(p.try_into().expect(Self::ERR)); 195 | let num_new = num_old & !Self::LIMIT_U16 | (num << Self::OFFSET); 196 | p.copy_from_slice(&num_new.to_ne_bytes()); 197 | } 198 | } 199 | 200 | pub fn set_u32(arr: &mut [u8], num: u32) { 201 | if Self::ACROSS_U32 { 202 | let p = &mut arr[Self::RANGE_ACROSS]; 203 | let num_old = u32::from_ne_bytes(p.try_into().unwrap()); 204 | let num_new = num_old & !Self::LIMIT_U32 | (num << Self::OFFSET); 205 | p.copy_from_slice(&num_new.to_ne_bytes()); 206 | 207 | let num_end = (num >> (32 - Self::OFFSET)) as u8; 208 | Self::across_end(arr, num_end); 209 | } else { 210 | let (num_old, range, len) = Self::u32(arr); 211 | let p = &mut arr[range]; 212 | // let num_old = u32::from_ne_bytes(p.try_into().expect(Self::ERR)); 213 | let num_new = num_old & !Self::LIMIT_U32 | (num << Self::OFFSET); 214 | if len == 4 { 215 | p.copy_from_slice(&num_new.to_ne_bytes()); 216 | } else { 217 | p.copy_from_slice(&num_new.to_le_bytes()[..len]) 218 | } 219 | } 220 | } 221 | 222 | pub fn set_u64(arr: &mut [u8], num: u64) { 223 | if Self::ACROSS_U64 { 224 | let p = &mut arr[Self::RANGE_ACROSS]; 225 | let num_old = u64::from_ne_bytes(p.try_into().unwrap()); 226 | let num_new = num_old & !Self::LIMIT_U64 | (num << Self::OFFSET); 227 | p.copy_from_slice(&num_new.to_ne_bytes()); 228 | 229 | let num_end = (num >> (64 - Self::OFFSET)) as u8; 230 | Self::across_end(arr, num_end); 231 | } else { 232 | let (num_old, range, len) = Self::u64(arr); 233 | let p = &mut arr[range]; 234 | // let num_old = u64::from_ne_bytes(p.try_into().expect(Self::ERR)); 235 | let num_new = num_old & !Self::LIMIT_U64 | (num << Self::OFFSET); 236 | if len == 8 { 237 | p.copy_from_slice(&num_new.to_ne_bytes()); 238 | } else { 239 | p.copy_from_slice(&num_new.to_le_bytes()[..len]) 240 | } 241 | } 242 | } 243 | } 244 | 245 | #[test] 246 | fn test_56_18_26_14_8() { 247 | // [56, 18, 26, 14, 8] 248 | const SIZE: usize = (56 + 18 + 26 + 14 + 8) / 8 + ((56 + 18 + 26 + 14 + 8) % 8 != 0) as usize; 249 | type Bit56 = BitsPos<56, 0>; 250 | type Bit18 = BitsPos<18, 56>; 251 | type Bit26 = BitsPos<26, 74>; 252 | type Bit14 = BitsPos<14, 100>; 253 | type Bit08 = BitsPos<8, 114>; 254 | 255 | assert_eq!(Bit56::RANGE, 0..=6); 256 | assert_eq!(Bit18::RANGE, 7..=9); 257 | assert_eq!(Bit26::RANGE, 9..=12); 258 | assert_eq!(Bit14::RANGE, 12..=14); 259 | assert_eq!(Bit08::RANGE, 14..=15); 260 | 261 | assert_eq!(Bit56::OFFSET, 0); 262 | assert_eq!(Bit18::OFFSET, 0); 263 | assert_eq!(Bit26::OFFSET, 2); 264 | assert_eq!(Bit14::OFFSET, 4); 265 | assert_eq!(Bit08::OFFSET, 2); 266 | 267 | let mut arr = [0; SIZE]; 268 | 269 | Bit08::set_u8(&mut arr, 250); 270 | assert_eq!(Bit08::get_u8(&arr), 250); 271 | Bit08::set_u8(&mut arr, 115); 272 | assert_eq!(Bit08::get_u8(&arr), 115); 273 | Bit08::set_u8(&mut arr, 10); 274 | assert_eq!(Bit08::get_u8(&arr), 10); 275 | 276 | Bit14::set_u16(&mut arr, 0b10_1111_0011_1001); 277 | assert_eq!(Bit14::get_u16(&arr), 0b10_1111_0011_1001); 278 | 279 | Bit18::set_u32(&mut arr, 0b10_1111_0011_1001); 280 | assert_eq!(Bit18::get_u32(&arr), 0b10_1111_0011_1001); 281 | Bit18::set_u32(&mut arr, 0b10_0001_1111_0011_1001); 282 | assert_eq!(Bit18::get_u32(&arr), 0b10_0001_1111_0011_1001); 283 | 284 | Bit26::set_u32(&mut arr, 0b10_0001_1111_0011_1001); 285 | assert_eq!(Bit26::get_u32(&arr), 0b10_0001_1111_0011_1001); 286 | Bit26::set_u32(&mut arr, 0b10_1111_0011_1001_1111_0011_1001); 287 | assert_eq!(Bit26::get_u32(&arr), 0b10_1111_0011_1001_1111_0011_1001); 288 | Bit26::set_u32(&mut arr, u32::MAX); 289 | assert_eq!(Bit26::get_u32(&arr), u32::MAX >> (32 - 26)); 290 | 291 | Bit56::set_u64(&mut arr, 0b10_0001_1111_0011_1001); 292 | assert_eq!(Bit56::get_u64(&arr), 0b10_0001_1111_0011_1001); 293 | Bit56::set_u64(&mut arr, u64::MAX); 294 | assert_eq!(Bit56::get_u64(&arr), u64::MAX >> (64 - 56)); 295 | 296 | Bit56::set_u64(&mut arr, 0); 297 | Bit18::set_u32(&mut arr, 0); 298 | Bit26::set_u32(&mut arr, 0); 299 | Bit14::set_u16(&mut arr, 0); 300 | Bit08::set_u8(&mut arr, 0); 301 | assert_eq!(arr.into_iter().sum::(), 0); 302 | } 303 | 304 | #[test] 305 | fn test_1_32_64() { 306 | const SIZE: usize = (1 + 32 + 64) / 8 + ((1 + 32 + 64) % 8 != 0) as usize; 307 | type Bit01 = BitsPos<1, 0>; 308 | type Bit32 = BitsPos<32, 1>; 309 | type Bit64 = BitsPos<64, 33>; 310 | 311 | let mut arr = [0; SIZE]; 312 | 313 | Bit01::set_u8(&mut arr, 1); 314 | assert_eq!(Bit01::get_u8(&arr), 1); 315 | Bit32::set_u32(&mut arr, u32::MAX); 316 | assert_eq!(Bit32::get_u32(&arr), u32::MAX); 317 | Bit64::set_u64(&mut arr, u64::MAX); 318 | assert_eq!(Bit64::get_u64(&arr), u64::MAX); 319 | } 320 | 321 | #[test] 322 | fn test_16_32_64() { 323 | const SIZE: usize = (16 + 32 + 64) / 8 + ((16 + 32 + 64) % 8 != 0) as usize; 324 | type Bit16 = BitsPos<16, 0>; 325 | type Bit32 = BitsPos<32, 16>; 326 | type Bit64 = BitsPos<64, 48>; 327 | 328 | let mut arr = [0; SIZE]; 329 | 330 | Bit16::set_u16(&mut arr, u16::MAX); 331 | assert_eq!(Bit16::get_u16(&arr), u16::MAX); 332 | Bit32::set_u32(&mut arr, u32::MAX); 333 | assert_eq!(Bit32::get_u32(&arr), u32::MAX); 334 | Bit64::set_u64(&mut arr, u64::MAX); 335 | assert_eq!(Bit64::get_u64(&arr), u64::MAX); 336 | } 337 | 338 | #[test] 339 | fn test_1_3_4_24() { 340 | const SIZE: usize = (1 + 3 + 4 + 24) / 8 + ((1 + 3 + 4 + 24) % 8 != 0) as usize; 341 | type Bit1 = BitsPos<1, 0>; 342 | type Bit3 = BitsPos<3, 1>; 343 | type Bit4 = BitsPos<4, 4>; 344 | type Bit24 = BitsPos<24, 8>; 345 | 346 | assert_eq!(Bit1::RANGE, 0..=0); 347 | assert_eq!(Bit3::RANGE, 0..=0); 348 | assert_eq!(Bit4::RANGE, 0..=0); 349 | assert_eq!(Bit24::RANGE, 1..=3); 350 | 351 | assert_eq!(Bit1::OFFSET, 0); 352 | assert_eq!(Bit3::OFFSET, 1); 353 | assert_eq!(Bit4::OFFSET, 4); 354 | assert_eq!(Bit24::OFFSET, 0); 355 | 356 | assert_eq!(Bit24::RANGE_U32, 1..=4); 357 | 358 | let mut arr = [0; SIZE]; 359 | 360 | Bit1::set_u8(&mut arr, u8::MAX); 361 | assert_eq!(Bit1::get_u8(&arr), u8::MAX >> (8 - 1)); 362 | Bit3::set_u8(&mut arr, u8::MAX); 363 | assert_eq!(Bit3::get_u8(&arr), u8::MAX >> (8 - 3)); 364 | Bit4::set_u8(&mut arr, u8::MAX); 365 | assert_eq!(Bit4::get_u8(&arr), u8::MAX >> (8 - 4)); 366 | Bit24::set_u32(&mut arr, u32::MAX); 367 | assert_eq!(Bit24::get_u32(&arr), u32::MAX >> (32 - 24)); 368 | } 369 | 370 | #[test] 371 | fn test_1_3_4_55_1() { 372 | const SIZE: usize = (1 + 3 + 4 + 55 + 1) / 8 + ((1 + 3 + 4 + 55 + 1) % 8 != 0) as usize; 373 | type Bit1 = BitsPos<1, 0>; 374 | type Bit3 = BitsPos<3, 1>; 375 | type Bit4 = BitsPos<4, 4>; 376 | type Bit55 = BitsPos<55, 8>; 377 | type Bit1_ = BitsPos<1, 63>; 378 | 379 | assert_eq!(Bit1::RANGE, 0..=0); 380 | assert_eq!(Bit3::RANGE, 0..=0); 381 | assert_eq!(Bit4::RANGE, 0..=0); 382 | assert_eq!(Bit55::RANGE, 1..=7); 383 | assert_eq!(Bit1_::RANGE, 7..=7); 384 | 385 | assert_eq!(Bit1::OFFSET, 0); 386 | assert_eq!(Bit3::OFFSET, 1); 387 | assert_eq!(Bit4::OFFSET, 4); 388 | assert_eq!(Bit55::OFFSET, 0); 389 | assert_eq!(Bit1_::OFFSET, 7); 390 | 391 | let mut arr = [0; SIZE]; 392 | 393 | Bit1::set_u8(&mut arr, u8::MAX); 394 | assert_eq!(Bit1::get_u8(&arr), u8::MAX >> (8 - 1)); 395 | Bit3::set_u8(&mut arr, u8::MAX); 396 | assert_eq!(Bit3::get_u8(&arr), u8::MAX >> (8 - 3)); 397 | Bit4::set_u8(&mut arr, u8::MAX); 398 | assert_eq!(Bit4::get_u8(&arr), u8::MAX >> (8 - 4)); 399 | Bit55::set_u64(&mut arr, u64::MAX); 400 | assert_eq!(Bit55::get_u64(&arr), u64::MAX >> (64 - 55)); 401 | Bit1_::set_u8(&mut arr, u8::MAX); 402 | assert_eq!(Bit1_::get_u8(&arr), u8::MAX >> (8 - 1)); 403 | 404 | Bit55::set_u64(&mut arr, u32::MAX as u64); 405 | assert_eq!(Bit55::get_u64(&arr), u32::MAX as u64); 406 | Bit1_::set_u8(&mut arr, 0); 407 | assert_eq!(Bit1_::get_u8(&arr), 0); 408 | 409 | Bit1::set_u8(&mut arr, 0); 410 | Bit3::set_u8(&mut arr, 0); 411 | Bit4::set_u8(&mut arr, 0); 412 | Bit55::set_u64(&mut arr, 0); 413 | 414 | assert_eq!(arr.iter().copied().sum::(), 0); 415 | 416 | Bit1::set_u8(&mut arr, u8::MAX); 417 | Bit3::set_u8(&mut arr, u8::MAX); 418 | Bit4::set_u8(&mut arr, u8::MAX); 419 | Bit55::set_u64(&mut arr, u64::MAX); 420 | Bit1_::set_u8(&mut arr, u8::MAX); 421 | assert_eq!( 422 | arr.iter().map(|&a| a as usize).sum::(), 423 | arr.len() * u8::MAX as usize 424 | ); 425 | } 426 | 427 | // This is on the contrary with test 04-multiple-of-8bits. 428 | // Because that test assumes the length of the array is a multiple of 8 bits, 429 | // but the lib implementation sets less constrains on array length: 430 | // array length can be equal or greater than needed bits. 431 | #[test] 432 | fn test_1_3_4_23() { 433 | const SIZE: usize = (1 + 3 + 4 + 23) / 8 + ((1 + 3 + 4 + 23) % 8 != 0) as usize; 434 | type Bit1 = BitsPos<1, 0>; 435 | type Bit3 = BitsPos<3, 1>; 436 | type Bit4 = BitsPos<4, 4>; 437 | type Bit23 = BitsPos<23, 8>; 438 | 439 | assert_eq!(Bit1::RANGE, 0..=0); 440 | assert_eq!(Bit3::RANGE, 0..=0); 441 | assert_eq!(Bit4::RANGE, 0..=0); 442 | assert_eq!(Bit23::RANGE, 1..=3); 443 | 444 | assert_eq!(Bit1::OFFSET, 0); 445 | assert_eq!(Bit3::OFFSET, 1); 446 | assert_eq!(Bit4::OFFSET, 4); 447 | assert_eq!(Bit23::OFFSET, 0); 448 | 449 | let mut arr = [0; SIZE]; 450 | 451 | Bit1::set_u8(&mut arr, u8::MAX); 452 | assert_eq!(Bit1::get_u8(&arr), u8::MAX >> (8 - 1)); 453 | Bit3::set_u8(&mut arr, u8::MAX); 454 | assert_eq!(Bit3::get_u8(&arr), u8::MAX >> (8 - 3)); 455 | Bit4::set_u8(&mut arr, u8::MAX); 456 | assert_eq!(Bit4::get_u8(&arr), u8::MAX >> (8 - 4)); 457 | Bit23::set_u32(&mut arr, u32::MAX); 458 | assert_eq!(Bit23::get_u32(&arr), u32::MAX >> (32 - 23)); 459 | 460 | assert_eq!( 461 | arr.iter().map(|&a| a as usize).sum::(), 462 | arr.len() * u8::MAX as usize 463 | ); 464 | } 465 | -------------------------------------------------------------------------------- /bitfield/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/01-specifier-types.rs"); 5 | t.pass("tests/02-storage.rs"); 6 | t.pass("tests/03-accessors.rs"); 7 | 8 | // see bits_pos.rs::test_1_3_4_23 9 | t.compile_fail("tests/04-multiple-of-8bits.rs"); 10 | 11 | t.pass("tests/05-accessor-signatures.rs"); 12 | t.pass("tests/06-enums.rs"); 13 | t.pass("tests/07-optional-discriminant.rs"); 14 | t.compile_fail("tests/08-non-power-of-two.rs"); 15 | 16 | // t.compile_fail("tests/09-variant-out-of-range.rs"); 17 | // This is an alternative test for 09: 18 | t.compile_fail("tests/09-variant-out-of-range2.rs"); 19 | 20 | t.pass("tests/10-bits-attribute.rs"); 21 | t.compile_fail("tests/11-bits-attribute-wrong.rs"); 22 | t.pass("tests/12-accessors-edge.rs"); 23 | } 24 | -------------------------------------------------------------------------------- /builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_builder" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = { version = "1.0.49", features = ["diff"] } 17 | 18 | [dependencies] 19 | quote = "1" 20 | syn = "1" 21 | # syn = {version = "1", features = ["extra-traits"]} 22 | proc-macro2 = "1" 23 | -------------------------------------------------------------------------------- /builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::{format_ident, quote}; 4 | use syn::{parse_macro_input, spanned::Spanned, DeriveInput, Error, Ident, Result, Type}; 5 | 6 | #[proc_macro_derive(Builder, attributes(builder))] 7 | pub fn derive(input: TokenStream) -> TokenStream { 8 | let input = parse_macro_input!(input as DeriveInput); 9 | match derive_builder(input) { 10 | Ok(token) => TokenStream::from(token), 11 | Err(err) => TokenStream::from(err.to_compile_error()), 12 | } 13 | } 14 | 15 | fn derive_builder(input: DeriveInput) -> Result { 16 | use syn::{Data, DataStruct, Fields, FieldsNamed}; 17 | if let Data::Struct(DataStruct { 18 | fields: Fields::Named(FieldsNamed { named, .. }), 19 | .. 20 | }) = input.data 21 | { 22 | let (input_name, vis) = (input.ident, input.vis); 23 | let builder_name = format_ident!("{}Builder", input_name); 24 | let fields = named 25 | .iter() 26 | .map(|f| (f.ident.as_ref().expect("field name not found"), &f.ty)); 27 | let idents = fields.clone().map(|(ident, _)| ident); 28 | let builder_fields = fields 29 | .clone() 30 | .map(|(i, t)| quote! {#i: ::core::option::Option<#t>}); 31 | let new_builder = fields.clone().map(__new); 32 | 33 | let mut each_names = Vec::with_capacity(named.len()); 34 | for field in named.iter() { 35 | if let Some(attr) = field.attrs.first() { 36 | each_names.push(each(attr)?); 37 | } else { 38 | each_names.push(None); 39 | } 40 | } 41 | 42 | let (more, impl_fns): (Vec<_>, Vec<_>) = fields 43 | .clone() 44 | .zip(each_names) 45 | .map(|((ident, ty), each_name)| match each_name { 46 | Some(name) => (&name != ident, impl_fn(&vis, ident, ty, Some(&name))), 47 | None => (false, impl_fn(&vis, ident, ty, None)), 48 | }) 49 | .unzip(); 50 | #[rustfmt::skip] 51 | let impl_fns_more = fields.zip(more) 52 | .filter_map(|((ident, ty), m)| { if m { Some(impl_fn(&vis, ident, ty, None)) } else { None } }); 53 | 54 | Ok(quote! { 55 | #vis struct #builder_name { 56 | #(#builder_fields),* 57 | } 58 | 59 | impl #builder_name { 60 | #(#impl_fns)* 61 | #(#impl_fns_more)* 62 | 63 | #vis fn build(&mut self) -> 64 | ::core::result::Result<#input_name, std::boxed::Box> 65 | { 66 | Ok(#input_name { 67 | #( 68 | #idents : self.#idents.take().ok_or_else(|| 69 | format!("`{}` is not set", stringify!(#idents)) 70 | )? 71 | ),* 72 | }) 73 | } 74 | } 75 | 76 | impl #input_name { 77 | #vis fn builder() -> #builder_name { 78 | #builder_name { 79 | #(#new_builder),* 80 | } 81 | } 82 | } 83 | }) 84 | } else { 85 | Err(Error::new(input.span(), "Named Struct Only :)")) 86 | } 87 | } 88 | 89 | fn impl_fn( 90 | vis: &syn::Visibility, 91 | ident: &Ident, 92 | mut ty: &Type, 93 | each_name: Option<&Ident>, 94 | ) -> TokenStream2 { 95 | let vec_t = each_name.is_some(); 96 | match check(&mut ty, vec_t) { 97 | CheckFieldType::Option => quote! { 98 | #vis fn #ident (&mut self, #ident : #ty) -> &mut Self { 99 | self.#ident = ::core::option::Option::Some(::core::option::Option::Some(#ident)); 100 | self 101 | } 102 | }, 103 | CheckFieldType::Vec if vec_t => { 104 | let each_name = each_name.expect("failed to get `each` name"); 105 | quote! { 106 | #vis fn #each_name (&mut self, #each_name : #ty) -> &mut Self { 107 | self.#ident.as_mut().map(|v| v.push(#each_name)); 108 | self 109 | } 110 | } 111 | } 112 | _ => quote! { 113 | #vis fn #ident (&mut self, #ident : #ty) -> &mut Self { 114 | self.#ident = ::core::option::Option::Some(#ident); 115 | self 116 | } 117 | }, 118 | } 119 | } 120 | 121 | fn __new((ident, mut ty): (&Ident, &Type)) -> TokenStream2 { 122 | match check(&mut ty, false) { 123 | CheckFieldType::Option => { 124 | quote! {#ident: ::core::option::Option::Some(::core::option::Option::None)} 125 | } 126 | CheckFieldType::Vec => { 127 | quote! {#ident: ::core::option::Option::Some(::std::vec::Vec::new())} 128 | } 129 | _ => quote! {#ident: ::core::option::Option::None}, 130 | } 131 | } 132 | 133 | // 把 Option 转化成 T 134 | fn check(ty: &mut &Type, vec_t: bool) -> CheckFieldType { 135 | use syn::{ 136 | AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, PathSegment, TypePath, 137 | }; 138 | if let Type::Path(TypePath { 139 | qself: None, 140 | path: Path { 141 | leading_colon, 142 | segments, 143 | }, 144 | }) = ty 145 | { 146 | if leading_colon.is_none() && segments.len() == 1 { 147 | if let Some(PathSegment { 148 | ident, 149 | arguments: 150 | PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }), 151 | }) = segments.first() 152 | { 153 | if let (1, Some(GenericArgument::Type(t))) = (args.len(), args.first()) { 154 | if ident == "Option" { 155 | *ty = t; 156 | return CheckFieldType::Option; 157 | } else if ident == "Vec" { 158 | if vec_t { 159 | *ty = t; 160 | } 161 | return CheckFieldType::Vec; 162 | } 163 | } 164 | } 165 | } 166 | } 167 | CheckFieldType::Raw 168 | } 169 | 170 | enum CheckFieldType { 171 | Raw, 172 | Option, 173 | Vec, 174 | } 175 | 176 | fn each(attr: &syn::Attribute) -> Result> { 177 | use syn::{Lit, Meta, MetaList, MetaNameValue, NestedMeta}; 178 | let meta = attr.parse_meta()?; 179 | match &meta { 180 | Meta::List(MetaList { path, nested, .. }) if path.is_ident("builder") => { 181 | if let Some(NestedMeta::Meta(Meta::NameValue(MetaNameValue { lit, path, .. }))) = 182 | nested.first() 183 | { 184 | match lit { 185 | Lit::Str(s) if path.is_ident("each") => { 186 | Ok(Some(format_ident!("{}", s.value()))) 187 | } 188 | _ => Err(Error::new( 189 | meta.span(), 190 | "expected `builder(each = \"...\")`", 191 | )), 192 | } 193 | } else { 194 | Err(Error::new( 195 | meta.span(), 196 | "expected `builder(each = \"...\")`", 197 | )) 198 | } 199 | } 200 | _ => Ok(None), 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /builder/tests/01-parse.rs: -------------------------------------------------------------------------------- 1 | // This test looks for a derive macro with the right name to exist. For now the 2 | // test doesn't require any specific code to be generated by the macro, so 3 | // returning an empty TokenStream should be sufficient. 4 | // 5 | // Before moving on, have your derive macro parse the macro input as a 6 | // syn::DeriveInput syntax tree. 7 | // 8 | // Spend some time exploring the syn::DeriveInput struct on docs.rs by clicking 9 | // through its fields in the documentation to see whether it matches your 10 | // expectations for what information is available for the macro to work with. 11 | // 12 | // 13 | // Resources: 14 | // 15 | // - The Syn crate for parsing procedural macro input: 16 | // https://github.com/dtolnay/syn 17 | // 18 | // - The DeriveInput syntax tree which represents input of a derive macro: 19 | // https://docs.rs/syn/1.0/syn/struct.DeriveInput.html 20 | // 21 | // - An example of a derive macro implemented using Syn: 22 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize 23 | 24 | use derive_builder::Builder; 25 | 26 | #[derive(Builder)] 27 | pub struct Command { 28 | executable: String, 29 | args: Vec, 30 | env: Vec, 31 | current_dir: String, 32 | } 33 | 34 | fn main() {} 35 | -------------------------------------------------------------------------------- /builder/tests/02-create-builder.rs: -------------------------------------------------------------------------------- 1 | // Have the macro produce a struct for the builder state, and a `builder` 2 | // function that creates an empty instance of the builder. 3 | // 4 | // As a quick start, try generating the following code (but make sure the type 5 | // name matches what is in the caller's input). 6 | // 7 | // impl Command { 8 | // pub fn builder() {} 9 | // } 10 | // 11 | // At this point the test should pass because it isn't doing anything with the 12 | // builder yet, so `()` as the builder type is as good as any other. 13 | // 14 | // Before moving on, have the macro also generate: 15 | // 16 | // pub struct CommandBuilder { 17 | // executable: Option, 18 | // args: Option>, 19 | // env: Option>, 20 | // current_dir: Option, 21 | // } 22 | // 23 | // and in the `builder` function: 24 | // 25 | // impl Command { 26 | // pub fn builder() -> CommandBuilder { 27 | // CommandBuilder { 28 | // executable: None, 29 | // args: None, 30 | // env: None, 31 | // current_dir: None, 32 | // } 33 | // } 34 | // } 35 | // 36 | // 37 | // Resources: 38 | // 39 | // - The Quote crate for putting together output from a macro: 40 | // https://github.com/dtolnay/quote 41 | // 42 | // - Joining together the type name + "Builder" to make the builder's name: 43 | // https://docs.rs/syn/1.0/syn/struct.Ident.html 44 | 45 | use derive_builder::Builder; 46 | 47 | #[derive(Builder)] 48 | pub struct Command { 49 | executable: String, 50 | args: Vec, 51 | env: Vec, 52 | current_dir: String, 53 | } 54 | 55 | fn main() { 56 | let builder = Command::builder(); 57 | 58 | let _ = builder; 59 | } 60 | -------------------------------------------------------------------------------- /builder/tests/03-call-setters.rs: -------------------------------------------------------------------------------- 1 | // Generate methods on the builder for setting a value of each of the struct 2 | // fields. 3 | // 4 | // impl CommandBuilder { 5 | // fn executable(&mut self, executable: String) -> &mut Self { 6 | // self.executable = Some(executable); 7 | // self 8 | // } 9 | // 10 | // ... 11 | // } 12 | 13 | use derive_builder::Builder; 14 | 15 | #[derive(Builder)] 16 | pub struct Command { 17 | executable: String, 18 | args: Vec, 19 | env: Vec, 20 | current_dir: String, 21 | } 22 | 23 | fn main() { 24 | let mut builder = Command::builder(); 25 | builder.executable("cargo".to_owned()); 26 | builder.args(vec!["build".to_owned(), "--release".to_owned()]); 27 | builder.env(vec![]); 28 | builder.current_dir("..".to_owned()); 29 | } 30 | -------------------------------------------------------------------------------- /builder/tests/04-call-build.rs: -------------------------------------------------------------------------------- 1 | // Generate a `build` method to go from builder to original struct. 2 | // 3 | // This method should require that every one of the fields has been explicitly 4 | // set; it should return an error if a field is missing. The precise error type 5 | // is not important. Consider using Box, which you can construct 6 | // using the impl From for Box. 7 | // 8 | // impl CommandBuilder { 9 | // pub fn build(&mut self) -> Result> { 10 | // ... 11 | // } 12 | // } 13 | 14 | use derive_builder::Builder; 15 | 16 | #[derive(Builder)] 17 | pub struct Command { 18 | executable: String, 19 | args: Vec, 20 | env: Vec, 21 | current_dir: String, 22 | } 23 | 24 | fn main() { 25 | let mut builder = Command::builder(); 26 | builder.executable("cargo".to_owned()); 27 | builder.args(vec!["build".to_owned(), "--release".to_owned()]); 28 | builder.env(vec![]); 29 | builder.current_dir("..".to_owned()); 30 | 31 | let command = builder.build().unwrap(); 32 | assert_eq!(command.executable, "cargo"); 33 | } 34 | -------------------------------------------------------------------------------- /builder/tests/05-method-chaining.rs: -------------------------------------------------------------------------------- 1 | // This test case should be a freebie if the previous ones are already working. 2 | // It shows that we can chain method calls on the builder. 3 | 4 | use derive_builder::Builder; 5 | 6 | #[derive(Builder)] 7 | pub struct Command { 8 | executable: String, 9 | args: Vec, 10 | env: Vec, 11 | current_dir: String, 12 | } 13 | 14 | fn main() { 15 | let command = Command::builder() 16 | .executable("cargo".to_owned()) 17 | .args(vec!["build".to_owned(), "--release".to_owned()]) 18 | .env(vec![]) 19 | .current_dir("..".to_owned()) 20 | .build() 21 | .unwrap(); 22 | 23 | assert_eq!(command.executable, "cargo"); 24 | } 25 | -------------------------------------------------------------------------------- /builder/tests/06-optional-field.rs: -------------------------------------------------------------------------------- 1 | // Some fields may not always need to be specified. Typically these would be 2 | // represented as Option in the struct being built. 3 | // 4 | // Have your macro identify fields in the macro input whose type is Option and 5 | // make the corresponding builder method optional for the caller. In the test 6 | // case below, current_dir is optional and not passed to one of the builders in 7 | // main. 8 | // 9 | // Be aware that the Rust compiler performs name resolution only after macro 10 | // expansion has finished completely. That means during the evaluation of a 11 | // procedural macro, "types" do not exist yet, only tokens. In general many 12 | // different token representations may end up referring to the same type: for 13 | // example `Option` and `std::option::Option` and `> as 14 | // IntoIterator>::Item` are all different names for the same type. Conversely, 15 | // a single token representation may end up referring to many different types in 16 | // different places; for example the meaning of `Error` will depend on whether 17 | // the surrounding scope has imported std::error::Error or std::io::Error. As a 18 | // consequence, it isn't possible in general for a macro to compare two token 19 | // representations and tell whether they refer to the same type. 20 | // 21 | // In the context of the current test case, all of this means that there isn't 22 | // some compiler representation of Option that our macro can compare fields 23 | // against to find out whether they refer to the eventual Option type after name 24 | // resolution. Instead all we get to look at are the tokens of how the user has 25 | // described the type in their code. By necessity, the macro will look for 26 | // fields whose type is written literally as Option<...> and will not realize 27 | // when the same type has been written in some different way. 28 | // 29 | // The syntax tree for types parsed from tokens is somewhat complicated because 30 | // there is such a large variety of type syntax in Rust, so here is the nested 31 | // data structure representation that your macro will want to identify: 32 | // 33 | // Type::Path( 34 | // TypePath { 35 | // qself: None, 36 | // path: Path { 37 | // segments: [ 38 | // PathSegment { 39 | // ident: "Option", 40 | // arguments: PathArguments::AngleBracketed( 41 | // AngleBracketedGenericArguments { 42 | // args: [ 43 | // GenericArgument::Type( 44 | // ... 45 | // ), 46 | // ], 47 | // }, 48 | // ), 49 | // }, 50 | // ], 51 | // }, 52 | // }, 53 | // ) 54 | 55 | use derive_builder::Builder; 56 | 57 | #[derive(Builder)] 58 | pub struct Command { 59 | executable: String, 60 | args: Vec, 61 | env: Vec, 62 | current_dir: Option, 63 | } 64 | 65 | fn main() { 66 | let command = Command::builder() 67 | .executable("cargo".to_owned()) 68 | .args(vec!["build".to_owned(), "--release".to_owned()]) 69 | .env(vec![]) 70 | .build() 71 | .unwrap(); 72 | assert!(command.current_dir.is_none()); 73 | 74 | let command = Command::builder() 75 | .executable("cargo".to_owned()) 76 | .args(vec!["build".to_owned(), "--release".to_owned()]) 77 | .env(vec![]) 78 | .current_dir("..".to_owned()) 79 | .build() 80 | .unwrap(); 81 | assert!(command.current_dir.is_some()); 82 | } 83 | -------------------------------------------------------------------------------- /builder/tests/07-repeated-field-same-name.rs: -------------------------------------------------------------------------------- 1 | // The std::process::Command builder handles args in a way that is potentially 2 | // more convenient than passing a full vector of args to the builder all at 3 | // once. 4 | // 5 | // Look for a field attribute #[builder(each = "...")] on each field. The 6 | // generated code may assume that fields with this attribute have the type Vec 7 | // and should use the word given in the string literal as the name for the 8 | // corresponding builder method which accepts one vector element at a time. 9 | // 10 | // In order for the compiler to know that these builder attributes are 11 | // associated with your macro, they must be declared at the entry point of the 12 | // derive macro. Otherwise the compiler will report them as unrecognized 13 | // attributes and refuse to compile the caller's code. 14 | // 15 | // #[proc_macro_derive(Builder, attributes(builder))] 16 | // 17 | // These are called inert attributes. The word "inert" indicates that these 18 | // attributes do not correspond to a macro invocation on their own; they are 19 | // simply looked at by other macro invocations. 20 | // 21 | // If the new one-at-a-time builder method is given the same name as the field, 22 | // avoid generating an all-at-once builder method for that field because the 23 | // names would conflict. 24 | // 25 | // 26 | // Resources: 27 | // 28 | // - Relevant syntax tree types: https://docs.rs/syn/1.0/syn/struct.Attribute.html https://docs.rs/syn/1.0/syn/enum.Meta.html 29 | 30 | use derive_builder::Builder; 31 | 32 | #[derive(Builder)] 33 | pub struct Command { 34 | executable: String, 35 | #[builder(each = "arg")] 36 | args: Vec, 37 | #[builder(each = "env")] 38 | env: Vec, 39 | current_dir: Option, 40 | } 41 | 42 | fn main() { 43 | let command = Command::builder().executable("cargo".to_owned()) 44 | .arg("build".to_owned()) 45 | .arg("--release".to_owned()) 46 | .env("LTO=true".to_owned()) 47 | .build() 48 | .unwrap(); 49 | 50 | assert_eq!(command.executable, "cargo"); 51 | assert_eq!(command.env, vec!["LTO=true"]); 52 | assert_eq!(command.args, vec!["build", "--release"]); 53 | } 54 | -------------------------------------------------------------------------------- /builder/tests/07-repeated-field.rs: -------------------------------------------------------------------------------- 1 | // The std::process::Command builder handles args in a way that is potentially 2 | // more convenient than passing a full vector of args to the builder all at 3 | // once. 4 | // 5 | // Look for a field attribute #[builder(each = "...")] on each field. The 6 | // generated code may assume that fields with this attribute have the type Vec 7 | // and should use the word given in the string literal as the name for the 8 | // corresponding builder method which accepts one vector element at a time. 9 | // 10 | // In order for the compiler to know that these builder attributes are 11 | // associated with your macro, they must be declared at the entry point of the 12 | // derive macro. Otherwise the compiler will report them as unrecognized 13 | // attributes and refuse to compile the caller's code. 14 | // 15 | // #[proc_macro_derive(Builder, attributes(builder))] 16 | // 17 | // These are called inert attributes. The word "inert" indicates that these 18 | // attributes do not correspond to a macro invocation on their own; they are 19 | // simply looked at by other macro invocations. 20 | // 21 | // If the new one-at-a-time builder method is given the same name as the field, 22 | // avoid generating an all-at-once builder method for that field because the 23 | // names would conflict. 24 | // 25 | // 26 | // Resources: 27 | // 28 | // - Relevant syntax tree types: 29 | // https://docs.rs/syn/1.0/syn/struct.Attribute.html 30 | // https://docs.rs/syn/1.0/syn/enum.Meta.html 31 | 32 | use derive_builder::Builder; 33 | 34 | #[derive(Builder)] 35 | pub struct Command { 36 | executable: String, 37 | #[builder(each = "arg")] 38 | args: Vec, 39 | #[builder(each = "env")] 40 | env: Vec, 41 | current_dir: Option, 42 | } 43 | 44 | fn main() { 45 | let command = Command::builder() 46 | .executable("cargo".to_owned()) 47 | .arg("build".to_owned()) 48 | .arg("--release".to_owned()) 49 | .build() 50 | .unwrap(); 51 | 52 | assert_eq!(command.executable, "cargo"); 53 | assert_eq!(command.args, vec!["build", "--release"]); 54 | } 55 | -------------------------------------------------------------------------------- /builder/tests/08-unrecognized-attribute.rs: -------------------------------------------------------------------------------- 1 | // Ensure that your macro reports a reasonable error message when the caller 2 | // mistypes the inert attribute in various ways. This is a compile_fail test. 3 | // 4 | // The preferred way to report an error from a procedural macro is by including 5 | // an invocation of the standard library's compile_error macro in the code 6 | // emitted by the procedural macro. 7 | // 8 | // 9 | // Resources: 10 | // 11 | // - The compile_error macro for emitting basic custom errors: 12 | // https://doc.rust-lang.org/std/macro.compile_error.html 13 | // 14 | // - Lowering a syn::Error into an invocation of compile_error: 15 | // https://docs.rs/syn/1.0/syn/struct.Error.html#method.to_compile_error 16 | 17 | use derive_builder::Builder; 18 | 19 | #[derive(Builder)] 20 | pub struct Command { 21 | executable: String, 22 | #[builder(eac = "arg")] 23 | args: Vec, 24 | env: Vec, 25 | current_dir: Option, 26 | } 27 | 28 | fn main() {} 29 | -------------------------------------------------------------------------------- /builder/tests/08-unrecognized-attribute.stderr: -------------------------------------------------------------------------------- 1 | error: expected `builder(each = "...")` 2 | --> tests/08-unrecognized-attribute.rs:22:7 3 | | 4 | 22 | #[builder(eac = "arg")] 5 | | ^^^^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /builder/tests/09-redefined-prelude-types.rs: -------------------------------------------------------------------------------- 1 | // Does your macro still work if some of the standard library prelude item names 2 | // mean something different in the caller's code? 3 | // 4 | // It may seem unreasonable to consider this case, but it does arise in 5 | // practice. Most commonly for Result, where crates sometimes use a Result type 6 | // alias with a single type parameter which assumes their crate's error type. 7 | // Such a type alias would break macro-generated code that expects Result to 8 | // have two type parameters. As another example, Hyper 0.10 used to define 9 | // hyper::Ok as a re-export of hyper::status::StatusCode::Ok which is totally 10 | // different from Result::Ok. This caused problems in code doing `use hyper::*` 11 | // together with macro-generated code referring to Ok. 12 | // 13 | // Generally all macros (procedural as well as macro_rules) designed to be used 14 | // by other people should refer to every single thing in their expanded code 15 | // through an absolute path, such as std::result::Result. 16 | 17 | use derive_builder::Builder; 18 | 19 | type Option = (); 20 | type Some = (); 21 | type None = (); 22 | type Result = (); 23 | type Box = (); 24 | 25 | #[derive(Builder)] 26 | pub struct Command { 27 | executable: String, 28 | } 29 | 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /builder/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/01-parse.rs"); 5 | t.pass("tests/02-create-builder.rs"); 6 | t.pass("tests/03-call-setters.rs"); 7 | t.pass("tests/04-call-build.rs"); 8 | t.pass("tests/05-method-chaining.rs"); 9 | t.pass("tests/06-optional-field.rs"); 10 | t.pass("tests/07-repeated-field.rs"); 11 | t.pass("tests/07-repeated-field-same-name.rs"); 12 | t.compile_fail("tests/08-unrecognized-attribute.rs"); 13 | t.pass("tests/09-redefined-prelude-types.rs"); 14 | } 15 | -------------------------------------------------------------------------------- /debug/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_debug" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = { version = "1.0.49", features = ["diff"] } 17 | 18 | [dependencies] 19 | quote = "1" 20 | # syn = "1" 21 | syn = {version = "1", features = ["extra-traits"]} 22 | proc-macro2 = "1" 23 | -------------------------------------------------------------------------------- /debug/src/bound.rs: -------------------------------------------------------------------------------- 1 | use syn::{ 2 | punctuated::Punctuated, token::Comma, Attribute, Ident, LitStr, Meta, Result, WherePredicate, 3 | }; 4 | 5 | type PunctPreds = Punctuated; 6 | type PredsIdent = (PunctPreds, std::collections::HashSet); 7 | pub type OptPredsIdent = Option; 8 | 9 | pub fn struct_attr(attrs: &[Attribute]) -> OptPredsIdent { 10 | attrs 11 | .iter() 12 | .find_map(|attr| attr.parse_meta().ok().and_then(search::debug)) 13 | } 14 | 15 | pub fn field_attr(meta: Meta, opt_preds_ident: &mut OptPredsIdent) -> Option> { 16 | fn transform( 17 | preds_ident: PredsIdent, 18 | opt_preds_ident: &mut OptPredsIdent, 19 | ) -> Option> { 20 | if let Some((p, s)) = opt_preds_ident.as_mut() { 21 | p.extend(preds_ident.0); 22 | s.extend(preds_ident.1); 23 | } else { 24 | opt_preds_ident.replace(preds_ident); 25 | } 26 | None 27 | } 28 | search::debug(meta).and_then(|preds_ident| transform(preds_ident, opt_preds_ident)) 29 | } 30 | 31 | mod search { 32 | use super::{OptPredsIdent, PunctPreds}; 33 | use syn::{ 34 | parse_quote, punctuated::Punctuated, Ident, Lit, Meta, MetaList, MetaNameValue, NestedMeta, 35 | Path, PredicateType, Type, TypePath, WherePredicate, 36 | }; 37 | 38 | pub fn debug(meta: Meta) -> OptPredsIdent { 39 | let debug: Path = parse_quote!(debug); 40 | if meta.path() == &debug { 41 | search_bound(meta) 42 | } else { 43 | None 44 | } 45 | } 46 | 47 | fn search_bound(meta: Meta) -> OptPredsIdent { 48 | if let Meta::List(MetaList { nested, .. }) = meta { 49 | nested.iter().find_map(predicate) 50 | } else { 51 | None 52 | } 53 | } 54 | 55 | fn predicate(m: &NestedMeta) -> OptPredsIdent { 56 | let bound: Path = parse_quote!(bound); 57 | match m { 58 | NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit, .. })) 59 | if path == &bound => 60 | { 61 | if let Lit::Str(s) = lit { 62 | let wp: PunctPreds = s.parse_with(Punctuated::parse_terminated).ok()?; 63 | let set = wp.iter().filter_map(search_generics_ident).collect(); 64 | Some((wp, set)) 65 | } else { 66 | None 67 | } 68 | } 69 | _ => None, 70 | } 71 | } 72 | 73 | fn search_generics_ident(w: &WherePredicate) -> Option { 74 | if let WherePredicate::Type(PredicateType { 75 | bounded_ty: Type::Path(TypePath { path, .. }), 76 | .. 77 | }) = w 78 | { 79 | // 最好校验 bound 属性内的泛型关联类型是否明确写明 `Debug`,但是这比较麻烦 80 | // 所以,只要在 bound 属性写了泛型 T 的相关语句都不会给 T 增加 Debug 约束 81 | path.segments.first().map(|seg| seg.ident.clone()) 82 | } else { 83 | None 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /debug/src/generics.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use syn::{parse_quote, Ident, Type}; 3 | 4 | pub fn add_debug<'f>( 5 | ty: &mut syn::TypeParam, 6 | field_ty: impl Iterator, 7 | associated: &mut HashSet<&'f Type>, 8 | bound: &HashSet, 9 | ) { 10 | let syn::TypeParam { 11 | ref ident, bounds, .. 12 | } = ty; 13 | let phantom_data: Type = parse_quote!(PhantomData<#ident>); 14 | // do not add Debug trait constrain 15 | // when the generics T contains associated types or T is PhantomData or 16 | // `T::Associated: Debug` is in bound 17 | if !field_ty.fold(bound.contains(ident), |acc, t| { 18 | search(t, ident, associated) || t == &phantom_data || acc 19 | }) { 20 | bounds.push(parse_quote!(::std::fmt::Debug)); 21 | } 22 | } 23 | 24 | // 处理字段类型的关联类型 25 | fn search<'f>(ty: &'f Type, ident: &Ident, associated: &mut HashSet<&'f Type>) -> bool { 26 | use syn::{AngleBracketedGenericArguments, GenericArgument, Path, PathArguments, TypePath}; 27 | 28 | // 把 T::Associated 添加到 where 语句增加项 29 | fn check_associated<'f>( 30 | ty: &'f Type, 31 | ident: &Ident, 32 | associated: &mut HashSet<&'f Type>, 33 | ) -> bool { 34 | if let Type::Path(TypePath { 35 | path: 36 | Path { 37 | segments, 38 | leading_colon: None, 39 | }, 40 | .. 41 | }) = ty 42 | { 43 | if segments.len() > 1 44 | && segments 45 | .first() 46 | .map(|seg| &seg.ident == ident) 47 | .unwrap_or(false) 48 | { 49 | associated.insert(ty); 50 | return true; 51 | } 52 | } 53 | false 54 | } 55 | 56 | // 一层尖括号泛型中的关联类型 path:: 57 | fn check_angle_bracket_associated<'f>( 58 | ty: &'f Type, 59 | ident: &Ident, 60 | associated: &mut HashSet<&'f Type>, 61 | ) -> bool { 62 | // 检查尖括号内的泛型是否为关联类型 63 | fn check<'f>( 64 | arg: &'f PathArguments, 65 | ident: &Ident, 66 | associated: &mut HashSet<&'f Type>, 67 | ) -> bool { 68 | if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { args, .. }) = arg 69 | { 70 | args.iter().fold(false, |acc, arg| { 71 | if let GenericArgument::Type(t) = arg { 72 | check_associated(t, ident, associated) || acc 73 | } else { 74 | acc 75 | } 76 | }) 77 | } else { 78 | false 79 | } 80 | } 81 | if let Type::Path(TypePath { 82 | path: Path { segments, .. }, 83 | .. 84 | }) = ty 85 | { 86 | // 只考虑最后路径上的泛型,即 a::b::c:: 形式 87 | return segments 88 | .last() 89 | .map(|seg| check(&seg.arguments, ident, associated)) 90 | .unwrap_or(false); 91 | } 92 | false 93 | } 94 | 95 | check_associated(ty, ident, associated) || check_angle_bracket_associated(ty, ident, associated) 96 | } 97 | -------------------------------------------------------------------------------- /debug/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::TokenStream as TokenStream2; 3 | use quote::quote; 4 | use std::collections::HashSet; 5 | use syn::{parse_macro_input, parse_quote, spanned::Spanned, DeriveInput, Result}; 6 | 7 | #[proc_macro_derive(CustomDebug, attributes(debug))] 8 | pub fn derive(input: TokenStream) -> TokenStream { 9 | let input = parse_macro_input!(input as DeriveInput); 10 | match custom_debug(input) { 11 | Ok(token) => token, 12 | Err(err) => err.to_compile_error(), 13 | } 14 | .into() 15 | } 16 | 17 | mod bound; 18 | mod generics; 19 | 20 | fn custom_debug(mut input: DeriveInput) -> Result { 21 | use syn::{Data, DataStruct, Fields, FieldsNamed}; 22 | if let Data::Struct(DataStruct { 23 | fields: Fields::Named(FieldsNamed { named, .. }), 24 | .. 25 | }) = &input.data 26 | { 27 | let (ident, generics) = (&input.ident, &mut input.generics); 28 | let mut opt = bound::struct_attr(&input.attrs); 29 | 30 | // 构造 fmt 方法内部的标记 31 | let ident_str = ident.to_string(); 32 | let field_idents = named.iter().map(|f| f.ident.as_ref().unwrap()); 33 | let field_idents_str = field_idents.clone().map(|i| i.to_string()); 34 | let field_rhs = field_idents 35 | .zip(named.iter().map(|f| f.attrs.as_slice())) 36 | .map(|(i, a)| attr_debug(a, i, &mut opt)) 37 | .collect::>>()?; 38 | 39 | // 在某些泛型关联类型的情况下,放宽 T: Debug 约束 40 | let mut associated = HashSet::with_capacity(8); 41 | let (mut bound_where_clause, bound_generics) = opt.unwrap_or_default(); 42 | let closure = |g: &mut syn::TypeParam| { 43 | generics::add_debug( 44 | g, 45 | named.iter().map(|f| &f.ty), 46 | &mut associated, 47 | &bound_generics, 48 | ) 49 | }; 50 | generics.type_params_mut().for_each(closure); 51 | let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); 52 | 53 | let mut where_clause = where_clause 54 | .cloned() 55 | .unwrap_or_else(|| parse_quote! { where }); 56 | let convert = 57 | |ty: &syn::Type| -> syn::WherePredicate { parse_quote!(#ty: ::std::fmt::Debug) }; 58 | bound_where_clause.extend(associated.into_iter().map(convert)); 59 | where_clause.predicates.extend(bound_where_clause); 60 | 61 | Ok(quote! { 62 | impl #impl_generics ::std::fmt::Debug for #ident #ty_generics #where_clause { 63 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::result::Result<(), ::std::fmt::Error> { 64 | f.debug_struct(&#ident_str) 65 | #( 66 | .field(&#field_idents_str, #field_rhs) 67 | )* 68 | .finish() 69 | } 70 | } 71 | }) 72 | } else { 73 | Err(syn::Error::new(input.span(), "Named Struct Only :)")) 74 | } 75 | } 76 | 77 | fn attr_debug( 78 | attrs: &[syn::Attribute], 79 | ident: &syn::Ident, 80 | opt_preds_ident: &mut bound::OptPredsIdent, 81 | ) -> Result { 82 | use syn::{Lit, LitStr, Meta, MetaNameValue}; 83 | fn debug( 84 | attr: &syn::Attribute, 85 | opt_preds_ident: &mut bound::OptPredsIdent, 86 | ) -> Option> { 87 | match attr.parse_meta() { 88 | Ok(Meta::NameValue(MetaNameValue { 89 | path, 90 | lit: Lit::Str(s), 91 | .. 92 | })) if path.is_ident("debug") => Some(Ok(s)), 93 | Ok(meta) => bound::field_attr(meta, opt_preds_ident), 94 | _ => Some(Err(syn::Error::new( 95 | attr.span(), 96 | "failed to parse attr meta", 97 | ))), 98 | } 99 | } 100 | match attrs.iter().find_map(|attr| debug(attr, opt_preds_ident)) { 101 | None => Ok(quote! { &self.#ident }), 102 | Some(Ok(fmt)) => Ok(quote! { &::std::format_args!(#fmt, self.#ident) }), 103 | Some(Err(err)) => Err(err), 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /debug/tests/01-parse.rs: -------------------------------------------------------------------------------- 1 | // This test looks for a derive macro with the right name to exist. For now the 2 | // test doesn't require any specific code to be generated by the macro, so 3 | // returning an empty TokenStream should be sufficient. 4 | // 5 | // Before moving on, have your derive macro parse the macro input as a 6 | // syn::DeriveInput syntax tree. 7 | // 8 | // 9 | // Resources: 10 | // 11 | // - The DeriveInput syntax tree which represents input of a derive macro: 12 | // https://docs.rs/syn/1.0/syn/struct.DeriveInput.html 13 | // 14 | // - An example of a derive macro implemented using Syn: 15 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize 16 | 17 | use derive_debug::CustomDebug; 18 | 19 | #[derive(CustomDebug)] 20 | pub struct Field { 21 | name: &'static str, 22 | bitmask: u16, 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /debug/tests/02-impl-debug.rs: -------------------------------------------------------------------------------- 1 | // Emit an implementation of std::fmt::Debug for a basic struct with named 2 | // fields and no generic type parameters. 3 | // 4 | // Note that there is no enforced relationship between the name of a derive 5 | // macro and the trait that it implements. Here the macro is named CustomDebug 6 | // but the trait impls it generates are for Debug. As a convention, typically 7 | // derive macros implement a trait with the same name as a macro. 8 | // 9 | // 10 | // Resources: 11 | // 12 | // - The Debug trait: 13 | // https://doc.rust-lang.org/std/fmt/trait.Debug.html 14 | // 15 | // - The DebugStruct helper for formatting structs correctly: 16 | // https://doc.rust-lang.org/std/fmt/struct.DebugStruct.html 17 | 18 | use derive_debug::CustomDebug; 19 | 20 | #[derive(CustomDebug)] 21 | pub struct Field { 22 | name: &'static str, 23 | bitmask: u8, 24 | } 25 | 26 | fn main() { 27 | let f = Field { 28 | name: "F", 29 | bitmask: 0b00011100, 30 | }; 31 | 32 | let debug = format!("{:?}", f); 33 | 34 | assert!(debug.starts_with(r#"Field { name: "F","#)); 35 | } 36 | -------------------------------------------------------------------------------- /debug/tests/03-custom-format.rs: -------------------------------------------------------------------------------- 1 | // Look for a field attribute #[debug = "..."] on each field. If present, find a 2 | // way to format the field according to the format string given by the caller in 3 | // the attribute. 4 | // 5 | // In order for the compiler to recognize this inert attribute as associated 6 | // with your derive macro, it will need to be declared at the entry point of the 7 | // derive macro. 8 | // 9 | // #[proc_macro_derive(CustomDebug, attributes(debug))] 10 | // 11 | // These are called inert attributes. The word "inert" indicates that these 12 | // attributes do not correspond to a macro invocation on their own; they are 13 | // simply looked at by other macro invocations. 14 | // 15 | // 16 | // Resources: 17 | // 18 | // - Relevant syntax tree types: 19 | // https://docs.rs/syn/1.0/syn/struct.Attribute.html 20 | // https://docs.rs/syn/1.0/syn/enum.Meta.html 21 | // 22 | // - Macro for applying a format string to some runtime value: 23 | // https://doc.rust-lang.org/std/macro.format_args.html 24 | 25 | use derive_debug::CustomDebug; 26 | 27 | #[derive(CustomDebug)] 28 | pub struct Field { 29 | name: &'static str, 30 | #[debug = "0b{:08b}"] 31 | bitmask: u8, 32 | } 33 | 34 | fn main() { 35 | let f = Field { 36 | name: "F", 37 | bitmask: 0b00011100, 38 | }; 39 | 40 | let debug = format!("{:?}", f); 41 | let expected = r#"Field { name: "F", bitmask: 0b00011100 }"#; 42 | 43 | assert_eq!(debug, expected); 44 | } 45 | -------------------------------------------------------------------------------- /debug/tests/04-type-parameter-debug.rs: -------------------------------------------------------------------------------- 1 | // Figure out what impl needs to be generated for the Debug impl of Field. 2 | // This will involve adding a trait bound to the T type parameter of the 3 | // generated impl. 4 | // 5 | // Callers should be free to instantiate Field with a type parameter T which 6 | // does not implement Debug, but such a Field will not fulfill the trait 7 | // bounds of the generated Debug impl and so will not be printable via Debug. 8 | // 9 | // 10 | // Resources: 11 | // 12 | // - Representation of generics in the Syn syntax tree: https://docs.rs/syn/1.0/syn/struct.Generics.html 13 | // 14 | // - A helper for placing generics into an impl signature: https://docs.rs/syn/1.0/syn/struct.Generics.html#method.split_for_impl 15 | // 16 | // - Example code from Syn which deals with type parameters: https://github.com/dtolnay/syn/tree/master/examples/heapsize 17 | 18 | use derive_debug::CustomDebug; 19 | 20 | #[derive(CustomDebug)] 21 | pub struct Field { 22 | value: T, 23 | #[debug = "0b{:08b}"] 24 | bitmask: u8, 25 | } 26 | 27 | fn main() { 28 | let f = Field { value: "F", bitmask: 0b00011100 }; 29 | 30 | let debug = format!("{:?}", f); 31 | let expected = r#"Field { value: "F", bitmask: 0b00011100 }"#; 32 | 33 | assert_eq!(debug, expected); 34 | } 35 | -------------------------------------------------------------------------------- /debug/tests/04-type-parameter.rs: -------------------------------------------------------------------------------- 1 | // Figure out what impl needs to be generated for the Debug impl of Field. 2 | // This will involve adding a trait bound to the T type parameter of the 3 | // generated impl. 4 | // 5 | // Callers should be free to instantiate Field with a type parameter T which 6 | // does not implement Debug, but such a Field will not fulfill the trait 7 | // bounds of the generated Debug impl and so will not be printable via Debug. 8 | // 9 | // 10 | // Resources: 11 | // 12 | // - Representation of generics in the Syn syntax tree: 13 | // https://docs.rs/syn/1.0/syn/struct.Generics.html 14 | // 15 | // - A helper for placing generics into an impl signature: 16 | // https://docs.rs/syn/1.0/syn/struct.Generics.html#method.split_for_impl 17 | // 18 | // - Example code from Syn which deals with type parameters: 19 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize 20 | 21 | use derive_debug::CustomDebug; 22 | 23 | #[derive(CustomDebug)] 24 | pub struct Field { 25 | value: T, 26 | #[debug = "0b{:08b}"] 27 | bitmask: u8, 28 | } 29 | 30 | fn main() { 31 | let f = Field { 32 | value: "F", 33 | bitmask: 0b00011100, 34 | }; 35 | 36 | let debug = format!("{:?}", f); 37 | let expected = r#"Field { value: "F", bitmask: 0b00011100 }"#; 38 | 39 | assert_eq!(debug, expected); 40 | } 41 | -------------------------------------------------------------------------------- /debug/tests/05-phantom-data.rs: -------------------------------------------------------------------------------- 1 | // Some generic types implement Debug even when their type parameters do not. 2 | // One example is PhantomData which has this impl: 3 | // 4 | // impl Debug for PhantomData {...} 5 | // 6 | // To accomodate this sort of situation, one way would be to generate a trait 7 | // bound `#field_ty: Debug` for each field type in the input, rather than 8 | // `#param: Debug` for each generic parameter. For example in the case of the 9 | // struct Field in the test case below, it would be: 10 | // 11 | // impl Debug for Field 12 | // where 13 | // PhantomData: Debug, 14 | // {...} 15 | // 16 | // This approach has fatal downsides that will be covered in subsequent test 17 | // cases. 18 | // 19 | // Instead we'll recognize PhantomData as a special case since it is so common, 20 | // and later provide an escape hatch for the caller to override inferred bounds 21 | // in other application-specific special cases. 22 | // 23 | // Concretely, for each type parameter #param in the input, you will need to 24 | // determine whether it is only ever mentioned inside of a PhantomData and if so 25 | // then avoid emitting a `#param: Debug` bound on that parameter. For the 26 | // purpose of the test suite it is sufficient to look for exactly the field type 27 | // PhantomData<#param>. In reality we may also care about recognizing other 28 | // possible arrangements like PhantomData<&'a #param> if the semantics of the 29 | // trait we are deriving would make it likely that callers would end up with 30 | // that sort of thing in their code. 31 | // 32 | // Notice that we are into the realm of heuristics at this point. In Rust's 33 | // macro system it is not possible for a derive macro to infer the "correct" 34 | // bounds in general. Doing so would require name-resolution, i.e. the ability 35 | // for the macro to look up what trait impl corresponds to some field's type by 36 | // name. The Rust compiler has chosen to perform all macro expansion fully 37 | // before name resolution (not counting name resolution of macros themselves, 38 | // which operates in a more restricted way than Rust name resolution in general 39 | // to make this possible). 40 | // 41 | // The clean separation between macro expansion and name resolution has huge 42 | // advantages that outweigh the limitation of not being able to expose type 43 | // information to procedural macros, so there are no plans to change it. Instead 44 | // macros rely on domain-specific heuristics and escape hatches to substitute 45 | // for type information where unavoidable or, more commonly, rely on the Rust 46 | // trait system to defer the need for name resolution. In particular pay 47 | // attention to how the derive macro invocation below is able to expand to code 48 | // that correctly calls String's Debug impl despite having no way to know that 49 | // the word "S" in its input refers to the type String. 50 | 51 | use derive_debug::CustomDebug; 52 | use std::fmt::Debug; 53 | use std::marker::PhantomData; 54 | 55 | type S = String; 56 | 57 | #[derive(CustomDebug)] 58 | pub struct Field { 59 | marker: PhantomData, 60 | string: S, 61 | #[debug = "0b{:08b}"] 62 | bitmask: u8, 63 | } 64 | 65 | fn assert_debug() {} 66 | 67 | fn main() { 68 | // Does not implement Debug. 69 | struct NotDebug; 70 | 71 | assert_debug::>(); 72 | assert_debug::>(); 73 | } 74 | -------------------------------------------------------------------------------- /debug/tests/06-bound-trouble.rs: -------------------------------------------------------------------------------- 1 | // This test case should not require any code change in your macro if you have 2 | // everything up to this point already passing, but is here to demonstrate why 3 | // inferring `#field_ty: Trait` bounds as mentioned in the previous test case is 4 | // not viable. 5 | // 6 | // #[derive(CustomDebug)] 7 | // pub struct One { 8 | // value: T, 9 | // two: Option>>, 10 | // } 11 | // 12 | // #[derive(CustomDebug)] 13 | // struct Two { 14 | // one: Box>, 15 | // } 16 | // 17 | // The problematic expansion would come out as: 18 | // 19 | // impl Debug for One 20 | // where 21 | // T: Debug, 22 | // Option>>: Debug, 23 | // {...} 24 | // 25 | // impl Debug for Two 26 | // where 27 | // Box>: Debug, 28 | // {...} 29 | // 30 | // There are two things wrong here. 31 | // 32 | // First, taking into account the relevant standard library impls `impl Debug 33 | // for Option where T: Debug` and `impl Debug for Box where T: ?Sized + 34 | // Debug`, we have the following cyclic definition: 35 | // 36 | // - One implements Debug if there is an impl for Option>>; 37 | // - Option>> implements Debug if there is an impl for Box>; 38 | // - Box> implements Debug if there is an impl for Two; 39 | // - Two implements Debug if there is an impl for Box>; 40 | // - Box> implements Debug if there is an impl for One; cycle! 41 | // 42 | // The Rust compiler detects and rejects this cycle by refusing to assume that 43 | // an impl for any of these types exists out of nowhere. The error manifests as: 44 | // 45 | // error[E0275]: overflow evaluating the requirement `One: std::fmt::Debug` 46 | // --> 47 | // | assert_debug::>(); 48 | // | ^^^^^^^^^^^^^^^^^^^^^^^ 49 | // 50 | // There is a technique known as co-inductive reasoning that may allow a 51 | // revamped trait solver in the compiler to process cycles like this in the 52 | // future, though there is still uncertainty about whether co-inductive 53 | // semantics would lead to unsoundness in some situations when applied to Rust 54 | // trait impls. There is no current activity pursuing this but some discussion 55 | // exists in a GitHub issue called "#[derive] sometimes uses incorrect bounds": 56 | // https://github.com/rust-lang/rust/issues/26925 57 | // 58 | // The second thing wrong is a private-in-public violation: 59 | // 60 | // error[E0446]: private type `Two` in public interface 61 | // --> 62 | // | struct Two { 63 | // | - `Two` declared as private 64 | // ... 65 | // | / impl Debug for One 66 | // | | where 67 | // | | T: Debug, 68 | // | | Option>>: Debug, 69 | // ... | 70 | // | | } 71 | // | |_^ can't leak private type 72 | // 73 | // Public APIs in Rust are not allowed to be defined in terms of private types. 74 | // That includes the argument types and return types of public function 75 | // signatures, as well as trait bounds on impls of public traits for public 76 | // types. 77 | 78 | use derive_debug::CustomDebug; 79 | use std::fmt::Debug; 80 | 81 | #[derive(CustomDebug)] 82 | pub struct One { 83 | value: T, 84 | two: Option>>, 85 | } 86 | 87 | #[derive(CustomDebug)] 88 | struct Two { 89 | one: Box>, 90 | } 91 | 92 | fn assert_debug() {} 93 | 94 | fn main() { 95 | assert_debug::>(); 96 | assert_debug::>(); 97 | } 98 | -------------------------------------------------------------------------------- /debug/tests/07-associated-type-direct.rs: -------------------------------------------------------------------------------- 1 | // This test case covers one more heuristic that is often worth incorporating 2 | // into derive macros that infer trait bounds. Here we look for the use of an 3 | // associated type of a type parameter. 4 | // 5 | // The generated impl will need to look like: 6 | // 7 | // impl Debug for Field 8 | // where 9 | // T::Value: Debug, 10 | // {...} 11 | // 12 | // You can identify associated types as any syn::TypePath in which the first 13 | // path segment is one of the type parameters and there is more than one 14 | // segment. 15 | // 16 | // 17 | // Resources: 18 | // 19 | // - The relevant types in the input will be represented in this syntax tree node: https://docs.rs/syn/1.0/syn/struct.TypePath.html 20 | 21 | use derive_debug::CustomDebug; 22 | use std::fmt::Debug; 23 | 24 | pub trait Trait { 25 | type Value; 26 | } 27 | 28 | #[derive(CustomDebug)] 29 | pub struct Field { 30 | values: T::Value, 31 | } 32 | 33 | fn assert_debug() {} 34 | 35 | fn main() { 36 | // Does not implement Debug, but its associated type does. 37 | struct Id; 38 | 39 | impl Trait for Id { 40 | type Value = u8; 41 | } 42 | 43 | assert_debug::>(); 44 | } 45 | -------------------------------------------------------------------------------- /debug/tests/07-associated-type-multi.rs: -------------------------------------------------------------------------------- 1 | // This test case covers one more heuristic that is often worth incorporating 2 | // into derive macros that infer trait bounds. Here we look for the use of an 3 | // associated type of a type parameter. 4 | // 5 | // The generated impl will need to look like: 6 | // 7 | // impl Debug for Field 8 | // where 9 | // T::Value: Debug, 10 | // {...} 11 | // 12 | // You can identify associated types as any syn::TypePath in which the first 13 | // path segment is one of the type parameters and there is more than one 14 | // segment. 15 | // 16 | // 17 | // Resources: 18 | // 19 | // - The relevant types in the input will be represented in this syntax tree node: https://docs.rs/syn/1.0/syn/struct.TypePath.html 20 | 21 | use derive_debug::CustomDebug; 22 | use std::fmt::Debug; 23 | 24 | pub trait Trait { 25 | type Value; 26 | type Value2; 27 | } 28 | 29 | #[derive(CustomDebug)] 30 | pub struct Field { 31 | values1: Vec, 32 | values2: Vec, 33 | values3: T::Value, 34 | values4: T::Value2, 35 | values5: std::collections::HashMap, 36 | } 37 | 38 | fn assert_debug() {} 39 | 40 | fn main() { 41 | // Does not implement Debug, but its associated type does. 42 | struct Id; 43 | 44 | impl Trait for Id { 45 | type Value = u8; 46 | type Value2 = u8; 47 | } 48 | 49 | assert_debug::>(); 50 | } 51 | -------------------------------------------------------------------------------- /debug/tests/07-associated-type.rs: -------------------------------------------------------------------------------- 1 | // This test case covers one more heuristic that is often worth incorporating 2 | // into derive macros that infer trait bounds. Here we look for the use of an 3 | // associated type of a type parameter. 4 | // 5 | // The generated impl will need to look like: 6 | // 7 | // impl Debug for Field 8 | // where 9 | // T::Value: Debug, 10 | // {...} 11 | // 12 | // You can identify associated types as any syn::TypePath in which the first 13 | // path segment is one of the type parameters and there is more than one 14 | // segment. 15 | // 16 | // 17 | // Resources: 18 | // 19 | // - The relevant types in the input will be represented in this syntax tree 20 | // node: https://docs.rs/syn/1.0/syn/struct.TypePath.html 21 | 22 | use derive_debug::CustomDebug; 23 | use std::fmt::Debug; 24 | 25 | pub trait Trait { 26 | type Value; 27 | } 28 | 29 | #[derive(CustomDebug)] 30 | pub struct Field { 31 | values: Vec, 32 | } 33 | 34 | fn assert_debug() {} 35 | 36 | fn main() { 37 | // Does not implement Debug, but its associated type does. 38 | struct Id; 39 | 40 | impl Trait for Id { 41 | type Value = u8; 42 | } 43 | 44 | assert_debug::>(); 45 | } 46 | -------------------------------------------------------------------------------- /debug/tests/08-escape-hatch-multi.rs: -------------------------------------------------------------------------------- 1 | // There are some cases where no heuristic would be sufficient to infer the 2 | // right trait bounds based only on the information available during macro 3 | // expansion. 4 | // 5 | // When this happens, we'll turn to attributes as a way for the caller to 6 | // handwrite the correct trait bounds themselves. 7 | // 8 | // The impl for Wrapper in the code below will need to include the bounds 9 | // provided in the `debug(bound = "...")` attribute. When such an attribute is 10 | // present, also disable all inference of bounds so that the macro does not 11 | // attach its own `T: Debug` inferred bound. 12 | // 13 | // impl Debug for Wrapper 14 | // where 15 | // T::Value: Debug, 16 | // {...} 17 | // 18 | // Optionally, though this is not covered by the test suite, also accept 19 | // `debug(bound = "...")` attributes on individual fields. This should 20 | // substitute only whatever bounds are inferred based on that field's type, 21 | // without removing bounds inferred based on the other fields: 22 | // 23 | // #[derive(CustomDebug)] 24 | // pub struct Wrapper { 25 | // #[debug(bound = "T::Value: Debug")] 26 | // field: Field, 27 | // normal: U, 28 | // } 29 | 30 | use derive_debug::CustomDebug; 31 | use std::fmt::Debug; 32 | 33 | pub trait Trait { 34 | type Value; 35 | type Value2; 36 | } 37 | 38 | #[derive(CustomDebug)] 39 | pub struct Wrapper2 { 40 | #[debug(bound = "T::Value: Debug")] 41 | #[debug = "\n\n\n{:?}"] 42 | field: Field, 43 | #[debug(bound = "T::Value2: Debug")] 44 | field2: std::collections::HashMap>, 45 | } 46 | 47 | #[derive(CustomDebug)] 48 | struct Field { 49 | values: Vec, 50 | } 51 | 52 | #[derive(CustomDebug)] 53 | struct Field2 { 54 | values: Vec, 55 | } 56 | 57 | fn assert_debug() {} 58 | 59 | fn main() { 60 | struct Id; 61 | 62 | impl Trait for Id { 63 | type Value = u8; 64 | type Value2 = u8; 65 | } 66 | 67 | assert_debug::>(); 68 | } 69 | -------------------------------------------------------------------------------- /debug/tests/08-escape-hatch.rs: -------------------------------------------------------------------------------- 1 | // There are some cases where no heuristic would be sufficient to infer the 2 | // right trait bounds based only on the information available during macro 3 | // expansion. 4 | // 5 | // When this happens, we'll turn to attributes as a way for the caller to 6 | // handwrite the correct trait bounds themselves. 7 | // 8 | // The impl for Wrapper in the code below will need to include the bounds 9 | // provided in the `debug(bound = "...")` attribute. When such an attribute is 10 | // present, also disable all inference of bounds so that the macro does not 11 | // attach its own `T: Debug` inferred bound. 12 | // 13 | // impl Debug for Wrapper 14 | // where 15 | // T::Value: Debug, 16 | // {...} 17 | // 18 | // Optionally, though this is not covered by the test suite, also accept 19 | // `debug(bound = "...")` attributes on individual fields. This should 20 | // substitute only whatever bounds are inferred based on that field's type, 21 | // without removing bounds inferred based on the other fields: 22 | // 23 | // #[derive(CustomDebug)] 24 | // pub struct Wrapper { 25 | // #[debug(bound = "T::Value: Debug")] 26 | // field: Field, 27 | // normal: U, 28 | // } 29 | 30 | use derive_debug::CustomDebug; 31 | use std::fmt::Debug; 32 | 33 | pub trait Trait { 34 | type Value; 35 | } 36 | 37 | #[derive(CustomDebug)] 38 | #[debug(bound = "T::Value: Debug")] 39 | pub struct Wrapper { 40 | field: Field, 41 | } 42 | 43 | #[derive(CustomDebug)] 44 | struct Field { 45 | values: Vec, 46 | } 47 | 48 | fn assert_debug() {} 49 | 50 | fn main() { 51 | struct Id; 52 | 53 | impl Trait for Id { 54 | type Value = u8; 55 | } 56 | 57 | assert_debug::>(); 58 | } 59 | -------------------------------------------------------------------------------- /debug/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/01-parse.rs"); 5 | t.pass("tests/02-impl-debug.rs"); 6 | t.pass("tests/03-custom-format.rs"); 7 | t.pass("tests/04-type-parameter.rs"); 8 | t.pass("tests/04-type-parameter-debug.rs"); 9 | t.pass("tests/05-phantom-data.rs"); 10 | t.pass("tests/06-bound-trouble.rs"); 11 | t.pass("tests/07-associated-type.rs"); 12 | t.pass("tests/07-associated-type-direct.rs"); 13 | t.pass("tests/07-associated-type-multi.rs"); 14 | t.pass("tests/08-escape-hatch.rs"); 15 | t.pass("tests/08-escape-hatch-multi.rs"); 16 | } 17 | -------------------------------------------------------------------------------- /main.rs: -------------------------------------------------------------------------------- 1 | // Write code here. 2 | // 3 | // To see what the code looks like after macro expansion: 4 | // $ cargo expand 5 | // 6 | // To run the code: 7 | // $ cargo run 8 | 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zjp-CN/proc-macro-workshop/c0b87cf933be7d713dec504d3c5f832a4df240e0/rustfmt.toml -------------------------------------------------------------------------------- /seq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seq" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = { version = "1.0.49", features = ["diff"] } 17 | 18 | [dependencies] 19 | quote = "1" 20 | # syn = "1" 21 | syn = {version = "1", features = ["extra-traits"]} 22 | proc-macro2 = "1" 23 | 24 | # [[bin]] 25 | # path = "./tests/02-parse-body.rs" 26 | # name = "02" 27 | # 28 | # [[bin]] 29 | # path = "./tests/04-paste-ident.rs" 30 | # name = "04" 31 | # 32 | # [[bin]] 33 | # path = "./tests/04-paste-ident2.rs" 34 | # name = "042" 35 | # 36 | # [[bin]] 37 | # path = "./tests/05-repeat-section.rs" 38 | # name = "05" 39 | # 40 | # [[bin]] 41 | # path = "./tests/05-repeat-section2.rs" 42 | # name = "052" 43 | # 44 | # [[bin]] 45 | # path = "./tests/06-init-array.rs" 46 | # name = "06" 47 | # 48 | # [[bin]] 49 | # path = "./tests/07-inclusive-range.rs" 50 | # name = "07" 51 | # 52 | # [[bin]] 53 | # path = "./tests/09-interaction-with-macrorules.rs" 54 | # name = "09" 55 | -------------------------------------------------------------------------------- /seq/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Group, Ident, Span, TokenStream as TokenStream2}; 3 | use syn::{parse::Parse, Error, LitInt, Token}; 4 | 5 | #[proc_macro] 6 | pub fn seq(input: TokenStream) -> TokenStream { 7 | let seq = syn::parse_macro_input!(input as Seq); 8 | TokenStream::from(seq.expand()) 9 | } 10 | 11 | #[allow(dead_code)] 12 | struct Seq { 13 | ident: Ident, 14 | in_token: Token![in], 15 | lhs: LitInt, 16 | dot2_token: Token![..], 17 | eq_token: Option, 18 | rhs: LitInt, 19 | brace_token: syn::token::Brace, 20 | tokens: TokenStream2, 21 | } 22 | 23 | impl Parse for Seq { 24 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 25 | let content; 26 | Ok(Seq { 27 | ident: input.parse()?, 28 | in_token: input.parse()?, 29 | lhs: input.parse()?, 30 | dot2_token: input.parse()?, 31 | eq_token: input.parse().ok(), 32 | rhs: input.parse()?, 33 | brace_token: syn::braced!(content in input), 34 | tokens: content.parse()?, 35 | }) 36 | } 37 | } 38 | 39 | impl Seq { 40 | fn expand(self) -> TokenStream2 { 41 | let Seq { 42 | ident, 43 | lhs, 44 | rhs, 45 | tokens, 46 | eq_token, 47 | .. 48 | } = self; 49 | let buffer = syn::buffer::TokenBuffer::new2(tokens); 50 | let cursor = buffer.begin(); 51 | match (lhs.base10_parse(), rhs.base10_parse()) { 52 | (Ok(lhs), Ok(rhs)) => { 53 | let range = if eq_token.is_some() { 54 | (lhs..=rhs).into() 55 | } else { 56 | (lhs..rhs).into() 57 | }; 58 | repeat::SeqToken::new(cursor, &ident, range).token_stream() 59 | } 60 | (Err(err), _) => lit_err(err, lhs.span()), 61 | (Ok(_), Err(err)) => lit_err(err, rhs.span()), 62 | } 63 | } 64 | } 65 | 66 | mod range; 67 | mod repeat; 68 | mod replace; 69 | 70 | // 把 Group 内的 TokenStream 替换掉(保留 delimiter 和 span) 71 | fn new_group(g: &Group, ts: TokenStream2) -> Group { 72 | let mut group = Group::new(g.delimiter(), ts); 73 | group.set_span(g.span()); 74 | group 75 | } 76 | 77 | fn lit_err(err: Error, span: Span) -> TokenStream2 { 78 | let err = format!("`{}`\nOnly support `usize` type for now!", err); 79 | Error::new(span, err).to_compile_error() 80 | } 81 | -------------------------------------------------------------------------------- /seq/src/range.rs: -------------------------------------------------------------------------------- 1 | //! 统一 Range 和 RangeInclusive 两种类型 2 | 3 | pub type RangeLit = usize; 4 | type StdRange = std::ops::Range; 5 | type StdRangeInc = std::ops::RangeInclusive; 6 | 7 | #[derive(Clone)] 8 | pub enum Range { 9 | Normal(StdRange), 10 | Inclusive(StdRangeInc), 11 | } 12 | 13 | impl Iterator for Range { 14 | type Item = RangeLit; 15 | 16 | fn next(&mut self) -> Option { 17 | match self { 18 | Range::Normal(i) => i.next(), 19 | Range::Inclusive(i) => i.next(), 20 | } 21 | } 22 | 23 | fn size_hint(&self) -> (usize, Option) { 24 | match self { 25 | Range::Normal(i) => i.size_hint(), 26 | Range::Inclusive(i) => i.size_hint(), 27 | } 28 | } 29 | } 30 | 31 | impl From for Range { 32 | fn from(r: StdRange) -> Self { 33 | Range::Normal(r) 34 | } 35 | } 36 | 37 | impl From for Range { 38 | fn from(r: StdRangeInc) -> Self { 39 | Range::Inclusive(r) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /seq/src/repeat.rs: -------------------------------------------------------------------------------- 1 | use crate::range::Range; 2 | use proc_macro2::{Group, Ident, TokenStream as TokenStream2, TokenTree as TT}; 3 | use syn::buffer::{Cursor, TokenBuffer}; 4 | 5 | // 区分是否需要重复 6 | pub struct SeqToken<'c, 'i> { 7 | output: Vec, 8 | cursor: Cursor<'c>, 9 | range: Range, 10 | ident: &'i Ident, 11 | } 12 | 13 | impl<'c, 'i> SeqToken<'c, 'i> { 14 | pub fn new(cursor: Cursor<'c>, ident: &'i Ident, range: Range) -> Self { 15 | SeqToken { 16 | output: Vec::with_capacity(32), 17 | cursor, 18 | range, 19 | ident, 20 | } 21 | } 22 | 23 | // 如果存在 `#()*`,则一边捕获,一边替换和重复;如果不存在 `#()*`,则替换和重复整个块 24 | pub fn token_stream(mut self) -> TokenStream2 { 25 | if self.search_repeat_tag() { 26 | self.capture_and_replace(); 27 | } else { 28 | self.repeat_and_replace(self.cursor); 29 | } 30 | TokenStream2::from_iter(self.output) 31 | } 32 | 33 | fn repeat_and_replace(&mut self, cursor: Cursor) { 34 | let iter = self 35 | .range 36 | .clone() 37 | .map(|lit| crate::replace::replace(cursor, self.ident, lit)); 38 | self.output.push(TokenStream2::from_iter(iter)); 39 | } 40 | 41 | // 查找是否存在 `#()*` 42 | // 最多只遍历一次:中途遇到第一个 `#()*` 标记时直接返回 true 43 | fn search_repeat_tag(&self) -> bool { 44 | let mut cursor = self.cursor; 45 | while let Some((token, cur)) = cursor.token_tree() { 46 | match token { 47 | TT::Punct(p) if p.as_char() == '#' => { 48 | if let Some((TT::Group(_), c_star)) = cur.token_tree() { 49 | match c_star.punct() { 50 | Some((p, _)) if p.as_char() == '*' => return true, 51 | _ => (), 52 | } 53 | } 54 | } 55 | TT::Group(g) => { 56 | if SeqToken::new( 57 | TokenBuffer::new2(g.stream()).begin(), 58 | self.ident, 59 | self.range.clone(), 60 | ) 61 | .search_repeat_tag() 62 | { 63 | return true; 64 | } 65 | } 66 | _ => (), 67 | } 68 | cursor = cur; 69 | } 70 | false 71 | } 72 | 73 | // 捕获并替换 `#()*` 74 | fn capture_and_replace(&mut self) { 75 | while let Some((token, cur)) = self.cursor.token_tree() { 76 | self.cursor = cur; 77 | match token { 78 | TT::Punct(p) if p.as_char() == '#' => { 79 | if !self.check_group(cur) { 80 | // `#` 不是属于 `#()*` 捕获组,则交还 `#` 符号 81 | self.output.push(TokenStream2::from(TT::Punct(p))); 82 | } 83 | } 84 | TT::Group(g) => { 85 | self.output 86 | .push(SeqToken::group(g, self.ident, self.range.clone())) 87 | } 88 | t => self.output.push(t.into()), 89 | } 90 | } 91 | } 92 | 93 | // 检查为 group 时,替换成字面值,并返回 true;否则返回 false 94 | fn check_group(&mut self, cur_group: Cursor<'c>) -> bool { 95 | if let Some((TT::Group(g), c_star)) = cur_group.token_tree() { 96 | match c_star.token_tree() { 97 | Some((token, c_next)) if matches!(&token, TT::Punct(p) if p.as_char() == '*') => { 98 | self.repeat_and_replace(TokenBuffer::new2(g.stream()).begin()); 99 | self.cursor = c_next; 100 | return true; 101 | } 102 | _ => (), 103 | } 104 | } 105 | false 106 | } 107 | 108 | // 替换后的新标记 109 | fn output(mut self) -> TokenStream2 { 110 | self.capture_and_replace(); 111 | TokenStream2::from_iter(self.output) 112 | } 113 | 114 | // 替换后的新标记(只针对 Group) 115 | fn group(g: Group, ident: &'i Ident, range: Range) -> TokenStream2 { 116 | let ts = SeqToken::new(TokenBuffer::new2(g.stream()).begin(), ident, range).output(); 117 | TokenStream2::from(TT::Group(crate::new_group(&g, ts))) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /seq/src/replace.rs: -------------------------------------------------------------------------------- 1 | use crate::range::RangeLit; 2 | use proc_macro2::{Group, Ident, Literal, TokenStream as TokenStream2, TokenTree as TT}; 3 | use syn::buffer::{Cursor, TokenBuffer}; 4 | 5 | // 将所有 ident 替换成字面值、`prefix~ident` 替换成 `prefix字面值` 6 | pub fn replace(mut cursor: Cursor, ident: &Ident, lit: RangeLit) -> TokenStream2 { 7 | let mut ts = Vec::with_capacity(32); 8 | while let Some((token, cur)) = cursor.token_tree() { 9 | cursor = cur; 10 | let t = match token { 11 | TT::Ident(i) => { 12 | if &i == ident { 13 | // TODO: 字面值暂时只假设为 usize(由 RangeLit 统一描述),它可以通过所有测试 14 | // 真实场景中,需要放宽这个类型(见 https://docs.rs/seq-macro ) 15 | Literal::usize_unsuffixed(lit).into() 16 | } else if let Some((matched, cur)) = 17 | search_tidle_ident(i.clone().into(), cur, ident, lit, &mut ts) 18 | { 19 | cursor = cur; 20 | if matched { 21 | quote::format_ident!("{}{}", i, lit).into() 22 | } else { 23 | continue; 24 | } 25 | } else { 26 | i.into() 27 | } 28 | } 29 | TT::Group(ref g) => match_group(g, ident, lit), 30 | t => t, 31 | }; 32 | ts.push(t); 33 | } 34 | TokenStream2::from_iter(ts) 35 | } 36 | 37 | fn match_group(g: &Group, ident: &Ident, lit: RangeLit) -> TT { 38 | let tokens = replace(TokenBuffer::new2(g.stream()).begin(), ident, lit); 39 | crate::new_group(g, tokens).into() 40 | } 41 | 42 | type Search<'c> = Option<(bool, Cursor<'c>)>; 43 | 44 | // 查找某个标识符 i 之后是否跟 `~ident`,如果返回: 45 | // - Some((true, cur)) 表示找到 46 | // - Some((false, cur)) 表示未找到,且把捕获的标记添加到 ts 47 | // - None 在 search_ident 函数中表示遇到标记流结束(虽然它最终不返回 None); 在 48 | // search_tidle_ident 函数中表示 **i 与此宏功能无关**,或者标记流结束 49 | fn search_tidle_ident<'c>( 50 | i: TT, 51 | cursor: Cursor<'c>, 52 | ident: &Ident, 53 | lit: RangeLit, 54 | ts: &mut Vec, 55 | ) -> Search<'c> { 56 | fn search_ident<'c>( 57 | i: TT, 58 | tidle: TT, 59 | cursor: Cursor<'c>, 60 | ident: &Ident, 61 | lit: RangeLit, 62 | ts: &mut Vec, 63 | ) -> Search<'c> { 64 | if let Some((token, cur)) = cursor.token_tree() { 65 | match &token { 66 | TT::Ident(id) if id == ident => Some((true, cur)), 67 | TT::Group(g) => { 68 | ts.extend([i, tidle, match_group(g, ident, lit)]); 69 | Some((false, cur)) 70 | } 71 | _ => { 72 | ts.extend([i, tidle, token]); 73 | Some((false, cur)) 74 | } 75 | } 76 | } else { 77 | // 虽然几乎不会遇到 `i~` 结尾的标记,但是还是需要考虑 78 | ts.extend([i, tidle]); 79 | Some((false, cursor)) 80 | } 81 | } 82 | cursor.token_tree().and_then(|(token, cur)| match &token { 83 | TT::Ident(_) => None, 84 | TT::Punct(p) if p.as_char() == '~' => search_ident(i, token, cur, ident, lit, ts), 85 | TT::Group(g) => { 86 | ts.extend([i, match_group(g, ident, lit)]); 87 | Some((false, cur)) 88 | } 89 | _ => { 90 | ts.extend([i, token]); 91 | Some((false, cur)) 92 | } 93 | }) 94 | } 95 | -------------------------------------------------------------------------------- /seq/tests/01-parse-header.rs: -------------------------------------------------------------------------------- 1 | // This test looks for a function-like macro with the right name to exist. For 2 | // now the test doesn't require any specific code to be generated by the macro, 3 | // so returning an empty TokenStream should be sufficient. 4 | // 5 | // Before moving on to the next test, you'll want some code in your 6 | // implementation to handle parsing the first few tokens of input. The macro 7 | // should expect the input to contain a syn::Ident, Token![in], syn::LitInt, 8 | // Token![..], syn::LitInt. 9 | // 10 | // It is also possible to implement this project without using Syn if you'd 11 | // like, though you will end up writing more code, more tedious code, and more 12 | // explicit error handling than when using Syn as a parsing library. 13 | // 14 | // 15 | // Resources: 16 | // 17 | // - Parsing in Syn: 18 | // https://docs.rs/syn/1.0/syn/parse/index.html 19 | // 20 | // - An example of a function-like procedural macro implemented using Syn: 21 | // https://github.com/dtolnay/syn/tree/master/examples/lazy-static 22 | 23 | use seq::seq; 24 | 25 | seq!(N in 0..8 { 26 | // nothing 27 | }); 28 | 29 | fn main() {} 30 | -------------------------------------------------------------------------------- /seq/tests/02-parse-body.rs: -------------------------------------------------------------------------------- 1 | // The macro invocation in the previous test case contained an empty loop body 2 | // inside the braces. In reality we want for the macro to accept arbitrary 3 | // tokens inside the braces. 4 | // 5 | // The caller should be free to write whatever they want inside the braces. The 6 | // seq macro won't care whether they write a statement, or a function, or a 7 | // struct, or whatever else. So we will work with the loop body as a TokenStream 8 | // rather than as a syntax tree. 9 | // 10 | // Before moving on, ensure that your implementation knows what has been written 11 | // inside the curly braces as a value of type TokenStream. 12 | // 13 | // 14 | // Resources: 15 | // 16 | // - Explanation of the purpose of proc-macro2: 17 | // https://docs.rs/proc-macro2/1.0/proc_macro2/ 18 | 19 | use seq::seq; 20 | 21 | macro_rules! expand_to_nothing { 22 | ($arg:literal) => { 23 | // nothing 24 | }; 25 | } 26 | 27 | seq!(N in 0..4 { 28 | expand_to_nothing!(N); 29 | }); 30 | 31 | fn main() {} 32 | -------------------------------------------------------------------------------- /seq/tests/03-expand-four-errors.rs: -------------------------------------------------------------------------------- 1 | // Now construct the generated code! Produce the output TokenStream by repeating 2 | // the loop body the correct number of times as specified by the loop bounds and 3 | // replacing the specified identifier with the loop counter. 4 | // 5 | // The invocation below will need to expand to a TokenStream containing: 6 | // 7 | // compile_error!(concat!("error number ", stringify!(0))); 8 | // compile_error!(concat!("error number ", stringify!(1))); 9 | // compile_error!(concat!("error number ", stringify!(2))); 10 | // compile_error!(concat!("error number ", stringify!(3))); 11 | // 12 | // This test is written as a compile_fail test because our macro isn't yet 13 | // powerful enough to do anything useful. For example if we made it generate 14 | // something like a function, every one of those functions would have the same 15 | // name and the program would not compile. 16 | 17 | use seq::seq; 18 | 19 | seq!(N in 0..4 { 20 | compile_error!(concat!("error number ", stringify!(N))); 21 | }); 22 | 23 | fn main() {} 24 | -------------------------------------------------------------------------------- /seq/tests/03-expand-four-errors.stderr: -------------------------------------------------------------------------------- 1 | error: error number 0 2 | --> tests/03-expand-four-errors.rs:20:5 3 | | 4 | 20 | compile_error!(concat!("error number ", stringify!(N))); 5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 6 | 7 | error: error number 1 8 | --> tests/03-expand-four-errors.rs:20:5 9 | | 10 | 20 | compile_error!(concat!("error number ", stringify!(N))); 11 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 12 | 13 | error: error number 2 14 | --> tests/03-expand-four-errors.rs:20:5 15 | | 16 | 20 | compile_error!(concat!("error number ", stringify!(N))); 17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | error: error number 3 20 | --> tests/03-expand-four-errors.rs:20:5 21 | | 22 | 20 | compile_error!(concat!("error number ", stringify!(N))); 23 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | -------------------------------------------------------------------------------- /seq/tests/04-paste-ident.rs: -------------------------------------------------------------------------------- 1 | // One of the big things callers will want to do with the sequential indices N 2 | // is use them as part of an identifier, like f0 f1 f2 etc. 3 | // 4 | // Implement some logic to paste together any Ident followed by `~` followed by 5 | // our loop variable into a single concatenated identifier. 6 | // 7 | // The invocation below will expand to: 8 | // 9 | // fn f1() -> u64 { 1 * 2 } 10 | // fn f2() -> u64 { 2 * 2 } 11 | // fn f3() -> u64 { 3 * 2 } 12 | // 13 | // Optionally, also support more flexible arrangements like `f~N~_suffix` -> 14 | // f0_suffix f1_suffix etc, though the test suite only requires `prefix~N` so 15 | // you will need to add your own tests for this feature. 16 | // 17 | // 18 | // Resources: 19 | // 20 | // - Example of creating a new Ident from a string: 21 | // https://docs.rs/syn/1.0/syn/struct.Ident.html 22 | 23 | use seq::seq; 24 | 25 | seq!(N in 1..4 { 26 | fn f~N () -> u64 { 27 | N * 2 28 | } 29 | }); 30 | 31 | // This f0 is written separately to detect whether your macro correctly starts 32 | // with the first iteration at N=1 as specified in the invocation. If the macro 33 | // incorrectly started at N=0 like in the previous tests cases, the first 34 | // generated function would conflict with this one and the program would not 35 | // compile. 36 | fn f0() -> u64 { 37 | 100 38 | } 39 | 40 | fn main() { 41 | let sum = f0() + f1() + f2() + f3(); 42 | 43 | assert_eq!(sum, 100 + 2 + 4 + 6); 44 | } 45 | -------------------------------------------------------------------------------- /seq/tests/04-paste-ident2.rs: -------------------------------------------------------------------------------- 1 | // One of the big things callers will want to do with the sequential indices N 2 | // is use them as part of an identifier, like f0 f1 f2 etc. 3 | // 4 | // Implement some logic to paste together any Ident followed by `~` followed by 5 | // our loop variable into a single concatenated identifier. 6 | // 7 | // The invocation below will expand to: 8 | // 9 | // fn f1() -> u64 { 1 * 2 } 10 | // fn f2() -> u64 { 2 * 2 } 11 | // fn f3() -> u64 { 3 * 2 } 12 | // 13 | // Optionally, also support more flexible arrangements like `f~N~_suffix` -> 14 | // f0_suffix f1_suffix etc, though the test suite only requires `prefix~N` so 15 | // you will need to add your own tests for this feature. 16 | // 17 | // 18 | // Resources: 19 | // 20 | // - Example of creating a new Ident from a string: https://docs.rs/syn/1.0/syn/struct.Ident.html 21 | 22 | use seq::seq; 23 | 24 | seq!(N in 1..4 { 25 | fn f~N () -> u64 { 26 | use std::collections::HashSet; 27 | let _: HashSet<_> = [N; N].into(); 28 | N * 2 29 | } 30 | }); 31 | 32 | const fn g(n: u64) -> u64 { n } 33 | 34 | seq!(N in 1..4 { 35 | const C~N: u64 = g(N); 36 | }); 37 | 38 | // This f0 is written separately to detect whether your macro correctly starts 39 | // with the first iteration at N=1 as specified in the invocation. If the macro 40 | // incorrectly started at N=0 like in the previous tests cases, the first 41 | // generated function would conflict with this one and the program would not 42 | // compile. 43 | fn f0() -> u64 { 100 } 44 | 45 | fn main() { 46 | let sum = f0() + f1() + f2() + f3(); 47 | 48 | assert_eq!(sum, 100 + 2 + 4 + 6); 49 | 50 | assert_eq!(C1, 1); 51 | assert_eq!(C2, 2); 52 | assert_eq!(C3, 3); 53 | } 54 | -------------------------------------------------------------------------------- /seq/tests/05-repeat-section.rs: -------------------------------------------------------------------------------- 1 | // So far our macro has repeated the entire loop body. This is not sufficient 2 | // for some use cases because there are restrictions on the syntactic position 3 | // that macro invocations can appear in. For example the Rust grammar would not 4 | // allow a caller to write: 5 | // 6 | // enum Interrupt { 7 | // seq!(N in 0..16 { 8 | // Irq~N, 9 | // }); 10 | // } 11 | // 12 | // because this is just not a legal place to put a macro call. 13 | // 14 | // Instead we will implement a way for the caller to designate a specific part 15 | // of the macro input to be repeated, so that anything outside that part does 16 | // not get repeated. The repeated part will be written surrounded by #(...)*. 17 | // 18 | // The invocation below should expand to: 19 | // 20 | // #[derive(Copy, Clone, PartialEq, Debug)] 21 | // enum Interrupt { 22 | // Irq0, 23 | // ... 24 | // Irq15, 25 | // } 26 | // 27 | // Optionally, allow for there to be multiple separate #(...)* sections, 28 | // although the test suite does not exercise this case. The #(...)* sections 29 | // will each need to be repeated according to the same loop bounds. 30 | 31 | use seq::seq; 32 | 33 | seq!(N in 0..16 { 34 | #[derive(Copy, Clone, PartialEq, Debug)] 35 | enum Interrupt { 36 | #( 37 | Irq~N, 38 | )* 39 | } 40 | }); 41 | 42 | fn main() { 43 | let interrupt = Interrupt::Irq8; 44 | 45 | assert_eq!(interrupt as u8, 8); 46 | assert_eq!(interrupt, Interrupt::Irq8); 47 | } 48 | -------------------------------------------------------------------------------- /seq/tests/05-repeat-section2.rs: -------------------------------------------------------------------------------- 1 | seq::seq!(N in 2..5 { 2 | const fn f() -> usize { 3 | 1 #( +N )* #( *N )* 4 | } 5 | }); 6 | 7 | fn main() { 8 | assert_eq!(f(), 1 + 2 + 3 + 4 * 2 * 3 * 4); 9 | } 10 | -------------------------------------------------------------------------------- /seq/tests/06-init-array.rs: -------------------------------------------------------------------------------- 1 | // This test case should hopefully be a freebie if all of the previous ones are 2 | // passing. This test demonstrates using the seq macro to construct a const 3 | // array literal. 4 | // 5 | // The generated code would be: 6 | // 7 | // [Proc::new(0), Proc::new(1), ..., Proc::new(255),] 8 | 9 | use seq::seq; 10 | 11 | const PROCS: [Proc; 256] = { 12 | seq!(N in 0..256 { 13 | [ 14 | #( 15 | Proc::new(N), 16 | )* 17 | ] 18 | }) 19 | }; 20 | 21 | struct Proc { 22 | id: usize, 23 | } 24 | 25 | impl Proc { 26 | const fn new(id: usize) -> Self { 27 | Proc { id } 28 | } 29 | } 30 | 31 | fn main() { 32 | assert_eq!(PROCS[32].id, 32); 33 | } 34 | -------------------------------------------------------------------------------- /seq/tests/07-inclusive-range.rs: -------------------------------------------------------------------------------- 1 | // The previous examples all used an exclusive range, MIN..MAX. Now make it work 2 | // for an inclusive range MIN..=MAX that includes the upper range bound! 3 | 4 | use seq::seq; 5 | 6 | seq!(N in 16..=20 { 7 | enum E { 8 | #( 9 | Variant~N, 10 | )* 11 | } 12 | }); 13 | 14 | fn main() { 15 | let e = E::Variant16; 16 | 17 | let desc = match e { 18 | E::Variant16 => "min", 19 | E::Variant17 | E::Variant18 | E::Variant19 => "in between", 20 | E::Variant20 => "max", 21 | }; 22 | 23 | assert_eq!(desc, "min"); 24 | } 25 | -------------------------------------------------------------------------------- /seq/tests/08-ident-span.rs: -------------------------------------------------------------------------------- 1 | // The procedural macro API uses a type called Span to attach source location 2 | // and hygiene information to every token. In order for compiler errors to 3 | // appear underlining the right places, procedural macros are responsible for 4 | // propagating and manipulating these spans correctly. 5 | // 6 | // The invocation below expands to code that mentions a value Missing0 which 7 | // does not exist. When the compiler reports that it "cannot find value 8 | // Missing0", we would like for the error to point directly to where the user 9 | // wrote `Missing~N` in their macro input. 10 | // 11 | // error[E0425]: cannot find value `Missing0` in this scope 12 | // | 13 | // | let _ = Missing~N; 14 | // | ^^^^^^^ not found in this scope 15 | // 16 | // For this test to pass, ensure that the pasted-together identifier is created 17 | // using the Span of the identifier written by the caller. 18 | // 19 | // If you are using a nightly toolchain, there is a nightly-only method called 20 | // Span::join which would allow joining the three spans of `Missing`, `~`, `N` 21 | // so that the resulting error is as follows, but I would recommend not 22 | // bothering with this for the purpose of this project while it is unstable. 23 | // 24 | // error[E0425]: cannot find value `Missing0` in this scope 25 | // | 26 | // | let _ = Missing~N; 27 | // | ^^^^^^^^^ not found in this scope 28 | // 29 | 30 | use seq::seq; 31 | 32 | seq!(N in 0..1 { 33 | fn main() { 34 | let _ = Missing~N; 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /seq/tests/08-ident-span.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `Missing0` in this scope 2 | --> tests/08-ident-span.rs:34:17 3 | | 4 | 34 | let _ = Missing~N; 5 | | ^^^^^^^ not found in this scope 6 | -------------------------------------------------------------------------------- /seq/tests/09-interaction-with-macrorules.rs: -------------------------------------------------------------------------------- 1 | // Suppose we wanted a seq invocation in which the upper bound is given by the 2 | // value of a const. Both macros and consts are compile-time things so this 3 | // seems like it should be easy. 4 | // 5 | // static PROCS: [Proc; NPROC] = seq!(N in 0..NPROC { ... }); 6 | // 7 | // In fact it isn't, because macro expansion in Rust happens entirely before 8 | // name resolution. That is, a macro might know that something is called NPROC 9 | // but has no way to know which of many NPROC values in the dependency graph 10 | // this identifier refers to. Or maybe the NPROC constant doesn't exist yet when 11 | // the macro runs because it is only emitted by a later macro that hasn't run 12 | // yet. In this compilation model it isn't possible to support a query API where 13 | // a macro can give it the name of a constant and receive back the value of the 14 | // constant. 15 | // 16 | // All hope is not lost; it just means that our source of truth for the value of 17 | // NPROC must be a macro rather than a constant. The code in this test case 18 | // implements this workaround. 19 | // 20 | // This test case may or may not require code changes in your seq macro 21 | // implementation depending on how you have implemented it so far. Before 22 | // jumping into any code changes, make sure you understand what the code in this 23 | // test case is trying to do. 24 | 25 | use seq::seq; 26 | 27 | // Source of truth. Call a given macro passing nproc as argument. 28 | // 29 | // We want this number to appear in only one place so that updating this one 30 | // number will correctly affect anything that depends on the number of procs. 31 | macro_rules! pass_nproc { 32 | ($mac:ident) => { 33 | $mac! { 256 } 34 | }; 35 | } 36 | 37 | macro_rules! literal_identity_macro { 38 | ($nproc:literal) => { 39 | $nproc 40 | }; 41 | } 42 | 43 | // Expands to: `const NPROC: usize = 256;` 44 | const NPROC: usize = pass_nproc!(literal_identity_macro); 45 | 46 | struct Proc; 47 | 48 | impl Proc { 49 | const fn new() -> Self { 50 | Proc 51 | } 52 | } 53 | 54 | macro_rules! make_procs_array { 55 | ($nproc:literal) => { 56 | seq!(N in 0..$nproc { [#(Proc::new(),)*] }) 57 | } 58 | } 59 | 60 | // Expands to: `static PROCS: [Proc; NPROC] = [Proc::new(), ..., Proc::new()];` 61 | static PROCS: [Proc; NPROC] = pass_nproc!(make_procs_array); 62 | 63 | fn main() {} 64 | -------------------------------------------------------------------------------- /seq/tests/09-interaction-with-macrorules2.rs: -------------------------------------------------------------------------------- 1 | //! 对照组:如果不使用 `09-interaction-with-macrorules.rs` 的技巧,那么无法成功生成 PROCS 2 | //! 3 | //! 因为声明宏生成的常量不一定在过程宏展开时被知晓(也可能那个常量根本还未生成)。 4 | 5 | use seq::seq; 6 | 7 | macro_rules! literal_identity_macro { 8 | () => { 9 | 256 10 | }; 11 | } 12 | 13 | // Expands to: `const NPROC: usize = 256;` 14 | const NPROC: usize = literal_identity_macro!(); 15 | 16 | struct Proc; 17 | 18 | impl Proc { 19 | const fn new() -> Self { Proc } 20 | } 21 | 22 | // Expands to: `static PROCS: [Proc; NPROC] = [Proc::new(), ..., Proc::new()];` 23 | static PROCS: [Proc; NPROC] = seq!(N in 0..NPROC { [#(Proc::new(),)*] }); 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /seq/tests/09-interaction-with-macrorules2.stderr: -------------------------------------------------------------------------------- 1 | error: expected integer literal 2 | --> tests/09-interaction-with-macrorules2.rs:23:44 3 | | 4 | 23 | static PROCS: [Proc; NPROC] = seq!(N in 0..NPROC { [#(Proc::new(),)*] }); 5 | | ^^^^^ 6 | -------------------------------------------------------------------------------- /seq/tests/10-literal-range-parse-err.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | let val = seq::seq!(N in -1..1 { 3 | 1 #( * N )* 4 | }); 5 | assert_eq!(val, 0); 6 | } 7 | -------------------------------------------------------------------------------- /seq/tests/10-literal-range-parse-err.stderr: -------------------------------------------------------------------------------- 1 | error: `invalid digit found in string` 2 | Only support `usize` type for now! 3 | --> tests/10-literal-range-parse-err.rs:2:30 4 | | 5 | 2 | let val = seq::seq!(N in -1..1 { 6 | | ^^ 7 | -------------------------------------------------------------------------------- /seq/tests/dcl-seq.rs: -------------------------------------------------------------------------------- 1 | macro_rules! seq { 2 | ($($lit:literal),*) => { 3 | $( 4 | compile_error!(concat!("error number ", stringify!($lit))); 5 | )* 6 | 7 | } 8 | } 9 | 10 | seq! { 0, 1, 2 } 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /seq/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/01-parse-header.rs"); 5 | t.pass("tests/02-parse-body.rs"); 6 | t.compile_fail("tests/03-expand-four-errors.rs"); 7 | t.pass("tests/04-paste-ident.rs"); 8 | t.pass("tests/04-paste-ident2.rs"); 9 | t.pass("tests/05-repeat-section.rs"); 10 | t.pass("tests/05-repeat-section2.rs"); 11 | t.pass("tests/06-init-array.rs"); 12 | t.pass("tests/07-inclusive-range.rs"); 13 | t.compile_fail("tests/08-ident-span.rs"); 14 | t.pass("tests/09-interaction-with-macrorules.rs"); 15 | t.compile_fail("tests/09-interaction-with-macrorules2.rs"); 16 | t.compile_fail("tests/10-literal-range-parse-err.rs"); 17 | } 18 | -------------------------------------------------------------------------------- /sorted/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sorted" 3 | version = "0.0.0" 4 | edition = "2021" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = { version = "1.0.49", features = ["diff"] } 17 | 18 | [dependencies] 19 | quote = "1" 20 | # syn = "1" 21 | syn = {version = "1", features = ["extra-traits", "full", "visit-mut"]} 22 | proc-macro2 = "1" 23 | -------------------------------------------------------------------------------- /sorted/src/_enum.rs: -------------------------------------------------------------------------------- 1 | pub fn process(input: &syn::Item) -> proc_macro2::TokenStream { 2 | let err = if let syn::Item::Enum(input) = input { 3 | if let Err(err) = sort(input) { 4 | err.to_compile_error() 5 | } else { 6 | use quote::ToTokens; 7 | return input.to_token_stream(); 8 | } 9 | } else { 10 | let span = proc_macro2::Span::call_site(); 11 | syn::Error::new(span, "expected enum or match expression").to_compile_error() 12 | }; 13 | 14 | quote::quote! { #input #err } 15 | } 16 | 17 | fn sort(input: &syn::ItemEnum) -> syn::Result<()> { 18 | let input = &input.variants; 19 | let vars: Vec<_> = input 20 | .iter() 21 | .map(|variant| variant.ident.to_string()) 22 | .collect(); 23 | crate::cmp(vars, |pos| input[pos].ident.span()) 24 | } 25 | -------------------------------------------------------------------------------- /sorted/src/_fn.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use syn::{ 3 | spanned::Spanned, 4 | visit_mut::{self, VisitMut}, 5 | Error, Pat, Result, 6 | }; 7 | 8 | pub fn process(input: &mut syn::ItemFn) -> proc_macro2::TokenStream { 9 | use quote::ToTokens; 10 | 11 | let mut pats = MatchSorted(Vec::with_capacity(8)); 12 | pats.visit_item_fn_mut(input); 13 | 14 | let input = input.to_token_stream(); 15 | if let Err(err) = pats.cmp() { 16 | let err = err.to_compile_error(); 17 | quote::quote! { #input #err } 18 | } else { 19 | input 20 | } 21 | } 22 | 23 | struct MatchSorted(Vec>); 24 | 25 | impl VisitMut for MatchSorted { 26 | fn visit_expr_match_mut(&mut self, node: &mut syn::ExprMatch) { 27 | // 移除 `#[sorted]` 并复制 match 表达式分支的模式部分 28 | let filter = |attr: &syn::Attribute| { 29 | attr.path 30 | .get_ident() 31 | .map(|i| i == "e::format_ident!("sorted")) 32 | .unwrap_or(false) 33 | }; 34 | if let Some(pos) = node.attrs.iter().position(filter) { 35 | node.attrs.remove(pos); 36 | self.0 37 | .push(node.arms.iter().map(|arm| arm.pat.clone()).collect()); 38 | } 39 | 40 | visit_mut::visit_expr_match_mut(self, node); 41 | } 42 | } 43 | 44 | impl MatchSorted { 45 | fn to_vec_string(&self) -> Result>> { 46 | let mut v = Vec::with_capacity(8); 47 | for match_item in self.0.iter() { 48 | let mut u = Vec::with_capacity(8); 49 | for pat in match_item.iter() { 50 | u.push(path_to_string(pat)?); 51 | } 52 | v.push(u); 53 | } 54 | Ok(v) 55 | } 56 | 57 | fn cmp(&self) -> Result<()> { 58 | self.to_vec_string()? 59 | .into_iter() 60 | .zip(self.0.iter()) 61 | .try_for_each(|(raw, match_item)| { 62 | crate::cmp(raw, |pos| extract_path(&match_item[pos]).unwrap().span()) 63 | }) 64 | } 65 | } 66 | 67 | // 只支持在部分模式中取路径 68 | fn extract_path(pat: &Pat) -> Result> { 69 | use syn::parse_quote_spanned; 70 | let path = match pat { 71 | Pat::Path(path) => Cow::Borrowed(&path.path), 72 | Pat::Struct(s) => Cow::Borrowed(&s.path), 73 | Pat::TupleStruct(s) => Cow::Borrowed(&s.path), 74 | Pat::Ident(syn::PatIdent { ident: i, .. }) => { 75 | Cow::Owned(parse_quote_spanned! { i.span()=> #i }) 76 | } 77 | Pat::Wild(w) => { 78 | // 无法使用 parse_quote_spanned! 把 `_` 转化成 Path,所以需要手动构造 79 | // Cow::Owned(parse_quote_spanned! { w.underscore_token.span()=> #underscore }) 80 | let underscore: syn::Ident = w.underscore_token.into(); 81 | let mut segments = syn::punctuated::Punctuated::new(); 82 | segments.push(underscore.into()); 83 | Cow::Owned(syn::Path { 84 | leading_colon: None, 85 | segments, 86 | }) 87 | } 88 | p => return Err(Error::new(p.span(), "unsupported by #[sorted]")), 89 | }; 90 | Ok(path) 91 | } 92 | 93 | // 把每个匹配分支中的路径(包括多路径形式的路径)拼接成一个字符串 94 | fn path_to_string(pat: &Pat) -> Result { 95 | extract_path(pat).map(|p| { 96 | p.segments 97 | .iter() 98 | .map(|s| s.ident.to_string()) 99 | .collect::>() 100 | .join("::") 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /sorted/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_attribute] 4 | pub fn sorted(_: TokenStream, input: TokenStream) -> TokenStream { 5 | let input = syn::parse_macro_input!(input as syn::Item); 6 | TokenStream::from(_enum::process(&input)) 7 | } 8 | 9 | // workaround for procedural macro invocations (like `#[sorted]`) on expressions 10 | // see test/05-match-expr.rs 11 | #[proc_macro_attribute] 12 | pub fn check(_: TokenStream, input: TokenStream) -> TokenStream { 13 | let mut input = syn::parse_macro_input!(input as syn::ItemFn); 14 | TokenStream::from(_fn::process(&mut input)) 15 | } 16 | 17 | mod _enum; 18 | mod _fn; 19 | 20 | fn cmp proc_macro2::Span>(raw: Vec, f: F) -> syn::Result<()> { 21 | let mut sorted = raw.clone(); 22 | sorted.sort(); 23 | let (mut raw, sorted) = (raw.iter(), sorted.iter()); 24 | sorted 25 | .zip(raw.clone()) 26 | .try_for_each(|(sorted_s, raw_s)| { 27 | if sorted_s != raw_s { 28 | let pos = raw.position(|i| i == sorted_s).unwrap(); // 一定能找到 29 | Err((sorted_s, raw_s, pos)) 30 | } else { 31 | Ok(()) 32 | } 33 | }) 34 | .map_err(|(sorted_s, raw_s, pos)| { 35 | syn::Error::new( 36 | (f)(pos), 37 | format!("{} should sort before {}", sorted_s, raw_s), 38 | ) 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /sorted/tests/01-parse-enum.rs: -------------------------------------------------------------------------------- 1 | // This test checks that an attribute macro #[sorted] exists and is imported 2 | // correctly in the module system. If you make the macro return an empty token 3 | // stream or exactly the original token stream, this test will already pass! 4 | // 5 | // Be aware that the meaning of the return value of an attribute macro is 6 | // slightly different from that of a derive macro. Derive macros are only 7 | // allowed to *add* code to the caller's crate. Thus, what they return is 8 | // compiled *in addition to* the struct/enum that is the macro input. On the 9 | // other hand attribute macros are allowed to add code to the caller's crate but 10 | // also modify or remove whatever input the attribute is on. The TokenStream 11 | // returned by the attribute macro completely replaces the input item. 12 | // 13 | // Before moving on to the next test, I recommend also parsing the input token 14 | // stream as a syn::Item. In order for Item to be available you will need to 15 | // enable `features = ["full"]` of your dependency on Syn, since by default Syn 16 | // only builds the minimum set of parsers needed for derive macros. 17 | // 18 | // After parsing, the macro can return back exactly the original token stream so 19 | // that the input enum remains in the callers code and continues to be usable by 20 | // code in the rest of the crate. 21 | // 22 | // 23 | // Resources: 24 | // 25 | // - The Syn crate for parsing procedural macro input: 26 | // https://github.com/dtolnay/syn 27 | // 28 | // - The syn::Item type which represents a parsed enum as a syntax tree: 29 | // https://docs.rs/syn/1.0/syn/enum.Item.html 30 | 31 | use sorted::sorted; 32 | 33 | #[sorted] 34 | pub enum Conference { 35 | RustBeltRust, 36 | RustConf, 37 | RustFest, 38 | RustLatam, 39 | RustRush, 40 | } 41 | 42 | fn main() {} 43 | -------------------------------------------------------------------------------- /sorted/tests/02-not-enum.rs: -------------------------------------------------------------------------------- 1 | // The #[sorted] macro is only defined to work on enum types, so this is a test 2 | // to ensure that when it's attached to a struct (or anything else) it produces 3 | // some reasonable error. Your macro will need to look into the syn::Item that 4 | // it parsed to ensure that it represents an enum, returning an error for any 5 | // other type of Item such as a struct. 6 | // 7 | // This is an exercise in exploring how to return errors from procedural macros. 8 | // The goal is to produce an understandable error message which is tailored to 9 | // this specific macro (saying that #[sorted] cannot be applied to things other 10 | // than enum). For this you'll want to look at the syn::Error type, how to 11 | // construct it, and how to return it. 12 | // 13 | // Notice that the return value of an attribute macro is simply a TokenStream, 14 | // not a Result with an error. The syn::Error type provides a method to render 15 | // your error as a TokenStream containing an invocation of the compile_error 16 | // macro. 17 | // 18 | // A final tweak you may want to make is to have the `sorted` function delegate 19 | // to a private helper function which works with Result, so most of the macro 20 | // can be written with Result-returning functions while the top-level function 21 | // handles the conversion down to TokenStream. 22 | // 23 | // 24 | // Resources 25 | // 26 | // - The syn::Error type: 27 | // https://docs.rs/syn/1.0/syn/struct.Error.html 28 | 29 | use sorted::sorted; 30 | 31 | #[sorted] 32 | pub struct Error { 33 | kind: ErrorKind, 34 | message: String, 35 | } 36 | 37 | enum ErrorKind { 38 | Io, 39 | Syntax, 40 | Eof, 41 | } 42 | 43 | fn main() {} 44 | -------------------------------------------------------------------------------- /sorted/tests/02-not-enum.stderr: -------------------------------------------------------------------------------- 1 | error: expected enum or match expression 2 | --> tests/02-not-enum.rs:31:1 3 | | 4 | 31 | #[sorted] 5 | | ^^^^^^^^^ 6 | | 7 | = note: this error originates in the attribute macro `sorted` (in Nightly builds, run with -Z macro-backtrace for more info) 8 | -------------------------------------------------------------------------------- /sorted/tests/03-out-of-order.rs: -------------------------------------------------------------------------------- 1 | // At this point we have an enum and we need to check whether the variants 2 | // appear in sorted order! 3 | // 4 | // When your implementation notices a variant that compares lexicographically 5 | // less than one of the earlier variants, you'll want to construct a syn::Error 6 | // that gets converted to TokenStream by the already existing code that handled 7 | // this conversion during the previous test case. 8 | // 9 | // The "span" of your error, which determines where the compiler places the 10 | // resulting error message, will be the span of whichever variant name that is 11 | // not in the right place. Ideally the error message should also identify which 12 | // other variant the user needs to move this one to be in front of. 13 | 14 | use sorted::sorted; 15 | 16 | #[sorted] 17 | pub enum Error { 18 | ThatFailed, 19 | ThisFailed, 20 | SomethingFailed, 21 | WhoKnowsWhatFailed, 22 | } 23 | 24 | fn main() {} 25 | -------------------------------------------------------------------------------- /sorted/tests/03-out-of-order.stderr: -------------------------------------------------------------------------------- 1 | error: SomethingFailed should sort before ThatFailed 2 | --> tests/03-out-of-order.rs:20:5 3 | | 4 | 20 | SomethingFailed, 5 | | ^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/04-variants-with-data.rs: -------------------------------------------------------------------------------- 1 | // This test is similar to the previous where want to ensure that the macro 2 | // correctly generates an error when the input enum is out of order, but this 3 | // time it is using an enum that also has data associated with each variant. 4 | 5 | use sorted::sorted; 6 | 7 | use std::env::VarError; 8 | use std::error::Error as StdError; 9 | use std::fmt; 10 | use std::io; 11 | use std::str::Utf8Error; 12 | 13 | #[sorted] 14 | pub enum Error { 15 | Fmt(fmt::Error), 16 | Io(io::Error), 17 | Utf8(Utf8Error), 18 | Var(VarError), 19 | Dyn(Box), 20 | } 21 | 22 | fn main() {} 23 | -------------------------------------------------------------------------------- /sorted/tests/04-variants-with-data.stderr: -------------------------------------------------------------------------------- 1 | error: Dyn should sort before Fmt 2 | --> tests/04-variants-with-data.rs:19:5 3 | | 4 | 19 | Dyn(Box), 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/05-match-expr.rs: -------------------------------------------------------------------------------- 1 | // Get ready for a challenging step -- this test case is going to be a much 2 | // bigger change than the others so far. 3 | // 4 | // Not only do we want #[sorted] to assert that variants of an enum are written 5 | // in order inside the enum definition, but also inside match-expressions that 6 | // match on that enum. 7 | // 8 | // #[sorted] 9 | // match conference { 10 | // RustBeltRust => "...", 11 | // RustConf => "...", 12 | // RustFest => "...", 13 | // RustLatam => "...", 14 | // RustRush => "...", 15 | // } 16 | // 17 | // Currently, though, procedural macro invocations on expressions are not 18 | // allowed by the stable compiler! To work around this limitation until the 19 | // feature stabilizes, we'll be implementing a new #[sorted::check] macro which 20 | // the user will need to place on whatever function contains such a match. 21 | // 22 | // #[sorted::check] 23 | // fn f() { 24 | // let conference = ...; 25 | // 26 | // #[sorted] 27 | // match conference { 28 | // ... 29 | // } 30 | // } 31 | // 32 | // The #[sorted::check] macro will expand by looking inside the function to find 33 | // any match-expressions carrying a #[sorted] attribute, checking the order of 34 | // the arms in that match-expression, and then stripping away the inner 35 | // #[sorted] attribute to prevent the stable compiler from refusing to compile 36 | // the code. 37 | // 38 | // Note that unlike what we have seen in the previous test cases, stripping away 39 | // the inner #[sorted] attribute will require the new macro to mutate the input 40 | // syntax tree rather than inserting it unchanged into the output TokenStream as 41 | // before. 42 | // 43 | // Overall, the steps to pass this test will be: 44 | // 45 | // - Introduce a new procedural attribute macro called `check`. 46 | // 47 | // - Parse the input as a syn::ItemFn. 48 | // 49 | // - Traverse the function body looking for match-expressions. This part will 50 | // be easiest if you can use the VisitMut trait from Syn and write a visitor 51 | // with a visit_expr_match_mut method. 52 | // 53 | // - For each match-expression, figure out whether it has #[sorted] as one of 54 | // its attributes. If so, check that the match arms are sorted and delete 55 | // the #[sorted] attribute from the list of attributes. 56 | // 57 | // The result should be that we get the expected compile-time error pointing out 58 | // that `Fmt` should come before `Io` in the match-expression. 59 | // 60 | // 61 | // Resources: 62 | // 63 | // - The VisitMut trait to iterate and mutate a syntax tree: 64 | // https://docs.rs/syn/1.0/syn/visit_mut/trait.VisitMut.html 65 | // 66 | // - The ExprMatch struct: 67 | // https://docs.rs/syn/1.0/syn/struct.ExprMatch.html 68 | 69 | use sorted::sorted; 70 | 71 | use std::fmt::{self, Display}; 72 | use std::io; 73 | 74 | #[sorted] 75 | pub enum Error { 76 | Fmt(fmt::Error), 77 | Io(io::Error), 78 | } 79 | 80 | impl Display for Error { 81 | #[sorted::check] 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | use self::Error::*; 84 | 85 | #[sorted] 86 | match self { 87 | Io(e) => write!(f, "{}", e), 88 | Fmt(e) => write!(f, "{}", e), 89 | } 90 | } 91 | } 92 | 93 | fn main() {} 94 | -------------------------------------------------------------------------------- /sorted/tests/05-match-expr.stderr: -------------------------------------------------------------------------------- 1 | error: Fmt should sort before Io 2 | --> tests/05-match-expr.rs:88:13 3 | | 4 | 88 | Fmt(e) => write!(f, "{}", e), 5 | | ^^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/06-pattern-path.rs: -------------------------------------------------------------------------------- 1 | // When we checked enum definitions for sortedness, it was sufficient to compare 2 | // a single identifier (the name of the variant) for each variant. Match 3 | // expressions are different in that each arm may have a pattern that consists 4 | // of more than just one identifier. 5 | // 6 | // Ensure that patterns consisting of a path are correctly tested for 7 | // sortedness. These patterns will be of type Pat::Path, Pat::TupleStruct, or 8 | // Pat::Struct. 9 | // 10 | // 11 | // Resources: 12 | // 13 | // - The syn::Pat syntax tree which forms the left hand side of a match arm: 14 | // https://docs.rs/syn/1.0/syn/enum.Pat.html 15 | 16 | use sorted::sorted; 17 | 18 | use std::fmt::{self, Display}; 19 | use std::io; 20 | 21 | #[sorted] 22 | pub enum Error { 23 | Fmt(fmt::Error), 24 | Io(io::Error), 25 | } 26 | 27 | impl Display for Error { 28 | #[sorted::check] 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | #[sorted] 31 | match self { 32 | Error::Io(e) => write!(f, "{}", e), 33 | Error::Fmt(e) => write!(f, "{}", e), 34 | } 35 | } 36 | } 37 | 38 | fn main() {} 39 | -------------------------------------------------------------------------------- /sorted/tests/06-pattern-path.stderr: -------------------------------------------------------------------------------- 1 | error: Error::Fmt should sort before Error::Io 2 | --> tests/06-pattern-path.rs:33:13 3 | | 4 | 33 | Error::Fmt(e) => write!(f, "{}", e), 5 | | ^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/07-unrecognized-pattern.rs: -------------------------------------------------------------------------------- 1 | // The macro won't need to define what it means for other sorts of patterns to 2 | // be sorted. It should be fine to trigger an error if any of the patterns is 3 | // not something that can be compared by path. 4 | // 5 | // Be sure that the resulting error message is understandable and placed 6 | // correctly underlining the unsupported pattern. 7 | 8 | #[sorted::check] 9 | fn f(bytes: &[u8]) -> Option { 10 | #[sorted] 11 | match bytes { 12 | [] => Some(0), 13 | [a] => Some(*a), 14 | [a, b] => Some(a + b), 15 | _other => None, 16 | } 17 | } 18 | 19 | fn main() {} 20 | -------------------------------------------------------------------------------- /sorted/tests/07-unrecognized-pattern.stderr: -------------------------------------------------------------------------------- 1 | error: unsupported by #[sorted] 2 | --> tests/07-unrecognized-pattern.rs:12:9 3 | | 4 | 12 | [] => Some(0), 5 | | ^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/08-underscore-failed.rs: -------------------------------------------------------------------------------- 1 | // There is one other common type of pattern that would be nice to support -- 2 | // the wildcard or underscore pattern. The #[sorted] macro should check that if 3 | // a wildcard pattern is present then it is the last one. 4 | 5 | use sorted::sorted; 6 | 7 | #[sorted] 8 | pub enum Conference { 9 | RustBeltRust, 10 | RustConf, 11 | RustFest, 12 | RustLatam, 13 | RustRush, 14 | } 15 | 16 | impl Conference { 17 | #[sorted::check] 18 | pub fn region(&self) -> &str { 19 | use self::Conference::*; 20 | 21 | #[sorted] 22 | match self { 23 | RustFest => "Europe", 24 | RustLatam => "Latin America", 25 | rest => 26 | { 27 | #[sorted] 28 | match rest { 29 | RustRush => "Rush", 30 | _ => "", 31 | } 32 | } 33 | }; 34 | 35 | #[sorted] 36 | match self { 37 | _ => "elsewhere", 38 | RustFest => "Europe", 39 | RustLatam => "Latin America", 40 | } 41 | } 42 | } 43 | 44 | fn main() {} 45 | -------------------------------------------------------------------------------- /sorted/tests/08-underscore-failed.stderr: -------------------------------------------------------------------------------- 1 | error: RustFest should sort before _ 2 | --> tests/08-underscore-failed.rs:38:13 3 | | 4 | 38 | RustFest => "Europe", 5 | | ^^^^^^^^ 6 | -------------------------------------------------------------------------------- /sorted/tests/08-underscore.rs: -------------------------------------------------------------------------------- 1 | // There is one other common type of pattern that would be nice to support -- 2 | // the wildcard or underscore pattern. The #[sorted] macro should check that if 3 | // a wildcard pattern is present then it is the last one. 4 | 5 | use sorted::sorted; 6 | 7 | #[sorted] 8 | pub enum Conference { 9 | RustBeltRust, 10 | RustConf, 11 | RustFest, 12 | RustLatam, 13 | RustRush, 14 | } 15 | 16 | impl Conference { 17 | #[sorted::check] 18 | pub fn region(&self) -> &str { 19 | use self::Conference::*; 20 | 21 | #[sorted] 22 | match self { 23 | RustFest => "Europe", 24 | RustLatam => "Latin America", 25 | _ => "elsewhere", 26 | } 27 | } 28 | } 29 | 30 | fn main() {} 31 | -------------------------------------------------------------------------------- /sorted/tests/progress.rs: -------------------------------------------------------------------------------- 1 | #[test] 2 | fn tests() { 3 | let t = trybuild::TestCases::new(); 4 | t.pass("tests/01-parse-enum.rs"); 5 | t.compile_fail("tests/02-not-enum.rs"); 6 | t.compile_fail("tests/03-out-of-order.rs"); 7 | t.compile_fail("tests/04-variants-with-data.rs"); 8 | t.compile_fail("tests/05-match-expr.rs"); 9 | t.compile_fail("tests/06-pattern-path.rs"); 10 | t.compile_fail("tests/07-unrecognized-pattern.rs"); 11 | t.pass("tests/08-underscore.rs"); 12 | t.compile_fail("tests/08-underscore-failed.rs"); 13 | } 14 | --------------------------------------------------------------------------------