├── .github
├── FUNDING.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .template
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
│ └── progress.rs
├── Cargo.toml
├── LICENSE-APACHE
├── LICENSE-MIT
├── README.md
├── bitfield
├── Cargo.toml
├── impl
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── src
│ └── lib.rs
└── tests
│ ├── 01-specifier-types.rs
│ ├── 02-storage.rs
│ ├── 03-accessors.rs
│ ├── 04-multiple-of-8bits.rs
│ ├── 04-multiple-of-8bits.stderr
│ ├── 05-accessor-signatures.rs
│ ├── 06-enums.rs
│ ├── 07-optional-discriminant.rs
│ ├── 08-non-power-of-two.rs
│ ├── 08-non-power-of-two.stderr
│ ├── 09-variant-out-of-range.rs
│ ├── 09-variant-out-of-range.stderr
│ ├── 10-bits-attribute.rs
│ ├── 11-bits-attribute-wrong.rs
│ ├── 11-bits-attribute-wrong.stderr
│ ├── 12-accessors-edge.rs
│ └── progress.rs
├── builder
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
│ ├── 01-parse.rs
│ ├── 02-create-builder.rs
│ ├── 03-call-setters.rs
│ ├── 04-call-build.rs
│ ├── 05-method-chaining.rs
│ ├── 06-optional-field.rs
│ ├── 07-repeated-field.rs
│ ├── 08-unrecognized-attribute.rs
│ ├── 08-unrecognized-attribute.stderr
│ ├── 09-redefined-prelude-types.rs
│ └── progress.rs
├── debug
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
│ ├── 01-parse.rs
│ ├── 02-impl-debug.rs
│ ├── 03-custom-format.rs
│ ├── 04-type-parameter.rs
│ ├── 05-phantom-data.rs
│ ├── 06-bound-trouble.rs
│ ├── 07-associated-type.rs
│ ├── 08-escape-hatch.rs
│ └── progress.rs
├── main.rs
├── seq
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
│ ├── 01-parse-header.rs
│ ├── 02-parse-body.rs
│ ├── 03-expand-four-errors.rs
│ ├── 03-expand-four-errors.stderr
│ ├── 04-paste-ident.rs
│ ├── 05-repeat-section.rs
│ ├── 06-init-array.rs
│ ├── 07-inclusive-range.rs
│ ├── 08-ident-span.rs
│ ├── 08-ident-span.stderr
│ ├── 09-interaction-with-macrorules.rs
│ └── progress.rs
└── sorted
├── Cargo.toml
├── src
└── lib.rs
└── tests
├── 01-parse-enum.rs
├── 02-not-enum.rs
├── 02-not-enum.stderr
├── 03-out-of-order.rs
├── 03-out-of-order.stderr
├── 04-variants-with-data.rs
├── 04-variants-with-data.stderr
├── 05-match-expr.rs
├── 05-match-expr.stderr
├── 06-pattern-path.rs
├── 06-pattern-path.stderr
├── 07-unrecognized-pattern.rs
├── 07-unrecognized-pattern.stderr
├── 08-underscore.rs
└── progress.rs
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: dtolnay
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | pull_request:
6 | workflow_dispatch:
7 | schedule: [cron: "40 1 * * *"]
8 |
9 | permissions:
10 | contents: read
11 |
12 | env:
13 | RUSTFLAGS: -Dwarnings
14 |
15 | jobs:
16 | test:
17 | name: Rust ${{matrix.rust}}
18 | runs-on: ubuntu-latest
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | rust: [nightly, beta, stable]
23 | timeout-minutes: 45
24 | steps:
25 | - uses: actions/checkout@v4
26 | - uses: dtolnay/rust-toolchain@master
27 | with:
28 | toolchain: ${{matrix.rust}}
29 | - run: cargo check
30 |
31 | solution:
32 | name: Project ${{matrix.project}}
33 | runs-on: ubuntu-latest
34 | strategy:
35 | fail-fast: false
36 | matrix:
37 | project: [builder, debug, seq, sorted, bitfield]
38 | env:
39 | GIT_COMMITTER_EMAIL: proc-macro-workshop@dtolnay.github.io
40 | GIT_COMMITTER_NAME: proc-macro-workshop CI
41 | timeout-minutes: 45
42 | steps:
43 | - uses: actions/checkout@v4
44 | - uses: dtolnay/rust-toolchain@nightly
45 | - uses: dtolnay/install@cargo-outdated
46 | - run: git fetch origin --unshallow refs/solution/${{matrix.project}} HEAD
47 | - run: git rev-parse FETCH_HEAD
48 | - run: git rebase HEAD FETCH_HEAD
49 | - run: cargo test
50 | working-directory: ${{matrix.project}}
51 | - run: cargo outdated --exit-code 1
52 | working-directory: ${{matrix.project}}
53 | if: github.event_name != 'pull_request'
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | Cargo.lock
2 | target/
3 | wip/
4 |
--------------------------------------------------------------------------------
/.template/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = ""
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [lib]
9 | proc-macro = true
10 |
11 | [[test]]
12 | name = "tests"
13 | path = "tests/progress.rs"
14 |
15 | [dev-dependencies]
16 | trybuild = "1.0"
17 |
18 | [dependencies]
19 | # TODO
20 |
--------------------------------------------------------------------------------
/.template/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
--------------------------------------------------------------------------------
/.template/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 |
5 | // TODO: add tests
6 | //
7 | // t.pass("tests/01-something-that-works.rs");
8 | // t.compile_fail("tests/02-some-compiler-error.rs");
9 | }
10 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "proc-macro-workshop"
3 | version = "0.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [workspace]
8 |
9 | [[bin]]
10 | name = "workshop"
11 | path = "main.rs"
12 |
13 | [dependencies]
14 | bitfield = { path = "bitfield" }
15 | derive_builder = { path = "builder" }
16 | derive_debug = { path = "debug" }
17 | seq = { path = "seq" }
18 | sorted = { path = "sorted" }
19 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Permission is hereby granted, free of charge, to any
2 | person obtaining a copy of this software and associated
3 | documentation files (the "Software"), to deal in the
4 | Software without restriction, including without
5 | limitation the rights to use, copy, modify, merge,
6 | publish, distribute, sublicense, and/or sell copies of
7 | the Software, and to permit persons to whom the Software
8 | is furnished to do so, subject to the following
9 | conditions:
10 |
11 | The above copyright notice and this permission notice
12 | shall be included in all copies or substantial portions
13 | of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
23 | DEALINGS IN THE SOFTWARE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Rust Latam: procedural macros workshop
2 |
3 | *This repo contains a selection of projects designed to learn to write Rust
4 | procedural macros — Rust code that generates Rust code.*
5 |
6 | *Each of these projects is drawn closely from a compelling real use case. Out of
7 | the 5 projects here, 3 are macros that I have personally implemented in
8 | industrial codebases for work, and the other 2 exist as libraries on crates.io
9 | by other authors.*
10 |
11 |
12 |
13 | ## Contents
14 |
15 | - [**Suggested prerequisites**](#suggested-prerequisites)
16 | - [**Projects**](#projects) — Introduction to each of the projects
17 | - [**Derive macro:** `derive(Builder)`](#derive-macro-derivebuilder)
18 | - [**Derive macro:** `derive(CustomDebug)`](#derive-macro-derivecustomdebug)
19 | - [**Function-like macro:** `seq!`](#function-like-macro-seq)
20 | - [**Attribute macro:** `#[sorted]`](#attribute-macro-sorted)
21 | - [**Attribute macro:** `#[bitfield]`](#attribute-macro-bitfield)
22 | - [**Project recommendations**](#project-recommendations) — What to work on
23 | depending on your interests
24 | - [**Test harness**](#test-harness) — Explanation of how testing is set up
25 | - [**Workflow**](#workflow) — Recommended way to work through the workshop
26 | - [**Debugging tips**](#debugging-tips)
27 |
28 |
29 |
30 | ## Suggested prerequisites
31 |
32 | This workshop covers attribute macros, derive macros, and function-like
33 | procedural macros.
34 |
35 | Be aware that the content of the workshop and the explanations in this repo will
36 | assume a working understanding of structs, enums, traits, trait impls, generic
37 | parameters, and trait bounds. You are welcome to dive into the workshop with any
38 | level of experience with Rust, but you may find that these basics are far easier
39 | to learn for the first time outside of the context of macros.
40 |
41 |
42 |
43 | ## Projects
44 |
45 | Here is an introduction to each of the projects. At the bottom, I give
46 | recommendations for what order to tackle them based on your interests. Note that
47 | each of these projects goes into more depth than what is described in the
48 | introduction here.
49 |
50 | ### Derive macro: `derive(Builder)`
51 |
52 | This macro generates the boilerplate code involved in implementing the [builder
53 | pattern] in Rust. Builders are a mechanism for instantiating structs, especially
54 | structs with many fields, and especially if many of those fields are optional or
55 | the set of fields may need to grow backward compatibly over time.
56 |
57 | [builder pattern]: https://en.wikipedia.org/wiki/Builder_pattern
58 |
59 | There are a few different possibilities for expressing builders in Rust. Unless
60 | you have a strong pre-existing preference, to keep things simple for this
61 | project I would recommend following the example of the standard library's
62 | [`std::process::Command`] builder in which the setter methods each receive and
63 | return `&mut self` to allow chained method calls.
64 |
65 | [`std::process::Command`]: https://doc.rust-lang.org/std/process/struct.Command.html
66 |
67 | Callers will invoke the macro as follows.
68 |
69 | ```rust
70 | use derive_builder::Builder;
71 |
72 | #[derive(Builder)]
73 | pub struct Command {
74 | executable: String,
75 | #[builder(each = "arg")]
76 | args: Vec,
77 | current_dir: Option,
78 | }
79 |
80 | fn main() {
81 | let command = Command::builder()
82 | .executable("cargo".to_owned())
83 | .arg("build".to_owned())
84 | .arg("--release".to_owned())
85 | .build()
86 | .unwrap();
87 |
88 | assert_eq!(command.executable, "cargo");
89 | }
90 | ```
91 |
92 | This project covers:
93 |
94 | - traversing syntax trees;
95 | - constructing output source code;
96 | - processing helper attributes to customize the generated code.
97 |
98 | *Project skeleton is located under the builder directory.*
99 |
100 | ### Derive macro: `derive(CustomDebug)`
101 |
102 | This macro implements a derive for the standard library [`std::fmt::Debug`]
103 | trait that is more customizable than the similar `Debug` derive macro exposed by
104 | the standard library.
105 |
106 | [`std::fmt::Debug`]: https://doc.rust-lang.org/std/fmt/trait.Debug.html
107 |
108 | In particular, we'd like to be able to select the formatting used for individual
109 | struct fields by providing a format string in the style expected by Rust string
110 | formatting macros like `format!` and `println!`.
111 |
112 | ```rust
113 | use derive_debug::CustomDebug;
114 |
115 | #[derive(CustomDebug)]
116 | pub struct Field {
117 | name: String,
118 | #[debug = "0b{:08b}"]
119 | bitmask: u8,
120 | }
121 | ```
122 |
123 | Here, one possible instance of the struct above might be printed by its
124 | generated `Debug` impl like this:
125 |
126 | ```console
127 | Field { name: "st0", bitmask: 0b00011100 }
128 | ```
129 |
130 | This project covers:
131 |
132 | - traversing syntax trees;
133 | - constructing output source code;
134 | - processing helper attributes;
135 | - dealing with lifetime parameters and type parameters;
136 | - inferring trait bounds on generic parameters of trait impls;
137 | - limitations of derive's ability to emit universally correct trait bounds.
138 |
139 | *Project skeleton is located under the debug directory.*
140 |
141 | ### Function-like macro: `seq!`
142 |
143 | This macro provides a syntax for stamping out sequentially indexed copies of an
144 | arbitrary chunk of code.
145 |
146 | For example our application may require an enum with sequentially numbered
147 | variants like `Cpu0` `Cpu1` `Cpu2` ... `Cpu511`. But note that the same `seq!`
148 | macro should work for any sort of compile-time loop; there is nothing specific
149 | to emitting enum variants. A different caller might use it for generating an
150 | expression like `tuple.0 + tuple.1 + ... + tuple.511`.
151 |
152 | ```rust
153 | use seq::seq;
154 |
155 | seq!(N in 0..512 {
156 | #[derive(Copy, Clone, PartialEq, Debug)]
157 | pub enum Processor {
158 | #(
159 | Cpu~N,
160 | )*
161 | }
162 | });
163 |
164 | fn main() {
165 | let cpu = Processor::Cpu8;
166 |
167 | assert_eq!(cpu as u8, 8);
168 | assert_eq!(cpu, Processor::Cpu8);
169 | }
170 | ```
171 |
172 | This project covers:
173 |
174 | - parsing custom syntax;
175 | - low-level representation of token streams;
176 | - constructing output source code.
177 |
178 | *Project skeleton is located under the seq directory.*
179 |
180 | ### Attribute macro: `#[sorted]`
181 |
182 | A macro for when your coworkers (or you yourself) cannot seem to keep enum
183 | variants in sorted order when adding variants or refactoring. The macro will
184 | detect unsorted variants at compile time and emit an error pointing out which
185 | variants are out of order.
186 |
187 | ```rust
188 | #[sorted]
189 | #[derive(Debug)]
190 | pub enum Error {
191 | BlockSignal(signal::Error),
192 | CreateCrasClient(libcras::Error),
193 | CreateEventFd(sys_util::Error),
194 | CreateSignalFd(sys_util::SignalFdError),
195 | CreateSocket(io::Error),
196 | DetectImageType(qcow::Error),
197 | DeviceJail(io_jail::Error),
198 | NetDeviceNew(virtio::NetError),
199 | SpawnVcpu(io::Error),
200 | }
201 | ```
202 |
203 | This project covers:
204 |
205 | - compile-time error reporting;
206 | - application of visitor pattern to traverse a syntax tree;
207 | - limitations of the currently stable macro API and some ways to work around
208 | them.
209 |
210 | *Project skeleton is located under the sorted directory.*
211 |
212 | ### Attribute macro: `#[bitfield]`
213 |
214 | This macro provides a mechanism for defining structs in a packed binary
215 | representation with access to ranges of bits, similar to the language-level
216 | support for [bit fields in C].
217 |
218 | [bit fields in C]: https://en.cppreference.com/w/cpp/language/bit_field
219 |
220 | The macro will conceptualize one of these structs as a sequence of bits 0..N.
221 | The bits are grouped into fields in the order specified by a struct written by
222 | the caller. The `#[bitfield]` attribute rewrites the caller's struct into a
223 | private byte array representation with public getter and setter methods for each
224 | field.
225 |
226 | The total number of bits N is required to be a multiple of 8 (this will be
227 | checked at compile time).
228 |
229 | For example, the following invocation builds a struct with a total size of 32
230 | bits or 4 bytes. It places field `a` in the least significant bit of the first
231 | byte, field `b` in the next three least significant bits, field `c` in the
232 | remaining four most significant bits of the first byte, and field `d` spanning
233 | the next three bytes.
234 |
235 | ```rust
236 | use bitfield::*;
237 |
238 | #[bitfield]
239 | pub struct MyFourBytes {
240 | a: B1,
241 | b: B3,
242 | c: B4,
243 | d: B24,
244 | }
245 | ```
246 |
247 | ```text
248 | least significant bit of third byte
249 | ┊ most significant
250 | ┊ ┊
251 | ┊ ┊
252 | ║ first byte ║ second byte ║ third byte ║ fourth byte ║
253 | ╟───────────────╫───────────────╫───────────────╫───────────────╢
254 | ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
255 | ╟─╫─────╫───────╫───────────────────────────────────────────────╢
256 | ║a║ b ║ c ║ d ║
257 | ┊ ┊
258 | ┊ ┊
259 | least significant bit of d most significant
260 | ```
261 |
262 | The code emitted by the `#[bitfield]` macro for this struct would be as follows.
263 | Note that the field getters and setters use whichever of `u8`, `u16`, `u32`,
264 | `u64` is the smallest while being at least as large as the number of bits in
265 | the field.
266 |
267 | ```rust
268 | impl MyFourBytes {
269 | // Initializes all fields to 0.
270 | pub fn new() -> Self;
271 |
272 | // Field getters and setters:
273 | pub fn get_a(&self) -> u8;
274 | pub fn set_a(&mut self, val: u8);
275 | pub fn get_b(&self) -> u8;
276 | pub fn set_b(&mut self, val: u8);
277 | pub fn get_c(&self) -> u8;
278 | pub fn set_c(&mut self, val: u8);
279 | pub fn get_d(&self) -> u32;
280 | pub fn set_d(&mut self, val: u32);
281 | }
282 | ```
283 |
284 | This project covers:
285 |
286 | - traversing syntax trees;
287 | - processing helper attributes;
288 | - constructing output source code;
289 | - interacting with traits and structs other than from the standard library;
290 | - techniques for compile-time assertions that require type information, by
291 | leveraging the trait system in interesting ways from generated code;
292 | - tricky code.
293 |
294 | *Project skeleton is located under the bitfield directory.*
295 |
296 | ### Project recommendations
297 |
298 | If this is your first time working with procedural macros, I would recommend
299 | starting with the `derive(Builder)` project. This will get you comfortable with
300 | traversing syntax trees and constructing output source code. These are the two
301 | fundamental components of a procedural macro.
302 |
303 | After that, it would be equally reasonable to jump to any of
304 | `derive(CustomDebug)`, `seq!`, or `#[sorted]`.
305 |
306 | - Go for `derive(CustomDebug)` if you are interested in exploring how macros
307 | manipulate trait bounds, which is one of the most complicated aspects of
308 | code generation in Rust involving generic code like [Serde]. This project
309 | provides an approachable introduction to trait bounds and digs into many of
310 | the challenging aspects.
311 |
312 | - Go for `seq!` if you are interested in parsing a custom input syntax yourself.
313 | The other projects will all mostly rely on parsers that have already been
314 | written and distributed as a library, since their input is ordinary Rust
315 | syntax.
316 |
317 | - Go for `#[sorted]` if you are interested in generating diagnostics (custom
318 | errors) via a macro. Part of this project also covers a different way of
319 | processing input syntax trees; the other projects will do most things through
320 | `if let`. The visitor approach is better suited to certain types of macros
321 | involving statements or expressions as we'll see here when checking that
322 | `match` arms are sorted.
323 |
324 | [Serde]: https://serde.rs/
325 |
326 | I would recommend starting on `#[bitfield]` only after you feel you have a
327 | strong grasp on at least two of the other projects. Note that completing the
328 | full intended design will involve writing at least one of all three types of
329 | procedural macros and substantially more code than the other projects.
330 |
331 |
332 |
333 | ## Test harness
334 |
335 | Testing macros thoroughly tends to be tricky. Rust and Cargo have a built-in
336 | testing framework via `cargo test` which can work for testing the success cases,
337 | but we also really care that our macros produce good error message when they
338 | detect a problem at compile time; Cargo isn't able to say that failing to
339 | compile is considered a success, and isn't able to compare that the error
340 | message produced by the compiler is exactly what we expect.
341 |
342 | The project skeletons in this repository use an alternative test harness called
343 | [trybuild].
344 |
345 | [trybuild]: https://github.com/dtolnay/trybuild
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 | The test harness is geared toward iterating on the implementation of a
354 | procedural macro, observing the errors emitted by failed executions of the
355 | macro, and testing that those errors are as expected.
356 |
357 |
358 |
359 | ## Workflow
360 |
361 | Every project has a test suite already written under its tests
362 | directory. (But feel free to add more tests, remove tests for functionality you
363 | don't want to implement, or modify tests as you see fit to align with your
364 | implementation.)
365 |
366 | Run `cargo test` inside any of the 5 top-level project directories to run the
367 | test suite for that project.
368 |
369 | Initially every projects starts with all of its tests disabled. Open up the
370 | project's *tests/progress.rs* file and enable tests one at a time as you work
371 | through the implementation. **The test files (for example *tests/01-parse.rs*)
372 | each contain a comment explaining what functionality is tested and giving some
373 | tips for how to implement it.** I recommend working through tests in numbered
374 | order, each time enabling one more test and getting it passing before moving on.
375 |
376 | Tests come in two flavors: tests that should compile+run successfully, and tests
377 | that should fail to compile with a specific error message.
378 |
379 | If a test should compile and run successfully, but fails, the test runner will
380 | surface the compiler error or runtime error output.
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 | For tests that should fail to compile, we compare the compilation output against
389 | a file of expected errors for that test. If those errors match, the test is
390 | considered to pass. If they do not match, the test runner will surface the
391 | expected and actual output.
392 |
393 | Expected output goes in a file with the same name as the test except with an
394 | extension of _*.stderr_ instead of _*.rs_.
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 | If there is no _*.stderr_ file for a test that is supposed to fail to compile,
403 | the test runner will save the compiler's output into a directory called
404 | wip adjacent to the tests directory. So the way to update
405 | the "expected" output is to delete the existing _*.stderr_ file, run the tests
406 | again so that the output is written to *wip*, and then move the new output from
407 | *wip* to *tests*.
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 | ## Debugging tips
418 |
419 | To look at what code a macro is expanding into, install the [cargo expand] Cargo
420 | subcommand and then run `cargo expand` in the repository root (outside of any of
421 | the project directories) to expand the main.rs file in that directory. You can
422 | copy any of the test cases into this main.rs and tweak it as you iterate on the
423 | macro.
424 |
425 | [cargo expand]: https://github.com/dtolnay/cargo-expand
426 |
427 | If a macro is emitting syntactically invalid code (not just code that fails
428 | type-checking) then cargo expand will not be able to show it. Instead have the
429 | macro print its generated TokenStream to stderr before returning the tokens.
430 |
431 | ```rust
432 | eprintln!("TOKENS: {}", tokens);
433 | ```
434 |
435 | Then a `cargo check` in the repository root (if you are iterating using main.rs)
436 | or `cargo test` in the corresponding project directory will display this output
437 | during macro expansion.
438 |
439 | Stderr is also a helpful way to see the structure of the syntax tree that gets
440 | parsed from the input of the macro.
441 |
442 | ```rust
443 | eprintln!("INPUT: {:#?}", syntax_tree);
444 | ```
445 |
446 | Note that in order for Syn's syntax tree types to provide Debug impls, you will
447 | need to set `features = ["extra-traits"]` on the dependency on Syn. This is
448 | because adding hundreds of Debug impls adds an appreciable amount of compile
449 | time to Syn, and we really only need this enabled while doing development on a
450 | macro rather than when the finished macro is published to users.
451 |
452 |
453 |
454 | ### License
455 |
456 |
457 | Licensed under either of Apache License, Version
458 | 2.0 or MIT license at your option.
459 |
460 |
461 |
462 |
463 |
464 | Unless you explicitly state otherwise, any contribution intentionally submitted
465 | for inclusion in this codebase by you, as defined in the Apache-2.0 license,
466 | shall be dual licensed as above, without any additional terms or conditions.
467 |
468 |
--------------------------------------------------------------------------------
/bitfield/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bitfield"
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [[test]]
9 | name = "tests"
10 | path = "tests/progress.rs"
11 |
12 | [dev-dependencies]
13 | trybuild = { version = "1.0.49", features = ["diff"] }
14 |
15 | [dependencies]
16 | bitfield-impl = { path = "impl" }
17 |
--------------------------------------------------------------------------------
/bitfield/impl/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "bitfield-impl"
3 | version = "0.0.0"
4 | edition = "2021"
5 | publish = false
6 |
7 | [lib]
8 | proc-macro = true
9 |
10 | [dependencies]
11 | # TODO
12 |
--------------------------------------------------------------------------------
/bitfield/impl/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
3 | #[proc_macro_attribute]
4 | pub fn bitfield(args: TokenStream, input: TokenStream) -> TokenStream {
5 | let _ = args;
6 | let _ = input;
7 |
8 | unimplemented!()
9 | }
10 |
--------------------------------------------------------------------------------
/bitfield/src/lib.rs:
--------------------------------------------------------------------------------
1 | // Crates that have the "proc-macro" crate type are only allowed to export
2 | // procedural macros. So we cannot have one crate that defines procedural macros
3 | // alongside other types of public APIs like traits and structs.
4 | //
5 | // For this project we are going to need a #[bitfield] macro but also a trait
6 | // and some structs. We solve this by defining the trait and structs in this
7 | // crate, defining the attribute macro in a separate bitfield-impl crate, and
8 | // then re-exporting the macro from this crate so that users only have one crate
9 | // that they need to import.
10 | //
11 | // From the perspective of a user of this crate, they get all the necessary APIs
12 | // (macro, trait, struct) through the one bitfield crate.
13 | pub use bitfield_impl::bitfield;
14 |
15 | // TODO other things
16 |
--------------------------------------------------------------------------------
/bitfield/tests/01-specifier-types.rs:
--------------------------------------------------------------------------------
1 | // Our design for #[bitfield] (see the readme) involves marker types B1 through
2 | // B64 to indicate the bit width of each field.
3 | //
4 | // It would be possible to implement this without having any actual types B1
5 | // through B64 -- the attribute macro could recognize the names "B1" through
6 | // "B64" and deduce the bit width from the number in the name. But this hurts
7 | // composability! Later we'll want to make bitfield members out of other things,
8 | // like enums or type aliases which won't necessarily have a width in their
9 | // name:
10 | //
11 | // #[bitfield]
12 | // struct RedirectionTableEntry {
13 | // vector: B8,
14 | // dest_mode: DestinationMode,
15 | // trigger_mode: TriggerMode,
16 | // destination: Destination,
17 | // }
18 | //
19 | // #[bitfield]
20 | // enum DestinationMode {
21 | // Physical = 0,
22 | // Logical = 1,
23 | // }
24 | //
25 | // #[bitfield]
26 | // enum TriggerMode {
27 | // Edge = 0,
28 | // Level = 1,
29 | // }
30 | //
31 | // #[target_pointer_width = "64"]
32 | // type Destination = B30;
33 | //
34 | // #[target_pointer_width = "32"]
35 | // type Destination = B22;
36 | //
37 | // So instead of parsing a bit width from the type name, the approach we will
38 | // follow will hold bit widths in an associated constant of a trait that is
39 | // implemented for legal bitfield specifier types, including B1 through B64.
40 | //
41 | // Create a trait called bitfield::Specifier with an associated constant BITS,
42 | // and write a function-like procedural macro to define some types B1 through
43 | // B64 with corresponding impls of the Specifier trait. The B* types can be
44 | // anything since we don't need them to carry any meaning outside of a
45 | // #[bitfield] struct definition; an uninhabited enum like `pub enum B1 {}`
46 | // would work best.
47 | //
48 | // Be aware that crates that have the "proc-macro" crate type are not allowed to
49 | // export anything other than procedural macros. The project skeleton for this
50 | // project has been set up with two crates, one for procedural macros and the
51 | // other an ordinary library crate for the Specifier trait and B types which
52 | // also re-exports from the procedural macro crate so that users can get
53 | // everything through one library.
54 |
55 | use bitfield::*;
56 |
57 | //#[bitfield]
58 | pub struct MyFourBytes {
59 | a: B1,
60 | b: B3,
61 | c: B4,
62 | d: B24,
63 | }
64 |
65 | fn main() {
66 | assert_eq!(::BITS, 24);
67 | }
68 |
--------------------------------------------------------------------------------
/bitfield/tests/02-storage.rs:
--------------------------------------------------------------------------------
1 | // Write an attribute macro that replaces the struct in its input with a byte
2 | // array representation of the correct size. For example the invocation in the
3 | // test case below might expand to the following where the `size` expression is
4 | // computed by summing the Specifier::BITS constant of each field type.
5 | //
6 | // #[repr(C)]
7 | // pub struct MyFourBytes {
8 | // data: [u8; #size],
9 | // }
10 | //
11 | // Don't worry for now what happens if the total bit size is not a multiple of
12 | // 8 bits. We will come back to that later to make it a compile-time error.
13 |
14 | use bitfield::*;
15 |
16 | #[bitfield]
17 | pub struct MyFourBytes {
18 | a: B1,
19 | b: B3,
20 | c: B4,
21 | d: B24,
22 | }
23 |
24 | fn main() {
25 | assert_eq!(std::mem::size_of::(), 4);
26 | }
27 |
--------------------------------------------------------------------------------
/bitfield/tests/03-accessors.rs:
--------------------------------------------------------------------------------
1 | // Generate getters and setters that manipulate the right range of bits
2 | // corresponding to each field.
3 | //
4 | //
5 | // ║ first byte ║ second byte ║ third byte ║ fourth byte ║
6 | // ╟───────────────╫───────────────╫───────────────╫───────────────╢
7 | // ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
8 | // ╟─╫─────╫───────╫───────────────────────────────────────────────╢
9 | // ║a║ b ║ c ║ d ║
10 | //
11 | //
12 | // Depending on your implementation, it's possible that this will require adding
13 | // some associated types, associated constants, or associated functions to your
14 | // bitfield::Specifier trait next to the existing Specifier::BITS constant, but
15 | // it may not.
16 | //
17 | // If it's easier for now, you can use u64 as the argument type for all the
18 | // setters and return type for all the getters. We will follow up with a more
19 | // precise signature in a later test case.
20 |
21 | use bitfield::*;
22 |
23 | #[bitfield]
24 | pub struct MyFourBytes {
25 | a: B1,
26 | b: B3,
27 | c: B4,
28 | d: B24,
29 | }
30 |
31 | fn main() {
32 | let mut bitfield = MyFourBytes::new();
33 | assert_eq!(0, bitfield.get_a());
34 | assert_eq!(0, bitfield.get_b());
35 | assert_eq!(0, bitfield.get_c());
36 | assert_eq!(0, bitfield.get_d());
37 |
38 | bitfield.set_c(14);
39 | assert_eq!(0, bitfield.get_a());
40 | assert_eq!(0, bitfield.get_b());
41 | assert_eq!(14, bitfield.get_c());
42 | assert_eq!(0, bitfield.get_d());
43 | }
44 |
--------------------------------------------------------------------------------
/bitfield/tests/04-multiple-of-8bits.rs:
--------------------------------------------------------------------------------
1 | // Make it so that a bitfield with a size not a multiple of 8 bits will not
2 | // compile.
3 | //
4 | // Aim to make the error message as relevant and free of distractions as you can
5 | // get. The stderr file next to this test case should give some idea as to the
6 | // approach taken by the reference implementation for this project, but feel
7 | // free to overwrite the stderr file to match the implementation you come up
8 | // with.
9 | //
10 | // ---
11 | // Tangent
12 | //
13 | // There is only one profound insight about Rust macro development, and this
14 | // test case begins to touch on it: what makes someone an "expert at macros"
15 | // mostly has nothing to do with how good they are "at macros".
16 | //
17 | // 95% of what enables people to write powerful and user-friendly macro
18 | // libraries is in their mastery of everything else about Rust outside of
19 | // macros, and their creativity to put together ordinary language features in
20 | // interesting ways that may not occur in handwritten code.
21 | //
22 | // You may occasionally come across procedural macros that you feel are really
23 | // advanced or magical. If you ever feel this way, I encourage you to take a
24 | // closer look and you'll discover that as far as the macro implementation
25 | // itself is concerned, none of those libraries are doing anything remotely
26 | // interesting. They always just parse some input in a boring way, crawl some
27 | // syntax trees in a boring way to find out about the input, and paste together
28 | // some output code in a boring way exactly like what you've been doing so far.
29 | // In fact once you've made it this far in the workshop, it's okay to assume you
30 | // basically know everything there is to know about the mechanics of writing
31 | // procedural macros.
32 | //
33 | // To the extent that there are any tricks to macro development, all of them
34 | // revolve around *what* code the macros emit, not *how* the macros emit the
35 | // code. This realization can be surprising to people who entered into macro
36 | // development with a vague notion of procedural macros as a "compiler plugin"
37 | // which they imagine must imply all sorts of complicated APIs for *how* to
38 | // integrate with the rest of the compiler. That's not how it works. The only
39 | // thing macros do is emit code that could have been written by hand. If you
40 | // couldn't have come up with some piece of tricky code from one of those
41 | // magical macros, learning more "about macros" won't change that; but learning
42 | // more about every other part of Rust will. Inversely, once you come up with
43 | // what code you want to generate, writing the macro to generate it is generally
44 | // the easy part.
45 |
46 | use bitfield::*;
47 |
48 | type A = B1;
49 | type B = B3;
50 | type C = B4;
51 | type D = B23;
52 |
53 | #[bitfield]
54 | pub struct NotQuiteFourBytes {
55 | a: A,
56 | b: B,
57 | c: C,
58 | d: D,
59 | }
60 |
61 | fn main() {}
62 |
--------------------------------------------------------------------------------
/bitfield/tests/04-multiple-of-8bits.stderr:
--------------------------------------------------------------------------------
1 | error[E0277]: the trait bound `bitfield::checks::SevenMod8: bitfield::checks::TotalSizeIsMultipleOfEightBits` is not satisfied
2 | --> tests/04-multiple-of-8bits.rs:53:1
3 | |
4 | 53 | #[bitfield]
5 | | ^^^^^^^^^^^ the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is not implemented for `bitfield::checks::SevenMod8`
6 | |
7 | = help: the trait `bitfield::checks::TotalSizeIsMultipleOfEightBits` is implemented for `bitfield::checks::ZeroMod8`
8 | = note: this error originates in the attribute macro `bitfield` (in Nightly builds, run with -Z macro-backtrace for more info)
9 |
--------------------------------------------------------------------------------
/bitfield/tests/05-accessor-signatures.rs:
--------------------------------------------------------------------------------
1 | // For getters and setters, we would like for the signature to be in terms of
2 | // the narrowest unsigned integer type that can hold the right number of bits.
3 | // That means the accessors for B1 through B8 would use u8, B9 through B16 would
4 | // use u16 etc.
5 |
6 | use bitfield::*;
7 | use std::mem::size_of_val;
8 |
9 | type A = B1;
10 | type B = B3;
11 | type C = B4;
12 | type D = B24;
13 |
14 | #[bitfield]
15 | pub struct MyFourBytes {
16 | a: A,
17 | b: B,
18 | c: C,
19 | d: D,
20 | }
21 |
22 | fn main() {
23 | let mut x = MyFourBytes::new();
24 |
25 | // I am testing the signatures in this roundabout way to avoid making it
26 | // possible to pass this test with a generic signature that is inconvenient
27 | // for callers, such as `fn get_a>(&self) -> T`.
28 |
29 | let a = 1;
30 | x.set_a(a); // expect fn(&mut MyFourBytes, u8)
31 | let b = 1;
32 | x.set_b(b);
33 | let c = 1;
34 | x.set_c(c);
35 | let d = 1;
36 | x.set_d(d); // expect fn(&mut MyFourBytes, u32)
37 |
38 | assert_eq!(size_of_val(&a), 1);
39 | assert_eq!(size_of_val(&b), 1);
40 | assert_eq!(size_of_val(&c), 1);
41 | assert_eq!(size_of_val(&d), 4);
42 |
43 | assert_eq!(size_of_val(&x.get_a()), 1); // expect fn(&MyFourBytes) -> u8
44 | assert_eq!(size_of_val(&x.get_b()), 1);
45 | assert_eq!(size_of_val(&x.get_c()), 1);
46 | assert_eq!(size_of_val(&x.get_d()), 4); // expect fn(&MyFourBytes) -> u32
47 | }
48 |
--------------------------------------------------------------------------------
/bitfield/tests/06-enums.rs:
--------------------------------------------------------------------------------
1 | // For some bitfield members, working with them as enums will make more sense to
2 | // the user than working with them as integers. We will require enums that have
3 | // a power-of-two number of variants so that they exhaustively cover a fixed
4 | // range of bits.
5 | //
6 | // // Works like B3, but getter and setter signatures will use
7 | // // the enum instead of u8.
8 | // #[derive(BitfieldSpecifier)]
9 | // enum DeliveryMode {
10 | // Fixed = 0b000,
11 | // Lowest = 0b001,
12 | // SMI = 0b010,
13 | // RemoteRead = 0b011,
14 | // NMI = 0b100,
15 | // Init = 0b101,
16 | // Startup = 0b110,
17 | // External = 0b111,
18 | // }
19 | //
20 | // For this test case it is okay to require that every enum variant has an
21 | // explicit discriminant that is an integer literal. We will relax this
22 | // requirement in a later test case.
23 | //
24 | // Optionally if you are interested, come up with a way to support enums with a
25 | // number of variants that is not a power of two, but this is not necessary for
26 | // the test suite. Maybe there could be a #[bits = N] attribute that determines
27 | // the bit width of the specifier, and the getter (only for such enums) would
28 | // return Result with the raw value accessible through the
29 | // error type as u64:
30 | //
31 | // #[derive(BitfieldSpecifier)]
32 | // #[bits = 4]
33 | // enum SmallPrime {
34 | // Two = 0b0010,
35 | // Three = 0b0011,
36 | // Five = 0b0101,
37 | // Seven = 0b0111,
38 | // Eleven = 0b1011,
39 | // Thirteen = 0b1101,
40 | // }
41 | //
42 | // ...
43 | // let mut bitfield = MyBitfield::new();
44 | // assert_eq!(0, bitfield.small_prime().unwrap_err().raw_value());
45 | //
46 | // bitfield.set_small_prime(SmallPrime::Seven);
47 | // let p = bitfield.small_prime().unwrap_or(SmallPrime::Two);
48 |
49 | use bitfield::*;
50 |
51 | #[bitfield]
52 | pub struct RedirectionTableEntry {
53 | acknowledged: bool,
54 | trigger_mode: TriggerMode,
55 | delivery_mode: DeliveryMode,
56 | reserved: B3,
57 | }
58 |
59 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
60 | pub enum TriggerMode {
61 | Edge = 0,
62 | Level = 1,
63 | }
64 |
65 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
66 | pub enum DeliveryMode {
67 | Fixed = 0b000,
68 | Lowest = 0b001,
69 | SMI = 0b010,
70 | RemoteRead = 0b011,
71 | NMI = 0b100,
72 | Init = 0b101,
73 | Startup = 0b110,
74 | External = 0b111,
75 | }
76 |
77 | fn main() {
78 | assert_eq!(std::mem::size_of::(), 1);
79 |
80 | // Initialized to all 0 bits.
81 | let mut entry = RedirectionTableEntry::new();
82 | assert_eq!(entry.get_acknowledged(), false);
83 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge);
84 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Fixed);
85 |
86 | entry.set_acknowledged(true);
87 | entry.set_delivery_mode(DeliveryMode::SMI);
88 | assert_eq!(entry.get_acknowledged(), true);
89 | assert_eq!(entry.get_trigger_mode(), TriggerMode::Edge);
90 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::SMI);
91 | }
92 |
--------------------------------------------------------------------------------
/bitfield/tests/07-optional-discriminant.rs:
--------------------------------------------------------------------------------
1 | // For bitfield use limited to a single binary, such as a space optimization for
2 | // some in-memory data structure, we may not care what exact bit representation
3 | // is used for enums.
4 | //
5 | // Make your BitfieldSpecifier derive macro for enums use the underlying
6 | // discriminant determined by the Rust compiler as the bit representation. Do
7 | // not assume that the compiler uses any particular scheme like PREV+1 for
8 | // implicit discriminants; make sure your implementation respects Rust's choice
9 | // of discriminant regardless of what scheme Rust uses. This is important for
10 | // performance so that the getter and setter both compile down to very simple
11 | // machine code after optimizations.
12 | //
13 | // Do not worry about what happens if discriminants are outside of the range
14 | // 0..2^BITS. We will do a compile-time check in a later test case to ensure
15 | // they are in range.
16 |
17 | use bitfield::*;
18 |
19 | #[bitfield]
20 | pub struct RedirectionTableEntry {
21 | delivery_mode: DeliveryMode,
22 | reserved: B5,
23 | }
24 |
25 | const F: isize = 3;
26 | const G: isize = 0;
27 |
28 | #[derive(BitfieldSpecifier, Debug, PartialEq)]
29 | pub enum DeliveryMode {
30 | Fixed = F,
31 | Lowest,
32 | SMI,
33 | RemoteRead,
34 | NMI,
35 | Init = G,
36 | Startup,
37 | External,
38 | }
39 |
40 | fn main() {
41 | assert_eq!(std::mem::size_of::(), 1);
42 |
43 | // Initialized to all 0 bits.
44 | let mut entry = RedirectionTableEntry::new();
45 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Init);
46 |
47 | entry.set_delivery_mode(DeliveryMode::Lowest);
48 | assert_eq!(entry.get_delivery_mode(), DeliveryMode::Lowest);
49 | }
50 |
--------------------------------------------------------------------------------
/bitfield/tests/08-non-power-of-two.rs:
--------------------------------------------------------------------------------
1 | // Bitfield enums with a number of variants other than a power of two should
2 | // fail to compile.
3 | //
4 | // (Or, if you implemented the optional #[bits = N] enum approach mentioned in
5 | // the explanation of test case 06, then enums with non-power-of-two variants
6 | // without a #[bits = N] attribute should fail to compile.)
7 |
8 | use bitfield::*;
9 |
10 | #[derive(BitfieldSpecifier)]
11 | pub enum Bad {
12 | Zero,
13 | One,
14 | Two,
15 | }
16 |
17 | fn main() {}
18 |
--------------------------------------------------------------------------------
/bitfield/tests/08-non-power-of-two.stderr:
--------------------------------------------------------------------------------
1 | error: BitfieldSpecifier expected a number of variants which is a power of 2
2 | --> tests/08-non-power-of-two.rs:10:10
3 | |
4 | 10 | #[derive(BitfieldSpecifier)]
5 | | ^^^^^^^^^^^^^^^^^
6 | |
7 | = note: this error originates in the derive macro `BitfieldSpecifier` (in Nightly builds, run with -Z macro-backtrace for more info)
8 |
--------------------------------------------------------------------------------
/bitfield/tests/09-variant-out-of-range.rs:
--------------------------------------------------------------------------------
1 | // Bitfield enums with any discriminant (implicit or explicit) outside of the
2 | // range 0..2^BITS should fail to compile.
3 |
4 | use bitfield::*;
5 |
6 | const F: isize = 1;
7 |
8 | #[derive(BitfieldSpecifier)]
9 | pub enum DeliveryMode {
10 | Fixed = F,
11 | Lowest,
12 | SMI,
13 | RemoteRead,
14 | NMI,
15 | Init,
16 | Startup,
17 | External,
18 | }
19 |
20 | fn main() {}
21 |
--------------------------------------------------------------------------------
/bitfield/tests/09-variant-out-of-range.stderr:
--------------------------------------------------------------------------------
1 | error[E0277]: the trait bound `bitfield::checks::False: bitfield::checks::DiscriminantInRange` is not satisfied
2 | --> tests/09-variant-out-of-range.rs:17:5
3 | |
4 | 17 | External,
5 | | ^^^^^^^^ the trait `bitfield::checks::DiscriminantInRange` is not implemented for `bitfield::checks::False`
6 | |
7 | = help: the trait `bitfield::checks::DiscriminantInRange` is implemented for `bitfield::checks::True`
8 |
--------------------------------------------------------------------------------
/bitfield/tests/10-bits-attribute.rs:
--------------------------------------------------------------------------------
1 | // One downside of the way we have implemented enum support so far is that it
2 | // makes it impossible to see from the definition of a bitfield struct how the
3 | // bits are being laid out. In something like the following bitfield, all we
4 | // know from this code is that the total size is a multiple of 8 bits. Maybe
5 | // trigger_mode is 11 bits and delivery_mode is 1 bit. This may tend to make
6 | // maintenance problematic when the types involved are defined across different
7 | // modules or even different crates.
8 | //
9 | // #[bitfield]
10 | // pub struct RedirectionTableEntry {
11 | // trigger_mode: TriggerMode,
12 | // delivery_mode: DeliveryMode,
13 | // reserved: B4,
14 | // }
15 | //
16 | // Introduce an optional #[bits = N] attribute to serve as compile-time checked
17 | // documentation of field size. Ensure that this attribute is entirely optional,
18 | // meaning that the code behaves the same whether or not you write it, but if
19 | // the user does provide the attribute then the program must not compile if
20 | // their value is wrong.
21 |
22 | use bitfield::*;
23 |
24 | #[bitfield]
25 | pub struct RedirectionTableEntry {
26 | #[bits = 1]
27 | trigger_mode: TriggerMode,
28 | #[bits = 3]
29 | delivery_mode: DeliveryMode,
30 | reserved: B4,
31 | }
32 |
33 | #[derive(BitfieldSpecifier, Debug)]
34 | pub enum TriggerMode {
35 | Edge = 0,
36 | Level = 1,
37 | }
38 |
39 | #[derive(BitfieldSpecifier, Debug)]
40 | pub enum DeliveryMode {
41 | Fixed = 0b000,
42 | Lowest = 0b001,
43 | SMI = 0b010,
44 | RemoteRead = 0b011,
45 | NMI = 0b100,
46 | Init = 0b101,
47 | Startup = 0b110,
48 | External = 0b111,
49 | }
50 |
51 | fn main() {}
52 |
--------------------------------------------------------------------------------
/bitfield/tests/11-bits-attribute-wrong.rs:
--------------------------------------------------------------------------------
1 | // This is just the compile_fail version of the previous test case, testing what
2 | // error happens if the user has written an incorrect #[bits = N] attribute.
3 | //
4 | // Ensure that the error message points to the incorrect attribute and contains
5 | // the correct number of bits in some form.
6 |
7 | use bitfield::*;
8 |
9 | #[bitfield]
10 | pub struct RedirectionTableEntry {
11 | #[bits = 9]
12 | trigger_mode: TriggerMode,
13 | reserved: B7,
14 | }
15 |
16 | #[derive(BitfieldSpecifier, Debug)]
17 | pub enum TriggerMode {
18 | Edge = 0,
19 | Level = 1,
20 | }
21 |
22 | fn main() {}
23 |
--------------------------------------------------------------------------------
/bitfield/tests/11-bits-attribute-wrong.stderr:
--------------------------------------------------------------------------------
1 | error[E0308]: mismatched types
2 | --> tests/11-bits-attribute-wrong.rs:11:14
3 | |
4 | 11 | #[bits = 9]
5 | | ^
6 | | |
7 | | expected an array with a size of 9, found one with a size of 1
8 | | help: consider specifying the actual array length: `1`
9 |
--------------------------------------------------------------------------------
/bitfield/tests/12-accessors-edge.rs:
--------------------------------------------------------------------------------
1 | // This test is equivalent to 03-accessors but with some fields spanning across
2 | // byte boundaries. This may or may not already work depending on how your
3 | // implementation has been done so far.
4 | //
5 | //
6 | // ║ first byte ║ second byte ║ third byte ║ fourth byte ║
7 | // ╟───────────────╫───────────────╫───────────────╫───────────────╢
8 | // ║▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒ ▒║
9 | // ╟─────────────────╫───────────╫─────────────────────────╫───────╢
10 | // ║ a ║ b ║ c ║ d ║
11 |
12 | use bitfield::*;
13 |
14 | #[bitfield]
15 | pub struct EdgeCaseBytes {
16 | a: B9,
17 | b: B6,
18 | c: B13,
19 | d: B4,
20 | }
21 |
22 | fn main() {
23 | let mut bitfield = EdgeCaseBytes::new();
24 | assert_eq!(0, bitfield.get_a());
25 | assert_eq!(0, bitfield.get_b());
26 | assert_eq!(0, bitfield.get_c());
27 | assert_eq!(0, bitfield.get_d());
28 |
29 | let a = 0b1100_0011_1;
30 | let b = 0b101_010;
31 | let c = 0x1675;
32 | let d = 0b1110;
33 |
34 | bitfield.set_a(a);
35 | bitfield.set_b(b);
36 | bitfield.set_c(c);
37 | bitfield.set_d(d);
38 |
39 | assert_eq!(a, bitfield.get_a());
40 | assert_eq!(b, bitfield.get_b());
41 | assert_eq!(c, bitfield.get_c());
42 | assert_eq!(d, bitfield.get_d());
43 | }
44 |
--------------------------------------------------------------------------------
/bitfield/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 | //t.pass("tests/01-specifier-types.rs");
5 | //t.pass("tests/02-storage.rs");
6 | //t.pass("tests/03-accessors.rs");
7 | //t.compile_fail("tests/04-multiple-of-8bits.rs");
8 | //t.pass("tests/05-accessor-signatures.rs");
9 | //t.pass("tests/06-enums.rs");
10 | //t.pass("tests/07-optional-discriminant.rs");
11 | //t.compile_fail("tests/08-non-power-of-two.rs");
12 | //t.compile_fail("tests/09-variant-out-of-range.rs");
13 | //t.pass("tests/10-bits-attribute.rs");
14 | //t.compile_fail("tests/11-bits-attribute-wrong.rs");
15 | //t.pass("tests/12-accessors-edge.rs");
16 | }
17 |
--------------------------------------------------------------------------------
/builder/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "derive_builder"
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [lib]
9 | proc-macro = true
10 |
11 | [[test]]
12 | name = "tests"
13 | path = "tests/progress.rs"
14 |
15 | [dev-dependencies]
16 | trybuild = { version = "1.0.49", features = ["diff"] }
17 |
18 | [dependencies]
19 | # TODO
20 |
--------------------------------------------------------------------------------
/builder/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
3 | #[proc_macro_derive(Builder)]
4 | pub fn derive(input: TokenStream) -> TokenStream {
5 | let _ = input;
6 |
7 | unimplemented!()
8 | }
9 |
--------------------------------------------------------------------------------
/builder/tests/01-parse.rs:
--------------------------------------------------------------------------------
1 | // This test looks for a derive macro with the right name to exist. For now the
2 | // test doesn't require any specific code to be generated by the macro, so
3 | // returning an empty TokenStream should be sufficient.
4 | //
5 | // Before moving on, have your derive macro parse the macro input as a
6 | // syn::DeriveInput syntax tree.
7 | //
8 | // Spend some time exploring the syn::DeriveInput struct on docs.rs by clicking
9 | // through its fields in the documentation to see whether it matches your
10 | // expectations for what information is available for the macro to work with.
11 | //
12 | //
13 | // Resources:
14 | //
15 | // - The Syn crate for parsing procedural macro input:
16 | // https://github.com/dtolnay/syn
17 | //
18 | // - The DeriveInput syntax tree which represents input of a derive macro:
19 | // https://docs.rs/syn/2.0/syn/struct.DeriveInput.html
20 | //
21 | // - An example of a derive macro implemented using Syn:
22 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize
23 |
24 | use derive_builder::Builder;
25 |
26 | #[derive(Builder)]
27 | pub struct Command {
28 | executable: String,
29 | args: Vec,
30 | env: Vec,
31 | current_dir: String,
32 | }
33 |
34 | fn main() {}
35 |
--------------------------------------------------------------------------------
/builder/tests/02-create-builder.rs:
--------------------------------------------------------------------------------
1 | // Have the macro produce a struct for the builder state, and a `builder`
2 | // function that creates an empty instance of the builder.
3 | //
4 | // As a quick start, try generating the following code (but make sure the type
5 | // name matches what is in the caller's input).
6 | //
7 | // impl Command {
8 | // pub fn builder() {}
9 | // }
10 | //
11 | // At this point the test should pass because it isn't doing anything with the
12 | // builder yet, so `()` as the builder type is as good as any other.
13 | //
14 | // Before moving on, have the macro also generate:
15 | //
16 | // pub struct CommandBuilder {
17 | // executable: Option,
18 | // args: Option>,
19 | // env: Option>,
20 | // current_dir: Option,
21 | // }
22 | //
23 | // and in the `builder` function:
24 | //
25 | // impl Command {
26 | // pub fn builder() -> CommandBuilder {
27 | // CommandBuilder {
28 | // executable: None,
29 | // args: None,
30 | // env: None,
31 | // current_dir: None,
32 | // }
33 | // }
34 | // }
35 | //
36 | //
37 | // Resources:
38 | //
39 | // - The Quote crate for putting together output from a macro:
40 | // https://github.com/dtolnay/quote
41 | //
42 | // - Joining together the type name + "Builder" to make the builder's name:
43 | // https://docs.rs/syn/2.0/syn/struct.Ident.html
44 |
45 | use derive_builder::Builder;
46 |
47 | #[derive(Builder)]
48 | pub struct Command {
49 | executable: String,
50 | args: Vec,
51 | env: Vec,
52 | current_dir: String,
53 | }
54 |
55 | fn main() {
56 | let builder = Command::builder();
57 |
58 | let _ = builder;
59 | }
60 |
--------------------------------------------------------------------------------
/builder/tests/03-call-setters.rs:
--------------------------------------------------------------------------------
1 | // Generate methods on the builder for setting a value of each of the struct
2 | // fields.
3 | //
4 | // impl CommandBuilder {
5 | // fn executable(&mut self, executable: String) -> &mut Self {
6 | // self.executable = Some(executable);
7 | // self
8 | // }
9 | //
10 | // ...
11 | // }
12 |
13 | use derive_builder::Builder;
14 |
15 | #[derive(Builder)]
16 | pub struct Command {
17 | executable: String,
18 | args: Vec,
19 | env: Vec,
20 | current_dir: String,
21 | }
22 |
23 | fn main() {
24 | let mut builder = Command::builder();
25 | builder.executable("cargo".to_owned());
26 | builder.args(vec!["build".to_owned(), "--release".to_owned()]);
27 | builder.env(vec![]);
28 | builder.current_dir("..".to_owned());
29 | }
30 |
--------------------------------------------------------------------------------
/builder/tests/04-call-build.rs:
--------------------------------------------------------------------------------
1 | // Generate a `build` method to go from builder to original struct.
2 | //
3 | // This method should require that every one of the fields has been explicitly
4 | // set; it should return an error if a field is missing. The precise error type
5 | // is not important. Consider using Box, which you can construct
6 | // using the impl From for Box.
7 | //
8 | // impl CommandBuilder {
9 | // pub fn build(&mut self) -> Result> {
10 | // ...
11 | // }
12 | // }
13 |
14 | use derive_builder::Builder;
15 |
16 | #[derive(Builder)]
17 | pub struct Command {
18 | executable: String,
19 | args: Vec,
20 | env: Vec,
21 | current_dir: String,
22 | }
23 |
24 | fn main() {
25 | let mut builder = Command::builder();
26 | builder.executable("cargo".to_owned());
27 | builder.args(vec!["build".to_owned(), "--release".to_owned()]);
28 | builder.env(vec![]);
29 | builder.current_dir("..".to_owned());
30 |
31 | let command = builder.build().unwrap();
32 | assert_eq!(command.executable, "cargo");
33 | }
34 |
--------------------------------------------------------------------------------
/builder/tests/05-method-chaining.rs:
--------------------------------------------------------------------------------
1 | // This test case should be a freebie if the previous ones are already working.
2 | // It shows that we can chain method calls on the builder.
3 |
4 | use derive_builder::Builder;
5 |
6 | #[derive(Builder)]
7 | pub struct Command {
8 | executable: String,
9 | args: Vec,
10 | env: Vec,
11 | current_dir: String,
12 | }
13 |
14 | fn main() {
15 | let command = Command::builder()
16 | .executable("cargo".to_owned())
17 | .args(vec!["build".to_owned(), "--release".to_owned()])
18 | .env(vec![])
19 | .current_dir("..".to_owned())
20 | .build()
21 | .unwrap();
22 |
23 | assert_eq!(command.executable, "cargo");
24 | }
25 |
--------------------------------------------------------------------------------
/builder/tests/06-optional-field.rs:
--------------------------------------------------------------------------------
1 | // Some fields may not always need to be specified. Typically these would be
2 | // represented as Option in the struct being built.
3 | //
4 | // Have your macro identify fields in the macro input whose type is Option and
5 | // make the corresponding builder method optional for the caller. In the test
6 | // case below, current_dir is optional and not passed to one of the builders in
7 | // main.
8 | //
9 | // Be aware that the Rust compiler performs name resolution only after macro
10 | // expansion has finished completely. That means during the evaluation of a
11 | // procedural macro, "types" do not exist yet, only tokens. In general many
12 | // different token representations may end up referring to the same type: for
13 | // example `Option` and `std::option::Option` and `> as
14 | // IntoIterator>::Item` are all different names for the same type. Conversely,
15 | // a single token representation may end up referring to many different types in
16 | // different places; for example the meaning of `Error` will depend on whether
17 | // the surrounding scope has imported std::error::Error or std::io::Error. As a
18 | // consequence, it isn't possible in general for a macro to compare two token
19 | // representations and tell whether they refer to the same type.
20 | //
21 | // In the context of the current test case, all of this means that there isn't
22 | // some compiler representation of Option that our macro can compare fields
23 | // against to find out whether they refer to the eventual Option type after name
24 | // resolution. Instead all we get to look at are the tokens of how the user has
25 | // described the type in their code. By necessity, the macro will look for
26 | // fields whose type is written literally as Option<...> and will not realize
27 | // when the same type has been written in some different way.
28 | //
29 | // The syntax tree for types parsed from tokens is somewhat complicated because
30 | // there is such a large variety of type syntax in Rust, so here is the nested
31 | // data structure representation that your macro will want to identify:
32 | //
33 | // Type::Path(
34 | // TypePath {
35 | // qself: None,
36 | // path: Path {
37 | // segments: [
38 | // PathSegment {
39 | // ident: "Option",
40 | // arguments: PathArguments::AngleBracketed(
41 | // AngleBracketedGenericArguments {
42 | // args: [
43 | // GenericArgument::Type(
44 | // ...
45 | // ),
46 | // ],
47 | // },
48 | // ),
49 | // },
50 | // ],
51 | // },
52 | // },
53 | // )
54 |
55 | use derive_builder::Builder;
56 |
57 | #[derive(Builder)]
58 | pub struct Command {
59 | executable: String,
60 | args: Vec,
61 | env: Vec,
62 | current_dir: Option,
63 | }
64 |
65 | fn main() {
66 | let command = Command::builder()
67 | .executable("cargo".to_owned())
68 | .args(vec!["build".to_owned(), "--release".to_owned()])
69 | .env(vec![])
70 | .build()
71 | .unwrap();
72 | assert!(command.current_dir.is_none());
73 |
74 | let command = Command::builder()
75 | .executable("cargo".to_owned())
76 | .args(vec!["build".to_owned(), "--release".to_owned()])
77 | .env(vec![])
78 | .current_dir("..".to_owned())
79 | .build()
80 | .unwrap();
81 | assert!(command.current_dir.is_some());
82 | }
83 |
--------------------------------------------------------------------------------
/builder/tests/07-repeated-field.rs:
--------------------------------------------------------------------------------
1 | // The std::process::Command builder handles args in a way that is potentially
2 | // more convenient than passing a full vector of args to the builder all at
3 | // once.
4 | //
5 | // Look for a field attribute #[builder(each = "...")] on each field. The
6 | // generated code may assume that fields with this attribute have the type Vec
7 | // and should use the word given in the string literal as the name for the
8 | // corresponding builder method which accepts one vector element at a time.
9 | //
10 | // In order for the compiler to know that these builder attributes are
11 | // associated with your macro, they must be declared at the entry point of the
12 | // derive macro. Otherwise the compiler will report them as unrecognized
13 | // attributes and refuse to compile the caller's code.
14 | //
15 | // #[proc_macro_derive(Builder, attributes(builder))]
16 | //
17 | // These are called inert attributes. The word "inert" indicates that these
18 | // attributes do not correspond to a macro invocation on their own; they are
19 | // simply looked at by other macro invocations.
20 | //
21 | // If the new one-at-a-time builder method is given the same name as the field,
22 | // avoid generating an all-at-once builder method for that field because the
23 | // names would conflict.
24 | //
25 | //
26 | // Resources:
27 | //
28 | // - Relevant syntax tree type:
29 | // https://docs.rs/syn/2.0/syn/struct.Attribute.html
30 |
31 | use derive_builder::Builder;
32 |
33 | #[derive(Builder)]
34 | pub struct Command {
35 | executable: String,
36 | #[builder(each = "arg")]
37 | args: Vec,
38 | #[builder(each = "env")]
39 | env: Vec,
40 | current_dir: Option,
41 | }
42 |
43 | fn main() {
44 | let command = Command::builder()
45 | .executable("cargo".to_owned())
46 | .arg("build".to_owned())
47 | .arg("--release".to_owned())
48 | .build()
49 | .unwrap();
50 |
51 | assert_eq!(command.executable, "cargo");
52 | assert_eq!(command.args, vec!["build", "--release"]);
53 | }
54 |
--------------------------------------------------------------------------------
/builder/tests/08-unrecognized-attribute.rs:
--------------------------------------------------------------------------------
1 | // Ensure that your macro reports a reasonable error message when the caller
2 | // mistypes the inert attribute in various ways. This is a compile_fail test.
3 | //
4 | // The preferred way to report an error from a procedural macro is by including
5 | // an invocation of the standard library's compile_error macro in the code
6 | // emitted by the procedural macro.
7 | //
8 | //
9 | // Resources:
10 | //
11 | // - The compile_error macro for emitting basic custom errors:
12 | // https://doc.rust-lang.org/std/macro.compile_error.html
13 | //
14 | // - Lowering a syn::Error into an invocation of compile_error:
15 | // https://docs.rs/syn/2.0/syn/struct.Error.html#method.to_compile_error
16 |
17 | use derive_builder::Builder;
18 |
19 | #[derive(Builder)]
20 | pub struct Command {
21 | executable: String,
22 | #[builder(eac = "arg")]
23 | args: Vec,
24 | env: Vec,
25 | current_dir: Option,
26 | }
27 |
28 | fn main() {}
29 |
--------------------------------------------------------------------------------
/builder/tests/08-unrecognized-attribute.stderr:
--------------------------------------------------------------------------------
1 | error: expected `builder(each = "...")`
2 | --> tests/08-unrecognized-attribute.rs:22:7
3 | |
4 | 22 | #[builder(eac = "arg")]
5 | | ^^^^^^^^^^^^^^^^^^^^
6 |
--------------------------------------------------------------------------------
/builder/tests/09-redefined-prelude-types.rs:
--------------------------------------------------------------------------------
1 | // Does your macro still work if some of the standard library prelude item names
2 | // mean something different in the caller's code?
3 | //
4 | // It may seem unreasonable to consider this case, but it does arise in
5 | // practice. Most commonly for Result, where crates sometimes use a Result type
6 | // alias with a single type parameter which assumes their crate's error type.
7 | // Such a type alias would break macro-generated code that expects Result to
8 | // have two type parameters. As another example, Hyper 0.10 used to define
9 | // hyper::Ok as a re-export of hyper::status::StatusCode::Ok which is totally
10 | // different from Result::Ok. This caused problems in code doing `use hyper::*`
11 | // together with macro-generated code referring to Ok.
12 | //
13 | // Generally all macros (procedural as well as macro_rules) designed to be used
14 | // by other people should refer to every single thing in their expanded code
15 | // through an absolute path, such as std::result::Result.
16 |
17 | use derive_builder::Builder;
18 |
19 | type Option = ();
20 | type Some = ();
21 | type None = ();
22 | type Result = ();
23 | type Box = ();
24 |
25 | #[derive(Builder)]
26 | pub struct Command {
27 | executable: String,
28 | }
29 |
30 | fn main() {}
31 |
--------------------------------------------------------------------------------
/builder/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 | //t.pass("tests/01-parse.rs");
5 | //t.pass("tests/02-create-builder.rs");
6 | //t.pass("tests/03-call-setters.rs");
7 | //t.pass("tests/04-call-build.rs");
8 | //t.pass("tests/05-method-chaining.rs");
9 | //t.pass("tests/06-optional-field.rs");
10 | //t.pass("tests/07-repeated-field.rs");
11 | //t.compile_fail("tests/08-unrecognized-attribute.rs");
12 | //t.pass("tests/09-redefined-prelude-types.rs");
13 | }
14 |
--------------------------------------------------------------------------------
/debug/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "derive_debug"
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [lib]
9 | proc-macro = true
10 |
11 | [[test]]
12 | name = "tests"
13 | path = "tests/progress.rs"
14 |
15 | [dev-dependencies]
16 | trybuild = { version = "1.0.49", features = ["diff"] }
17 |
18 | [dependencies]
19 | # TODO
20 |
--------------------------------------------------------------------------------
/debug/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
3 | #[proc_macro_derive(CustomDebug)]
4 | pub fn derive(input: TokenStream) -> TokenStream {
5 | let _ = input;
6 |
7 | unimplemented!()
8 | }
9 |
--------------------------------------------------------------------------------
/debug/tests/01-parse.rs:
--------------------------------------------------------------------------------
1 | // This test looks for a derive macro with the right name to exist. For now the
2 | // test doesn't require any specific code to be generated by the macro, so
3 | // returning an empty TokenStream should be sufficient.
4 | //
5 | // Before moving on, have your derive macro parse the macro input as a
6 | // syn::DeriveInput syntax tree.
7 | //
8 | //
9 | // Resources:
10 | //
11 | // - The DeriveInput syntax tree which represents input of a derive macro:
12 | // https://docs.rs/syn/2.0/syn/struct.DeriveInput.html
13 | //
14 | // - An example of a derive macro implemented using Syn:
15 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize
16 |
17 | use derive_debug::CustomDebug;
18 |
19 | #[derive(CustomDebug)]
20 | pub struct Field {
21 | name: &'static str,
22 | bitmask: u16,
23 | }
24 |
25 | fn main() {}
26 |
--------------------------------------------------------------------------------
/debug/tests/02-impl-debug.rs:
--------------------------------------------------------------------------------
1 | // Emit an implementation of std::fmt::Debug for a basic struct with named
2 | // fields and no generic type parameters.
3 | //
4 | // Note that there is no enforced relationship between the name of a derive
5 | // macro and the trait that it implements. Here the macro is named CustomDebug
6 | // but the trait impls it generates are for Debug. As a convention, typically
7 | // derive macros implement a trait with the same name as a macro.
8 | //
9 | //
10 | // Resources:
11 | //
12 | // - The Debug trait:
13 | // https://doc.rust-lang.org/std/fmt/trait.Debug.html
14 | //
15 | // - The DebugStruct helper for formatting structs correctly:
16 | // https://doc.rust-lang.org/std/fmt/struct.DebugStruct.html
17 |
18 | use derive_debug::CustomDebug;
19 |
20 | #[derive(CustomDebug)]
21 | pub struct Field {
22 | name: &'static str,
23 | bitmask: u8,
24 | }
25 |
26 | fn main() {
27 | let f = Field {
28 | name: "F",
29 | bitmask: 0b00011100,
30 | };
31 |
32 | let debug = format!("{:?}", f);
33 |
34 | assert!(debug.starts_with(r#"Field { name: "F","#));
35 | }
36 |
--------------------------------------------------------------------------------
/debug/tests/03-custom-format.rs:
--------------------------------------------------------------------------------
1 | // Look for a field attribute #[debug = "..."] on each field. If present, find a
2 | // way to format the field according to the format string given by the caller in
3 | // the attribute.
4 | //
5 | // In order for the compiler to recognize this inert attribute as associated
6 | // with your derive macro, it will need to be declared at the entry point of the
7 | // derive macro.
8 | //
9 | // #[proc_macro_derive(CustomDebug, attributes(debug))]
10 | //
11 | // These are called inert attributes. The word "inert" indicates that these
12 | // attributes do not correspond to a macro invocation on their own; they are
13 | // simply looked at by other macro invocations.
14 | //
15 | //
16 | // Resources:
17 | //
18 | // - Relevant syntax tree type:
19 | // https://docs.rs/syn/2.0/syn/struct.Attribute.html
20 | //
21 | // - Macro for applying a format string to some runtime value:
22 | // https://doc.rust-lang.org/std/macro.format_args.html
23 |
24 | use derive_debug::CustomDebug;
25 |
26 | #[derive(CustomDebug)]
27 | pub struct Field {
28 | name: &'static str,
29 | #[debug = "0b{:08b}"]
30 | bitmask: u8,
31 | }
32 |
33 | fn main() {
34 | let f = Field {
35 | name: "F",
36 | bitmask: 0b00011100,
37 | };
38 |
39 | let debug = format!("{:?}", f);
40 | let expected = r#"Field { name: "F", bitmask: 0b00011100 }"#;
41 |
42 | assert_eq!(debug, expected);
43 | }
44 |
--------------------------------------------------------------------------------
/debug/tests/04-type-parameter.rs:
--------------------------------------------------------------------------------
1 | // Figure out what impl needs to be generated for the Debug impl of Field.
2 | // This will involve adding a trait bound to the T type parameter of the
3 | // generated impl.
4 | //
5 | // Callers should be free to instantiate Field with a type parameter T which
6 | // does not implement Debug, but such a Field will not fulfill the trait
7 | // bounds of the generated Debug impl and so will not be printable via Debug.
8 | //
9 | //
10 | // Resources:
11 | //
12 | // - Representation of generics in the Syn syntax tree:
13 | // https://docs.rs/syn/2.0/syn/struct.Generics.html
14 | //
15 | // - A helper for placing generics into an impl signature:
16 | // https://docs.rs/syn/2.0/syn/struct.Generics.html#method.split_for_impl
17 | //
18 | // - Example code from Syn which deals with type parameters:
19 | // https://github.com/dtolnay/syn/tree/master/examples/heapsize
20 |
21 | use derive_debug::CustomDebug;
22 |
23 | #[derive(CustomDebug)]
24 | pub struct Field {
25 | value: T,
26 | #[debug = "0b{:08b}"]
27 | bitmask: u8,
28 | }
29 |
30 | fn main() {
31 | let f = Field {
32 | value: "F",
33 | bitmask: 0b00011100,
34 | };
35 |
36 | let debug = format!("{:?}", f);
37 | let expected = r#"Field { value: "F", bitmask: 0b00011100 }"#;
38 |
39 | assert_eq!(debug, expected);
40 | }
41 |
--------------------------------------------------------------------------------
/debug/tests/05-phantom-data.rs:
--------------------------------------------------------------------------------
1 | // Some generic types implement Debug even when their type parameters do not.
2 | // One example is PhantomData which has this impl:
3 | //
4 | // impl Debug for PhantomData {...}
5 | //
6 | // To accommodate this sort of situation, one way would be to generate a trait
7 | // bound `#field_ty: Debug` for each field type in the input, rather than
8 | // `#param: Debug` for each generic parameter. For example in the case of the
9 | // struct Field in the test case below, it would be:
10 | //
11 | // impl Debug for Field
12 | // where
13 | // PhantomData: Debug,
14 | // {...}
15 | //
16 | // This approach has fatal downsides that will be covered in subsequent test
17 | // cases.
18 | //
19 | // Instead we'll recognize PhantomData as a special case since it is so common,
20 | // and later provide an escape hatch for the caller to override inferred bounds
21 | // in other application-specific special cases.
22 | //
23 | // Concretely, for each type parameter #param in the input, you will need to
24 | // determine whether it is only ever mentioned inside of a PhantomData and if so
25 | // then avoid emitting a `#param: Debug` bound on that parameter. For the
26 | // purpose of the test suite it is sufficient to look for exactly the field type
27 | // PhantomData<#param>. In reality we may also care about recognizing other
28 | // possible arrangements like PhantomData<&'a #param> if the semantics of the
29 | // trait we are deriving would make it likely that callers would end up with
30 | // that sort of thing in their code.
31 | //
32 | // Notice that we are into the realm of heuristics at this point. In Rust's
33 | // macro system it is not possible for a derive macro to infer the "correct"
34 | // bounds in general. Doing so would require name-resolution, i.e. the ability
35 | // for the macro to look up what trait impl corresponds to some field's type by
36 | // name. The Rust compiler has chosen to perform all macro expansion fully
37 | // before name resolution (not counting name resolution of macros themselves,
38 | // which operates in a more restricted way than Rust name resolution in general
39 | // to make this possible).
40 | //
41 | // The clean separation between macro expansion and name resolution has huge
42 | // advantages that outweigh the limitation of not being able to expose type
43 | // information to procedural macros, so there are no plans to change it. Instead
44 | // macros rely on domain-specific heuristics and escape hatches to substitute
45 | // for type information where unavoidable or, more commonly, rely on the Rust
46 | // trait system to defer the need for name resolution. In particular pay
47 | // attention to how the derive macro invocation below is able to expand to code
48 | // that correctly calls String's Debug impl despite having no way to know that
49 | // the word "S" in its input refers to the type String.
50 |
51 | use derive_debug::CustomDebug;
52 | use std::fmt::Debug;
53 | use std::marker::PhantomData;
54 |
55 | type S = String;
56 |
57 | #[derive(CustomDebug)]
58 | pub struct Field {
59 | marker: PhantomData,
60 | string: S,
61 | #[debug = "0b{:08b}"]
62 | bitmask: u8,
63 | }
64 |
65 | fn assert_debug() {}
66 |
67 | fn main() {
68 | // Does not implement Debug.
69 | struct NotDebug;
70 |
71 | assert_debug::>();
72 | assert_debug::>();
73 | }
74 |
--------------------------------------------------------------------------------
/debug/tests/06-bound-trouble.rs:
--------------------------------------------------------------------------------
1 | // This test case should not require any code change in your macro if you have
2 | // everything up to this point already passing, but is here to demonstrate why
3 | // inferring `#field_ty: Trait` bounds as mentioned in the previous test case is
4 | // not viable.
5 | //
6 | // #[derive(CustomDebug)]
7 | // pub struct One {
8 | // value: T,
9 | // two: Option>>,
10 | // }
11 | //
12 | // #[derive(CustomDebug)]
13 | // struct Two {
14 | // one: Box>,
15 | // }
16 | //
17 | // The problematic expansion would come out as:
18 | //
19 | // impl Debug for One
20 | // where
21 | // T: Debug,
22 | // Option>>: Debug,
23 | // {...}
24 | //
25 | // impl Debug for Two
26 | // where
27 | // Box>: Debug,
28 | // {...}
29 | //
30 | // There are two things wrong here.
31 | //
32 | // First, taking into account the relevant standard library impls `impl Debug
33 | // for Option where T: Debug` and `impl Debug for Box where T: ?Sized +
34 | // Debug`, we have the following cyclic definition:
35 | //
36 | // - One implements Debug if there is an impl for Option>>;
37 | // - Option>> implements Debug if there is an impl for Box>;
38 | // - Box> implements Debug if there is an impl for Two;
39 | // - Two implements Debug if there is an impl for Box>;
40 | // - Box> implements Debug if there is an impl for One; cycle!
41 | //
42 | // The Rust compiler detects and rejects this cycle by refusing to assume that
43 | // an impl for any of these types exists out of nowhere. The error manifests as:
44 | //
45 | // error[E0275]: overflow evaluating the requirement `One: std::fmt::Debug`
46 | // -->
47 | // | assert_debug::>();
48 | // | ^^^^^^^^^^^^^^^^^^^^^^^
49 | //
50 | // There is a technique known as co-inductive reasoning that may allow a
51 | // revamped trait solver in the compiler to process cycles like this in the
52 | // future, though there is still uncertainty about whether co-inductive
53 | // semantics would lead to unsoundness in some situations when applied to Rust
54 | // trait impls. There is no current activity pursuing this but some discussion
55 | // exists in a GitHub issue called "#[derive] sometimes uses incorrect bounds":
56 | // https://github.com/rust-lang/rust/issues/26925
57 | //
58 | // The second thing wrong is a private-in-public violation:
59 | //
60 | // error[E0446]: private type `Two` in public interface
61 | // -->
62 | // | struct Two {
63 | // | - `Two` declared as private
64 | // ...
65 | // | / impl Debug for One
66 | // | | where
67 | // | | T: Debug,
68 | // | | Option>>: Debug,
69 | // ... |
70 | // | | }
71 | // | |_^ can't leak private type
72 | //
73 | // Public APIs in Rust are not allowed to be defined in terms of private types.
74 | // That includes the argument types and return types of public function
75 | // signatures, as well as trait bounds on impls of public traits for public
76 | // types.
77 |
78 | use derive_debug::CustomDebug;
79 | use std::fmt::Debug;
80 |
81 | #[derive(CustomDebug)]
82 | pub struct One {
83 | value: T,
84 | two: Option>>,
85 | }
86 |
87 | #[derive(CustomDebug)]
88 | struct Two {
89 | one: Box>,
90 | }
91 |
92 | fn assert_debug() {}
93 |
94 | fn main() {
95 | assert_debug::>();
96 | assert_debug::>();
97 | }
98 |
--------------------------------------------------------------------------------
/debug/tests/07-associated-type.rs:
--------------------------------------------------------------------------------
1 | // This test case covers one more heuristic that is often worth incorporating
2 | // into derive macros that infer trait bounds. Here we look for the use of an
3 | // associated type of a type parameter.
4 | //
5 | // The generated impl will need to look like:
6 | //
7 | // impl Debug for Field
8 | // where
9 | // T::Value: Debug,
10 | // {...}
11 | //
12 | // You can identify associated types as any syn::TypePath in which the first
13 | // path segment is one of the type parameters and there is more than one
14 | // segment.
15 | //
16 | //
17 | // Resources:
18 | //
19 | // - The relevant types in the input will be represented in this syntax tree
20 | // node: https://docs.rs/syn/2.0/syn/struct.TypePath.html
21 |
22 | use derive_debug::CustomDebug;
23 | use std::fmt::Debug;
24 |
25 | pub trait Trait {
26 | type Value;
27 | }
28 |
29 | #[derive(CustomDebug)]
30 | pub struct Field {
31 | values: Vec,
32 | }
33 |
34 | fn assert_debug() {}
35 |
36 | fn main() {
37 | // Does not implement Debug, but its associated type does.
38 | struct Id;
39 |
40 | impl Trait for Id {
41 | type Value = u8;
42 | }
43 |
44 | assert_debug::>();
45 | }
46 |
--------------------------------------------------------------------------------
/debug/tests/08-escape-hatch.rs:
--------------------------------------------------------------------------------
1 | // There are some cases where no heuristic would be sufficient to infer the
2 | // right trait bounds based only on the information available during macro
3 | // expansion.
4 | //
5 | // When this happens, we'll turn to attributes as a way for the caller to
6 | // handwrite the correct trait bounds themselves.
7 | //
8 | // The impl for Wrapper in the code below will need to include the bounds
9 | // provided in the `debug(bound = "...")` attribute. When such an attribute is
10 | // present, also disable all inference of bounds so that the macro does not
11 | // attach its own `T: Debug` inferred bound.
12 | //
13 | // impl Debug for Wrapper
14 | // where
15 | // T::Value: Debug,
16 | // {...}
17 | //
18 | // Optionally, though this is not covered by the test suite, also accept
19 | // `debug(bound = "...")` attributes on individual fields. This should
20 | // substitute only whatever bounds are inferred based on that field's type,
21 | // without removing bounds inferred based on the other fields:
22 | //
23 | // #[derive(CustomDebug)]
24 | // pub struct Wrapper {
25 | // #[debug(bound = "T::Value: Debug")]
26 | // field: Field,
27 | // normal: U,
28 | // }
29 |
30 | use derive_debug::CustomDebug;
31 | use std::fmt::Debug;
32 |
33 | pub trait Trait {
34 | type Value;
35 | }
36 |
37 | #[derive(CustomDebug)]
38 | #[debug(bound = "T::Value: Debug")]
39 | pub struct Wrapper {
40 | field: Field,
41 | }
42 |
43 | #[derive(CustomDebug)]
44 | struct Field {
45 | values: Vec,
46 | }
47 |
48 | fn assert_debug() {}
49 |
50 | fn main() {
51 | struct Id;
52 |
53 | impl Trait for Id {
54 | type Value = u8;
55 | }
56 |
57 | assert_debug::>();
58 | }
59 |
--------------------------------------------------------------------------------
/debug/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 | //t.pass("tests/01-parse.rs");
5 | //t.pass("tests/02-impl-debug.rs");
6 | //t.pass("tests/03-custom-format.rs");
7 | //t.pass("tests/04-type-parameter.rs");
8 | //t.pass("tests/05-phantom-data.rs");
9 | //t.pass("tests/06-bound-trouble.rs");
10 | //t.pass("tests/07-associated-type.rs");
11 | //t.pass("tests/08-escape-hatch.rs");
12 | }
13 |
--------------------------------------------------------------------------------
/main.rs:
--------------------------------------------------------------------------------
1 | // Write code here.
2 | //
3 | // To see what the code looks like after macro expansion:
4 | // $ cargo expand
5 | //
6 | // To run the code:
7 | // $ cargo run
8 |
9 | fn main() {}
10 |
--------------------------------------------------------------------------------
/seq/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "seq"
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [lib]
9 | proc-macro = true
10 |
11 | [[test]]
12 | name = "tests"
13 | path = "tests/progress.rs"
14 |
15 | [dev-dependencies]
16 | trybuild = { version = "1.0.49", features = ["diff"] }
17 |
18 | [dependencies]
19 | # TODO
20 |
--------------------------------------------------------------------------------
/seq/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
3 | #[proc_macro]
4 | pub fn seq(input: TokenStream) -> TokenStream {
5 | let _ = input;
6 |
7 | unimplemented!()
8 | }
9 |
--------------------------------------------------------------------------------
/seq/tests/01-parse-header.rs:
--------------------------------------------------------------------------------
1 | // This test looks for a function-like macro with the right name to exist. For
2 | // now the test doesn't require any specific code to be generated by the macro,
3 | // so returning an empty TokenStream should be sufficient.
4 | //
5 | // Before moving on to the next test, you'll want some code in your
6 | // implementation to handle parsing the first few tokens of input. The macro
7 | // should expect the input to contain a syn::Ident, Token![in], syn::LitInt,
8 | // Token![..], syn::LitInt.
9 | //
10 | // It is also possible to implement this project without using Syn if you'd
11 | // like, though you will end up writing more code, more tedious code, and more
12 | // explicit error handling than when using Syn as a parsing library.
13 | //
14 | //
15 | // Resources:
16 | //
17 | // - Parsing in Syn:
18 | // https://docs.rs/syn/2.0/syn/parse/index.html
19 | //
20 | // - An example of a function-like procedural macro implemented using Syn:
21 | // https://github.com/dtolnay/syn/tree/master/examples/lazy-static
22 |
23 | use seq::seq;
24 |
25 | seq!(N in 0..8 {
26 | // nothing
27 | });
28 |
29 | fn main() {}
30 |
--------------------------------------------------------------------------------
/seq/tests/02-parse-body.rs:
--------------------------------------------------------------------------------
1 | // The macro invocation in the previous test case contained an empty loop body
2 | // inside the braces. In reality we want for the macro to accept arbitrary
3 | // tokens inside the braces.
4 | //
5 | // The caller should be free to write whatever they want inside the braces. The
6 | // seq macro won't care whether they write a statement, or a function, or a
7 | // struct, or whatever else. So we will work with the loop body as a TokenStream
8 | // rather than as a syntax tree.
9 | //
10 | // Before moving on, ensure that your implementation knows what has been written
11 | // inside the curly braces as a value of type TokenStream.
12 | //
13 | //
14 | // Resources:
15 | //
16 | // - Explanation of the purpose of proc-macro2:
17 | // https://docs.rs/proc-macro2/1.0/proc_macro2/
18 |
19 | use seq::seq;
20 |
21 | macro_rules! expand_to_nothing {
22 | ($arg:literal) => {
23 | // nothing
24 | };
25 | }
26 |
27 | seq!(N in 0..4 {
28 | expand_to_nothing!(N);
29 | });
30 |
31 | fn main() {}
32 |
--------------------------------------------------------------------------------
/seq/tests/03-expand-four-errors.rs:
--------------------------------------------------------------------------------
1 | // Now construct the generated code! Produce the output TokenStream by repeating
2 | // the loop body the correct number of times as specified by the loop bounds and
3 | // replacing the specified identifier with the loop counter.
4 | //
5 | // The invocation below will need to expand to a TokenStream containing:
6 | //
7 | // compile_error!(concat!("error number ", stringify!(0)));
8 | // compile_error!(concat!("error number ", stringify!(1)));
9 | // compile_error!(concat!("error number ", stringify!(2)));
10 | // compile_error!(concat!("error number ", stringify!(3)));
11 | //
12 | // This test is written as a compile_fail test because our macro isn't yet
13 | // powerful enough to do anything useful. For example if we made it generate
14 | // something like a function, every one of those functions would have the same
15 | // name and the program would not compile.
16 |
17 | use seq::seq;
18 |
19 | seq!(N in 0..4 {
20 | compile_error!(concat!("error number ", stringify!(N)));
21 | });
22 |
23 | fn main() {}
24 |
--------------------------------------------------------------------------------
/seq/tests/03-expand-four-errors.stderr:
--------------------------------------------------------------------------------
1 | error: error number 0
2 | --> tests/03-expand-four-errors.rs:20:5
3 | |
4 | 20 | compile_error!(concat!("error number ", stringify!(N)));
5 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6 |
7 | error: error number 1
8 | --> tests/03-expand-four-errors.rs:20:5
9 | |
10 | 20 | compile_error!(concat!("error number ", stringify!(N)));
11 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12 |
13 | error: error number 2
14 | --> tests/03-expand-four-errors.rs:20:5
15 | |
16 | 20 | compile_error!(concat!("error number ", stringify!(N)));
17 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
18 |
19 | error: error number 3
20 | --> tests/03-expand-four-errors.rs:20:5
21 | |
22 | 20 | compile_error!(concat!("error number ", stringify!(N)));
23 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
24 |
--------------------------------------------------------------------------------
/seq/tests/04-paste-ident.rs:
--------------------------------------------------------------------------------
1 | // One of the big things callers will want to do with the sequential indices N
2 | // is use them as part of an identifier, like f0 f1 f2 etc.
3 | //
4 | // Implement some logic to paste together any Ident followed by `~` followed by
5 | // our loop variable into a single concatenated identifier.
6 | //
7 | // The invocation below will expand to:
8 | //
9 | // fn f1() -> u64 { 1 * 2 }
10 | // fn f2() -> u64 { 2 * 2 }
11 | // fn f3() -> u64 { 3 * 2 }
12 | //
13 | // Optionally, also support more flexible arrangements like `f~N~_suffix` ->
14 | // f0_suffix f1_suffix etc, though the test suite only requires `prefix~N` so
15 | // you will need to add your own tests for this feature.
16 | //
17 | //
18 | // Resources:
19 | //
20 | // - Example of creating a new Ident from a string:
21 | // https://docs.rs/syn/2.0/syn/struct.Ident.html
22 |
23 | use seq::seq;
24 |
25 | seq!(N in 1..4 {
26 | fn f~N () -> u64 {
27 | N * 2
28 | }
29 | });
30 |
31 | // This f0 is written separately to detect whether your macro correctly starts
32 | // with the first iteration at N=1 as specified in the invocation. If the macro
33 | // incorrectly started at N=0 like in the previous tests cases, the first
34 | // generated function would conflict with this one and the program would not
35 | // compile.
36 | fn f0() -> u64 {
37 | 100
38 | }
39 |
40 | fn main() {
41 | let sum = f0() + f1() + f2() + f3();
42 |
43 | assert_eq!(sum, 100 + 2 + 4 + 6);
44 | }
45 |
--------------------------------------------------------------------------------
/seq/tests/05-repeat-section.rs:
--------------------------------------------------------------------------------
1 | // So far our macro has repeated the entire loop body. This is not sufficient
2 | // for some use cases because there are restrictions on the syntactic position
3 | // that macro invocations can appear in. For example the Rust grammar would not
4 | // allow a caller to write:
5 | //
6 | // enum Interrupt {
7 | // seq!(N in 0..16 {
8 | // Irq~N,
9 | // });
10 | // }
11 | //
12 | // because this is just not a legal place to put a macro call.
13 | //
14 | // Instead we will implement a way for the caller to designate a specific part
15 | // of the macro input to be repeated, so that anything outside that part does
16 | // not get repeated. The repeated part will be written surrounded by #(...)*.
17 | //
18 | // The invocation below should expand to:
19 | //
20 | // #[derive(Copy, Clone, PartialEq, Debug)]
21 | // enum Interrupt {
22 | // Irq0,
23 | // ...
24 | // Irq15,
25 | // }
26 | //
27 | // Optionally, allow for there to be multiple separate #(...)* sections,
28 | // although the test suite does not exercise this case. The #(...)* sections
29 | // will each need to be repeated according to the same loop bounds.
30 |
31 | use seq::seq;
32 |
33 | seq!(N in 0..16 {
34 | #[derive(Copy, Clone, PartialEq, Debug)]
35 | enum Interrupt {
36 | #(
37 | Irq~N,
38 | )*
39 | }
40 | });
41 |
42 | fn main() {
43 | let interrupt = Interrupt::Irq8;
44 |
45 | assert_eq!(interrupt as u8, 8);
46 | assert_eq!(interrupt, Interrupt::Irq8);
47 | }
48 |
--------------------------------------------------------------------------------
/seq/tests/06-init-array.rs:
--------------------------------------------------------------------------------
1 | // This test case should hopefully be a freebie if all of the previous ones are
2 | // passing. This test demonstrates using the seq macro to construct a const
3 | // array literal.
4 | //
5 | // The generated code would be:
6 | //
7 | // [Proc::new(0), Proc::new(1), ..., Proc::new(255),]
8 |
9 | use seq::seq;
10 |
11 | const PROCS: [Proc; 256] = {
12 | seq!(N in 0..256 {
13 | [
14 | #(
15 | Proc::new(N),
16 | )*
17 | ]
18 | })
19 | };
20 |
21 | struct Proc {
22 | id: usize,
23 | }
24 |
25 | impl Proc {
26 | const fn new(id: usize) -> Self {
27 | Proc { id }
28 | }
29 | }
30 |
31 | fn main() {
32 | assert_eq!(PROCS[32].id, 32);
33 | }
34 |
--------------------------------------------------------------------------------
/seq/tests/07-inclusive-range.rs:
--------------------------------------------------------------------------------
1 | // The previous examples all used an exclusive range, MIN..MAX. Now make it work
2 | // for an inclusive range MIN..=MAX that includes the upper range bound!
3 |
4 | use seq::seq;
5 |
6 | seq!(N in 16..=20 {
7 | enum E {
8 | #(
9 | Variant~N,
10 | )*
11 | }
12 | });
13 |
14 | fn main() {
15 | let e = E::Variant16;
16 |
17 | let desc = match e {
18 | E::Variant16 => "min",
19 | E::Variant17 | E::Variant18 | E::Variant19 => "in between",
20 | E::Variant20 => "max",
21 | };
22 |
23 | assert_eq!(desc, "min");
24 | }
25 |
--------------------------------------------------------------------------------
/seq/tests/08-ident-span.rs:
--------------------------------------------------------------------------------
1 | // The procedural macro API uses a type called Span to attach source location
2 | // and hygiene information to every token. In order for compiler errors to
3 | // appear underlining the right places, procedural macros are responsible for
4 | // propagating and manipulating these spans correctly.
5 | //
6 | // The invocation below expands to code that mentions a value Missing0 which
7 | // does not exist. When the compiler reports that it "cannot find value
8 | // Missing0", we would like for the error to point directly to where the user
9 | // wrote `Missing~N` in their macro input.
10 | //
11 | // error[E0425]: cannot find value `Missing0` in this scope
12 | // |
13 | // | let _ = Missing~N;
14 | // | ^^^^^^^ not found in this scope
15 | //
16 | // For this test to pass, ensure that the pasted-together identifier is created
17 | // using the Span of the identifier written by the caller.
18 | //
19 | // If you are using a nightly toolchain, there is a nightly-only method called
20 | // Span::join which would allow joining the three spans of `Missing`, `~`, `N`
21 | // so that the resulting error is as follows, but I would recommend not
22 | // bothering with this for the purpose of this project while it is unstable.
23 | //
24 | // error[E0425]: cannot find value `Missing0` in this scope
25 | // |
26 | // | let _ = Missing~N;
27 | // | ^^^^^^^^^ not found in this scope
28 | //
29 |
30 | use seq::seq;
31 |
32 | seq!(N in 0..1 {
33 | fn main() {
34 | let _ = Missing~N;
35 | }
36 | });
37 |
--------------------------------------------------------------------------------
/seq/tests/08-ident-span.stderr:
--------------------------------------------------------------------------------
1 | error[E0425]: cannot find value `Missing0` in this scope
2 | --> tests/08-ident-span.rs:34:17
3 | |
4 | 34 | let _ = Missing~N;
5 | | ^^^^^^^ not found in this scope
6 |
--------------------------------------------------------------------------------
/seq/tests/09-interaction-with-macrorules.rs:
--------------------------------------------------------------------------------
1 | // Suppose we wanted a seq invocation in which the upper bound is given by the
2 | // value of a const. Both macros and consts are compile-time things so this
3 | // seems like it should be easy.
4 | //
5 | // static PROCS: [Proc; NPROC] = seq!(N in 0..NPROC { ... });
6 | //
7 | // In fact it isn't, because macro expansion in Rust happens entirely before
8 | // name resolution. That is, a macro might know that something is called NPROC
9 | // but has no way to know which of many NPROC values in the dependency graph
10 | // this identifier refers to. Or maybe the NPROC constant doesn't exist yet when
11 | // the macro runs because it is only emitted by a later macro that hasn't run
12 | // yet. In this compilation model it isn't possible to support a query API where
13 | // a macro can give it the name of a constant and receive back the value of the
14 | // constant.
15 | //
16 | // All hope is not lost; it just means that our source of truth for the value of
17 | // NPROC must be a macro rather than a constant. The code in this test case
18 | // implements this workaround.
19 | //
20 | // This test case may or may not require code changes in your seq macro
21 | // implementation depending on how you have implemented it so far. Before
22 | // jumping into any code changes, make sure you understand what the code in this
23 | // test case is trying to do.
24 |
25 | use seq::seq;
26 |
27 | // Source of truth. Call a given macro passing nproc as argument.
28 | //
29 | // We want this number to appear in only one place so that updating this one
30 | // number will correctly affect anything that depends on the number of procs.
31 | macro_rules! pass_nproc {
32 | ($mac:ident) => {
33 | $mac! { 256 }
34 | };
35 | }
36 |
37 | macro_rules! literal_identity_macro {
38 | ($nproc:literal) => {
39 | $nproc
40 | };
41 | }
42 |
43 | // Expands to: `const NPROC: usize = 256;`
44 | const NPROC: usize = pass_nproc!(literal_identity_macro);
45 |
46 | struct Proc;
47 |
48 | impl Proc {
49 | const fn new() -> Self {
50 | Proc
51 | }
52 | }
53 |
54 | macro_rules! make_procs_array {
55 | ($nproc:literal) => {
56 | seq!(N in 0..$nproc { [#(Proc::new(),)*] })
57 | }
58 | }
59 |
60 | // Expands to: `static PROCS: [Proc; NPROC] = [Proc::new(), ..., Proc::new()];`
61 | static PROCS: [Proc; NPROC] = pass_nproc!(make_procs_array);
62 |
63 | fn main() {}
64 |
--------------------------------------------------------------------------------
/seq/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 | //t.pass("tests/01-parse-header.rs");
5 | //t.pass("tests/02-parse-body.rs");
6 | //t.compile_fail("tests/03-expand-four-errors.rs");
7 | //t.pass("tests/04-paste-ident.rs");
8 | //t.pass("tests/05-repeat-section.rs");
9 | //t.pass("tests/06-init-array.rs");
10 | //t.pass("tests/07-inclusive-range.rs");
11 | //t.compile_fail("tests/08-ident-span.rs");
12 | //t.pass("tests/09-interaction-with-macrorules.rs");
13 | }
14 |
--------------------------------------------------------------------------------
/sorted/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "sorted"
3 | version = "0.0.0"
4 | autotests = false
5 | edition = "2021"
6 | publish = false
7 |
8 | [lib]
9 | proc-macro = true
10 |
11 | [[test]]
12 | name = "tests"
13 | path = "tests/progress.rs"
14 |
15 | [dev-dependencies]
16 | trybuild = { version = "1.0.49", features = ["diff"] }
17 |
18 | [dependencies]
19 | # TODO
20 |
--------------------------------------------------------------------------------
/sorted/src/lib.rs:
--------------------------------------------------------------------------------
1 | use proc_macro::TokenStream;
2 |
3 | #[proc_macro_attribute]
4 | pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
5 | let _ = args;
6 | let _ = input;
7 |
8 | unimplemented!()
9 | }
10 |
--------------------------------------------------------------------------------
/sorted/tests/01-parse-enum.rs:
--------------------------------------------------------------------------------
1 | // This test checks that an attribute macro #[sorted] exists and is imported
2 | // correctly in the module system. If you make the macro return an empty token
3 | // stream or exactly the original token stream, this test will already pass!
4 | //
5 | // Be aware that the meaning of the return value of an attribute macro is
6 | // slightly different from that of a derive macro. Derive macros are only
7 | // allowed to *add* code to the caller's crate. Thus, what they return is
8 | // compiled *in addition to* the struct/enum that is the macro input. On the
9 | // other hand attribute macros are allowed to add code to the caller's crate but
10 | // also modify or remove whatever input the attribute is on. The TokenStream
11 | // returned by the attribute macro completely replaces the input item.
12 | //
13 | // Before moving on to the next test, I recommend also parsing the input token
14 | // stream as a syn::Item. In order for Item to be available you will need to
15 | // enable `features = ["full"]` of your dependency on Syn, since by default Syn
16 | // only builds the minimum set of parsers needed for derive macros.
17 | //
18 | // After parsing, the macro can return back exactly the original token stream so
19 | // that the input enum remains in the callers code and continues to be usable by
20 | // code in the rest of the crate.
21 | //
22 | //
23 | // Resources:
24 | //
25 | // - The Syn crate for parsing procedural macro input:
26 | // https://github.com/dtolnay/syn
27 | //
28 | // - The syn::Item type which represents a parsed enum as a syntax tree:
29 | // https://docs.rs/syn/2.0/syn/enum.Item.html
30 |
31 | use sorted::sorted;
32 |
33 | #[sorted]
34 | pub enum Conference {
35 | RustBeltRust,
36 | RustConf,
37 | RustFest,
38 | RustLatam,
39 | RustRush,
40 | }
41 |
42 | fn main() {}
43 |
--------------------------------------------------------------------------------
/sorted/tests/02-not-enum.rs:
--------------------------------------------------------------------------------
1 | // The #[sorted] macro is only defined to work on enum types, so this is a test
2 | // to ensure that when it's attached to a struct (or anything else) it produces
3 | // some reasonable error. Your macro will need to look into the syn::Item that
4 | // it parsed to ensure that it represents an enum, returning an error for any
5 | // other type of Item such as a struct.
6 | //
7 | // This is an exercise in exploring how to return errors from procedural macros.
8 | // The goal is to produce an understandable error message which is tailored to
9 | // this specific macro (saying that #[sorted] cannot be applied to things other
10 | // than enum). For this you'll want to look at the syn::Error type, how to
11 | // construct it, and how to return it.
12 | //
13 | // Notice that the return value of an attribute macro is simply a TokenStream,
14 | // not a Result with an error. The syn::Error type provides a method to render
15 | // your error as a TokenStream containing an invocation of the compile_error
16 | // macro.
17 | //
18 | // A final tweak you may want to make is to have the `sorted` function delegate
19 | // to a private helper function which works with Result, so most of the macro
20 | // can be written with Result-returning functions while the top-level function
21 | // handles the conversion down to TokenStream.
22 | //
23 | //
24 | // Resources
25 | //
26 | // - The syn::Error type:
27 | // https://docs.rs/syn/2.0/syn/struct.Error.html
28 |
29 | use sorted::sorted;
30 |
31 | #[sorted]
32 | pub struct Error {
33 | kind: ErrorKind,
34 | message: String,
35 | }
36 |
37 | enum ErrorKind {
38 | Io,
39 | Syntax,
40 | Eof,
41 | }
42 |
43 | fn main() {}
44 |
--------------------------------------------------------------------------------
/sorted/tests/02-not-enum.stderr:
--------------------------------------------------------------------------------
1 | error: expected enum or match expression
2 | --> tests/02-not-enum.rs:31:1
3 | |
4 | 31 | #[sorted]
5 | | ^^^^^^^^^
6 | |
7 | = note: this error originates in the attribute macro `sorted` (in Nightly builds, run with -Z macro-backtrace for more info)
8 |
--------------------------------------------------------------------------------
/sorted/tests/03-out-of-order.rs:
--------------------------------------------------------------------------------
1 | // At this point we have an enum and we need to check whether the variants
2 | // appear in sorted order!
3 | //
4 | // When your implementation notices a variant that compares lexicographically
5 | // less than one of the earlier variants, you'll want to construct a syn::Error
6 | // that gets converted to TokenStream by the already existing code that handled
7 | // this conversion during the previous test case.
8 | //
9 | // The "span" of your error, which determines where the compiler places the
10 | // resulting error message, will be the span of whichever variant name that is
11 | // not in the right place. Ideally the error message should also identify which
12 | // other variant the user needs to move this one to be in front of.
13 |
14 | use sorted::sorted;
15 |
16 | #[sorted]
17 | pub enum Error {
18 | ThatFailed,
19 | ThisFailed,
20 | SomethingFailed,
21 | WhoKnowsWhatFailed,
22 | }
23 |
24 | fn main() {}
25 |
--------------------------------------------------------------------------------
/sorted/tests/03-out-of-order.stderr:
--------------------------------------------------------------------------------
1 | error: SomethingFailed should sort before ThatFailed
2 | --> tests/03-out-of-order.rs:20:5
3 | |
4 | 20 | SomethingFailed,
5 | | ^^^^^^^^^^^^^^^
6 |
--------------------------------------------------------------------------------
/sorted/tests/04-variants-with-data.rs:
--------------------------------------------------------------------------------
1 | // This test is similar to the previous where want to ensure that the macro
2 | // correctly generates an error when the input enum is out of order, but this
3 | // time it is using an enum that also has data associated with each variant.
4 |
5 | use sorted::sorted;
6 |
7 | use std::env::VarError;
8 | use std::error::Error as StdError;
9 | use std::fmt;
10 | use std::io;
11 | use std::str::Utf8Error;
12 |
13 | #[sorted]
14 | pub enum Error {
15 | Fmt(fmt::Error),
16 | Io(io::Error),
17 | Utf8(Utf8Error),
18 | Var(VarError),
19 | Dyn(Box),
20 | }
21 |
22 | fn main() {}
23 |
--------------------------------------------------------------------------------
/sorted/tests/04-variants-with-data.stderr:
--------------------------------------------------------------------------------
1 | error: Dyn should sort before Fmt
2 | --> tests/04-variants-with-data.rs:19:5
3 | |
4 | 19 | Dyn(Box),
5 | | ^^^
6 |
--------------------------------------------------------------------------------
/sorted/tests/05-match-expr.rs:
--------------------------------------------------------------------------------
1 | // Get ready for a challenging step -- this test case is going to be a much
2 | // bigger change than the others so far.
3 | //
4 | // Not only do we want #[sorted] to assert that variants of an enum are written
5 | // in order inside the enum definition, but also inside match-expressions that
6 | // match on that enum.
7 | //
8 | // #[sorted]
9 | // match conference {
10 | // RustBeltRust => "...",
11 | // RustConf => "...",
12 | // RustFest => "...",
13 | // RustLatam => "...",
14 | // RustRush => "...",
15 | // }
16 | //
17 | // Currently, though, procedural macro invocations on expressions are not
18 | // allowed by the stable compiler! To work around this limitation until the
19 | // feature stabilizes, we'll be implementing a new #[sorted::check] macro which
20 | // the user will need to place on whatever function contains such a match.
21 | //
22 | // #[sorted::check]
23 | // fn f() {
24 | // let conference = ...;
25 | //
26 | // #[sorted]
27 | // match conference {
28 | // ...
29 | // }
30 | // }
31 | //
32 | // The #[sorted::check] macro will expand by looking inside the function to find
33 | // any match-expressions carrying a #[sorted] attribute, checking the order of
34 | // the arms in that match-expression, and then stripping away the inner
35 | // #[sorted] attribute to prevent the stable compiler from refusing to compile
36 | // the code.
37 | //
38 | // Note that unlike what we have seen in the previous test cases, stripping away
39 | // the inner #[sorted] attribute will require the new macro to mutate the input
40 | // syntax tree rather than inserting it unchanged into the output TokenStream as
41 | // before.
42 | //
43 | // Overall, the steps to pass this test will be:
44 | //
45 | // - Introduce a new procedural attribute macro called `check`.
46 | //
47 | // - Parse the input as a syn::ItemFn.
48 | //
49 | // - Traverse the function body looking for match-expressions. This part will
50 | // be easiest if you can use the VisitMut trait from Syn and write a visitor
51 | // with a visit_expr_match_mut method.
52 | //
53 | // - For each match-expression, figure out whether it has #[sorted] as one of
54 | // its attributes. If so, check that the match arms are sorted and delete
55 | // the #[sorted] attribute from the list of attributes.
56 | //
57 | // The result should be that we get the expected compile-time error pointing out
58 | // that `Fmt` should come before `Io` in the match-expression.
59 | //
60 | //
61 | // Resources:
62 | //
63 | // - The VisitMut trait to iterate and mutate a syntax tree:
64 | // https://docs.rs/syn/2.0/syn/visit_mut/trait.VisitMut.html
65 | //
66 | // - The ExprMatch struct:
67 | // https://docs.rs/syn/2.0/syn/struct.ExprMatch.html
68 |
69 | use sorted::sorted;
70 |
71 | use std::fmt::{self, Display};
72 | use std::io;
73 |
74 | #[sorted]
75 | pub enum Error {
76 | Fmt(fmt::Error),
77 | Io(io::Error),
78 | }
79 |
80 | impl Display for Error {
81 | #[sorted::check]
82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
83 | use self::Error::*;
84 |
85 | #[sorted]
86 | match self {
87 | Io(e) => write!(f, "{}", e),
88 | Fmt(e) => write!(f, "{}", e),
89 | }
90 | }
91 | }
92 |
93 | fn main() {}
94 |
--------------------------------------------------------------------------------
/sorted/tests/05-match-expr.stderr:
--------------------------------------------------------------------------------
1 | error: Fmt should sort before Io
2 | --> tests/05-match-expr.rs:88:13
3 | |
4 | 88 | Fmt(e) => write!(f, "{}", e),
5 | | ^^^
6 |
--------------------------------------------------------------------------------
/sorted/tests/06-pattern-path.rs:
--------------------------------------------------------------------------------
1 | // When we checked enum definitions for sortedness, it was sufficient to compare
2 | // a single identifier (the name of the variant) for each variant. Match
3 | // expressions are different in that each arm may have a pattern that consists
4 | // of more than just one identifier.
5 | //
6 | // Ensure that patterns consisting of a path are correctly tested for
7 | // sortedness. These patterns will be of type Pat::Path, Pat::TupleStruct, or
8 | // Pat::Struct.
9 | //
10 | //
11 | // Resources:
12 | //
13 | // - The syn::Pat syntax tree which forms the left hand side of a match arm:
14 | // https://docs.rs/syn/2.0/syn/enum.Pat.html
15 |
16 | use sorted::sorted;
17 |
18 | use std::fmt::{self, Display};
19 | use std::io;
20 |
21 | #[sorted]
22 | pub enum Error {
23 | Fmt(fmt::Error),
24 | Io(io::Error),
25 | }
26 |
27 | impl Display for Error {
28 | #[sorted::check]
29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
30 | #[sorted]
31 | match self {
32 | Error::Io(e) => write!(f, "{}", e),
33 | Error::Fmt(e) => write!(f, "{}", e),
34 | }
35 | }
36 | }
37 |
38 | fn main() {}
39 |
--------------------------------------------------------------------------------
/sorted/tests/06-pattern-path.stderr:
--------------------------------------------------------------------------------
1 | error: Error::Fmt should sort before Error::Io
2 | --> tests/06-pattern-path.rs:33:13
3 | |
4 | 33 | Error::Fmt(e) => write!(f, "{}", e),
5 | | ^^^^^^^^^^
6 |
--------------------------------------------------------------------------------
/sorted/tests/07-unrecognized-pattern.rs:
--------------------------------------------------------------------------------
1 | // The macro won't need to define what it means for other sorts of patterns to
2 | // be sorted. It should be fine to trigger an error if any of the patterns is
3 | // not something that can be compared by path.
4 | //
5 | // Be sure that the resulting error message is understandable and placed
6 | // correctly underlining the unsupported pattern.
7 |
8 | #[sorted::check]
9 | fn f(bytes: &[u8]) -> Option {
10 | #[sorted]
11 | match bytes {
12 | [] => Some(0),
13 | [a] => Some(*a),
14 | [a, b] => Some(a + b),
15 | _other => None,
16 | }
17 | }
18 |
19 | fn main() {}
20 |
--------------------------------------------------------------------------------
/sorted/tests/07-unrecognized-pattern.stderr:
--------------------------------------------------------------------------------
1 | error: unsupported by #[sorted]
2 | --> tests/07-unrecognized-pattern.rs:12:9
3 | |
4 | 12 | [] => Some(0),
5 | | ^^
6 |
--------------------------------------------------------------------------------
/sorted/tests/08-underscore.rs:
--------------------------------------------------------------------------------
1 | // There is one other common type of pattern that would be nice to support --
2 | // the wildcard or underscore pattern. The #[sorted] macro should check that if
3 | // a wildcard pattern is present then it is the last one.
4 |
5 | use sorted::sorted;
6 |
7 | #[sorted]
8 | pub enum Conference {
9 | RustBeltRust,
10 | RustConf,
11 | RustFest,
12 | RustLatam,
13 | RustRush,
14 | }
15 |
16 | impl Conference {
17 | #[sorted::check]
18 | pub fn region(&self) -> &str {
19 | use self::Conference::*;
20 |
21 | #[sorted]
22 | match self {
23 | RustFest => "Europe",
24 | RustLatam => "Latin America",
25 | _ => "elsewhere",
26 | }
27 | }
28 | }
29 |
30 | fn main() {}
31 |
--------------------------------------------------------------------------------
/sorted/tests/progress.rs:
--------------------------------------------------------------------------------
1 | #[test]
2 | fn tests() {
3 | let t = trybuild::TestCases::new();
4 | //t.pass("tests/01-parse-enum.rs");
5 | //t.compile_fail("tests/02-not-enum.rs");
6 | //t.compile_fail("tests/03-out-of-order.rs");
7 | //t.compile_fail("tests/04-variants-with-data.rs");
8 | //t.compile_fail("tests/05-match-expr.rs");
9 | //t.compile_fail("tests/06-pattern-path.rs");
10 | //t.compile_fail("tests/07-unrecognized-pattern.rs");
11 | //t.pass("tests/08-underscore.rs");
12 | }
13 |
--------------------------------------------------------------------------------