use std::collections::{HashMap, HashSet};
use crate::core::ir::TypeDef;
use crate::e2e::codegen::call_ir::named_type;
use super::parse::{parse_path, segment_name};
use super::types::IrCollectionMap;
pub(super) fn build_ir_collection_map(type_defs: &[TypeDef]) -> IrCollectionMap {
let struct_names: HashSet<&str> = type_defs.iter().map(|t| t.name.as_str()).collect();
let mut field_types: HashMap<String, HashMap<String, String>> = HashMap::new();
let mut collection_fields: HashMap<String, HashSet<String>> = HashMap::new();
for type_def in type_defs {
for field in &type_def.fields {
if is_vec_type(&field.ty) {
collection_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
}
let Some(named) = named_type(&field.ty) else {
continue;
};
if struct_names.contains(named) {
field_types
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone(), named.to_string());
}
}
}
IrCollectionMap {
field_types,
collection_fields,
root_type: None,
}
}
fn is_vec_type(ty: &crate::core::ir::TypeRef) -> bool {
match ty {
crate::core::ir::TypeRef::Vec(_) => true,
crate::core::ir::TypeRef::Optional(inner) => is_vec_type(inner),
_ => false,
}
}
pub(super) fn is_collection_path(map: &IrCollectionMap, path: &str) -> bool {
let Some(root) = map.root_type.as_deref() else {
return false;
};
let segments = parse_path(path);
let Some((last, prefix)) = segments.split_last() else {
return false;
};
let mut owner = root;
for segment in prefix {
let Some(name) = segment_name(segment) else {
return false;
};
match map.field_types.get(owner).and_then(|fields| fields.get(name)) {
Some(next) => owner = next.as_str(),
None => return false,
}
}
let Some(name) = segment_name(last) else {
return false;
};
map.collection_fields
.get(owner)
.is_some_and(|fields| fields.contains(name))
}
pub(super) fn element_type_at_path(map: &IrCollectionMap, path: &str) -> Option<String> {
let root = map.root_type.as_deref()?;
let segments = parse_path(path);
let mut owner = root;
for segment in &segments {
let name = segment_name(segment)?;
owner = map.field_types.get(owner)?.get(name)?.as_str();
}
Some(owner.to_string())
}
#[cfg(test)]
mod element_type_at_path_tests {
use super::*;
use crate::core::ir::{FieldDef, TypeDef, TypeRef};
fn field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
fn type_defs() -> Vec<TypeDef> {
vec![
TypeDef {
name: "Container".to_string(),
fields: vec![field("rows", TypeRef::Vec(Box::new(TypeRef::Named("Row".to_string()))))],
..TypeDef::default()
},
TypeDef {
name: "Row".to_string(),
fields: vec![field("values", TypeRef::Vec(Box::new(TypeRef::String)))],
..TypeDef::default()
},
]
}
fn anchored_map() -> IrCollectionMap {
let mut map = build_ir_collection_map(&type_defs());
map.root_type = Some("Container".to_string());
map
}
#[test]
fn a_vec_field_resolves_to_its_element_type() {
assert_eq!(element_type_at_path(&anchored_map(), "rows"), Some("Row".to_string()));
}
#[test]
fn an_indexed_vec_field_resolves_the_same_way() {
assert_eq!(
element_type_at_path(&anchored_map(), "rows[0]"),
Some("Row".to_string())
);
}
#[test]
fn an_unknown_field_resolves_to_nothing() {
assert_eq!(element_type_at_path(&anchored_map(), "not_a_real_field"), None);
}
#[test]
fn no_anchored_root_resolves_to_nothing() {
let map = build_ir_collection_map(&type_defs());
assert_eq!(element_type_at_path(&map, "rows"), None);
}
}