weaveffi-core 0.9.0

Generator trait, orchestrator, validation, and shared utilities for WeaveFFI
Documentation
//! Type-reference resolution: rewrites bare struct names into enum
//! references and qualifies cross-module references with the owning
//! module's dot-joined path. Runs after the rule checks pass.

use std::collections::{BTreeMap, BTreeSet};
use weaveffi_ir::ir::{Api, Module, TypeRef};

pub fn resolve_type_refs(api: &mut Api) {
    let mut global_types: BTreeMap<String, (String, bool)> = BTreeMap::new();
    for module in &api.modules {
        index_module_types(module, "", &mut global_types);
    }

    for module in &mut api.modules {
        resolve_module_type_refs(module, "", &global_types);
    }
}

/// Recursively index every struct/enum in the module tree by its bare name,
/// recording the owner's *dot-joined* module path (e.g. `graphics.shapes`) so
/// cross-module and nested references can be auto-qualified. First definition
/// wins on a name clash (pre-existing behavior).
fn index_module_types(
    module: &Module,
    parent_path: &str,
    out: &mut BTreeMap<String, (String, bool)>,
) {
    let path = join_module_path(parent_path, &module.name);
    for s in &module.structs {
        out.entry(s.name.clone()).or_insert((path.clone(), false));
    }
    for e in &module.enums {
        out.entry(e.name.clone()).or_insert((path.clone(), true));
    }
    for child in &module.modules {
        index_module_types(child, &path, out);
    }
}

/// Resolve every type reference inside `module` (and recursively its
/// submodules), qualifying cross-module references against `global_types`.
/// `current_module` is tracked as a dot-joined path so multi-level nesting
/// resolves correctly.
fn resolve_module_type_refs(
    module: &mut Module,
    parent_path: &str,
    global_types: &BTreeMap<String, (String, bool)>,
) {
    let module_path = join_module_path(parent_path, &module.name);
    let local_enum_names: BTreeSet<String> = module.enums.iter().map(|e| e.name.clone()).collect();
    let local_struct_names: BTreeSet<String> =
        module.structs.iter().map(|s| s.name.clone()).collect();
    for f in &mut module.functions {
        for p in &mut f.params {
            resolve_single_type_ref(
                &mut p.ty,
                &local_enum_names,
                &local_struct_names,
                &module_path,
                global_types,
            );
        }
        if let Some(ret) = &mut f.returns {
            resolve_single_type_ref(
                ret,
                &local_enum_names,
                &local_struct_names,
                &module_path,
                global_types,
            );
        }
    }
    for s in &mut module.structs {
        for field in &mut s.fields {
            resolve_single_type_ref(
                &mut field.ty,
                &local_enum_names,
                &local_struct_names,
                &module_path,
                global_types,
            );
        }
    }
    for child in &mut module.modules {
        resolve_module_type_refs(child, &module_path, global_types);
    }
}

/// Join a parent module path with a child segment using `.`. A top-level
/// module (empty parent) is just its own name, preserving the single-segment
/// behavior that existed before nested resolution.
fn join_module_path(parent_path: &str, name: &str) -> String {
    if parent_path.is_empty() {
        name.to_string()
    } else {
        format!("{parent_path}.{name}")
    }
}

fn resolve_single_type_ref(
    ty: &mut TypeRef,
    local_enum_names: &BTreeSet<String>,
    local_struct_names: &BTreeSet<String>,
    current_module: &str,
    global_types: &BTreeMap<String, (String, bool)>,
) {
    match ty {
        TypeRef::Struct(name) if local_enum_names.contains(name.as_str()) => {
            let name = std::mem::take(name);
            *ty = TypeRef::Enum(name);
        }
        TypeRef::Struct(name) if !local_struct_names.contains(name.as_str()) => {
            if let Some((mod_name, is_enum)) = global_types.get(name.as_str()) {
                if mod_name != current_module {
                    let qualified = format!("{mod_name}.{name}");
                    if *is_enum {
                        *ty = TypeRef::Enum(qualified);
                    } else {
                        *name = qualified;
                    }
                }
            }
        }
        // A typed handle's target is always a struct. When that struct lives in
        // a different module (e.g. `handle<Store>` in a `kv.stats` submodule
        // referring to `kv.Store`), qualify it to the owner's path so the C ABI
        // lowering and every generator emit the owner's symbol prefix rather
        // than the referrer's. Mirrors the `Struct` arm above.
        TypeRef::TypedHandle(name) if !local_struct_names.contains(name.as_str()) => {
            if let Some((mod_name, _is_enum)) = global_types.get(name.as_str()) {
                if mod_name != current_module {
                    *name = format!("{mod_name}.{name}");
                }
            }
        }
        TypeRef::Optional(inner) | TypeRef::List(inner) | TypeRef::Iterator(inner) => {
            resolve_single_type_ref(
                inner,
                local_enum_names,
                local_struct_names,
                current_module,
                global_types,
            );
        }
        TypeRef::Map(k, v) => {
            resolve_single_type_ref(
                k,
                local_enum_names,
                local_struct_names,
                current_module,
                global_types,
            );
            resolve_single_type_ref(
                v,
                local_enum_names,
                local_struct_names,
                current_module,
                global_types,
            );
        }
        _ => {}
    }
}

pub fn find_type_in_api(api: &Api, name: &str) -> Option<(String, bool)> {
    fn search(module: &Module, parent_path: &str, name: &str) -> Option<(String, bool)> {
        let path = join_module_path(parent_path, &module.name);
        if module.structs.iter().any(|s| s.name == name) {
            return Some((path, false));
        }
        if module.enums.iter().any(|e| e.name == name) {
            return Some((path, true));
        }
        module
            .modules
            .iter()
            .find_map(|child| search(child, &path, name))
    }
    api.modules
        .iter()
        .find_map(|module| search(module, "", name))
}