1#[cfg(doctest)]
16#[doc = include_str!("../README.md")]
17mod readme_doctests {}
18
19mod analysis;
20mod cache;
21mod callsite;
22mod module_path;
23mod parser;
24mod pathing;
25mod query;
26mod syntax;
27
28moddef::moddef!(
29 flat (pub) mod {
30 },
31 flat (pub(crate)) mod {
32 presentation,
33 state,
34 machine,
35 transition,
36 validators
37 }
38);
39
40pub(crate) use presentation::{
41 PresentationAttr, PresentationTypesAttr, parse_present_attrs, parse_presentation_types_attr,
42 strip_present_attrs,
43};
44pub(crate) use syntax::{
45 ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
46 extract_derives, source_file_fingerprint,
47};
48
49use crate::callsite::{current_module_path_opt, module_path_for_span};
50use crate::{
51 LoadedMachineLookupFailure, MachinePath, ambiguous_transition_machine_error,
52 ambiguous_transition_machine_fallback_error, lookup_loaded_machine_in_module,
53 lookup_unique_loaded_machine_by_name,
54};
55use proc_macro::TokenStream;
56use proc_macro2::Span;
57use syn::spanned::Spanned;
58use syn::{Item, ItemImpl, parse_macro_input};
59
60#[proc_macro_attribute]
67pub fn state(_attr: TokenStream, item: TokenStream) -> TokenStream {
68 let input = parse_macro_input!(item as Item);
69 let input = match input {
70 Item::Enum(item_enum) => item_enum,
71 other => return invalid_state_target_error(&other).into(),
72 };
73
74 if let Some(error) = validate_state_enum(&input) {
76 return error.into();
77 }
78
79 let enum_info = match EnumInfo::from_item_enum(&input) {
80 Ok(info) => info,
81 Err(err) => return err.to_compile_error().into(),
82 };
83
84 store_state_enum(&enum_info);
86
87 let expanded = generate_state_impls(&enum_info);
89
90 TokenStream::from(expanded)
91}
92
93#[proc_macro_attribute]
102pub fn machine(_attr: TokenStream, item: TokenStream) -> TokenStream {
103 let input = parse_macro_input!(item as Item);
104 let input = match input {
105 Item::Struct(item_struct) => item_struct,
106 other => return invalid_machine_target_error(&other).into(),
107 };
108
109 let machine_info = match MachineInfo::from_item_struct(&input) {
110 Ok(info) => info,
111 Err(err) => return err.to_compile_error().into(),
112 };
113
114 if let Some(error) = validate_machine_struct(&input, &machine_info) {
116 return error.into();
117 }
118
119 store_machine_struct(&machine_info);
121
122 let expanded = generate_machine_impls(&machine_info, &input);
124
125 TokenStream::from(expanded)
126}
127
128#[proc_macro_attribute]
137pub fn transition(
138 _attr: proc_macro::TokenStream,
139 item: proc_macro::TokenStream,
140) -> proc_macro::TokenStream {
141 let input = parse_macro_input!(item as ItemImpl);
142
143 let tr_impl = match parse_transition_impl(&input) {
145 Ok(parsed) => parsed,
146 Err(err) => return err.into(),
147 };
148
149 let module_path = match resolved_current_module_path(tr_impl.machine_span, "#[transition]") {
150 Ok(path) => path,
151 Err(err) => return err,
152 };
153
154 let machine_path: MachinePath = module_path.clone().into();
155 let machine_info_owned =
156 match lookup_loaded_machine_in_module(&machine_path, &tr_impl.machine_name) {
157 Ok(info) => Some(info),
158 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
159 return ambiguous_transition_machine_error(
160 &tr_impl.machine_name,
161 &module_path,
162 &candidates,
163 tr_impl.machine_span,
164 )
165 .into();
166 }
167 Err(LoadedMachineLookupFailure::NotFound) => {
168 match lookup_unique_loaded_machine_by_name(&tr_impl.machine_name) {
169 Ok(info) => Some(info),
170 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
171 return ambiguous_transition_machine_fallback_error(
172 &tr_impl.machine_name,
173 &module_path,
174 &candidates,
175 tr_impl.machine_span,
176 )
177 .into();
178 }
179 Err(LoadedMachineLookupFailure::NotFound) => None,
180 }
181 }
182 };
183 let machine_info = match machine_info_owned.as_ref() {
184 Some(info) => info,
185 None => {
186 return missing_transition_machine_error(
187 &tr_impl.machine_name,
188 &module_path,
189 tr_impl.machine_span,
190 )
191 .into();
192 }
193 };
194
195 if let Some(err) = validate_transition_functions(&tr_impl, machine_info) {
196 return err.into();
197 }
198
199 let expanded = generate_transition_impl(&input, &tr_impl, machine_info);
201
202 expanded.into()
205}
206
207#[proc_macro_attribute]
217pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
218 let item_impl = parse_macro_input!(item as ItemImpl);
219 let module_path = match resolved_current_module_path(item_impl.self_ty.span(), "#[validators]")
220 {
221 Ok(path) => path,
222 Err(err) => return err,
223 };
224 parse_validators(attr, item_impl, &module_path)
225}
226
227fn resolved_current_module_path(span: Span, macro_name: &str) -> Result<String, TokenStream> {
228 let resolved = module_path_for_span(span)
229 .or_else(current_module_path_opt)
230 .or_else(|| {
231 crate::callsite::source_info_for_span(span)
232 .is_none()
233 .then_some("crate".to_string())
234 });
235
236 resolved.ok_or_else(|| {
237 let message = format!(
238 "Internal error: could not resolve the module path for `{macro_name}` at this call site."
239 );
240 quote::quote_spanned! { span =>
241 compile_error!(#message);
242 }
243 .into()
244 })
245}