use std::collections::HashSet;
use crate::backends::go::emission_facts::GoEmissionFacts;
use crate::codegen::shared::binding_fields;
use crate::core::ir::{EnumDef, FieldDef, TypeDef, TypeRef};
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 {
build_ir_result_field_map_with_enums(type_defs, &[], rule)
}
pub(super) fn build_ir_result_field_map_with_enums(
type_defs: &[TypeDef],
enums: &[EnumDef],
rule: OptionalityRule,
) -> IrResultFieldMap {
let emitted = GoEmissionFacts::new(type_defs, enums, HashSet::new(), HashSet::new());
build_go_ir_result_field_map(type_defs, enums, rule, &emitted)
}
pub(super) fn build_go_ir_result_field_map(
type_defs: &[TypeDef],
enums: &[EnumDef],
rule: OptionalityRule,
emitted: &GoEmissionFacts<'_>,
) -> IrResultFieldMap {
let names = GoFieldTypeNames {
structs: &emitted.structs,
enums: &emitted.unit_enums,
passthrough_enums: &emitted.passthrough_enums,
data_enums: &emitted.data_enums,
externally_tagged_struct_enums: &emitted.externally_tagged_struct_enums,
};
let mut map = IrResultFieldMap::default();
for type_def in type_defs
.iter()
.filter(|definition| emitted.structs.contains(definition.name.as_str()))
{
for field in binding_fields(&type_def.fields) {
record_ir_result_field(&mut map, type_def, field, rule, &names);
}
}
for enum_def in enums.iter().filter(|definition| {
emitted
.externally_tagged_struct_enums
.contains(definition.name.as_str())
}) {
record_externally_tagged_enum_variants(&mut map, enum_def, names.structs);
}
map
}
fn record_externally_tagged_enum_variants(
map: &mut IrResultFieldMap,
enum_def: &EnumDef,
struct_names: &HashSet<&str>,
) {
for variant in &enum_def.variants {
let Some(field) = variant.fields.first() else {
continue;
};
let TypeRef::Named(struct_type_name) = &field.ty else {
continue;
};
let wire_name = crate::codegen::naming::wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
);
map.declared_fields
.entry(enum_def.name.clone())
.or_default()
.insert(wire_name.clone());
map.pointer_fields
.entry(enum_def.name.clone())
.or_default()
.insert(wire_name.clone());
map.optional_fields
.entry(enum_def.name.clone())
.or_default()
.insert(wire_name.clone());
if struct_names.contains(struct_type_name.as_str()) {
map.field_types
.entry(enum_def.name.clone())
.or_default()
.insert(wire_name, struct_type_name.clone());
}
}
}
struct GoFieldTypeNames<'a> {
structs: &'a HashSet<&'a str>,
enums: &'a HashSet<&'a str>,
passthrough_enums: &'a HashSet<&'a str>,
data_enums: &'a HashSet<&'a str>,
externally_tagged_struct_enums: &'a HashSet<&'a str>,
}
fn record_ir_result_field(
map: &mut IrResultFieldMap,
type_def: &TypeDef,
field: &FieldDef,
rule: OptionalityRule,
names: &GoFieldTypeNames<'_>,
) {
map.declared_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
if rule.applies_to(field, type_def) {
map.optional_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
}
let go_type = crate::backends::go::go_struct_field_type(
type_def,
field,
names.enums,
names.passthrough_enums,
names.data_enums,
names.structs,
);
if go_type.starts_with('*') {
map.pointer_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
}
if named_type(&field.ty).is_some_and(|name| names.data_enums.contains(name)) {
map.data_interface_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
}
record_ir_result_field_kind(map, type_def, field, names);
}
fn record_ir_result_field_kind(
map: &mut IrResultFieldMap,
type_def: &TypeDef,
field: &FieldDef,
names: &GoFieldTypeNames<'_>,
) {
if type_ref_is_display_safe(&field.ty) {
map.display_safe_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
}
let Some(named) = named_type(&field.ty) else {
return;
};
let target = if names.structs.contains(named) || names.externally_tagged_struct_enums.contains(named) {
&mut map.field_types
} else {
map.unresolvable_named_fields
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone());
return;
};
target
.entry(type_def.name.clone())
.or_default()
.insert(field.name.clone(), named.to_string());
}
pub(super) fn type_ref_is_display_safe(ty: &TypeRef) -> bool {
matches!(ty, TypeRef::String | TypeRef::Char | TypeRef::Primitive(_))
}
pub(super) fn is_optional_path(map: &IrResultFieldMap, path: &str) -> bool {
optionality_at_path(map, path).unwrap_or(false)
}
pub(super) fn optionality_at_path(map: &IrResultFieldMap, path: &str) -> Option<bool> {
let root = map.root_type.as_deref()?;
let (owner, leaf) = walk_to_owner_from(map, root, path)?;
Some(
map.optional_fields
.get(owner)
.is_some_and(|fields| fields.contains(&leaf)),
)
}
pub(super) fn pointer_at_path(map: &IrResultFieldMap, path: &str) -> Option<bool> {
let root = map.root_type.as_deref()?;
let (owner, leaf) = walk_to_owner_from(map, root, path)?;
Some(
map.pointer_fields
.get(owner)
.is_some_and(|fields| fields.contains(&leaf)),
)
}
pub(super) fn data_interface_at_path(map: &IrResultFieldMap, path: &str) -> Option<bool> {
let root = map.root_type.as_deref()?;
let (owner, leaf) = walk_to_owner_from(map, root, path)?;
Some(
map.data_interface_fields
.get(owner)
.is_some_and(|fields| fields.contains(&leaf)),
)
}
pub(super) fn is_optional_path_from(map: &IrResultFieldMap, root: &str, path: &str) -> bool {
let Some((owner, leaf)) = walk_to_owner_from(map, root, path) else {
return false;
};
map.optional_fields
.get(owner)
.is_some_and(|fields| fields.contains(&leaf))
}
pub(super) fn leaf_is_named_type(map: &IrResultFieldMap, path: &str) -> bool {
let Some((owner, leaf)) = walk_to_owner(map, path) else {
return false;
};
map.field_types
.get(owner)
.is_some_and(|fields| fields.contains_key(&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))
}
pub(super) fn root_declares_path(map: &IrResultFieldMap, path: &str) -> Option<bool> {
let root = map.root_type.as_deref()?;
type_declares_path(map, root, path)
}
pub(super) fn type_declares_path(map: &IrResultFieldMap, owner_type: &str, path: &str) -> Option<bool> {
let segments = parse_path(path);
let (last, prefix) = segments.split_last()?;
let mut owner = owner_type;
for segment in prefix {
let name = segment_name(segment)?;
if !map.declared_fields.get(owner)?.contains(name) {
return Some(false);
}
owner = map.field_types.get(owner)?.get(name)?.as_str();
}
Some(map.declared_fields.get(owner)?.contains(segment_name(last)?))
}
pub(super) fn path_crosses_unwalkable_field(map: &IrResultFieldMap, 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;
};
let Some(declared) = map.declared_fields.get(owner) else {
return false;
};
if !declared.contains(name) {
return false;
}
if map
.unresolvable_named_fields
.get(owner)
.is_some_and(|fields| fields.contains(name))
{
return true;
}
match map.field_types.get(owner).and_then(|fields| fields.get(name)) {
Some(next) => owner = next.as_str(),
None => return false,
}
}
false
}
fn walk_to_owner<'a>(map: &'a IrResultFieldMap, path: &str) -> Option<(&'a str, String)> {
let root = map.root_type.as_deref()?;
walk_to_owner_from(map, root, path)
}
fn walk_to_owner_from<'a>(map: &'a IrResultFieldMap, root: &'a str, path: &str) -> Option<(&'a str, String)> {
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()))
}
#[cfg(test)]
mod display_safe_field_tests {
use super::*;
use crate::core::ir::PrimitiveType;
fn field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
#[test]
fn type_ref_is_display_safe_only_for_bare_scalars() {
let cases: &[(&str, TypeRef, bool)] = &[
("string", TypeRef::String, true),
("char", TypeRef::Char, true),
("bool", TypeRef::Primitive(PrimitiveType::Bool), true),
("i32", TypeRef::Primitive(PrimitiveType::I32), true),
("f64", TypeRef::Primitive(PrimitiveType::F64), true),
(
"option_of_string_is_unsafe",
TypeRef::Optional(Box::new(TypeRef::String)),
false,
),
(
"vec_of_string_is_unsafe",
TypeRef::Vec(Box::new(TypeRef::String)),
false,
),
(
"nested_vec_of_string_is_unsafe",
TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String)))),
false,
),
(
"map_is_unsafe",
TypeRef::Map(Box::new(TypeRef::String), Box::new(TypeRef::String)),
false,
),
("bytes_is_unsafe", TypeRef::Bytes, false),
("named_is_unsafe", TypeRef::Named("Widget".to_string()), false),
("path_is_unsafe", TypeRef::Path, false),
("json_is_unsafe", TypeRef::Json, false),
("duration_is_unsafe", TypeRef::Duration, false),
("unit_is_unsafe", TypeRef::Unit, false),
];
for (name, ty, expected) in cases {
assert_eq!(
type_ref_is_display_safe(ty),
*expected,
"case `{name}` expected display-safe={expected}"
);
}
}
#[test]
fn build_ir_result_field_map_populates_display_safe_fields_per_owner_type() {
let type_defs = vec![TypeDef {
name: "Table".to_string(),
fields: vec![
field("name", TypeRef::String),
field("cells", TypeRef::Vec(Box::new(TypeRef::Vec(Box::new(TypeRef::String))))),
],
..TypeDef::default()
}];
let map = build_ir_result_field_map(&type_defs, OptionalityRule::DeclaredType);
assert!(map.display_safe_fields.get("Table").is_some_and(|f| f.contains("name")));
assert!(
!map.display_safe_fields
.get("Table")
.is_some_and(|f| f.contains("cells"))
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{FieldDef, TypeDef};
fn field(name: &str, ty: crate::core::ir::TypeRef) -> FieldDef {
FieldDef {
name: name.to_string(),
ty,
..FieldDef::default()
}
}
fn type_defs_with_unresolvable_variant_field() -> Vec<TypeDef> {
vec![
TypeDef {
name: "Envelope".to_string(),
fields: vec![field(
"metadata",
crate::core::ir::TypeRef::Named("Metadata".to_string()),
)],
..TypeDef::default()
},
TypeDef {
name: "Metadata".to_string(),
fields: vec![
field("format", crate::core::ir::TypeRef::Named("VariantInfo".to_string())),
field("title", crate::core::ir::TypeRef::String),
],
..TypeDef::default()
},
]
}
fn anchored_map(type_defs: &[TypeDef]) -> IrResultFieldMap {
let mut map = build_ir_result_field_map(type_defs, OptionalityRule::DeclaredType);
map.root_type = Some("Envelope".to_string());
map
}
#[test]
fn a_path_continuing_past_a_declared_but_unwalkable_field_crosses() {
let map = anchored_map(&type_defs_with_unresolvable_variant_field());
assert!(path_crosses_unwalkable_field(&map, "metadata.format.variant.detail"));
}
#[test]
fn a_path_stopping_at_the_unwalkable_field_does_not_cross() {
let map = anchored_map(&type_defs_with_unresolvable_variant_field());
assert!(!path_crosses_unwalkable_field(&map, "metadata.format"));
}
#[test]
fn a_path_through_a_real_struct_field_does_not_cross() {
let map = anchored_map(&type_defs_with_unresolvable_variant_field());
assert!(!path_crosses_unwalkable_field(&map, "metadata.title"));
}
#[test]
fn a_path_through_an_undeclared_segment_does_not_cross() {
let map = anchored_map(&type_defs_with_unresolvable_variant_field());
assert!(!path_crosses_unwalkable_field(&map, "not_a_real_field.anything"));
}
#[test]
fn a_path_through_a_field_with_no_named_type_at_all_does_not_cross() {
let type_defs = vec![TypeDef {
name: "Envelope".to_string(),
fields: vec![field("payload", crate::core::ir::TypeRef::Json)],
..TypeDef::default()
}];
let map = anchored_map(&type_defs);
assert!(!path_crosses_unwalkable_field(&map, "payload.anything"));
}
#[test]
fn no_anchored_root_never_crosses() {
let mut map = build_ir_result_field_map(
&type_defs_with_unresolvable_variant_field(),
OptionalityRule::DeclaredType,
);
map.root_type = None;
assert!(!path_crosses_unwalkable_field(&map, "metadata.format.variant.detail"));
}
}