├── .gitignore ├── .template ├── Cargo.toml ├── src │ └── lib.rs └── tests │ └── progress.rs ├── .travis.yml ├── 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 ├── seq-proc │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── 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-make-work-in-function.rs │ ├── 07-init-array.rs │ ├── 08-inclusive-range.rs │ ├── 09-ident-span.rs │ ├── 09-ident-span.stderr │ ├── 10-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 /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | wip 4 | -------------------------------------------------------------------------------- /.template/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "" 3 | version = "0.0.0" 4 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | 18 | [dependencies] 19 | # TODO 20 | -------------------------------------------------------------------------------- /.template/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | 3 | rust: 4 | - stable 5 | - beta 6 | - nightly 7 | 8 | script: 9 | - cargo check 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proc-macro-workshop" 3 | version = "0.0.0" 4 | edition = "2018" 5 | publish = false 6 | 7 | [workspace] 8 | 9 | [[bin]] 10 | name = "workshop" 11 | path = "main.rs" 12 | 13 | [dependencies] 14 | bitfield = { path = "bitfield" } 15 | derive_builder = { path = "builder" } 16 | derive_debug = { path = "debug" } 17 | seq = { path = "seq" } 18 | sorted = { path = "sorted" } 19 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /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 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [[test]] 9 | name = "tests" 10 | path = "tests/progress.rs" 11 | 12 | [dev-dependencies] 13 | trybuild = "1.0" 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 = "2018" 5 | publish = false 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | # TODO 12 | -------------------------------------------------------------------------------- /bitfield/impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | #[proc_macro_attribute] 6 | pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream { 7 | let _ = args; 8 | let _ = input; 9 | 10 | unimplemented!() 11 | } 12 | -------------------------------------------------------------------------------- /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 | --> $DIR/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 | For more information about this error, try `rustc --explain E0277`. 8 | -------------------------------------------------------------------------------- /bitfield/tests/05-accessor-signatures.rs: -------------------------------------------------------------------------------- 1 | // For getters and setters, we would like for the signature to be in terms of 2 | // the narrowest unsigned integer type that can hold the right number of bits. 3 | // That means the accessors for B1 through B8 would use u8, B9 through B16 would 4 | // use u16 etc. 5 | 6 | use bitfield::*; 7 | use std::mem::size_of_val; 8 | 9 | type A = B1; 10 | type B = B3; 11 | type C = B4; 12 | type D = B24; 13 | 14 | #[bitfield] 15 | pub struct MyFourBytes { 16 | a: A, 17 | b: B, 18 | c: C, 19 | d: D, 20 | } 21 | 22 | fn main() { 23 | let mut x = MyFourBytes::new(); 24 | 25 | // I am testing the signatures in this roundabout way to avoid making it 26 | // possible to pass this test with a generic signature that is inconvenient 27 | // for callers, such as `fn get_a>(&self) -> T`. 28 | 29 | let a = 1; 30 | x.set_a(a); // expect fn(&mut MyFourBytes, u8) 31 | let b = 1; 32 | x.set_b(b); 33 | let c = 1; 34 | x.set_c(c); 35 | let d = 1; 36 | x.set_d(d); // expect fn(&mut MyFourBytes, u32) 37 | 38 | assert_eq!(size_of_val(&a), 1); 39 | assert_eq!(size_of_val(&b), 1); 40 | assert_eq!(size_of_val(&c), 1); 41 | assert_eq!(size_of_val(&d), 4); 42 | 43 | assert_eq!(size_of_val(&x.get_a()), 1); // expect fn(&MyFourBytes) -> u8 44 | assert_eq!(size_of_val(&x.get_b()), 1); 45 | assert_eq!(size_of_val(&x.get_c()), 1); 46 | assert_eq!(size_of_val(&x.get_d()), 4); // expect fn(&MyFourBytes) -> u32 47 | } 48 | -------------------------------------------------------------------------------- /bitfield/tests/06-enums.rs: -------------------------------------------------------------------------------- 1 | // For some bitfield members, working with them as enums will make more sense to 2 | // the user than working with them as integers. We will require enums that have 3 | // a power-of-two number of variants so that they exhaustively cover a fixed 4 | // range of bits. 5 | // 6 | // // Works like B3, but getter and setter signatures will use 7 | // // the enum instead of u8. 8 | // #[derive(BitfieldSpecifier)] 9 | // enum DeliveryMode { 10 | // Fixed = 0b000, 11 | // Lowest = 0b001, 12 | // SMI = 0b010, 13 | // RemoteRead = 0b011, 14 | // NMI = 0b100, 15 | // Init = 0b101, 16 | // Startup = 0b110, 17 | // External = 0b111, 18 | // } 19 | // 20 | // For this test case it is okay to require that every enum variant has an 21 | // explicit discriminant that is an integer literal. We will relax this 22 | // requirement in a later test case. 23 | // 24 | // Optionally if you are interested, come up with a way to support enums with a 25 | // number of variants that is not a power of two, but this is not necessary for 26 | // the test suite. Maybe there could be a #[bits = N] attribute that determines 27 | // the bit width of the specifier, and the getter (only for such enums) would 28 | // return Result with the raw value accessible through the 29 | // error type as u64: 30 | // 31 | // #[derive(BitfieldSpecifier)] 32 | // #[bits = 4] 33 | // enum SmallPrime { 34 | // Two = 0b0010, 35 | // Three = 0b0011, 36 | // Five = 0b0101, 37 | // Seven = 0b0111, 38 | // Eleven = 0b1011, 39 | // Thirteen = 0b1101, 40 | // } 41 | // 42 | // ... 43 | // let mut bitfield = MyBitfield::new(); 44 | // assert_eq!(0, bitfield.small_prime().unwrap_err().raw_value()); 45 | // 46 | // bitfield.set_small_prime(SmallPrime::Seven); 47 | // let p = bitfield.small_prime().unwrap_or(SmallPrime::Two); 48 | 49 | use bitfield::*; 50 | 51 | #[bitfield] 52 | pub struct RedirectionTableEntry { 53 | acknowledged: bool, 54 | trigger_mode: TriggerMode, 55 | delivery_mode: DeliveryMode, 56 | reserved: B3, 57 | } 58 | 59 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 60 | pub enum TriggerMode { 61 | Edge = 0, 62 | Level = 1, 63 | } 64 | 65 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 66 | pub enum DeliveryMode { 67 | Fixed = 0b000, 68 | Lowest = 0b001, 69 | SMI = 0b010, 70 | RemoteRead = 0b011, 71 | NMI = 0b100, 72 | Init = 0b101, 73 | Startup = 0b110, 74 | External = 0b111, 75 | } 76 | 77 | fn main() { 78 | assert_eq!(std::mem::size_of::(), 1); 79 | 80 | // Initialized to all 0 bits. 81 | let mut entry = RedirectionTableEntry::new(); 82 | assert_eq!(entry.get_acknowledged(), false); 83 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge); 84 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Fixed); 85 | 86 | entry.set_acknowledged(true); 87 | entry.set_delivery_mode(DeliveryMode::SMI); 88 | assert_eq!(entry.get_acknowledged(), true); 89 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge); 90 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::SMI); 91 | } 92 | -------------------------------------------------------------------------------- /bitfield/tests/07-optional-discriminant.rs: -------------------------------------------------------------------------------- 1 | // For bitfield use limited to a single binary, such as a space optimization for 2 | // some in-memory data structure, we may not care what exact bit representation 3 | // is used for enums. 4 | // 5 | // Make your BitfieldSpecifier derive macro for enums use the underlying 6 | // discriminant determined by the Rust compiler as the bit representation. Do 7 | // not assume that the compiler uses any particular scheme like PREV+1 for 8 | // implicit discriminants; make sure your implementation respects Rust's choice 9 | // of discriminant regardless of what scheme Rust uses. This is important for 10 | // performance so that the getter and setter both compile down to very simple 11 | // machine code after optimizations. 12 | // 13 | // Do not worry about what happens if discriminants are outside of the range 14 | // 0..2^BITS. We will do a compile-time check in a later test case to ensure 15 | // they are in range. 16 | 17 | use bitfield::*; 18 | 19 | #[bitfield] 20 | pub struct RedirectionTableEntry { 21 | delivery_mode: DeliveryMode, 22 | reserved: B5, 23 | } 24 | 25 | const F: isize = 3; 26 | const G: isize = 0; 27 | 28 | #[derive(BitfieldSpecifier, Debug, PartialEq)] 29 | pub enum DeliveryMode { 30 | Fixed = F, 31 | Lowest, 32 | SMI, 33 | RemoteRead, 34 | NMI, 35 | Init = G, 36 | Startup, 37 | External, 38 | } 39 | 40 | fn main() { 41 | assert_eq!(std::mem::size_of::(), 1); 42 | 43 | // Initialized to all 0 bits. 44 | let mut entry = RedirectionTableEntry::new(); 45 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Init); 46 | 47 | entry.set_delivery_mode(DeliveryMode::Lowest); 48 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Lowest); 49 | } 50 | -------------------------------------------------------------------------------- /bitfield/tests/08-non-power-of-two.rs: -------------------------------------------------------------------------------- 1 | // Bitfield enums with a number of variants other than a power of two should 2 | // fail to compile. 3 | // 4 | // (Or, if you implemented the optional #[bits = N] enum approach mentioned in 5 | // the explanation of test case 06, then enums with non-power-of-two variants 6 | // without a #[bits = N] attribute should fail to compile.) 7 | 8 | use bitfield::*; 9 | 10 | #[derive(BitfieldSpecifier)] 11 | pub enum Bad { 12 | Zero, 13 | One, 14 | Two, 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /bitfield/tests/08-non-power-of-two.stderr: -------------------------------------------------------------------------------- 1 | error: BitfieldSpecifier expected a number of variants which is a power of 2 2 | --> $DIR/08-non-power-of-two.rs:10:10 3 | | 4 | 10 | #[derive(BitfieldSpecifier)] 5 | | ^^^^^^^^^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /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 | --> $DIR/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 | For more information about this error, try `rustc --explain E0277`. 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 | --> $DIR/11-bits-attribute-wrong.rs:11:14 3 | | 4 | 11 | #[bits = 9] 5 | | ^ expected an array with a fixed size of 9 elements, found one with 1 element 6 | | 7 | = note: expected type `[(); 9]` 8 | found type `[(); 1]` 9 | 10 | For more information about this error, try `rustc --explain E0308`. 11 | -------------------------------------------------------------------------------- /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 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | 18 | [dependencies] 19 | syn = { version = "0.15", features = ["extra-traits"] } 20 | quote = "0.6" 21 | proc-macro2 = "0.4" 22 | -------------------------------------------------------------------------------- /builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | extern crate proc_macro; 4 | 5 | use proc_macro::TokenStream; 6 | use quote::quote; 7 | use syn::{parse_macro_input, DeriveInput}; 8 | 9 | #[proc_macro_derive(Builder, attributes(builder))] 10 | pub fn derive(input: TokenStream) -> TokenStream { 11 | let ast = parse_macro_input!(input as DeriveInput); 12 | let name = &ast.ident; 13 | let bname = format!("{}Builder", name); 14 | let bident = syn::Ident::new(&bname, name.span()); 15 | let fields = if let syn::Data::Struct(syn::DataStruct { 16 | fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }), 17 | .. 18 | }) = ast.data 19 | { 20 | named 21 | } else { 22 | unimplemented!(); 23 | }; 24 | 25 | let builder_fields = fields.iter().map(|f| { 26 | let name = &f.ident; 27 | let ty = &f.ty; 28 | if ty_inner_type("Option", ty).is_some() || builder_of(&f).is_some() { 29 | quote! { #name: #ty } 30 | } else { 31 | quote! { #name: std::option::Option<#ty> } 32 | } 33 | }); 34 | 35 | let methods = fields.iter().map(|f| { 36 | let name = f.ident.as_ref().unwrap(); 37 | let ty = &f.ty; 38 | 39 | let (arg_type, value) = if let Some(inner_ty) = ty_inner_type("Option", ty) { 40 | // if the field is an Option, setting should take just a T, 41 | // but we then need to store it within a Some. 42 | (inner_ty, quote! { std::option::Option::Some(#name) }) 43 | } else if builder_of(&f).is_some() { 44 | // if the field is a builder, it is a Vec, 45 | // and the value in the builder is _not_ wrapped in an Option, 46 | // so we shouldn't wrap the value in Some. 47 | (ty, quote! { #name }) 48 | } else { 49 | // otherwise, we take the type used by the target, 50 | // and we store it in an Option in the builder 51 | // in case it was never set. 52 | (ty, quote! { std::option::Option::Some(#name) }) 53 | }; 54 | let set_method = quote! { 55 | pub fn #name(&mut self, #name: #arg_type) -> &mut Self { 56 | self.#name = #value; 57 | self 58 | } 59 | }; 60 | 61 | // we need to take care not to include a builder method with the same name as the set 62 | // method. for example, consider this struct: 63 | // 64 | // ``` 65 | // #[derive(Builder)] 66 | // struct Command { 67 | // #[builder(each = "env")] 68 | // env: Vec 69 | // } 70 | // ``` 71 | // 72 | // It would not be okay to generate both `env(Vec)` for the field 73 | // *and* `env(String)` for the builder. 74 | match extend_method(&f) { 75 | None => set_method.into(), 76 | Some((true, extend_method)) => extend_method, 77 | Some((false, extend_method)) => { 78 | // safe to generate both! 79 | let expr = quote! { 80 | #set_method 81 | #extend_method 82 | }; 83 | expr.into() 84 | } 85 | } 86 | }); 87 | 88 | // for when you call Builder::build 89 | let build_fields = fields.iter().map(|f| { 90 | let name = &f.ident; 91 | if ty_inner_type("Option", &f.ty).is_some() || builder_of(f).is_some() { 92 | quote! { 93 | #name: self.#name.clone() 94 | } 95 | } else { 96 | quote! { 97 | #name: self.#name.clone().ok_or(concat!(stringify!(#name), " is not set"))? 98 | } 99 | } 100 | }); 101 | 102 | let build_empty = fields.iter().map(|f| { 103 | let name = &f.ident; 104 | if builder_of(f).is_some() { 105 | quote! { #name: std::vec::Vec::new() } 106 | } else { 107 | quote! { #name: std::option::Option::None } 108 | } 109 | }); 110 | 111 | let doc = format!("\ 112 | Implements the [builder pattern] for [`{}`].\n\ 113 | \n\ 114 | [builder pattern]: https://rust-lang-nursery.github.io/api-guidelines/type-safety.html#c-builder", name); 115 | 116 | let expanded = quote! { 117 | #[doc = #doc] 118 | pub struct #bident { 119 | #(#builder_fields,)* 120 | } 121 | impl #bident { 122 | #(#methods)* 123 | 124 | pub fn build(&self) -> std::result::Result<#name, std::boxed::Box> { 125 | std::result::Result::Ok(#name { 126 | #(#build_fields,)* 127 | }) 128 | } 129 | } 130 | impl #name { 131 | pub fn builder() -> #bident { 132 | #bident { 133 | #(#build_empty,)* 134 | } 135 | } 136 | } 137 | }; 138 | 139 | expanded.into() 140 | } 141 | 142 | fn builder_of(f: &syn::Field) -> Option<&syn::Attribute> { 143 | for attr in &f.attrs { 144 | if attr.path.segments.len() == 1 && attr.path.segments[0].ident == "builder" { 145 | return Some(attr); 146 | } 147 | } 148 | None 149 | } 150 | 151 | fn extend_method(f: &syn::Field) -> Option<(bool, proc_macro2::TokenStream)> { 152 | let name = f.ident.as_ref().unwrap(); 153 | let g = builder_of(f)?; 154 | 155 | fn mk_err(t: T) -> Option<(bool, proc_macro2::TokenStream)> { 156 | Some(( 157 | false, 158 | syn::Error::new_spanned(t, "expected `builder(each = \"...\")`").to_compile_error(), 159 | )) 160 | } 161 | 162 | let meta = match g.parse_meta() { 163 | Ok(syn::Meta::List(mut nvs)) => { 164 | // list here is .. in #[builder(..)] 165 | assert_eq!(nvs.ident, "builder"); 166 | if nvs.nested.len() != 1 { 167 | return mk_err(nvs); 168 | } 169 | 170 | // nvs.nested[0] here is (hopefully): each = "foo" 171 | match nvs.nested.pop().unwrap().into_value() { 172 | syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => { 173 | if nv.ident != "each" { 174 | return mk_err(nvs); 175 | } 176 | nv 177 | } 178 | meta => { 179 | // nvs.nested[0] was not k = v 180 | return mk_err(meta); 181 | } 182 | } 183 | } 184 | Ok(meta) => { 185 | // inside of #[] there was either just an identifier (`#[builder]`) or a key-value 186 | // mapping (`#[builder = "foo"]`), neither of which are okay. 187 | return mk_err(meta); 188 | } 189 | Err(e) => { 190 | return Some((false, e.to_compile_error())); 191 | } 192 | }; 193 | 194 | match meta.lit { 195 | syn::Lit::Str(s) => { 196 | let arg = syn::Ident::new(&s.value(), s.span()); 197 | let inner_ty = ty_inner_type("Vec", &f.ty).unwrap(); 198 | let method = quote! { 199 | pub fn #arg(&mut self, #arg: #inner_ty) -> &mut Self { 200 | self.#name.push(#arg); 201 | self 202 | } 203 | }; 204 | Some((&arg == name, method)) 205 | } 206 | lit => panic!("expected string, found {:?}", lit), 207 | } 208 | } 209 | 210 | fn ty_inner_type<'a>(wrapper: &str, ty: &'a syn::Type) -> Option<&'a syn::Type> { 211 | if let syn::Type::Path(ref p) = ty { 212 | if p.path.segments.len() != 1 || p.path.segments[0].ident != wrapper { 213 | return None; 214 | } 215 | 216 | if let syn::PathArguments::AngleBracketed(ref inner_ty) = p.path.segments[0].arguments { 217 | if inner_ty.args.len() != 1 { 218 | return None; 219 | } 220 | 221 | let inner_ty = inner_ty.args.first().unwrap(); 222 | if let syn::GenericArgument::Type(ref t) = inner_ty.value() { 223 | return Some(t); 224 | } 225 | } 226 | } 227 | None 228 | } 229 | -------------------------------------------------------------------------------- /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/0.15/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/0.15/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 | // 22 | // Resources: 23 | // 24 | // - Relevant syntax tree types: 25 | // https://docs.rs/syn/0.15/syn/struct.Attribute.html 26 | // https://docs.rs/syn/0.15/syn/enum.Meta.html 27 | 28 | use derive_builder::Builder; 29 | 30 | #[derive(Builder)] 31 | pub struct Command { 32 | executable: String, 33 | #[builder(each = "arg")] 34 | args: Vec, 35 | #[builder(each = "env")] 36 | env: Vec, 37 | current_dir: Option, 38 | } 39 | 40 | fn main() { 41 | let command = Command::builder() 42 | .executable("cargo".to_owned()) 43 | .arg("build".to_owned()) 44 | .arg("--release".to_owned()) 45 | .build() 46 | .unwrap(); 47 | 48 | assert_eq!(command.executable, "cargo"); 49 | } 50 | -------------------------------------------------------------------------------- /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/0.15/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 | --> $DIR/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 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | 18 | [dependencies] 19 | # TODO 20 | -------------------------------------------------------------------------------- /debug/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | 5 | #[proc_macro_derive(CustomDebug)] 6 | pub fn derive(input: TokenStream) -> TokenStream { 7 | let _ = input; 8 | 9 | unimplemented!() 10 | } 11 | -------------------------------------------------------------------------------- /debug/tests/01-parse.rs: -------------------------------------------------------------------------------- 1 | // This test looks for a derive macro with the right name to exist. For now the 2 | // test doens'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/0.15/syn/struct.DeriveInput.html 13 | // 14 | // - An example of a derive macro implemented using Syn: 15 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize 16 | 17 | use derive_debug::CustomDebug; 18 | 19 | #[derive(CustomDebug)] 20 | pub struct Field { 21 | name: &'static str, 22 | bitmask: u16, 23 | } 24 | 25 | fn main() {} 26 | -------------------------------------------------------------------------------- /debug/tests/02-impl-debug.rs: -------------------------------------------------------------------------------- 1 | // Emit an implementation of std::fmt::Debug for a basic struct with named 2 | // fields and no generic type parameters. 3 | // 4 | // Note that there is no enforced relationship between the name of a derive 5 | // macro and the trait that it implements. Here the macro is named CustomDebug 6 | // but the trait impls it generates are for Debug. As a convention, typically 7 | // derive macros implement a trait with the same name as a macro. 8 | // 9 | // 10 | // Resources: 11 | // 12 | // - The Debug trait: 13 | // https://doc.rust-lang.org/std/fmt/trait.Debug.html 14 | // 15 | // - The DebugStruct helper for formatting structs correctly: 16 | // https://doc.rust-lang.org/std/fmt/struct.DebugStruct.html 17 | 18 | use derive_debug::CustomDebug; 19 | 20 | #[derive(CustomDebug)] 21 | pub struct Field { 22 | name: &'static str, 23 | bitmask: u8, 24 | } 25 | 26 | fn main() { 27 | let f = Field { 28 | name: "F", 29 | bitmask: 0b00011100, 30 | }; 31 | 32 | let debug = format!("{:?}", f); 33 | 34 | assert!(debug.starts_with(r#"Field { name: "F","#)); 35 | } 36 | -------------------------------------------------------------------------------- /debug/tests/03-custom-format.rs: -------------------------------------------------------------------------------- 1 | // Look for a field attribute #[debug = "..."] on each field. If present, find a 2 | // way to format the field according to the format string given by the caller in 3 | // the attribute. 4 | // 5 | // In order for the compiler to recognize this inert attribute as associated 6 | // with your derive macro, it will need to be declared at the entry point of the 7 | // derive macro. 8 | // 9 | // #[proc_macro_derive(CustomDebug, attributes(debug))] 10 | // 11 | // These are called inert attributes. The word "inert" indicates that these 12 | // attributes do not correspond to a macro invocation on their own; they are 13 | // simply looked at by other macro invocations. 14 | // 15 | // 16 | // Resources: 17 | // 18 | // - Relevant syntax tree types: 19 | // https://docs.rs/syn/0.15/syn/struct.Attribute.html 20 | // https://docs.rs/syn/0.15/syn/enum.Meta.html 21 | // 22 | // - Macro for applying a format string to some runtime value: 23 | // https://doc.rust-lang.org/std/macro.format_args.html 24 | 25 | use derive_debug::CustomDebug; 26 | 27 | #[derive(CustomDebug)] 28 | pub struct Field { 29 | name: &'static str, 30 | #[debug = "0b{:08b}"] 31 | bitmask: u8, 32 | } 33 | 34 | fn main() { 35 | let f = Field { 36 | name: "F", 37 | bitmask: 0b00011100, 38 | }; 39 | 40 | let debug = format!("{:?}", f); 41 | let expected = r#"Field { name: "F", bitmask: 0b00011100 }"#; 42 | 43 | assert_eq!(debug, expected); 44 | } 45 | -------------------------------------------------------------------------------- /debug/tests/04-type-parameter.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/0.15/syn/struct.Generics.html 14 | // 15 | // - A helper for placing generics into an impl signature: 16 | // https://docs.rs/syn/0.15/syn/struct.Generics.html#method.split_for_impl 17 | // 18 | // - Example code from Syn which deals with type parameters: 19 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize 20 | 21 | use derive_debug::CustomDebug; 22 | 23 | #[derive(CustomDebug)] 24 | pub struct Field { 25 | value: T, 26 | #[debug = "0b{:08b}"] 27 | bitmask: u8, 28 | } 29 | 30 | fn main() { 31 | let f = Field { 32 | value: "F", 33 | bitmask: 0b00011100, 34 | }; 35 | 36 | let debug = format!("{:?}", f); 37 | let expected = r#"Field { value: "F", bitmask: 0b00011100 }"#; 38 | 39 | assert_eq!(debug, expected); 40 | } 41 | -------------------------------------------------------------------------------- /debug/tests/05-phantom-data.rs: -------------------------------------------------------------------------------- 1 | // Some generic types implement Debug even when their type parameters do not. 2 | // One example is PhantomData which has this impl: 3 | // 4 | // impl Debug for PhantomData {...} 5 | // 6 | // To accomodate this sort of situation, one way would be to generate a trait 7 | // bound `#field_ty: Debug` for each field type in the input, rather than 8 | // `#param: Debug` for each generic parameter. For example in the case of the 9 | // struct Field in the test case below, it would be: 10 | // 11 | // impl Debug for Field 12 | // where 13 | // PhantomData: Debug, 14 | // {...} 15 | // 16 | // This approach has fatal downsides that will be covered in subsequent test 17 | // cases. 18 | // 19 | // Instead we'll recognize PhantomData as a special case since it is so common, 20 | // and later provide an escape hatch for the caller to override inferred bounds 21 | // in other application-specific special cases. 22 | // 23 | // Concretely, for each type parameter #param in the input, you will need to 24 | // determine whether it is only ever mentioned inside of a PhantomData and if so 25 | // then avoid emitting a `#param: Debug` bound on that parameter. For the 26 | // purpose of the test suite it is sufficient to look for exactly the field type 27 | // PhantomData<#param>. In reality we may also care about recognizing other 28 | // possible arrangements like PhantomData<&'a #param> if the semantics of the 29 | // trait we are deriving would make it likely that callers would end up with 30 | // that sort of thing in their code. 31 | // 32 | // Notice that we are into the realm of heuristics at this point. In Rust's 33 | // macro system it is not possible for a derive macro to infer the "correct" 34 | // bounds in general. Doing so would require name-resolution, i.e. the ability 35 | // for the macro to look up what trait impl corresponds to some field's type by 36 | // name. The Rust compiler has chosen to perform all macro expansion fully 37 | // before name resolution (not counting name resolution of macros themselves, 38 | // which operates in a more restricted way than Rust name resolution in general 39 | // to make this possible). 40 | // 41 | // The clean separation between macro expansion and name resolution has huge 42 | // advantages that outweigh the limitation of not being able to expose type 43 | // information to procedural macros, so there are no plans to change it. Instead 44 | // macros rely on domain-specific heuristics and escape hatches to substitute 45 | // for type information where unavoidable or, more commonly, rely on the Rust 46 | // trait system to defer the need for name resolution. In particular pay 47 | // attention to how the derive macro invocation below is able to expand to code 48 | // that correctly calls String's Debug impl despite having no way to know that 49 | // the word "S" in its input refers to the type String. 50 | 51 | use derive_debug::CustomDebug; 52 | use std::fmt::Debug; 53 | use std::marker::PhantomData; 54 | 55 | type S = String; 56 | 57 | #[derive(CustomDebug)] 58 | pub struct Field { 59 | marker: PhantomData, 60 | string: S, 61 | #[debug = "0b{:08b}"] 62 | bitmask: u8, 63 | } 64 | 65 | fn assert_debug() {} 66 | 67 | fn main() { 68 | // Does not implement Debug. 69 | struct NotDebug; 70 | 71 | assert_debug::>(); 72 | assert_debug::>(); 73 | } 74 | -------------------------------------------------------------------------------- /debug/tests/06-bound-trouble.rs: -------------------------------------------------------------------------------- 1 | // This test case should not require any code change in your macro if you have 2 | // everything up to this point already passing, but is here to demonstrate why 3 | // inferring `#field_ty: Trait` bounds as mentioned in the previous test case is 4 | // not viable. 5 | // 6 | // #[derive(CustomDebug)] 7 | // pub struct One { 8 | // value: T, 9 | // two: Option>>, 10 | // } 11 | // 12 | // #[derive(CustomDebug)] 13 | // struct Two { 14 | // one: Box>, 15 | // } 16 | // 17 | // The problematic expansion would come out as: 18 | // 19 | // impl Debug for One 20 | // where 21 | // T: Debug, 22 | // Option>>: Debug, 23 | // {...} 24 | // 25 | // impl Debug for Two 26 | // where 27 | // Box>: Debug, 28 | // {...} 29 | // 30 | // There are two things wrong here. 31 | // 32 | // First, taking into account the relevant standard library impls `impl Debug 33 | // for Option where T: Debug` and `impl Debug for Box where T: ?Sized + 34 | // Debug`, we have the following cyclic definition: 35 | // 36 | // - One implements Debug if there is an impl for Option>>; 37 | // - Option>> implements Debug if there is an impl for Box>; 38 | // - Box> implements Debug if there is an impl for Two; 39 | // - Two implements Debug if there is an impl for Box>; 40 | // - Box> implements Debug if there is an impl for One; cycle! 41 | // 42 | // The Rust compiler detects and rejects this cycle by refusing to assume that 43 | // an impl for any of these types exists out of nowhere. The error manifests as: 44 | // 45 | // error[E0275]: overflow evaluating the requirement `One: std::fmt::Debug` 46 | // --> 47 | // | assert_debug::>(); 48 | // | ^^^^^^^^^^^^^^^^^^^^^^^ 49 | // 50 | // There is a technique known as co-inductive reasoning that may allow a 51 | // revamped trait solver in the compiler to process cycles like this in the 52 | // future, though there is still uncertainty about whether co-inductive 53 | // semantics would lead to unsoundness in some situations when applied to Rust 54 | // trait impls. There is no current activity pursuing this but some discussion 55 | // exists in a GitHub issue called "#[derive] sometimes uses incorrect bounds": 56 | // https://github.com/rust-lang/rust/issues/26925 57 | // 58 | // The second thing wrong is a private-in-public violation: 59 | // 60 | // error[E0446]: private type `Two` in public interface 61 | // --> 62 | // | struct Two { 63 | // | - `Two` declared as private 64 | // ... 65 | // | / impl Debug for One 66 | // | | where 67 | // | | T: Debug, 68 | // | | Option>>: Debug, 69 | // ... | 70 | // | | } 71 | // | |_^ can't leak private type 72 | // 73 | // Public APIs in Rust are not allowed to be defined in terms of private types. 74 | // That includes the argument types and return types of public function 75 | // signatures, as well as trait bounds on impls of public traits for public 76 | // types. 77 | 78 | use derive_debug::CustomDebug; 79 | use std::fmt::Debug; 80 | 81 | #[derive(CustomDebug)] 82 | pub struct One { 83 | value: T, 84 | two: Option>>, 85 | } 86 | 87 | #[derive(CustomDebug)] 88 | struct Two { 89 | one: Box>, 90 | } 91 | 92 | fn assert_debug() {} 93 | 94 | fn main() { 95 | assert_debug::>(); 96 | assert_debug::>(); 97 | } 98 | -------------------------------------------------------------------------------- /debug/tests/07-associated-type.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/0.15/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 | use sorted::sorted; 2 | 3 | #[sorted] 4 | pub enum Conference { 5 | RustBeltRust, 6 | RustConf, 7 | RustFest, 8 | RustLatam, 9 | RustRush, 10 | } 11 | 12 | impl Conference { 13 | #[sorted::check] 14 | pub fn region(&self) -> &str { 15 | use self::Conference::*; 16 | 17 | #[sorted] 18 | match self { 19 | RustFest => "Europe", 20 | RustLatam => "Latin America", 21 | _ => "elsewhere", 22 | } 23 | } 24 | } 25 | 26 | fn main() {} 27 | -------------------------------------------------------------------------------- /seq/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seq" 3 | version = "0.0.0" 4 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [[test]] 9 | name = "tests" 10 | path = "tests/progress.rs" 11 | 12 | [dev-dependencies] 13 | trybuild = "1.0" 14 | 15 | [dependencies] 16 | proc-macro-hack = "0.5" 17 | seq-proc = { path = "seq-proc/" } 18 | -------------------------------------------------------------------------------- /seq/seq-proc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "seq-proc" 3 | version = "0.0.0" 4 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = { version = "0.15", features = ["extra-traits", "full"] } 13 | quote = "0.6" 14 | proc-macro2 = "0.4" 15 | proc-macro-hack = "0.5" 16 | -------------------------------------------------------------------------------- /seq/seq-proc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | extern crate proc_macro; 4 | 5 | use proc_macro::TokenStream; 6 | use proc_macro_hack::proc_macro_hack; 7 | use syn::parse::{Parse, ParseStream}; 8 | use syn::{parse_macro_input, Result, Token}; 9 | 10 | #[derive(Debug)] 11 | struct SeqMacroInput { 12 | from: syn::LitInt, 13 | to: syn::LitInt, 14 | inclusive: bool, 15 | ident: syn::Ident, 16 | tt: proc_macro2::TokenStream, 17 | } 18 | 19 | impl Parse for SeqMacroInput { 20 | fn parse(input: ParseStream) -> Result { 21 | let ident = syn::Ident::parse(input)?; 22 | let _in = ::parse(input)?; 23 | let from = syn::LitInt::parse(input)?; 24 | let inclusive = input.peek(Token![..=]); 25 | if inclusive { 26 | ::parse(input)?; 27 | } else { 28 | ::parse(input)?; 29 | } 30 | let to = syn::LitInt::parse(input)?; 31 | let content; 32 | let _braces = syn::braced!(content in input); 33 | let tt = proc_macro2::TokenStream::parse(&content)?; 34 | 35 | Ok(SeqMacroInput { 36 | from, 37 | to, 38 | inclusive, 39 | tt, 40 | ident, 41 | }) 42 | } 43 | } 44 | 45 | impl Into for SeqMacroInput { 46 | fn into(self) -> proc_macro2::TokenStream { 47 | self.expand(self.tt.clone()) 48 | } 49 | } 50 | 51 | #[derive(Copy, Clone, Debug)] 52 | enum Mode { 53 | ReplaceIdent(u64), 54 | ReplaceSequence, 55 | } 56 | 57 | impl SeqMacroInput { 58 | fn range(&self) -> impl Iterator { 59 | if self.inclusive { 60 | self.from.value()..(self.to.value() + 1) 61 | } else { 62 | self.from.value()..self.to.value() 63 | } 64 | } 65 | 66 | fn expand2( 67 | &self, 68 | tt: proc_macro2::TokenTree, 69 | rest: &mut proc_macro2::token_stream::IntoIter, 70 | mutated: &mut bool, 71 | mode: Mode, 72 | ) -> proc_macro2::TokenStream { 73 | let tt = match tt { 74 | proc_macro2::TokenTree::Group(g) => { 75 | let (expanded, g_mutated) = self.expand_pass(g.stream(), mode); 76 | let mut expanded = proc_macro2::Group::new(g.delimiter(), expanded); 77 | *mutated |= g_mutated; 78 | expanded.set_span(g.span()); 79 | proc_macro2::TokenTree::Group(expanded) 80 | } 81 | proc_macro2::TokenTree::Ident(ref ident) if ident == &self.ident => { 82 | if let Mode::ReplaceIdent(i) = mode { 83 | let mut lit = proc_macro2::Literal::u64_unsuffixed(i); 84 | lit.set_span(ident.span()); 85 | *mutated = true; 86 | proc_macro2::TokenTree::Literal(lit) 87 | } else { 88 | // not allowed to replace idents in first pass 89 | proc_macro2::TokenTree::Ident(ident.clone()) 90 | } 91 | } 92 | proc_macro2::TokenTree::Ident(mut ident) => { 93 | // search for # followed by self.ident at the end of an identifier 94 | // OR # self.ident # 95 | let mut peek = rest.clone(); 96 | match (mode, peek.next(), peek.next()) { 97 | ( 98 | Mode::ReplaceIdent(i), 99 | Some(proc_macro2::TokenTree::Punct(ref punct)), 100 | Some(proc_macro2::TokenTree::Ident(ref ident2)), 101 | ) if punct.as_char() == '#' && ident2 == &self.ident => { 102 | // have seen ident # N 103 | ident = proc_macro2::Ident::new(&format!("{}{}", ident, i), ident.span()); 104 | *rest = peek.clone(); 105 | *mutated = true; 106 | 107 | // we may need to also consume another # 108 | match peek.next() { 109 | Some(proc_macro2::TokenTree::Punct(ref punct)) 110 | if punct.as_char() == '#' => 111 | { 112 | *rest = peek.clone(); 113 | } 114 | _ => {} 115 | } 116 | } 117 | _ => {} 118 | } 119 | 120 | proc_macro2::TokenTree::Ident(ident) 121 | } 122 | proc_macro2::TokenTree::Punct(ref p) if p.as_char() == '#' => { 123 | if let Mode::ReplaceSequence = mode { 124 | // is this #(...)* ? 125 | let mut peek = rest.clone(); 126 | match (peek.next(), peek.next()) { 127 | ( 128 | Some(proc_macro2::TokenTree::Group(ref rep)), 129 | Some(proc_macro2::TokenTree::Punct(ref star)), 130 | ) if rep.delimiter() == proc_macro2::Delimiter::Parenthesis 131 | && star.as_char() == '*' => 132 | { 133 | // yes! expand ... for each sequence in the range 134 | *mutated = true; 135 | *rest = peek; 136 | return self 137 | .range() 138 | .map(|i| self.expand_pass(rep.stream(), Mode::ReplaceIdent(i))) 139 | .map(|(ts, _)| ts) 140 | .collect(); 141 | } 142 | _ => {} 143 | } 144 | } 145 | proc_macro2::TokenTree::Punct(p.clone()) 146 | } 147 | tt => tt, 148 | }; 149 | std::iter::once(tt).collect() 150 | } 151 | 152 | fn expand_pass( 153 | &self, 154 | stream: proc_macro2::TokenStream, 155 | mode: Mode, 156 | ) -> (proc_macro2::TokenStream, bool) { 157 | let mut out = proc_macro2::TokenStream::new(); 158 | let mut mutated = false; 159 | let mut tts = stream.into_iter(); 160 | while let Some(tt) = tts.next() { 161 | out.extend(self.expand2(tt, &mut tts, &mut mutated, mode)); 162 | } 163 | (out, mutated) 164 | } 165 | 166 | fn expand(&self, stream: proc_macro2::TokenStream) -> proc_macro2::TokenStream { 167 | let (out, mutated) = self.expand_pass(stream.clone(), Mode::ReplaceSequence); 168 | if mutated { 169 | return out; 170 | } 171 | 172 | self.range() 173 | .map(|i| self.expand_pass(stream.clone(), Mode::ReplaceIdent(i))) 174 | .map(|(ts, _)| ts) 175 | .collect() 176 | } 177 | } 178 | 179 | #[proc_macro] 180 | pub fn seq(input: TokenStream) -> TokenStream { 181 | let input = parse_macro_input!(input as SeqMacroInput); 182 | let output: proc_macro2::TokenStream = input.into(); 183 | output.into() 184 | } 185 | 186 | #[proc_macro_hack] 187 | pub fn eseq(input: TokenStream) -> TokenStream { 188 | seq(input) 189 | } 190 | -------------------------------------------------------------------------------- /seq/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro_hack::proc_macro_hack; 2 | 3 | pub use seq_proc::seq; 4 | 5 | #[proc_macro_hack] 6 | pub use seq_proc::eseq; 7 | -------------------------------------------------------------------------------- /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/0.15/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/0.4/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 | --> $DIR/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 | --> $DIR/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 | --> $DIR/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 | --> $DIR/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/0.15/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-make-work-in-function.rs: -------------------------------------------------------------------------------- 1 | // As of Rust 1.34, function-like procedural macro calls are not supported 2 | // inside of a function body by the stable compiler. When you enable this test 3 | // case you should see an error like this: 4 | // 5 | // error[E0658]: procedural macros cannot be expanded to statements (see issue #54727) 6 | // | 7 | // | / seq!(N in 0..4 { 8 | // | | sum += tuple.N as u64; 9 | // | | }); 10 | // | |_______^ 11 | // | 12 | // = help: add #![feature(proc_macro_hygiene)] to the crate attributes to enable 13 | // 14 | // (The error message refers to https://github.com/rust-lang/rust/issues/54727.) 15 | // 16 | // Optionally, if you have a nightly toolchain installed, try temporarily adding 17 | // the following feature to this test case as recommended by the compiler's 18 | // error message to see the test pass with no additional effort: 19 | // 20 | // #![feature(proc_macro_hygiene)] 21 | // 22 | // But before you move on, let's fix this in a stable way. Check out the 23 | // proc-macro-hack crate for a way to make this code work on a stable compiler 24 | // with relatively little effort. 25 | // 26 | // Keep the original `seq!` macro for use outside of function bodies, and 27 | // introduce a new `fseq` macro using proc-macro-hack. Your proc-macro-hack 28 | // implementation crate" will look like: 29 | // 30 | // #[proc_macro] 31 | // pub fn seq(input: TokenStream) -> TokenStream { 32 | // /* what you had before...! */ 33 | // } 34 | // 35 | // #[proc_macro_hack] 36 | // pub fn eseq(input: TokenStream) -> TokenStream { 37 | // seq(input) 38 | // } 39 | // 40 | // The expanded code will look like: 41 | // 42 | // { 43 | // sum += tuple.0 as u64; 44 | // sum += tuple.1 as u64; 45 | // sum += tuple.2 as u64; 46 | // sum += tuple.3 as u64; 47 | // } 48 | // 49 | // 50 | // Resources: 51 | // 52 | // - A stable workaround for procedural macros inside a function body: 53 | // https://github.com/dtolnay/proc-macro-hack 54 | 55 | use seq::eseq; 56 | 57 | fn main() { 58 | let tuple = (9u8, 90u16, 900u32, 9000u64); 59 | 60 | let mut sum = 0; 61 | 62 | eseq!(N in 0..4 {{ 63 | #( 64 | sum += tuple.N as u64; 65 | )* 66 | }}); 67 | 68 | assert_eq!(sum, 9999); 69 | } 70 | -------------------------------------------------------------------------------- /seq/tests/07-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::eseq; 10 | 11 | const PROCS: [Proc; 256] = { 12 | eseq!(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/08-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/09-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/09-ident-span.stderr: -------------------------------------------------------------------------------- 1 | error[E0425]: cannot find value `Missing0` in this scope 2 | --> $DIR/09-ident-span.rs:34:17 3 | | 4 | 34 | let _ = Missing#N; 5 | | ^^^^^^^ not found in this scope 6 | 7 | For more information about this error, try `rustc --explain E0425`. 8 | -------------------------------------------------------------------------------- /seq/tests/10-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::eseq; 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 | eseq!(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-make-work-in-function.rs"); 10 | t.pass("tests/07-init-array.rs"); 11 | t.pass("tests/08-inclusive-range.rs"); 12 | t.compile_fail("tests/09-ident-span.rs"); 13 | t.pass("tests/10-interaction-with-macrorules.rs"); 14 | } 15 | -------------------------------------------------------------------------------- /sorted/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sorted" 3 | version = "0.0.0" 4 | edition = "2018" 5 | autotests = false 6 | publish = false 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [[test]] 12 | name = "tests" 13 | path = "tests/progress.rs" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | 18 | [dependencies] 19 | syn = { version = "0.15", features = ["extra-traits", "full", "visit-mut"] } 20 | proc-macro2 = "0.4" 21 | quote = "0.6" 22 | -------------------------------------------------------------------------------- /sorted/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | 3 | use proc_macro::TokenStream; 4 | use quote::quote; 5 | use syn::parse_macro_input; 6 | use syn::spanned::Spanned; 7 | use syn::visit_mut::VisitMut; 8 | 9 | #[proc_macro_attribute] 10 | pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream { 11 | let mut out = input.clone(); 12 | 13 | let ty = parse_macro_input!(input as syn::Item); 14 | assert!(args.is_empty()); 15 | 16 | if let Err(e) = sorted_variants(ty) { 17 | out.extend(TokenStream::from(e.to_compile_error())); 18 | } 19 | out 20 | } 21 | 22 | fn sorted_variants(input: syn::Item) -> Result<(), syn::Error> { 23 | if let syn::Item::Enum(e) = input { 24 | // now check variant order 25 | let mut names = Vec::new(); 26 | for variant in e.variants.iter() { 27 | let name = variant.ident.to_string(); 28 | if names.last().map(|last| &name < last).unwrap_or(false) { 29 | let next_lex_i = names.binary_search(&name).unwrap_err(); 30 | return Err(syn::Error::new( 31 | variant.span(), 32 | format!("{} should sort before {}", name, names[next_lex_i]), 33 | )); 34 | } 35 | names.push(name); 36 | } 37 | Ok(()) 38 | } else { 39 | Err(syn::Error::new( 40 | proc_macro2::Span::call_site(), 41 | "expected enum or match expression", 42 | )) 43 | } 44 | } 45 | 46 | #[derive(Default)] 47 | struct LexiographicMatching { 48 | errors: Vec, 49 | } 50 | 51 | impl syn::visit_mut::VisitMut for LexiographicMatching { 52 | fn visit_expr_match_mut(&mut self, m: &mut syn::ExprMatch) { 53 | if m.attrs.iter().any(|a| a.path.is_ident("sorted")) { 54 | // remove #[sorted] 55 | m.attrs.retain(|a| !a.path.is_ident("sorted")); 56 | // check the variants 57 | let mut names = Vec::new(); 58 | let mut wild = None; 59 | for arm in m.arms.iter() { 60 | if let Some(ref w) = wild { 61 | self.errors.push(syn::Error::new_spanned( 62 | &w, 63 | "wildcard pattern should come last", 64 | )); 65 | break; 66 | } 67 | 68 | let path = if let Some(path) = get_arm_path(arm.pats.iter().next().unwrap()) { 69 | path 70 | } else if let Some(syn::Pat::Wild(w)) = arm.pats.iter().next() { 71 | wild = Some(w); 72 | continue; 73 | } else { 74 | self.errors.push(syn::Error::new_spanned( 75 | &arm.pats, 76 | "unsupported by #[remain::sorted]", 77 | )); 78 | continue; 79 | }; 80 | let name = path_as_string(&path); 81 | if names.last().map(|last| &name < last).unwrap_or(false) { 82 | let next_lex_i = names.binary_search(&name).unwrap_err(); 83 | self.errors.push(syn::Error::new_spanned( 84 | path, 85 | format!("{} should sort before {}", name, names[next_lex_i]), 86 | )); 87 | } 88 | names.push(name); 89 | } 90 | } 91 | 92 | // keep recursing 93 | syn::visit_mut::visit_expr_match_mut(self, m) 94 | } 95 | } 96 | 97 | fn path_as_string(path: &syn::Path) -> String { 98 | path.segments 99 | .iter() 100 | .map(|s| format!("{}", quote! {#s})) 101 | .collect::>() 102 | .join("::") 103 | } 104 | 105 | fn get_arm_path(arm: &syn::Pat) -> Option { 106 | match *arm { 107 | syn::Pat::Ident(syn::PatIdent { ident: ref id, .. }) => Some(id.clone().into()), 108 | syn::Pat::Path(ref p) => Some(p.path.clone()), 109 | syn::Pat::Struct(ref s) => Some(s.path.clone()), 110 | syn::Pat::TupleStruct(ref s) => Some(s.path.clone()), 111 | _ => None, 112 | } 113 | } 114 | 115 | #[proc_macro_attribute] 116 | pub fn check(args: TokenStream, input: TokenStream) -> TokenStream { 117 | let mut f = parse_macro_input!(input as syn::ItemFn); 118 | assert!(args.is_empty()); 119 | 120 | let mut lm = LexiographicMatching::default(); 121 | lm.visit_item_fn_mut(&mut f); 122 | let mut ts = quote! {#f}; 123 | ts.extend(lm.errors.into_iter().take(1).map(|e| e.to_compile_error())); 124 | ts.into() 125 | } 126 | -------------------------------------------------------------------------------- /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/0.15/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/0.15/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 | --> $DIR/02-not-enum.rs:31:1 3 | | 4 | 31 | #[sorted] 5 | | ^^^^^^^^^ 6 | -------------------------------------------------------------------------------- /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 | --> $DIR/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 | --> $DIR/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/0.15/syn/visit_mut/trait.VisitMut.html 65 | // 66 | // - The ExprMatch struct: 67 | // https://docs.rs/syn/0.15/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 | --> $DIR/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/0.15/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 | --> $DIR/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 #[remain::sorted] 2 | --> $DIR/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 | --------------------------------------------------------------------------------