use heck::ToSnakeCase;
use std::collections::HashMap;
fn peel_optional(ty: &crate::core::ir::TypeRef) -> &crate::core::ir::TypeRef {
match ty {
crate::core::ir::TypeRef::Optional(inner) => peel_optional(inner),
other => other,
}
}
pub(super) fn enum_fields_c_types_from_ir(
type_defs: &[crate::core::ir::TypeDef],
enums: &[crate::core::ir::EnumDef],
) -> HashMap<String, String> {
let mut derived = HashMap::new();
for type_def in type_defs {
let parent_snake = type_def.name.to_snake_case();
for field in &type_def.fields {
let crate::core::ir::TypeRef::Named(type_name) = peel_optional(&field.ty) else {
continue;
};
if !enums.iter().any(|e| &e.name == type_name) {
continue;
}
derived.insert(
format!("{parent_snake}.{}", field.name.to_snake_case()),
type_name.clone(),
);
}
}
derived
}
#[cfg(test)]
mod tests {
use super::*;
use crate::e2e::codegen::c::{enum_fields_from_ir, try_emit_enum_accessor};
use std::collections::HashSet;
#[test]
fn enum_fields_c_types_from_ir_recovers_an_undeclared_enum_leaf_field() {
let enums = vec![crate::core::ir::EnumDef {
name: "DataNodeKind".into(),
..crate::core::ir::EnumDef::default()
}];
let type_defs = vec![crate::core::ir::TypeDef {
name: "DataNode".into(),
fields: vec![crate::core::ir::FieldDef {
name: "kind".into(),
ty: crate::core::ir::TypeRef::Named("DataNodeKind".into()),
..crate::core::ir::FieldDef::default()
}],
..crate::core::ir::TypeDef::default()
}];
let derived = enum_fields_c_types_from_ir(&type_defs, &enums);
assert_eq!(
derived.get("data_node.kind").map(String::as_str),
Some("DataNodeKind"),
"got: {derived:?}"
);
}
#[test]
fn enum_fields_c_types_from_ir_sees_through_optional() {
let enums = vec![crate::core::ir::EnumDef {
name: "DataNodeKind".into(),
..crate::core::ir::EnumDef::default()
}];
let type_defs = vec![crate::core::ir::TypeDef {
name: "DataNode".into(),
fields: vec![crate::core::ir::FieldDef {
name: "kind".into(),
ty: crate::core::ir::TypeRef::Optional(Box::new(crate::core::ir::TypeRef::Named(
"DataNodeKind".into(),
))),
..crate::core::ir::FieldDef::default()
}],
..crate::core::ir::TypeDef::default()
}];
let derived = enum_fields_c_types_from_ir(&type_defs, &enums);
assert_eq!(derived.get("data_node.kind").map(String::as_str), Some("DataNodeKind"));
}
#[test]
fn enum_fields_c_types_from_ir_ignores_non_enum_named_fields() {
let enums = vec![crate::core::ir::EnumDef {
name: "DataNodeKind".into(),
..crate::core::ir::EnumDef::default()
}];
let type_defs = vec![crate::core::ir::TypeDef {
name: "ProcessResult".into(),
fields: vec![crate::core::ir::FieldDef {
name: "metrics".into(),
ty: crate::core::ir::TypeRef::Named("FileMetrics".into()),
..crate::core::ir::FieldDef::default()
}],
..crate::core::ir::TypeDef::default()
}];
let derived = enum_fields_c_types_from_ir(&type_defs, &enums);
assert!(derived.is_empty(), "got: {derived:?}");
}
#[test]
fn enum_leaf_accessor_fires_with_zero_operator_config() {
let enums = vec![crate::core::ir::EnumDef {
name: "DataNodeKind".into(),
..crate::core::ir::EnumDef::default()
}];
let type_defs = vec![crate::core::ir::TypeDef {
name: "DataNode".into(),
fields: vec![crate::core::ir::FieldDef {
name: "kind".into(),
ty: crate::core::ir::TypeRef::Named("DataNodeKind".into()),
..crate::core::ir::FieldDef::default()
}],
..crate::core::ir::TypeDef::default()
}];
let mut fields_c_types: HashMap<String, String> = HashMap::new();
for (key, type_name) in enum_fields_c_types_from_ir(&type_defs, &enums) {
fields_c_types.entry(key).or_insert(type_name);
}
let mut fields_enum: HashSet<String> = HashSet::new();
fields_enum.extend(enum_fields_from_ir(&fields_c_types, &enums));
let mut out = String::new();
let mut handles = Vec::new();
let fired = try_emit_enum_accessor(
&mut out,
"ts_pack",
"TS_PACK",
"data.kind",
"kind",
"data_node",
"ts_pack_data_node_kind",
"data_handle",
"data_kind",
&fields_c_types,
&fields_enum,
&mut handles,
);
assert!(fired, "enum accessor must fire with zero fields_c_types config");
assert!(
out.contains("ts_pack_data_node_kind_to_string("),
"must convert via _to_string, not leave a bare handle for strcmp: {out}"
);
assert!(!out.contains("char* data_kind = ts_pack_data_node_kind("), "{out}");
}
}