use crate::backends::swift::gen_bindings::dto::{swift_type_based_default, swift_typed_default_literal};
use crate::codegen::shared::binding_fields;
use crate::core::ir::{ApiSurface, DefaultValue, FieldDef, TypeDef, TypeRef};
use std::collections::HashSet;
pub(crate) fn swift_memberwise_default_literal(field: &FieldDef) -> Option<String> {
let collection_type = match &field.ty {
TypeRef::Optional(inner) => matches!(inner.as_ref(), TypeRef::Vec(_) | TypeRef::Map(_, _)),
TypeRef::Vec(_) | TypeRef::Map(_, _) => true,
_ => false,
};
if collection_type && (field.optional || matches!(field.ty, TypeRef::Optional(_))) {
return None;
}
field
.typed_default
.as_ref()
.and_then(swift_typed_default_literal)
.or_else(|| {
let uses_type_zero = matches!(field.typed_default, Some(DefaultValue::Empty))
|| (field.typed_default.is_none() && field.has_bare_serde_default());
if !uses_type_zero {
return None;
}
match &field.ty {
TypeRef::Vec(_) => Some("[]".to_string()),
TypeRef::Map(_, _) => Some("[:]".to_string()),
_ => None,
}
})
}
pub(crate) fn swift_constructor_default_literal(owner: &TypeDef, field: &FieldDef) -> Option<String> {
swift_memberwise_default_literal(field).or_else(|| match owner.constructor_extension_value(field)? {
DefaultValue::Empty if !field.optional => swift_type_based_default(&field.ty),
_ => None,
})
}
pub(crate) fn compute_zero_arg_constructible_names(
api: &ApiSurface,
known_dto_names: &HashSet<String>,
) -> HashSet<String> {
api.types
.iter()
.filter(|t| !t.is_trait && !t.is_opaque && t.has_serde && !t.fields.is_empty())
.filter(|t| known_dto_names.contains(&t.name))
.filter(|t| {
binding_fields(&t.fields).all(|field| {
let already_optional = matches!(&field.ty, TypeRef::Optional(_));
field.optional || already_optional || swift_constructor_default_literal(t, field).is_some()
})
})
.map(|t| t.name.clone())
.collect()
}
pub(crate) fn zero_arg_named_default(
typed_default: &Option<DefaultValue>,
ty: &TypeRef,
swift_ty: &str,
zero_arg_constructible_names: &HashSet<String>,
) -> Option<String> {
if !matches!(typed_default, Some(DefaultValue::Empty)) {
return None;
}
let TypeRef::Named(name) = ty else {
return None;
};
zero_arg_constructible_names
.contains(name)
.then(|| format!("{swift_ty}()"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn zero_arg_detection_uses_the_same_recorded_defaults_as_the_initializer() {
let mut owner = TypeDef {
name: "Options".into(),
has_serde: true,
fields: vec![
FieldDef {
name: "seed".into(),
ty: TypeRef::Optional(Box::new(TypeRef::String)),
..Default::default()
},
FieldDef {
name: "added_value".into(),
ty: TypeRef::String,
typed_default: Some(DefaultValue::Empty),
..Default::default()
},
],
..Default::default()
};
let known = HashSet::from([owner.name.clone()]);
let names = |owner| {
compute_zero_arg_constructible_names(
&ApiSurface {
types: vec![owner],
..Default::default()
},
&known,
)
};
assert!(names(owner.clone()).is_empty());
owner.version.legacy_constructor_fields = vec!["seed".into()];
assert_eq!(names(owner.clone()), known);
owner.fields[1].typed_default = Some(DefaultValue::FunctionCall("unknown".into()));
assert!(names(owner).is_empty());
}
}