use super::*;
use crate::codegen::generators::AsyncPattern;
use crate::core::ir::{CoreWrapper, EnumVariant, FieldDef, MethodDef, PrimitiveType, TypeRef};
fn variant(name: &str, fields: Vec<FieldDef>) -> EnumVariant {
EnumVariant {
name: name.to_string(),
fields,
doc: String::new(),
is_default: false,
serde_rename: None,
binding_excluded: false,
binding_exclusion_reason: None,
is_tuple: false,
originally_had_data_fields: false,
cfg: None,
version: Default::default(),
}
}
fn field(name: &str) -> FieldDef {
FieldDef {
name: name.to_string(),
ty: TypeRef::String,
optional: false,
default: None,
doc: String::new(),
sanitized: false,
is_boxed: false,
type_rust_path: None,
cfg: None,
typed_default: None,
core_wrapper: CoreWrapper::None,
vec_inner_core_wrapper: CoreWrapper::None,
newtype_wrapper: None,
serde_rename: None,
serde_flatten: false,
binding_excluded: false,
binding_exclusion_reason: None,
original_type: None,
}
}
fn enum_def(name: &str, variants: Vec<EnumVariant>) -> EnumDef {
EnumDef {
name: name.to_string(),
rust_path: format!("crate::{name}"),
original_rust_path: String::new(),
variants,
methods: vec![],
doc: String::new(),
cfg: None,
is_copy: false,
has_serde: true,
has_default: false,
serde_tag: None,
serde_untagged: false,
serde_rename_all: None,
binding_excluded: false,
binding_exclusion_reason: None,
excluded_variants: vec![],
version: Default::default(),
}
}
#[test]
fn gen_pyo3_data_enum_emits_string_methods() {
let generated = gen_pyo3_data_enum(
&enum_def("StructureKind", vec![variant("Other", vec![field("value")])]),
"core",
);
assert!(
generated.contains("fn __str__(&self) -> PyResult<String>"),
"{generated}"
);
assert!(generated.contains("serde_json::to_value(&self.inner)"), "{generated}");
assert!(
generated.contains("fn __repr__(&self) -> PyResult<String>"),
"{generated}"
);
}
#[test]
fn gen_pyo3_data_enum_emits_default_when_core_derives_default() {
let mut default_variant = variant("Pending", vec![]);
default_variant.is_default = true;
let generated = gen_pyo3_data_enum(
&enum_def(
"EnrichStatus",
vec![default_variant, variant("Done", vec![field("value")])],
),
"core",
);
assert!(
generated.contains("impl Default for EnrichStatus"),
"expected delegating Default impl when a variant is #[default]: {generated}"
);
assert!(generated.contains("Self { inner: Default::default() }"), "{generated}");
}
#[test]
fn gen_pyo3_data_enum_emits_default_when_core_has_manual_default() {
let mut enum_def = enum_def(
"ClassificationMode",
vec![variant("Known", vec![]), variant("Custom", vec![field("value")])],
);
enum_def.has_default = true;
let generated = gen_pyo3_data_enum(&enum_def, "core");
assert!(
generated.contains("impl Default for ClassificationMode"),
"expected delegating Default impl when the core enum has a manual Default impl: {generated}"
);
assert!(generated.contains("Self { inner: Default::default() }"), "{generated}");
}
#[test]
fn gen_pyo3_data_enum_omits_default_when_core_lacks_default() {
let generated = gen_pyo3_data_enum(
&enum_def(
"ChunkingReason",
vec![variant("TooLong", vec![field("limit")]), variant("Forced", vec![])],
),
"core",
);
assert!(
!generated.contains("impl Default for ChunkingReason"),
"expected no Default impl when no variant is #[default]: {generated}"
);
assert!(
!generated.contains("inner: Default::default()"),
"expected no inner: Default::default() when core lacks Default: {generated}"
);
}
#[test]
fn gen_pyo3_data_enum_wraps_string_for_internally_tagged_enum() {
let mut def = enum_def(
"ImageOutputFormat",
vec![variant("Png", vec![]), variant("Jpeg", vec![field("quality")])],
);
def.serde_tag = Some("type".to_string());
let generated = gen_pyo3_data_enum(&def, "core");
assert!(
generated.contains(r#"serde_json::to_string(&serde_json::json!({ "type": s }))"#),
"expected tagged string wrap for internally-tagged enum: {generated}"
);
assert!(
!generated.contains("serde_json::to_string(&s)"),
"internally-tagged enum must not emit the bare-string path: {generated}"
);
}
#[test]
fn gen_pyo3_data_enum_keeps_bare_string_for_externally_tagged_enum() {
let generated = gen_pyo3_data_enum(
&enum_def("StructureKind", vec![variant("Other", vec![field("value")])]),
"core",
);
assert!(
generated.contains("serde_json::to_string(&s)"),
"expected bare-string path for externally-tagged enum: {generated}"
);
assert!(
!generated.contains("serde_json::json!({"),
"externally-tagged enum must not wrap the string in a tag object: {generated}"
);
}
fn typed_field(name: &str, ty: TypeRef) -> FieldDef {
FieldDef { ty, ..field(name) }
}
fn static_method(name: &str) -> MethodDef {
MethodDef {
name: name.to_string(),
is_static: true,
..Default::default()
}
}
#[test]
fn variant_constructors_emit_factory_per_struct_variant() {
use crate::codegen::type_mapper::IdentityMapper;
let mut def = enum_def(
"Shape",
vec![
variant(
"Circle",
vec![typed_field("radius", TypeRef::Primitive(PrimitiveType::F64))],
),
variant(
"Rect",
vec![
typed_field("width", TypeRef::Primitive(PrimitiveType::F64)),
typed_field("height", TypeRef::Primitive(PrimitiveType::F64)),
],
),
],
);
def.serde_tag = Some("type".to_string());
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(generated.contains(r#"#[pyo3(name = "circle")]"#), "{generated}");
assert!(
generated.contains("pub fn _factory_circle(radius: f64) -> Self"),
"{generated}"
);
assert!(
generated.contains("Self { inner: crate::Shape::Circle { radius } }"),
"{generated}"
);
assert!(generated.contains(r#"#[pyo3(name = "rect")]"#), "{generated}");
assert!(
generated.contains("pub fn _factory_rect(width: f64, height: f64) -> Self"),
"{generated}"
);
assert!(
generated.contains("Self { inner: crate::Shape::Rect { width, height } }"),
"{generated}"
);
}
#[test]
fn variant_constructors_convert_named_dto_fields() {
use crate::codegen::type_mapper::IdentityMapper;
let def = enum_def(
"Wrapper",
vec![variant(
"Llm",
vec![typed_field("llm", TypeRef::Named("LlmConfig".to_string()))],
)],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains("pub fn _factory_llm(llm: LlmConfig) -> Self"),
"{generated}"
);
assert!(
generated.contains("Self { inner: crate::Wrapper::Llm { llm: llm.into() } }"),
"{generated}"
);
assert!(!generated.contains("llm_core"), "{generated}");
}
#[test]
fn variant_constructors_box_named_field_when_core_is_boxed() {
use crate::codegen::type_mapper::IdentityMapper;
let boxed = FieldDef {
is_boxed: true,
..typed_field("result", TypeRef::Named("CrawlPageResult".to_string()))
};
let def = enum_def("CrawlEvent", vec![variant("Page", vec![boxed])]);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains("Self { inner: crate::CrawlEvent::Page { result: Box::new(result.into()) } }"),
"{generated}"
);
}
#[test]
fn variant_constructors_coerce_dataclass_backed_dto_field() {
use crate::codegen::generators::gen_pyo3_data_enum_with_coercion;
use crate::codegen::type_mapper::IdentityMapper;
use ahash::AHashSet;
let def = enum_def(
"Wrapper",
vec![variant(
"Llm",
vec![typed_field("llm", TypeRef::Named("LlmConfig".to_string()))],
)],
);
let coercible: AHashSet<&str> = ["LlmConfig"].into_iter().collect();
let generated = gen_pyo3_data_enum_with_coercion(&def, "core", Some(&IdentityMapper), &coercible);
assert!(
generated
.contains("pub fn _factory_llm(py: Python<'_>, llm: &Bound<'_, pyo3::types::PyAny>) -> PyResult<Self>"),
"{generated}"
);
assert!(
generated.contains(
"Ok(Self { inner: crate::Wrapper::Llm { llm: __alef_coerce_dto(py, llm, __ALEF_WIRE_LLM_CONFIG)? } })"
),
"{generated}"
);
assert!(!generated.contains("llm: llm.into()"), "{generated}");
assert!(
crate::codegen::generators::data_enum_needs_dto_coercion(&def, &coercible),
"expected coercion to be flagged"
);
}
#[test]
fn variant_constructors_coerce_only_dto_leaving_primitive_typed() {
use crate::codegen::generators::gen_pyo3_data_enum_with_coercion;
use crate::codegen::type_mapper::IdentityMapper;
use ahash::AHashSet;
let def = enum_def(
"Job",
vec![variant(
"Run",
vec![
typed_field("retries", TypeRef::Primitive(PrimitiveType::U32)),
typed_field("config", TypeRef::Named("RunConfig".to_string())),
],
)],
);
let coercible: AHashSet<&str> = ["RunConfig"].into_iter().collect();
let generated = gen_pyo3_data_enum_with_coercion(&def, "core", Some(&IdentityMapper), &coercible);
assert!(
generated.contains(
"pub fn _factory_run(py: Python<'_>, retries: u32, config: &Bound<'_, pyo3::types::PyAny>) -> PyResult<Self>"
),
"{generated}"
);
assert!(
generated.contains(
"Ok(Self { inner: crate::Job::Run { retries, config: __alef_coerce_dto(py, config, __ALEF_WIRE_RUN_CONFIG)? } })"
),
"{generated}"
);
}
#[test]
fn variant_constructors_box_optional_named_field_when_core_is_boxed() {
use crate::codegen::type_mapper::IdentityMapper;
let boxed_opt = FieldDef {
is_boxed: true,
optional: true,
..typed_field("result", TypeRef::Named("CrawlPageResult".to_string()))
};
let def = enum_def("CrawlEvent", vec![variant("Page", vec![boxed_opt])]);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains("result: result.map(Into::into).map(Box::new)"),
"{generated}"
);
}
#[test]
fn variant_constructors_skip_coercion_for_non_dataclass_named_field() {
use crate::codegen::generators::{data_enum_needs_dto_coercion, gen_pyo3_data_enum_with_coercion};
use crate::codegen::type_mapper::IdentityMapper;
use ahash::AHashSet;
let def = enum_def(
"Wrapper",
vec![variant(
"Llm",
vec![typed_field("llm", TypeRef::Named("LlmConfig".to_string()))],
)],
);
let empty: AHashSet<&str> = AHashSet::new();
let generated = gen_pyo3_data_enum_with_coercion(&def, "core", Some(&IdentityMapper), &empty);
assert!(
generated.contains("pub fn _factory_llm(llm: LlmConfig) -> Self"),
"{generated}"
);
assert!(generated.contains("llm: llm.into()"), "{generated}");
assert!(!generated.contains("__alef_coerce_dto"), "{generated}");
assert!(!data_enum_needs_dto_coercion(&def, &empty), "should not flag coercion");
}
#[test]
fn variant_constructors_coerce_vec_and_map_of_dto_payloads() {
use crate::codegen::generators::gen_pyo3_data_enum_with_coercion;
use crate::codegen::type_mapper::IdentityMapper;
use ahash::AHashSet;
let def = enum_def(
"Pipeline",
vec![variant(
"Run",
vec![
typed_field(
"stages",
TypeRef::Vec(Box::new(TypeRef::Named("StageConfig".to_string()))),
),
typed_field(
"overrides",
TypeRef::Map(
Box::new(TypeRef::String),
Box::new(TypeRef::Named("StageConfig".to_string())),
),
),
],
)],
);
let coercible: AHashSet<&str> = ["StageConfig"].into_iter().collect();
let generated = gen_pyo3_data_enum_with_coercion(&def, "core", Some(&IdentityMapper), &coercible);
assert!(
generated.contains("stages: __alef_coerce_dto_seq(py, stages, __ALEF_WIRE_STAGE_CONFIG)?"),
"{generated}"
);
assert!(
generated.contains("overrides: __alef_coerce_dto_map(py, overrides, __ALEF_WIRE_STAGE_CONFIG)?"),
"{generated}"
);
assert!(
generated.contains("stages: &Bound<'_, pyo3::types::PyAny>"),
"{generated}"
);
}
#[test]
fn variant_constructors_pair_interleaved_field_exprs_by_position() {
use crate::codegen::type_mapper::IdentityMapper;
let def = enum_def(
"Job",
vec![variant(
"Run",
vec![
typed_field("retries", TypeRef::Primitive(PrimitiveType::U32)),
typed_field("config", TypeRef::Named("RunConfig".to_string())),
typed_field("steps", TypeRef::Vec(Box::new(TypeRef::Named("Step".to_string())))),
],
)],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains(
"Self { inner: crate::Job::Run { retries, config: config.into(), steps: steps.into_iter().map(Into::into).collect() } }"
),
"{generated}"
);
}
#[test]
fn variant_constructors_skip_unit_tuple_and_excluded() {
use crate::codegen::type_mapper::IdentityMapper;
let mut tuple_variant = variant("Pair", vec![typed_field("_0", TypeRef::String)]);
tuple_variant.is_tuple = true;
let mut excluded = variant("Hidden", vec![typed_field("value", TypeRef::String)]);
excluded.binding_excluded = true;
let def = enum_def(
"Mixed",
vec![
variant("Empty", vec![]),
tuple_variant,
excluded,
variant("Real", vec![typed_field("value", TypeRef::String)]),
],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(!generated.contains("_factory_empty"), "{generated}");
assert!(!generated.contains("_factory_pair"), "{generated}");
assert!(!generated.contains("_factory_hidden"), "{generated}");
assert!(
generated.contains("pub fn _factory_real(value: String) -> Self"),
"{generated}"
);
}
#[test]
fn variant_constructors_skip_variant_with_sanitized_field() {
use crate::codegen::type_mapper::IdentityMapper;
let mut sanitized = typed_field("points", TypeRef::String);
sanitized.sanitized = true;
let def = enum_def(
"OcrBoundingGeometry",
vec![
variant("Quadrilateral", vec![sanitized]),
variant(
"Rectangle",
vec![typed_field("left", TypeRef::Primitive(PrimitiveType::U32))],
),
],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(!generated.contains("_factory_quadrilateral"), "{generated}");
assert!(generated.contains("_factory_rectangle"), "{generated}");
}
#[test]
fn variant_constructors_skip_variant_with_binding_excluded_field() {
use crate::codegen::type_mapper::IdentityMapper;
let mut excluded = typed_field("entries", TypeRef::String);
excluded.binding_excluded = true;
let def = enum_def(
"NodeContent",
vec![
variant("MetadataBlock", vec![excluded]),
variant("Title", vec![typed_field("text", TypeRef::String)]),
],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(!generated.contains("_factory_metadata_block"), "{generated}");
assert!(generated.contains("_factory_title"), "{generated}");
}
#[test]
fn variant_constructors_pass_through_genuine_optional_core_field() {
use crate::codegen::type_mapper::IdentityMapper;
let mut opt = typed_field("value", TypeRef::String);
opt.optional = true;
let def = enum_def("AnnotationKind", vec![variant("Custom", vec![opt])]);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains("Self { inner: crate::AnnotationKind::Custom { value } }"),
"{generated}"
);
assert!(!generated.contains("unwrap_or_default"), "{generated}");
}
#[test]
fn variant_constructors_unwrap_promoted_optional_field() {
use crate::codegen::type_mapper::IdentityMapper;
let mut model_file = typed_field("model_file", TypeRef::String);
model_file.optional = true;
let extra = typed_field("extra", TypeRef::Vec(Box::new(TypeRef::String)));
let def = enum_def("RerankerModelType", vec![variant("Custom", vec![model_file, extra])]);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated.contains("pub fn _factory_custom(model_file: Option<String>, extra: Option<Vec<String>>) -> Self"),
"{generated}"
);
assert!(
generated.contains(
"Self { inner: crate::RerankerModelType::Custom { model_file, extra: extra.unwrap_or_default().into_iter().collect() } }"
),
"{generated}"
);
}
#[test]
fn variant_constructors_convert_optional_path_field() {
use crate::codegen::type_mapper::IdentityMapper;
let mut cache_dir = typed_field("cache_dir", TypeRef::Path);
cache_dir.optional = true;
let def = enum_def(
"ChunkSizing",
vec![variant(
"Tokenizer",
vec![typed_field("model", TypeRef::String), cache_dir],
)],
);
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
generated
.contains("Self { inner: crate::ChunkSizing::Tokenizer { model, cache_dir: cache_dir.map(Into::into) } }"),
"{generated}"
);
}
#[test]
fn variant_constructors_use_inline_into_for_non_reexported_core_type() {
use crate::codegen::type_mapper::IdentityMapper;
let mut def = enum_def(
"EnrichStatus",
vec![variant(
"Completed",
vec![typed_field("result", TypeRef::Named("EnrichResult".to_string()))],
)],
);
def.rust_path = "pkg::enrich::EnrichStatus".to_string();
let generated = gen_pyo3_data_enum_with_mapper(&def, "pkg", Some(&IdentityMapper));
assert!(
generated.contains("Self { inner: pkg::enrich::EnrichStatus::Completed { result: result.into() } }"),
"{generated}"
);
assert!(!generated.contains("EnrichResult ="), "{generated}");
assert!(!generated.contains("result_core"), "{generated}");
}
#[test]
fn variant_constructors_yield_to_hand_written_method() {
use crate::codegen::type_mapper::IdentityMapper;
let mut def = enum_def(
"Shape",
vec![
variant(
"Circle",
vec![typed_field("radius", TypeRef::Primitive(PrimitiveType::F64))],
),
variant(
"Square",
vec![typed_field("side", TypeRef::Primitive(PrimitiveType::F64))],
),
],
);
def.methods = vec![static_method("circle")];
let generated = gen_pyo3_data_enum_with_mapper(&def, "core", Some(&IdentityMapper));
assert!(
!generated.contains("Self { inner: core::Shape::Circle"),
"consumer method must win for Circle: {generated}"
);
assert!(
generated.contains("pub fn _factory_square(side: f64) -> Self"),
"{generated}"
);
}
#[test]
fn variant_constructors_absent_without_mapper() {
let def = enum_def(
"Shape",
vec![variant(
"Circle",
vec![typed_field("radius", TypeRef::Primitive(PrimitiveType::F64))],
)],
);
let generated = gen_pyo3_data_enum(&def, "core");
assert!(!generated.contains("_factory_circle"), "{generated}");
}
#[test]
fn gen_pyo3_unit_enum_emits_string_methods() {
let cfg = RustBindingConfig {
struct_attrs: &[],
field_attrs: &[],
struct_derives: &[],
method_block_attr: None,
constructor_attr: "",
static_attr: None,
function_attr: "",
enum_attrs: &["pyclass(eq, eq_int, from_py_object)"],
enum_derives: &["Clone", "PartialEq"],
needs_signature: false,
signature_prefix: "",
signature_suffix: "",
core_import: "core",
async_pattern: AsyncPattern::None,
has_serde: true,
type_name_prefix: "",
option_duration_on_defaults: false,
opaque_type_names: &[],
skip_impl_constructor: false,
cast_uints_to_i32: false,
cast_large_ints_to_f64: false,
named_non_opaque_params_by_ref: false,
lossy_skip_types: &[],
serializable_opaque_type_names: &[],
never_skip_cfg_field_names: &[],
emit_delegating_default_impl: false,
skip_methods_when_not_delegatable: false,
};
let generated = gen_enum(&enum_def("StructureKind", vec![variant("Function", Vec::new())]), &cfg);
assert!(
generated.contains("fn __str__(&self) -> PyResult<String>"),
"{generated}"
);
assert!(generated.contains("serde_json::to_value(self)"), "{generated}");
}