Skip to content

Commit 752ebb9

Browse files
tobocop2Goldziher
authored andcommitted
refactor(pyo3): move DTO-coercion emission into wire_schema, stay under file cap
The #1165 schema/helper emission grew gen_bindings/mod.rs to 1021 lines, over the 1000-line file-modularization cap. Move the classification + emission orchestration (coercible_dto_names, emit_dto_coercion_section) into wire_schema.rs, where the schema generation already lives; mod.rs now calls them in a few lines. Generated output is byte-identical. mod.rs 1021 -> 998; wire_schema.rs 164 -> 205.
1 parent 81af6b4 commit 752ebb9

2 files changed

Lines changed: 51 additions & 33 deletions

File tree

src/backends/pyo3/gen_bindings/mod.rs

Lines changed: 9 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -603,38 +603,15 @@ impl Backend for Pyo3Backend {
603603
opaque_types.is_empty(),
604604
);
605605

606-
// Classify which config-DTO types are dataclass-backed: their public name resolves to a
607-
// `@dataclass`/`dict` (via options.py), not the compiled `#[pyclass]`. Enum-variant payload
608-
// fields of these types must accept the public wrapper or a dict — coerced into the core
609-
// type — for parity with struct-field `_to_rust_*` coercion. Native-return types stay
610-
// compiled and are left untouched. Same source of truth as `gen_init_py`'s import routing.
611-
let dto_output_style = config.dto.python_output_style();
612-
let reexported_dto_names: ahash::AHashSet<&str> = config
613-
.python
614-
.as_ref()
615-
.map(|p| p.reexported_types.iter().map(String::as_str).collect())
616-
.unwrap_or_default();
617-
let coercible_dto_names: ahash::AHashSet<&str> = api
618-
.types
619-
.iter()
620-
.filter(|t| errors::is_dataclass_backed_config(t, dto_output_style, &reexported_dto_names))
621-
.map(|t| t.name.as_str())
622-
.collect();
623-
// Emit the runtime coercion helper once when any data-enum variant constructor needs it
624-
// (gated on serde availability — the helper deserializes the coerced JSON into the core type),
625-
// followed by the per-DTO `__ALEF_WIRE_*` rename-schema consts that give the helper full
626-
// serde-rename fidelity (handling `#[serde(rename)]`/`#[serde(rename_all)]` and nesting).
627-
if has_serde
628-
&& api
629-
.enums
630-
.iter()
631-
.any(|e| generators::data_enum_needs_dto_coercion(e, &coercible_dto_names))
632-
{
633-
builder.add_item(generators::PYO3_DTO_COERCE_HELPER);
634-
let wire_schema_consts = wire_schema::gen_wire_schema_consts(api, &coercible_dto_names);
635-
if !wire_schema_consts.is_empty() {
636-
builder.add_item(&wire_schema_consts);
637-
}
606+
// Classify which config-DTO types are dataclass-backed (their public name is a
607+
// `@dataclass`/`dict`, not the compiled `#[pyclass]`) so enum-variant payloads of those
608+
// types are coerced rather than required as compiled instances. Then emit the runtime
609+
// coercion helper plus the per-DTO `__ALEF_WIRE_*` rename-schema consts (both live in
610+
// `wire_schema`). `coercible_dto_names` is reused below to drive the variant constructors.
611+
let coercible_dto_names = wire_schema::coercible_dto_names(api, config);
612+
let coercion_section = wire_schema::emit_dto_coercion_section(api, has_serde, &coercible_dto_names);
613+
if !coercion_section.is_empty() {
614+
builder.add_item(&coercion_section);
638615
}
639616

640617
for e in &api.enums {

src/backends/pyo3/gen_bindings/wire_schema.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,55 @@
99
//! same single source of truth the Python `_to_rust_*` converters use) and recurses through nested
1010
//! DTOs, sequences, maps, and optionals so renamed fields at any depth survive the round-trip.
1111
12+
use super::errors::is_dataclass_backed_config;
1213
use crate::codegen::generators::{
13-
coercible_payload, collect_variant_constructors, enum_has_data_variants, pyo3_wire_schema_const_name,
14+
PYO3_DTO_COERCE_HELPER, coercible_payload, collect_variant_constructors, data_enum_needs_dto_coercion,
15+
enum_has_data_variants, pyo3_wire_schema_const_name,
1416
};
1517
use crate::codegen::naming::wire_field_name;
1618
use crate::codegen::shared::binding_fields;
19+
use crate::core::config::ResolvedCrateConfig;
1720
use crate::core::ir::{ApiSurface, TypeDef};
1821
use ahash::{AHashMap, AHashSet};
1922

23+
/// Classify the dataclass-backed config-DTO type names: their public name resolves to a
24+
/// `@dataclass`/`dict` (via `options.py`), not the compiled `#[pyclass]`. Enum-variant payload
25+
/// fields of these types must accept the public wrapper or a dict — coerced into the core type —
26+
/// for parity with struct-field `_to_rust_*` coercion. Native-return types stay compiled and are
27+
/// left untouched. Same source of truth as `gen_init_py`'s import routing.
28+
pub(super) fn coercible_dto_names<'a>(api: &'a ApiSurface, config: &ResolvedCrateConfig) -> AHashSet<&'a str> {
29+
let output_style = config.dto.python_output_style();
30+
let reexported: AHashSet<&str> = config
31+
.python
32+
.as_ref()
33+
.map(|p| p.reexported_types.iter().map(String::as_str).collect())
34+
.unwrap_or_default();
35+
api.types
36+
.iter()
37+
.filter(|t| is_dataclass_backed_config(t, output_style, &reexported))
38+
.map(|t| t.name.as_str())
39+
.collect()
40+
}
41+
42+
/// Emit the data-enum DTO-coercion section for the generated pyo3 module: the runtime coercion
43+
/// helper ([`PYO3_DTO_COERCE_HELPER`]) followed by the per-DTO `__ALEF_WIRE_*` rename-schema consts.
44+
/// Returns an empty string when no data-enum variant constructor needs coercion (or `serde` is
45+
/// unavailable — the helper deserializes the coerced JSON into the core type). The helper and the
46+
/// schema consts are joined the same way `RustFileBuilder` joins items (`"\n\n"`), so emitting this
47+
/// as a single item is byte-identical to emitting them separately.
48+
pub(super) fn emit_dto_coercion_section(api: &ApiSurface, has_serde: bool, coercible: &AHashSet<&str>) -> String {
49+
let needed = has_serde && api.enums.iter().any(|e| data_enum_needs_dto_coercion(e, coercible));
50+
if !needed {
51+
return String::new();
52+
}
53+
let schema_consts = gen_wire_schema_consts(api, coercible);
54+
if schema_consts.is_empty() {
55+
PYO3_DTO_COERCE_HELPER.to_string()
56+
} else {
57+
format!("{PYO3_DTO_COERCE_HELPER}\n\n{schema_consts}")
58+
}
59+
}
60+
2061
/// One emitted `__AlefAlias` row.
2162
struct AliasEntry {
2263
rust: String,

0 commit comments

Comments
 (0)