├── .github ├── FUNDING.yml └── 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 │ │ └── lib.rs ├── src │ └── lib.rs └── tests │ ├── 01-specifier-types.rs │ ├── 02-storage.rs │ ├── 03-accessors.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 │ ├── 10-bits-attribute.rs │ ├── 11-bits-attribute-wrong.rs │ ├── 11-bits-attribute-wrong.stderr │ ├── 12-accessors-edge.rs │ └── progress.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.rs │ ├── 08-unrecognized-attribute.rs │ ├── 08-unrecognized-attribute.stderr │ ├── 09-redefined-prelude-types.rs │ └── progress.rs ├── debug ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── 01-parse.rs │ ├── 02-impl-debug.rs │ ├── 03-custom-format.rs │ ├── 04-type-parameter.rs │ ├── 05-phantom-data.rs │ ├── 06-bound-trouble.rs │ ├── 07-associated-type.rs │ ├── 08-escape-hatch.rs │ └── progress.rs ├── main.rs ├── seq ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── 01-parse-header.rs │ ├── 02-parse-body.rs │ ├── 03-expand-four-errors.rs │ ├── 03-expand-four-errors.stderr │ ├── 04-paste-ident.rs │ ├── 05-repeat-section.rs │ ├── 06-init-array.rs │ ├── 07-inclusive-range.rs │ ├── 08-ident-span.rs │ ├── 08-ident-span.stderr │ ├── 09-interaction-with-macrorules.rs │ └── progress.rs └── sorted ├── Cargo.toml ├── src └── 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.rs └── progress.rs /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dtolnay 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | schedule: [cron: "40 1 * * *"] 8 | 9 | permissions: 10 | contents: read 11 | 12 | env: 13 | RUSTFLAGS: -Dwarnings 14 | 15 | jobs: 16 | test: 17 | name: Rust ${{matrix.rust}} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | rust: [nightly, beta, stable] 23 | timeout-minutes: 45 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: dtolnay/rust-toolchain@master 27 | with: 28 | toolchain: ${{matrix.rust}} 29 | - run: cargo check 30 | 31 | solution: 32 | name: Project ${{matrix.project}} 33 | runs-on: ubuntu-latest 34 | strategy: 35 | fail-fast: false 36 | matrix: 37 | project: [builder, debug, seq, sorted, bitfield] 38 | env: 39 | GIT_COMMITTER_EMAIL: proc-macro-workshop@dtolnay.github.io 40 | GIT_COMMITTER_NAME: proc-macro-workshop CI 41 | timeout-minutes: 45 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: dtolnay/rust-toolchain@nightly 45 | - uses: dtolnay/install@cargo-outdated 46 | - run: git fetch origin --unshallow refs/solution/${{matrix.project}} HEAD 47 | - run: git rev-parse FETCH_HEAD 48 | - run: git rebase HEAD FETCH_HEAD 49 | - run: cargo test 50 | working-directory: ${{matrix.project}} 51 | - run: cargo outdated --exit-code 1 52 | working-directory: ${{matrix.project}} 53 | if: github.event_name != 'pull_request' 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target/ 3 | wip/ 4 | -------------------------------------------------------------------------------- /.template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rust Latam: procedural macros workshop 2 | 3 | *This repo contains a selection of projects designed to learn to write Rust 4 | procedural macros — Rust code that generates Rust code.* 5 | 6 | *Each of these projects is drawn closely from a compelling real use case. Out of 7 | the 5 projects here, 3 are macros that I have personally implemented in 8 | industrial codebases for work, and the other 2 exist as libraries on crates.io 9 | by other authors.* 10 | 11 |
12 | 13 | ## Contents 14 | 15 | - [**Suggested prerequisites**](#suggested-prerequisites) 16 | - [**Projects**](#projects) — Introduction to each of the projects 17 | - [**Derive macro:** `derive(Builder)`](#derive-macro-derivebuilder) 18 | - [**Derive macro:** `derive(CustomDebug)`](#derive-macro-derivecustomdebug) 19 | - [**Function-like macro:** `seq!`](#function-like-macro-seq) 20 | - [**Attribute macro:** `#[sorted]`](#attribute-macro-sorted) 21 | - [**Attribute macro:** `#[bitfield]`](#attribute-macro-bitfield) 22 | - [**Project recommendations**](#project-recommendations) — What to work on 23 | depending on your interests 24 | - [**Test harness**](#test-harness) — Explanation of how testing is set up 25 | - [**Workflow**](#workflow) — Recommended way to work through the workshop 26 | - [**Debugging tips**](#debugging-tips) 27 | 28 |
29 | 30 | ## Suggested prerequisites 31 | 32 | This workshop covers attribute macros, derive macros, and function-like 33 | procedural macros. 34 | 35 | Be aware that the content of the workshop and the explanations in this repo will 36 | assume a working understanding of structs, enums, traits, trait impls, generic 37 | parameters, and trait bounds. You are welcome to dive into the workshop with any 38 | level of experience with Rust, but you may find that these basics are far easier 39 | to learn for the first time outside of the context of macros. 40 | 41 |
42 | 43 | ## Projects 44 | 45 | Here is an introduction to each of the projects. At the bottom, I give 46 | recommendations for what order to tackle them based on your interests. Note that 47 | each of these projects goes into more depth than what is described in the 48 | introduction here. 49 | 50 | ### Derive macro: `derive(Builder)` 51 | 52 | This macro generates the boilerplate code involved in implementing the [builder 53 | pattern] in Rust. Builders are a mechanism for instantiating structs, especially 54 | structs with many fields, and especially if many of those fields are optional or 55 | the set of fields may need to grow backward compatibly over time. 56 | 57 | [builder pattern]: https://en.wikipedia.org/wiki/Builder_pattern 58 | 59 | There are a few different possibilities for expressing builders in Rust. Unless 60 | you have a strong pre-existing preference, to keep things simple for this 61 | project I would recommend following the example of the standard library's 62 | [`std::process::Command`] builder in which the setter methods each receive and 63 | return `&mut self` to allow chained method calls. 64 | 65 | [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html 66 | 67 | Callers will invoke the macro as follows. 68 | 69 | ```rust 70 | use derive_builder::Builder; 71 | 72 | #[derive(Builder)] 73 | pub struct Command { 74 | executable: String, 75 | #[builder(each = "arg")] 76 | args: Vec, 77 | current_dir: Option, 78 | } 79 | 80 | fn main() { 81 | let command = Command::builder() 82 | .executable("cargo".to_owned()) 83 | .arg("build".to_owned()) 84 | .arg("--release".to_owned()) 85 | .build() 86 | .unwrap(); 87 | 88 | assert_eq!(command.executable, "cargo"); 89 | } 90 | ``` 91 | 92 | This project covers: 93 | 94 | - traversing syntax trees; 95 | - constructing output source code; 96 | - processing helper attributes to customize the generated code. 97 | 98 | *Project skeleton is located under the builder directory.* 99 | 100 | ### Derive macro: `derive(CustomDebug)` 101 | 102 | This macro implements a derive for the standard library [`std::fmt::Debug`] 103 | trait that is more customizable than the similar `Debug` derive macro exposed by 104 | the standard library. 105 | 106 | [`std::fmt::Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html 107 | 108 | In particular, we'd like to be able to select the formatting used for individual 109 | struct fields by providing a format string in the style expected by Rust string 110 | formatting macros like `format!` and `println!`. 111 | 112 | ```rust 113 | use derive_debug::CustomDebug; 114 | 115 | #[derive(CustomDebug)] 116 | pub struct Field { 117 | name: String, 118 | #[debug = "0b{:08b}"] 119 | bitmask: u8, 120 | } 121 | ``` 122 | 123 | Here, one possible instance of the struct above might be printed by its 124 | generated `Debug` impl like this: 125 | 126 | ```console 127 | Field { name: "st0", bitmask: 0b00011100 } 128 | ``` 129 | 130 | This project covers: 131 | 132 | - traversing syntax trees; 133 | - constructing output source code; 134 | - processing helper attributes; 135 | - dealing with lifetime parameters and type parameters; 136 | - inferring trait bounds on generic parameters of trait impls; 137 | - limitations of derive's ability to emit universally correct trait bounds. 138 | 139 | *Project skeleton is located under the debug directory.* 140 | 141 | ### Function-like macro: `seq!` 142 | 143 | This macro provides a syntax for stamping out sequentially indexed copies of an 144 | arbitrary chunk of code. 145 | 146 | For example our application may require an enum with sequentially numbered 147 | variants like `Cpu0` `Cpu1` `Cpu2` ... `Cpu511`. But note that the same `seq!` 148 | macro should work for any sort of compile-time loop; there is nothing specific 149 | to emitting enum variants. A different caller might use it for generating an 150 | expression like `tuple.0 + tuple.1 + ... + tuple.511`. 151 | 152 | ```rust 153 | use seq::seq; 154 | 155 | seq!(N in 0..512 { 156 | #[derive(Copy, Clone, PartialEq, Debug)] 157 | pub enum Processor { 158 | #( 159 | Cpu~N, 160 | )* 161 | } 162 | }); 163 | 164 | fn main() { 165 | let cpu = Processor::Cpu8; 166 | 167 | assert_eq!(cpu as u8, 8); 168 | assert_eq!(cpu, Processor::Cpu8); 169 | } 170 | ``` 171 | 172 | This project covers: 173 | 174 | - parsing custom syntax; 175 | - low-level representation of token streams; 176 | - constructing output source code. 177 | 178 | *Project skeleton is located under the seq directory.* 179 | 180 | ### Attribute macro: `#[sorted]` 181 | 182 | A macro for when your coworkers (or you yourself) cannot seem to keep enum 183 | variants in sorted order when adding variants or refactoring. The macro will 184 | detect unsorted variants at compile time and emit an error pointing out which 185 | variants are out of order. 186 | 187 | ```rust 188 | #[sorted] 189 | #[derive(Debug)] 190 | pub enum Error { 191 | BlockSignal(signal::Error), 192 | CreateCrasClient(libcras::Error), 193 | CreateEventFd(sys_util::Error), 194 | CreateSignalFd(sys_util::SignalFdError), 195 | CreateSocket(io::Error), 196 | DetectImageType(qcow::Error), 197 | DeviceJail(io_jail::Error), 198 | NetDeviceNew(virtio::NetError), 199 | SpawnVcpu(io::Error), 200 | } 201 | ``` 202 | 203 | This project covers: 204 | 205 | - compile-time error reporting; 206 | - application of visitor pattern to traverse a syntax tree; 207 | - limitations of the currently stable macro API and some ways to work around 208 | them. 209 | 210 | *Project skeleton is located under the sorted directory.* 211 | 212 | ### Attribute macro: `#[bitfield]` 213 | 214 | This macro provides a mechanism for defining structs in a packed binary 215 | representation with access to ranges of bits, similar to the language-level 216 | support for [bit fields in C]. 217 | 218 | [bit fields in C]: https://en.cppreference.com/w/cpp/language/bit_field 219 | 220 | The macro will conceptualize one of these structs as a sequence of bits 0..N. 221 | The bits are grouped into fields in the order specified by a struct written by 222 | the caller. The `#[bitfield]` attribute rewrites the caller's struct into a 223 | private byte array representation with public getter and setter methods for each 224 | field. 225 | 226 | The total number of bits N is required to be a multiple of 8 (this will be 227 | checked at compile time). 228 | 229 | For example, the following invocation builds a struct with a total size of 32 230 | bits or 4 bytes. It places field `a` in the least significant bit of the first 231 | byte, field `b` in the next three least significant bits, field `c` in the 232 | remaining four most significant bits of the first byte, and field `d` spanning 233 | the next three bytes. 234 | 235 | ```rust 236 | use bitfield::*; 237 | 238 | #[bitfield] 239 | pub struct MyFourBytes { 240 | a: B1, 241 | b: B3, 242 | c: B4, 243 | d: B24, 244 | } 245 | ``` 246 | 247 | ```text 248 | least significant bit of third byte 249 | ┊ most significant 250 | ┊ ┊ 251 | ┊ ┊ 252 | ║ first byte ║ second byte ║ third byte ║ fourth byte ║ 253 | ╟───────────────╫───────────────╫───────────────╫───────────────╢ 254 | ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║ 255 | ╟─╫─────╫───────╫───────────────────────────────────────────────╢ 256 | ║a║ b ║ c ║ d ║ 257 | ┊ ┊ 258 | ┊ ┊ 259 | least significant bit of d most significant 260 | ``` 261 | 262 | The code emitted by the `#[bitfield]` macro for this struct would be as follows. 263 | Note that the field getters and setters use whichever of `u8`, `u16`, `u32`, 264 | `u64` is the smallest while being at least as large as the number of bits in 265 | the field. 266 | 267 | ```rust 268 | impl MyFourBytes { 269 | // Initializes all fields to 0. 270 | pub fn new() -> Self; 271 | 272 | // Field getters and setters: 273 | pub fn get_a(&self) -> u8; 274 | pub fn set_a(&mut self, val: u8); 275 | pub fn get_b(&self) -> u8; 276 | pub fn set_b(&mut self, val: u8); 277 | pub fn get_c(&self) -> u8; 278 | pub fn set_c(&mut self, val: u8); 279 | pub fn get_d(&self) -> u32; 280 | pub fn set_d(&mut self, val: u32); 281 | } 282 | ``` 283 | 284 | This project covers: 285 | 286 | - traversing syntax trees; 287 | - processing helper attributes; 288 | - constructing output source code; 289 | - interacting with traits and structs other than from the standard library; 290 | - techniques for compile-time assertions that require type information, by 291 | leveraging the trait system in interesting ways from generated code; 292 | - tricky code. 293 | 294 | *Project skeleton is located under the bitfield directory.* 295 | 296 | ### Project recommendations 297 | 298 | If this is your first time working with procedural macros, I would recommend 299 | starting with the `derive(Builder)` project. This will get you comfortable with 300 | traversing syntax trees and constructing output source code. These are the two 301 | fundamental components of a procedural macro. 302 | 303 | After that, it would be equally reasonable to jump to any of 304 | `derive(CustomDebug)`, `seq!`, or `#[sorted]`. 305 | 306 | - Go for `derive(CustomDebug)` if you are interested in exploring how macros 307 | manipulate trait bounds, which is one of the most complicated aspects of 308 | code generation in Rust involving generic code like [Serde]. This project 309 | provides an approachable introduction to trait bounds and digs into many of 310 | the challenging aspects. 311 | 312 | - Go for `seq!` if you are interested in parsing a custom input syntax yourself. 313 | The other projects will all mostly rely on parsers that have already been 314 | written and distributed as a library, since their input is ordinary Rust 315 | syntax. 316 | 317 | - Go for `#[sorted]` if you are interested in generating diagnostics (custom 318 | errors) via a macro. Part of this project also covers a different way of 319 | processing input syntax trees; the other projects will do most things through 320 | `if let`. The visitor approach is better suited to certain types of macros 321 | involving statements or expressions as we'll see here when checking that 322 | `match` arms are sorted. 323 | 324 | [Serde]: https://serde.rs/ 325 | 326 | I would recommend starting on `#[bitfield]` only after you feel you have a 327 | strong grasp on at least two of the other projects. Note that completing the 328 | full intended design will involve writing at least one of all three types of 329 | procedural macros and substantially more code than the other projects. 330 | 331 |
332 | 333 | ## Test harness 334 | 335 | Testing macros thoroughly tends to be tricky. Rust and Cargo have a built-in 336 | testing framework via `cargo test` which can work for testing the success cases, 337 | but we also really care that our macros produce good error message when they 338 | detect a problem at compile time; Cargo isn't able to say that failing to 339 | compile is considered a success, and isn't able to compare that the error 340 | message produced by the compiler is exactly what we expect. 341 | 342 | The project skeletons in this repository use an alternative test harness called 343 | [trybuild]. 344 | 345 | [trybuild]: https://github.com/dtolnay/trybuild 346 | 347 |

348 | 349 | 350 | 351 |

352 | 353 | The test harness is geared toward iterating on the implementation of a 354 | procedural macro, observing the errors emitted by failed executions of the 355 | macro, and testing that those errors are as expected. 356 | 357 |
358 | 359 | ## Workflow 360 | 361 | Every project has a test suite already written under its tests 362 | directory. (But feel free to add more tests, remove tests for functionality you 363 | don't want to implement, or modify tests as you see fit to align with your 364 | implementation.) 365 | 366 | Run `cargo test` inside any of the 5 top-level project directories to run the 367 | test suite for that project. 368 | 369 | Initially every projects starts with all of its tests disabled. Open up the 370 | project's *tests/progress.rs* file and enable tests one at a time as you work 371 | through the implementation. **The test files (for example *tests/01-parse.rs*) 372 | each contain a comment explaining what functionality is tested and giving some 373 | tips for how to implement it.** I recommend working through tests in numbered 374 | order, each time enabling one more test and getting it passing before moving on. 375 | 376 | Tests come in two flavors: tests that should compile+run successfully, and tests 377 | that should fail to compile with a specific error message. 378 | 379 | If a test should compile and run successfully, but fails, the test runner will 380 | surface the compiler error or runtime error output. 381 | 382 |

383 | 384 | 385 | 386 |

387 | 388 | For tests that should fail to compile, we compare the compilation output against 389 | a file of expected errors for that test. If those errors match, the test is 390 | considered to pass. If they do not match, the test runner will surface the 391 | expected and actual output. 392 | 393 | Expected output goes in a file with the same name as the test except with an 394 | extension of _*.stderr_ instead of _*.rs_. 395 | 396 |

397 | 398 | 399 | 400 |

401 | 402 | If there is no _*.stderr_ file for a test that is supposed to fail to compile, 403 | the test runner will save the compiler's output into a directory called 404 | wip adjacent to the tests directory. So the way to update 405 | the "expected" output is to delete the existing _*.stderr_ file, run the tests 406 | again so that the output is written to *wip*, and then move the new output from 407 | *wip* to *tests*. 408 | 409 |

410 | 411 | 412 | 413 |

414 | 415 |
416 | 417 | ## Debugging tips 418 | 419 | To look at what code a macro is expanding into, install the [cargo expand] Cargo 420 | subcommand and then run `cargo expand` in the repository root (outside of any of 421 | the project directories) to expand the main.rs file in that directory. You can 422 | copy any of the test cases into this main.rs and tweak it as you iterate on the 423 | macro. 424 | 425 | [cargo expand]: https://github.com/dtolnay/cargo-expand 426 | 427 | If a macro is emitting syntactically invalid code (not just code that fails 428 | type-checking) then cargo expand will not be able to show it. Instead have the 429 | macro print its generated TokenStream to stderr before returning the tokens. 430 | 431 | ```rust 432 | eprintln!("TOKENS: {}", tokens); 433 | ``` 434 | 435 | Then a `cargo check` in the repository root (if you are iterating using main.rs) 436 | or `cargo test` in the corresponding project directory will display this output 437 | during macro expansion. 438 | 439 | Stderr is also a helpful way to see the structure of the syntax tree that gets 440 | parsed from the input of the macro. 441 | 442 | ```rust 443 | eprintln!("INPUT: {:#?}", syntax_tree); 444 | ``` 445 | 446 | Note that in order for Syn's syntax tree types to provide Debug impls, you will 447 | need to set `features = ["extra-traits"]` on the dependency on Syn. This is 448 | because adding hundreds of Debug impls adds an appreciable amount of compile 449 | time to Syn, and we really only need this enabled while doing development on a 450 | macro rather than when the finished macro is published to users. 451 | 452 |
453 | 454 | ### License 455 | 456 | 457 | Licensed under either of Apache License, Version 458 | 2.0 or MIT license at your option. 459 | 460 | 461 |
462 | 463 | 464 | Unless you explicitly state otherwise, any contribution intentionally submitted 465 | for inclusion in this codebase by you, as defined in the Apache-2.0 license, 466 | shall be dual licensed as above, without any additional terms or conditions. 467 | 468 | -------------------------------------------------------------------------------- /bitfield/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitfield" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 6 | publish = false 7 | 8 | [[test]] 9 | name = "tests" 10 | path = "tests/progress.rs" 11 | 12 | [dev-dependencies] 13 | trybuild = { version = "1.0.49", features = ["diff"] } 14 | 15 | [dependencies] 16 | bitfield-impl = { path = "impl" } 17 | -------------------------------------------------------------------------------- /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 | # TODO 12 | -------------------------------------------------------------------------------- /bitfield/impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_attribute] 4 | pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { 5 | let _ = args; 6 | let _ = input; 7 | 8 | unimplemented!() 9 | } 10 | -------------------------------------------------------------------------------- /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 | 15 | // TODO other things 16 | -------------------------------------------------------------------------------- /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/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[E0277]: the trait bound `bitfield::checks::SevenMod8: bitfield::checks::TotalSizeIsMultipleOfEightBits` is not satisfied 2 | --> tests/04-multiple-of-8bits.rs:53:1 3 | | 4 | 53 | #[bitfield] 5 | | ^^^^^^^^^^^ the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is not implemented for `bitfield::checks::SevenMod8` 6 | | 7 | = help: the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is implemented for `bitfield::checks::ZeroMod8` 8 | = note: this error originates in the attribute macro `bitfield` (in Nightly builds, run with -Z macro-backtrace for more info) 9 | -------------------------------------------------------------------------------- /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 | | 7 | = help: the trait `bitfield::checks::DiscriminantInRange` is implemented for `bitfield::checks::True` 8 | -------------------------------------------------------------------------------- /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 size of 9, found one with a size of 1 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/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 | //t.compile_fail("tests/04-multiple-of-8bits.rs"); 8 | //t.pass("tests/05-accessor-signatures.rs"); 9 | //t.pass("tests/06-enums.rs"); 10 | //t.pass("tests/07-optional-discriminant.rs"); 11 | //t.compile_fail("tests/08-non-power-of-two.rs"); 12 | //t.compile_fail("tests/09-variant-out-of-range.rs"); 13 | //t.pass("tests/10-bits-attribute.rs"); 14 | //t.compile_fail("tests/11-bits-attribute-wrong.rs"); 15 | //t.pass("tests/12-accessors-edge.rs"); 16 | } 17 | -------------------------------------------------------------------------------- /builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_builder" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 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 | # TODO 20 | -------------------------------------------------------------------------------- /builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_derive(Builder)] 4 | pub fn derive(input: TokenStream) -> TokenStream { 5 | let _ = input; 6 | 7 | unimplemented!() 8 | } 9 | -------------------------------------------------------------------------------- /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/2.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/2.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.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 type: 29 | // https://docs.rs/syn/2.0/syn/struct.Attribute.html 30 | 31 | use derive_builder::Builder; 32 | 33 | #[derive(Builder)] 34 | pub struct Command { 35 | executable: String, 36 | #[builder(each = "arg")] 37 | args: Vec, 38 | #[builder(each = "env")] 39 | env: Vec, 40 | current_dir: Option, 41 | } 42 | 43 | fn main() { 44 | let command = Command::builder() 45 | .executable("cargo".to_owned()) 46 | .arg("build".to_owned()) 47 | .arg("--release".to_owned()) 48 | .build() 49 | .unwrap(); 50 | 51 | assert_eq!(command.executable, "cargo"); 52 | assert_eq!(command.args, vec!["build", "--release"]); 53 | } 54 | -------------------------------------------------------------------------------- /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/2.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.compile_fail("tests/08-unrecognized-attribute.rs"); 12 | //t.pass("tests/09-redefined-prelude-types.rs"); 13 | } 14 | -------------------------------------------------------------------------------- /debug/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive_debug" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 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 | # TODO 20 | -------------------------------------------------------------------------------- /debug/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_derive(CustomDebug)] 4 | pub fn derive(input: TokenStream) -> TokenStream { 5 | let _ = input; 6 | 7 | unimplemented!() 8 | } 9 | -------------------------------------------------------------------------------- /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/2.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 type: 19 | // https://docs.rs/syn/2.0/syn/struct.Attribute.html 20 | // 21 | // - Macro for applying a format string to some runtime value: 22 | // https://doc.rust-lang.org/std/macro.format_args.html 23 | 24 | use derive_debug::CustomDebug; 25 | 26 | #[derive(CustomDebug)] 27 | pub struct Field { 28 | name: &'static str, 29 | #[debug = "0b{:08b}"] 30 | bitmask: u8, 31 | } 32 | 33 | fn main() { 34 | let f = Field { 35 | name: "F", 36 | bitmask: 0b00011100, 37 | }; 38 | 39 | let debug = format!("{:?}", f); 40 | let expected = r#"Field { name: "F", bitmask: 0b00011100 }"#; 41 | 42 | assert_eq!(debug, expected); 43 | } 44 | -------------------------------------------------------------------------------- /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/2.0/syn/struct.Generics.html 14 | // 15 | // - A helper for placing generics into an impl signature: 16 | // https://docs.rs/syn/2.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 accommodate 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.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/2.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.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/05-phantom-data.rs"); 9 | //t.pass("tests/06-bound-trouble.rs"); 10 | //t.pass("tests/07-associated-type.rs"); 11 | //t.pass("tests/08-escape-hatch.rs"); 12 | } 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /seq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seq" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 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 | # TODO 20 | -------------------------------------------------------------------------------- /seq/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro] 4 | pub fn seq(input: TokenStream) -> TokenStream { 5 | let _ = input; 6 | 7 | unimplemented!() 8 | } 9 | -------------------------------------------------------------------------------- /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/2.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/2.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/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/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/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/05-repeat-section.rs"); 9 | //t.pass("tests/06-init-array.rs"); 10 | //t.pass("tests/07-inclusive-range.rs"); 11 | //t.compile_fail("tests/08-ident-span.rs"); 12 | //t.pass("tests/09-interaction-with-macrorules.rs"); 13 | } 14 | -------------------------------------------------------------------------------- /sorted/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sorted" 3 | version = "0.0.0" 4 | autotests = false 5 | edition = "2021" 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 | # TODO 20 | -------------------------------------------------------------------------------- /sorted/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | #[proc_macro_attribute] 4 | pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream { 5 | let _ = args; 6 | let _ = input; 7 | 8 | unimplemented!() 9 | } 10 | -------------------------------------------------------------------------------- /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/2.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/2.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/2.0/syn/visit_mut/trait.VisitMut.html 65 | // 66 | // - The ExprMatch struct: 67 | // https://docs.rs/syn/2.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/2.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.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 | } 13 | --------------------------------------------------------------------------------