use std::collections::{HashMap, HashSet};
use crate::codegen::shared::binding_fields;
use crate::core::ir::{FieldDef, TypeDef};
use crate::e2e::codegen::call_ir::named_type;
use super::parse::{parse_path, segment_name};
use super::types::IrResultFieldMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum OptionalityRule {
DeclaredType,
Napi,
}
impl OptionalityRule {
pub(crate) fn for_language(language: &str) -> Self {
match language {
"node" | "typescript" => Self::Napi,
_ => Self::DeclaredType,
}
}
fn applies_to(self, field: &FieldDef, owner: &TypeDef) -> bool {
match self {
Self::DeclaredType => field.optional,
Self::Napi => crate::backends::napi::napi_field_is_optional(field, owner),
}
}
}
pub(super) fn build_ir_result_field_map(type_defs: &[TypeDef], rule: OptionalityRule) -> IrResultFieldMap {
let struct_names: HashSet<&str> = type_defs.iter().map(|type_def| type_def.name.as_str()).collect();
let mut field_types: HashMap<String, HashMap<String, String>> = HashMap::new();
let mut optional_fields: HashMap<String, HashSet<String>> = HashMap::new();
let mut declared_fields: HashMap<String, HashSet<String>> = HashMap::new();
for type_def in type_defs {
for field in binding_fields(&type_def.fields) {
declared_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
if rule.applies_to(field, type_def) {
optional_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());
}
}
}
IrResultFieldMap {
field_types,
optional_fields,
declared_fields,
root_type: None,
}
}
pub(super) fn is_optional_path(map: &IrResultFieldMap, path: &str) -> bool {
let Some((owner, leaf)) = walk_to_owner(map, path) else {
return false;
};
map.optional_fields
.get(owner)
.is_some_and(|fields| fields.contains(&leaf))
}
pub(super) fn root_declares_first_segment(map: &IrResultFieldMap, first_segment: &str) -> Option<bool> {
let root = map.root_type.as_deref()?;
let declared = map.declared_fields.get(root)?;
Some(declared.contains(first_segment))
}
fn walk_to_owner<'a>(map: &'a IrResultFieldMap, path: &str) -> Option<(&'a str, String)> {
let root = map.root_type.as_deref()?;
let segments = parse_path(path);
let (last, prefix) = segments.split_last()?;
let mut owner = root;
for segment in prefix {
let name = segment_name(segment)?;
owner = map.field_types.get(owner)?.get(name)?.as_str();
}
Some((owner, segment_name(last)?.to_string()))
}