#[cfg(doctest)]
#[doc = include_str!("../README.md")]
mod readme_doctests {}
mod syntax;
moddef::moddef!(
flat (pub) mod {
},
flat (pub(crate)) mod {
presentation,
state,
machine,
transition,
validators
}
);
pub(crate) use presentation::{
PresentationAttr, PresentationTypesAttr, parse_present_attrs, parse_presentation_types_attr,
strip_present_attrs,
};
pub(crate) use syntax::{
ItemTarget, ModulePath, SourceFingerprint, crate_root_for_file, current_crate_root,
extract_derives, source_file_fingerprint,
};
use crate::{
LoadedMachineLookupFailure, MachinePath, ambiguous_transition_machine_error,
ambiguous_transition_machine_fallback_error, lookup_loaded_machine_in_module,
lookup_unique_loaded_machine_by_name,
};
use macro_registry::callsite::{current_module_path_at_line, current_module_path_opt};
use proc_macro::TokenStream;
use proc_macro2::Span;
use syn::{Item, ItemImpl, parse_macro_input};
#[proc_macro_attribute]
pub fn state(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as Item);
let input = match input {
Item::Enum(item_enum) => item_enum,
other => return invalid_state_target_error(&other).into(),
};
if let Some(error) = validate_state_enum(&input) {
return error.into();
}
let enum_info = match EnumInfo::from_item_enum(&input) {
Ok(info) => info,
Err(err) => return err.to_compile_error().into(),
};
store_state_enum(&enum_info);
let expanded = generate_state_impls(&enum_info);
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn machine(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = parse_macro_input!(item as Item);
let input = match input {
Item::Struct(item_struct) => item_struct,
other => return invalid_machine_target_error(&other).into(),
};
let machine_info = match MachineInfo::from_item_struct(&input) {
Ok(info) => info,
Err(err) => return err.to_compile_error().into(),
};
if let Some(error) = validate_machine_struct(&input, &machine_info) {
return error.into();
}
store_machine_struct(&machine_info);
let expanded = generate_machine_impls(&machine_info, &input);
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn transition(
_attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let input = parse_macro_input!(item as ItemImpl);
let tr_impl = match parse_transition_impl(&input) {
Ok(parsed) => parsed,
Err(err) => return err.into(),
};
let module_path = match resolved_current_module_path(tr_impl.machine_span, "#[transition]") {
Ok(path) => path,
Err(err) => return err,
};
let machine_path: MachinePath = module_path.clone().into();
let machine_info_owned =
match lookup_loaded_machine_in_module(&machine_path, &tr_impl.machine_name) {
Ok(info) => Some(info),
Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
return ambiguous_transition_machine_error(
&tr_impl.machine_name,
&module_path,
&candidates,
tr_impl.machine_span,
)
.into();
}
Err(LoadedMachineLookupFailure::NotFound) => {
match lookup_unique_loaded_machine_by_name(&tr_impl.machine_name) {
Ok(info) => Some(info),
Err(LoadedMachineLookupFailure::Ambiguous(candidates)) => {
return ambiguous_transition_machine_fallback_error(
&tr_impl.machine_name,
&module_path,
&candidates,
tr_impl.machine_span,
)
.into();
}
Err(LoadedMachineLookupFailure::NotFound) => None,
}
}
};
let machine_info = match machine_info_owned.as_ref() {
Some(info) => info,
None => {
return missing_transition_machine_error(
&tr_impl.machine_name,
&module_path,
tr_impl.machine_span,
)
.into();
}
};
if let Some(err) = validate_transition_functions(&tr_impl, machine_info) {
return err.into();
}
let expanded = generate_transition_impl(&input, &tr_impl, machine_info);
expanded.into()
}
#[proc_macro_attribute]
pub fn validators(attr: TokenStream, item: TokenStream) -> TokenStream {
let module_path = match resolved_current_module_path(Span::call_site(), "#[validators]") {
Ok(path) => path,
Err(err) => return err,
};
parse_validators(attr, item, &module_path)
}
fn resolved_current_module_path(span: Span, macro_name: &str) -> Result<String, TokenStream> {
let line_number = span.start().line;
let resolved = if line_number == 0 {
current_module_path_opt()
} else {
current_module_path_at_line(line_number).or_else(current_module_path_opt)
};
resolved.ok_or_else(|| {
let message = format!(
"Internal error: could not resolve the module path for `{macro_name}` at this call site."
);
quote::quote_spanned! { span =>
compile_error!(#message);
}
.into()
})
}