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 presentation,
26 state,
27 machine,
28 transition,
29 validators
30 }
31);
32
33pub(crate) use presentation::{
34 PresentationAttr, PresentationTypesAttr, parse_present_attrs, parse_presentation_types_attr,
35 strip_present_attrs,
36};
37pub(crate) use syntax::{
38 ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
39 extract_derives, source_file_fingerprint,
40};
41
42use crate::{
43 LoadedMachineLookupFailure, MachinePath, ambiguous_transition_machine_error,
44 ambiguous_transition_machine_fallback_error, lookup_loaded_machine_in_module,
45 lookup_unique_loaded_machine_by_name,
46};
47use macro_registry::callsite::{current_module_path_at_line, current_module_path_opt};
48use proc_macro::TokenStream;
49use proc_macro2::Span;
50use syn::{Item, ItemImpl, parse_macro_input};
51
52#[proc_macro_attribute]
59pub fn state(_attr: TokenStream, item: TokenStream) -> TokenStream {
60 let input = parse_macro_input!(item as Item);
61 let input = match input {
62 Item::Enum(item_enum) => item_enum,
63 other => return invalid_state_target_error(&other).into(),
64 };
65
66 if let Some(error) = validate_state_enum(&input) {
68 return error.into();
69 }
70
71 let enum_info = match EnumInfo::from_item_enum(&input) {
72 Ok(info) => info,
73 Err(err) => return err.to_compile_error().into(),
74 };
75
76 store_state_enum(&enum_info);
78
79 let expanded = generate_state_impls(&enum_info);
81
82 TokenStream::from(expanded)
83}
84
85#[proc_macro_attribute]
94pub fn machine(_attr: TokenStream, item: TokenStream) -> TokenStream {
95 let input = parse_macro_input!(item as Item);
96 let input = match input {
97 Item::Struct(item_struct) => item_struct,
98 other => return invalid_machine_target_error(&other).into(),
99 };
100
101 let machine_info = match MachineInfo::from_item_struct(&input) {
102 Ok(info) => info,
103 Err(err) => return err.to_compile_error().into(),
104 };
105
106 if let Some(error) = validate_machine_struct(&input, &machine_info) {
108 return error.into();
109 }
110
111 store_machine_struct(&machine_info);
113
114 let expanded = generate_machine_impls(&machine_info, &input);
116
117 TokenStream::from(expanded)
118}
119
120#[proc_macro_attribute]
128pub fn transition(
129 _attr: proc_macro::TokenStream,
130 item: proc_macro::TokenStream,
131) -> proc_macro::TokenStream {
132 let input = parse_macro_input!(item as ItemImpl);
133
134 let tr_impl = match parse_transition_impl(&input) {
136 Ok(parsed) => parsed,
137 Err(err) => return err.into(),
138 };
139
140 let module_path = match resolved_current_module_path(tr_impl.machine_span, "#[transition]") {
141 Ok(path) => path,
142 Err(err) => return err,
143 };
144
145 let machine_path: MachinePath = module_path.clone().into();
146 let machine_info_owned =
147 match lookup_loaded_machine_in_module(&machine_path, &tr_impl.machine_name) {
148 Ok(info) => Some(info),
149 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
150 return ambiguous_transition_machine_error(
151 &tr_impl.machine_name,
152 &module_path,
153 &candidates,
154 tr_impl.machine_span,
155 )
156 .into();
157 }
158 Err(LoadedMachineLookupFailure::NotFound) => {
159 match lookup_unique_loaded_machine_by_name(&tr_impl.machine_name) {
160 Ok(info) => Some(info),
161 Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
162 return ambiguous_transition_machine_fallback_error(
163 &tr_impl.machine_name,
164 &module_path,
165 &candidates,
166 tr_impl.machine_span,
167 )
168 .into();
169 }
170 Err(LoadedMachineLookupFailure::NotFound) => None,
171 }
172 }
173 };
174 let machine_info = match machine_info_owned.as_ref() {
175 Some(info) => info,
176 None => {
177 return missing_transition_machine_error(
178 &tr_impl.machine_name,
179 &module_path,
180 tr_impl.machine_span,
181 )
182 .into();
183 }
184 };
185
186 if let Some(err) = validate_transition_functions(&tr_impl, machine_info) {
187 return err.into();
188 }
189
190 let expanded = generate_transition_impl(&input, &tr_impl, machine_info);
192
193 expanded.into()
196}
197
198#[proc_macro_attribute]
208pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
209 let module_path = match resolved_current_module_path(Span::call_site(), "#[validators]") {
210 Ok(path) => path,
211 Err(err) => return err,
212 };
213 parse_validators(attr, item, &module_path)
214}
215
216fn resolved_current_module_path(span: Span, macro_name: &str) -> Result<String, TokenStream> {
217 let line_number = span.start().line;
218 let resolved = if line_number == 0 {
219 current_module_path_opt()
220 } else {
221 current_module_path_at_line(line_number).or_else(current_module_path_opt)
222 };
223
224 resolved.ok_or_else(|| {
225 let message = format!(
226 "Internal error: could not resolve the module path for `{macro_name}` at this call site."
227 );
228 quote::quote_spanned! { span =>
229 compile_error!(#message);
230 }
231 .into()
232 })
233}