1#[cfg(doctest)]
16#[doc = include_str!("../README.md")]
17mod readme_doctests {}
18
19mod syntax;
20
21moddef::moddef!(
22 flat (pub) mod {
23 },
24 flat (pub(crate)) mod {
25 state,
26 machine,
27 transition,
28 validators
29 }
30);
31
32pub(crate) use syntax::{
33 ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
34 extract_derives, source_file_fingerprint,
35};
36
37use crate::{
38 LoadedMachineLookupFailure, MachinePath, ambiguous_transition_machine_error,
39 ambiguous_transition_machine_fallback_error, lookup_loaded_machine_in_module,
40 lookup_unique_loaded_machine_by_name,
41};
42use macro_registry::callsite::current_module_path_opt;
43use proc_macro::TokenStream;
44use proc_macro2::Span;
45use syn::{Item, ItemImpl, parse_macro_input};
46
47#[proc_macro_attribute]
53pub fn state(_attr: TokenStream, item: TokenStream) -> TokenStream {
54 let input = parse_macro_input!(item as Item);
55 let input = match input {
56 Item::Enum(item_enum) => item_enum,
57 other => return invalid_state_target_error(&other).into(),
58 };
59
60 if let Some(error) = validate_state_enum(&input) {
62 return error.into();
63 }
64
65 let enum_info = match EnumInfo::from_item_enum(&input) {
66 Ok(info) => info,
67 Err(err) => return err.to_compile_error().into(),
68 };
69
70 store_state_enum(&enum_info);
72
73 let expanded = generate_state_impls(&enum_info);
75
76 TokenStream::from(expanded)
77}
78
79#[proc_macro_attribute]
87pub fn machine(_attr: TokenStream, item: TokenStream) -> TokenStream {
88 let input = parse_macro_input!(item as Item);
89 let input = match input {
90 Item::Struct(item_struct) => item_struct,
91 other => return invalid_machine_target_error(&other).into(),
92 };
93
94 let machine_info = match MachineInfo::from_item_struct(&input) {
95 Ok(info) => info,
96 Err(err) => return err.to_compile_error().into(),
97 };
98
99 if let Some(error) = validate_machine_struct(&input, &machine_info) {
101 return error.into();
102 }
103
104 store_machine_struct(&machine_info);
106
107 let expanded = generate_machine_impls(&machine_info, &input);
109
110 TokenStream::from(expanded)
111}
112
113#[proc_macro_attribute]
119pub fn transition(
120 _attr: proc_macro::TokenStream,
121 item: proc_macro::TokenStream,
122) -> proc_macro::TokenStream {
123 let input = parse_macro_input!(item as ItemImpl);
124
125 let tr_impl = match parse_transition_impl(&input) {
127 Ok(parsed) => parsed,
128 Err(err) => return err.into(),
129 };
130
131 let module_path = match resolved_current_module_path(tr_impl.machine_span, "#[transition]") {
132 Ok(path) => path,
133 Err(err) => return err,
134 };
135
136 let machine_path: MachinePath = module_path.clone().into();
137 let machine_info_owned =
138 match lookup_loaded_machine_in_module(&machine_path, &tr_impl.machine_name) {
139 Ok(info) => Some(info),
140 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
141 return ambiguous_transition_machine_error(
142 &tr_impl.machine_name,
143 &module_path,
144 &candidates,
145 tr_impl.machine_span,
146 )
147 .into();
148 }
149 Err(LoadedMachineLookupFailure::NotFound) => {
150 match lookup_unique_loaded_machine_by_name(&tr_impl.machine_name) {
151 Ok(info) => Some(info),
152 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
153 return ambiguous_transition_machine_fallback_error(
154 &tr_impl.machine_name,
155 &module_path,
156 &candidates,
157 tr_impl.machine_span,
158 )
159 .into();
160 }
161 Err(LoadedMachineLookupFailure::NotFound) => None,
162 }
163 }
164 };
165 let machine_info = match machine_info_owned.as_ref() {
166 Some(info) => info,
167 None => {
168 return missing_transition_machine_error(
169 &tr_impl.machine_name,
170 &module_path,
171 tr_impl.machine_span,
172 )
173 .into();
174 }
175 };
176
177 if let Some(err) = validate_transition_functions(&tr_impl, machine_info) {
178 return err.into();
179 }
180
181 let expanded = generate_transition_impl(&input, &tr_impl, machine_info);
183
184 expanded.into()
187}
188
189#[proc_macro_attribute]
196pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
197 let module_path = match resolved_current_module_path(Span::call_site(), "#[validators]") {
198 Ok(path) => path,
199 Err(err) => return err,
200 };
201 parse_validators(attr, item, &module_path)
202}
203
204fn resolved_current_module_path(span: Span, macro_name: &str) -> Result<String, TokenStream> {
205 current_module_path_opt().ok_or_else(|| {
206 let message = format!(
207 "Internal error: could not resolve the module path for `{macro_name}` at this call site."
208 );
209 quote::quote_spanned! { span =>
210 compile_error!(#message);
211 }
212 .into()
213 })
214}