assert_type_match/lib.rs
1mod args;
2mod enums;
3mod fields;
4mod flags;
5mod structs;
6mod utils;
7mod variants;
8mod wrap;
9
10use crate::args::Args;
11use crate::enums::enum_assert;
12use crate::structs::struct_assert;
13use crate::wrap::wrap_assertions;
14use proc_macro::TokenStream;
15use syn::{parse_macro_input, Data, DeriveInput};
16
17const ATTRIBUTE: &str = "assert_type_match";
18
19/// An attribute macro that can be used to statically verify that the annotated struct or enum
20/// matches the structure of a foreign type.
21///
22/// For structs, it will verify that the fields of the annotated struct match the fields of the
23/// foreign struct.
24///
25/// For enums, it will verify that the variants of the annotated enum match the variants of the
26/// foreign enum, and that the fields of each variant match the fields of the corresponding variant
27/// in the foreign enum.
28///
29/// This will also output the original annotated struct or enum,
30/// unless the `test_only` argument is set to `true`.
31///
32/// # Arguments
33///
34/// This macro accepts arguments to control its behavior.
35/// These arguments are passed as a comma-separated list after the foreign type.
36///
37/// All boolean arguments may be set to true by using either the `foo` or `foo = true` syntax.
38///
39/// ## `test_only`
40///
41/// Type: `bool`
42///
43/// Controls whether to output the annotated struct or enum in the generated code.
44///
45/// ## `from`
46///
47/// Type: `bool`
48///
49/// Controls whether a `From` implementation should be generated.
50///
51/// If true, two `From` implementations will be generated:
52/// one for converting from the annotated type to the foreign type,
53/// and one for converting from the foreign type to the annotated type.
54///
55/// ## `skip_name`
56///
57/// Type: `bool`
58///
59/// Controls whether checking that the name of the annotated struct or enum matches
60/// the name of the foreign type should be skipped.
61///
62/// For example, comparing `struct Foo(u32)` to `struct Bar(u32)` would pass
63/// when this argument is set to `true`.
64///
65/// ## `skip_types`
66///
67/// Type: `bool`
68///
69/// Controls whether checking field types should be skipped.
70///
71/// For example, comparing `struct Foo(i32)` to `struct Foo(f32)` would pass
72/// when this argument is set to `true`.
73///
74/// ## Field Arguments
75///
76/// This macro also supports field attributes.
77///
78/// These are also defined with the `#[assert_type_match(...)]` attribute.
79///
80/// ### `skip`
81///
82/// Type: `bool`
83///
84/// Controls whether the field should be skipped.
85///
86/// This allows you to skip fields that are not present on the foreign type.
87///
88/// ### `skip_type`
89///
90/// Type: `bool`
91///
92/// Controls whether checking the field type should be skipped.
93///
94/// ## Variant Arguments
95///
96/// This macro also supports variant attributes.
97///
98/// These are also defined with the `#[assert_type_match(...)]` attribute.
99///
100/// ### `skip`
101///
102/// Type: `bool`
103///
104/// Controls whether the variant should be skipped.
105///
106/// This allows you to skip variants that are not present on the foreign type.
107///
108/// # Example
109///
110/// A passing example:
111///
112/// ```
113/// # use assert_type_match::assert_type_match;
114/// mod other {
115/// pub struct Test {
116/// pub x: i32,
117/// pub y: i32,
118/// }
119/// }
120///
121/// #[assert_type_match(other::Test)]
122/// struct Test {
123/// x: i32,
124/// y: i32,
125/// }
126/// ```
127///
128/// A failing example:
129///
130/// ```compile_fail
131/// # use assert_type_match::assert_type_match;
132/// mod other {
133/// pub struct Test {
134/// pub x: i32,
135/// pub y: i32,
136/// }
137/// }
138///
139/// #[assert_type_match(other::Test)]
140/// struct Test {
141/// x: i32,
142/// z: i32, // Error: struct `other::Test` has no field named `z`
143/// }
144/// ```
145///
146#[proc_macro_attribute]
147pub fn assert_type_match(args: TokenStream, input: TokenStream) -> TokenStream {
148 let input = parse_macro_input!(input as DeriveInput);
149 let args = parse_macro_input!(args as Args);
150
151 wrap_assertions(input, args, |input, args| match &input.data {
152 Data::Struct(data) => struct_assert(data, input, args),
153 Data::Enum(data) => enum_assert(data, input, args),
154 Data::Union(data) => Err(syn::Error::new(
155 data.union_token.span,
156 "unions are not supported",
157 )),
158 })
159 .into()
160}