1 //! [![github]](https://github.com/dtolnay/remain) [![crates-io]](https://crates.io/crates/remain) [![docs-rs]](https://docs.rs/remain)
2 //!
3 //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
4 //! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust
5 //! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K
6 //!
7 //! <br>
8 //!
9 //! This crate provides an attribute macro to check at compile time that the
10 //! variants of an enum or the arms of a match expression are written in sorted
11 //! order.
12 //!
13 //! # Syntax
14 //!
15 //! Place a `#[remain::sorted]` attribute on enums, structs, match-expressions,
16 //! or let-statements whose value is a match-expression.
17 //!
18 //! Alternatively, import as `use remain::sorted;` and use `#[sorted]` as the
19 //! attribute.
20 //!
21 //! ```
22 //! # use std::error::Error as StdError;
23 //! # use std::fmt::{self, Display};
24 //! # use std::io;
25 //! #
26 //! #[remain::sorted]
27 //! #[derive(Debug)]
28 //! pub enum Error {
29 //! BlockSignal(signal::Error),
30 //! CreateCrasClient(libcras::Error),
31 //! CreateEventFd(sys_util::Error),
32 //! CreateSignalFd(sys_util::SignalFdError),
33 //! CreateSocket(io::Error),
34 //! DetectImageType(qcow::Error),
35 //! DeviceJail(io_jail::Error),
36 //! NetDeviceNew(virtio::NetError),
37 //! SpawnVcpu(io::Error),
38 //! }
39 //!
40 //! impl Display for Error {
41 //! # #[remain::check]
42 //! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 //! use self::Error::*;
44 //!
45 //! #[remain::sorted]
46 //! match self {
47 //! BlockSignal(e) => write!(f, "failed to block signal: {}", e),
48 //! CreateCrasClient(e) => write!(f, "failed to create cras client: {}", e),
49 //! CreateEventFd(e) => write!(f, "failed to create eventfd: {}", e),
50 //! CreateSignalFd(e) => write!(f, "failed to create signalfd: {}", e),
51 //! CreateSocket(e) => write!(f, "failed to create socket: {}", e),
52 //! DetectImageType(e) => write!(f, "failed to detect disk image type: {}", e),
53 //! DeviceJail(e) => write!(f, "failed to jail device: {}", e),
54 //! NetDeviceNew(e) => write!(f, "failed to set up virtio networking: {}", e),
55 //! SpawnVcpu(e) => write!(f, "failed to spawn VCPU thread: {}", e),
56 //! }
57 //! }
58 //! }
59 //! #
60 //! # mod signal {
61 //! # pub use std::io::Error;
62 //! # }
63 //! #
64 //! # mod libcras {
65 //! # pub use std::io::Error;
66 //! # }
67 //! #
68 //! # mod sys_util {
69 //! # pub use std::io::{Error, Error as SignalFdError};
70 //! # }
71 //! #
72 //! # mod qcow {
73 //! # pub use std::io::Error;
74 //! # }
75 //! #
76 //! # mod io_jail {
77 //! # pub use std::io::Error;
78 //! # }
79 //! #
80 //! # mod virtio {
81 //! # pub use std::io::Error as NetError;
82 //! # }
83 //! #
84 //! # fn main() {}
85 //! ```
86 //!
87 //! If an enum variant, struct field, or match arm is inserted out of order,\
88 //!
89 //! ```diff
90 //! NetDeviceNew(virtio::NetError),
91 //! SpawnVcpu(io::Error),
92 //! + AaaUhOh(Box<dyn StdError>),
93 //! }
94 //! ```
95 //!
96 //! then the macro produces a compile error.
97 //!
98 //! ```console
99 //! error: AaaUhOh should sort before BlockSignal
100 //! --> tests/stable.rs:49:5
101 //! |
102 //! 49 | AaaUhOh(Box<dyn StdError>),
103 //! | ^^^^^^^
104 //! ```
105 //!
106 //! # Compiler support
107 //!
108 //! The attribute on enums is supported on any rustc version 1.31+.
109 //!
110 //! Rust does not yet have stable support for user-defined attributes within a
111 //! function body, so the attribute on match-expressions and let-statements
112 //! requires a nightly compiler and the following two features enabled:
113 //!
114 //! ```
115 //! # const IGNORE: &str = stringify! {
116 //! #![feature(proc_macro_hygiene, stmt_expr_attributes)]
117 //! # };
118 //! ```
119 //!
120 //! As a stable alternative, this crate provides a function-level attribute
121 //! called `#[remain::check]` which makes match-expression and let-statement
122 //! attributes work on any rustc version 1.31+. Place this attribute on any
123 //! function containing `#[sorted]` to make them work on a stable compiler.
124 //!
125 //! ```
126 //! # use std::fmt::{self, Display};
127 //! #
128 //! # enum Error {}
129 //! #
130 //! impl Display for Error {
131 //! #[remain::check]
132 //! fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
133 //! use self::Error::*;
134 //!
135 //! #[sorted]
136 //! match self {
137 //! /* ... */
138 //! # _ => unimplemented!(),
139 //! }
140 //! }
141 //! }
142 //! #
143 //! # fn main() {}
144 //! ```
145
146 #![allow(clippy::needless_doctest_main)]
147
148 extern crate proc_macro;
149
150 mod atom;
151 mod check;
152 mod compare;
153 mod emit;
154 mod format;
155 mod parse;
156 mod visit;
157
158 use proc_macro::TokenStream;
159 use quote::quote;
160 use syn::{parse_macro_input, ItemFn};
161
162 use crate::emit::emit;
163 use crate::parse::{Input, Nothing};
164
165 #[proc_macro_attribute]
sorted(args: TokenStream, input: TokenStream) -> TokenStream166 pub fn sorted(args: TokenStream, input: TokenStream) -> TokenStream {
167 let _ = parse_macro_input!(args as Nothing);
168 let mut input = parse_macro_input!(input as Input);
169 let kind = input.kind();
170
171 let result = check::sorted(&mut input);
172 let output = TokenStream::from(quote!(#input));
173
174 match result {
175 Ok(_) => output,
176 Err(err) => emit(err, kind, output),
177 }
178 }
179
180 #[proc_macro_attribute]
check(args: TokenStream, input: TokenStream) -> TokenStream181 pub fn check(args: TokenStream, input: TokenStream) -> TokenStream {
182 let _ = parse_macro_input!(args as Nothing);
183 let mut input = parse_macro_input!(input as ItemFn);
184
185 visit::check(&mut input);
186
187 TokenStream::from(quote!(#input))
188 }
189