statum-macros 0.8.10

Proc macros for representing legal workflow and protocol states explicitly in Rust
Documentation
use std::collections::HashSet;

use syn::Type;

use crate::source::{AliasResolutionContext, expand_source_type_alias};

use super::shape::{
    SupportedWrapper, extract_first_generic_type_ref, extract_generic_type_refs,
    extract_machine_state_from_segment, machine_segment_matching_target, supported_wrapper,
    type_path,
};

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn parse_machine_and_state(ty: &Type, target_type: &Type) -> Option<(String, String)> {
    parse_primary_machine_and_state_in_context(ty, target_type, None)
}

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn parse_machine_and_state_in_context(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
) -> Option<(String, String)> {
    parse_primary_machine_and_state_with_alias_policy(ty, target_type, context, true)
}

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn parse_primary_machine_and_state(
    ty: &Type,
    target_type: &Type,
) -> Option<(String, String)> {
    parse_primary_machine_and_state_with_alias_policy(ty, target_type, None, true)
}

fn parse_primary_machine_and_state_in_context(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
) -> Option<(String, String)> {
    parse_primary_machine_and_state_with_alias_policy(ty, target_type, context, true)
}

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn parse_primary_machine_and_state_strict(
    ty: &Type,
    target_type: &Type,
) -> Option<(String, String)> {
    parse_primary_machine_and_state_with_alias_policy(ty, target_type, None, false)
}

fn parse_primary_machine_and_state_with_alias_policy(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
    allow_source_aliases: bool,
) -> Option<(String, String)> {
    let mut visited = HashSet::new();
    parse_primary_machine_and_state_inner(
        ty,
        target_type,
        context,
        allow_source_aliases,
        &mut visited,
    )
}

fn parse_primary_machine_and_state_inner(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
    allow_source_aliases: bool,
    visited: &mut HashSet<String>,
) -> Option<(String, String)> {
    let type_path = type_path(ty)?;
    let segment = type_path.path.segments.last()?;

    if machine_segment_matching_target(&type_path.path, target_type).is_some() {
        return extract_machine_state_from_segment(segment)
            .map(|(machine, state, _)| (machine, state));
    }

    if allow_source_aliases
        && let Some(expanded_alias) = expand_source_type_alias(ty, context, visited)
    {
        let (expanded, alias_context, visit_key) = expanded_alias.into_parts();
        let result = parse_primary_machine_and_state_inner(
            &expanded,
            target_type,
            Some(&alias_context),
            allow_source_aliases,
            visited,
        );
        visited.remove(&visit_key);
        return result;
    }

    match supported_wrapper(&type_path.path)? {
        SupportedWrapper::Option => extract_first_generic_type_ref(&segment.arguments).and_then(
            |inner| {
                parse_primary_machine_and_state_inner(
                    inner,
                    target_type,
                    context,
                    allow_source_aliases,
                    visited,
                )
            },
        ),
        SupportedWrapper::Result | SupportedWrapper::Branch => {
            extract_first_generic_type_ref(&segment.arguments).and_then(|inner| {
                parse_primary_machine_and_state_inner(
                    inner,
                    target_type,
                    context,
                    allow_source_aliases,
                    visited,
                )
            })
        }
    }
}

#[cfg_attr(not(test), allow(dead_code))]
pub(crate) fn collect_machine_and_states(
    ty: &Type,
    target_type: &Type,
) -> Vec<(String, String)> {
    collect_machine_and_states_with_alias_policy(ty, target_type, None, true)
}

pub(crate) fn collect_machine_and_states_in_context(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
) -> Vec<(String, String)> {
    collect_machine_and_states_with_alias_policy(ty, target_type, context, true)
}

pub(crate) fn collect_machine_and_states_strict(
    ty: &Type,
    target_type: &Type,
) -> Vec<(String, String)> {
    collect_machine_and_states_with_alias_policy(ty, target_type, None, false)
}

fn collect_machine_and_states_with_alias_policy(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
    allow_source_aliases: bool,
) -> Vec<(String, String)> {
    let mut targets = Vec::new();
    let mut visited = HashSet::new();
    collect_machine_targets_inner(
        ty,
        target_type,
        context,
        allow_source_aliases,
        &mut visited,
        &mut targets,
    );
    targets
}

fn collect_machine_targets_inner(
    ty: &Type,
    target_type: &Type,
    context: Option<&AliasResolutionContext>,
    allow_source_aliases: bool,
    visited: &mut HashSet<String>,
    targets: &mut Vec<(String, String)>,
) {
    let Some(type_path) = type_path(ty) else {
        return;
    };
    let Some(segment) = type_path.path.segments.last() else {
        return;
    };

    if machine_segment_matching_target(&type_path.path, target_type).is_some() {
        if let Some((machine, state, _)) = extract_machine_state_from_segment(segment) {
            push_unique_target(targets, machine, state);
        }
        return;
    }

    if allow_source_aliases
        && let Some(expanded_alias) = expand_source_type_alias(ty, context, visited)
    {
        let (expanded, alias_context, visit_key) = expanded_alias.into_parts();
        collect_machine_targets_inner(
            &expanded,
            target_type,
            Some(&alias_context),
            allow_source_aliases,
            visited,
            targets,
        );
        visited.remove(&visit_key);
        return;
    }

    match supported_wrapper(&type_path.path) {
        Some(SupportedWrapper::Option) => {
            if let Some(inner) = extract_first_generic_type_ref(&segment.arguments) {
                collect_machine_targets_inner(
                    inner,
                    target_type,
                    context,
                    allow_source_aliases,
                    visited,
                    targets,
                );
            }
        }
        Some(SupportedWrapper::Result | SupportedWrapper::Branch) => {
            if let Some(types) = extract_generic_type_refs(&segment.arguments) {
                for inner in types {
                    collect_machine_targets_inner(
                        inner,
                        target_type,
                        context,
                        allow_source_aliases,
                        visited,
                        targets,
                    );
                }
            }
        }
        None => {}
    }
}

fn push_unique_target(targets: &mut Vec<(String, String)>, machine: String, state: String) {
    if !targets.iter().any(|(existing_machine, existing_state)| {
        existing_machine == &machine && existing_state == &state
    }) {
        targets.push((machine, state));
    }
}