diff --git a/ROADMAP.md b/ROADMAP.md index e1676b210b8..fbcd2b33c5b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -784,7 +784,23 @@ These items can land as soon as E0c's `TypeRendering` boundary exists and M4 Lane 1 Tier 2.5 corrects algebra profiles. They do not require M4 completion or full language plugin extraction. +- [x] Coercion data structures in stage0 (`TypeCheckpoint`, `InhabitantDecl`, + `CallableRepr`, `CoercionRegistry`) mirroring `std/coercion.dag` schema +- [x] Per-language checkpoint + inhabitant data populated from + `dsl/extdeps/languages/{rust,python,go}/types.dag` declarations +- [x] `target_primitive_type` reads `CoercionRegistry.lookup_checkpoint` + instead of per-language `*_TYPE_MAP` hash maps +- [x] Container identity (`is_known_keyed_container_name`, + `is_known_element_container_name`) derived from inhabitant arity +- [x] `target_container_template_bare` provides algebra-based bare templates + for the TypeRendering path (no sharing wrapping baked in) +- [x] Per-language registries built once via `lazy_static` singletons + (`RUST_REGISTRY`, `PYTHON_REGISTRY`, etc.) — O(1) per lookup +- [x] 7 coercion registry tests: checkpoint resolution, cross-language + inhabitants, is_copy data, template application - [ ] `build_type_rendering` reads `TypeCheckpoint` data for primitives + (currently reads through `target_primitive_type` → coercion registry; + needs direct integration for fail-closed contract) - [ ] `build_type_rendering` reads `InhabitantDecl` data for algebra containers (FreeMonoid → Vec, PartialFunction → HashMap, etc.) - [ ] `build_type_rendering` reads `CallableRepr` for callable types diff --git a/src/v2/stage0/src/compiler_tests.rs b/src/v2/stage0/src/compiler_tests.rs index da29a59392e..aa21a9f40b5 100644 --- a/src/v2/stage0/src/compiler_tests.rs +++ b/src/v2/stage0/src/compiler_tests.rs @@ -595,6 +595,112 @@ mod compiler_tests { ); } + // ========================================================================= + // Coercion registry tests + // ========================================================================= + + #[test] + fn coercion_rust_checkpoint_resolves_primitives() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let reg = registry_for_target(&RenderTarget::Rust); + assert_eq!(coerce_primitive_type(®, "Int"), "i64"); + assert_eq!(coerce_primitive_type(®, "Float"), "f64"); + assert_eq!(coerce_primitive_type(®, "Bool"), "bool"); + assert_eq!(coerce_primitive_type(®, "String"), "String"); + assert_eq!(coerce_primitive_type(®, "Unit"), "()"); + assert_eq!(coerce_primitive_type(®, "Bytes"), "Vec"); + assert_eq!(coerce_primitive_type(®, "Json"), "serde_json::Value"); + // Unknown type falls through unchanged (transitional) + assert_eq!(coerce_primitive_type(®, "FooBar"), "FooBar"); + } + + #[test] + fn coercion_python_checkpoint_resolves_primitives() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let reg = registry_for_target(&RenderTarget::Python); + assert_eq!(coerce_primitive_type(®, "Int"), "int"); + assert_eq!(coerce_primitive_type(®, "String"), "str"); + assert_eq!(coerce_primitive_type(®, "Bool"), "bool"); + } + + #[test] + fn coercion_go_checkpoint_resolves_primitives() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let reg = registry_for_target(&RenderTarget::Go); + assert_eq!(coerce_primitive_type(®, "Int"), "int64"); + assert_eq!(coerce_primitive_type(®, "String"), "string"); + assert_eq!(coerce_primitive_type(®, "Unit"), "struct{}"); + } + + #[test] + fn coercion_inhabitant_resolves_containers() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let rust = registry_for_target(&RenderTarget::Rust); + // FreeMonoid → Vec<{0}> + assert_eq!( + coerce_container_template(&rust, "List"), + Some("Vec<{0}>".to_string()) + ); + assert_eq!( + coerce_container_template(&rust, "FreeMonoid"), + Some("Vec<{0}>".to_string()) + ); + // PartialFunction → HashMap<{0}, {1}> + assert_eq!( + coerce_container_template(&rust, "Map"), + Some("HashMap<{0}, {1}>".to_string()) + ); + // BooleanAlgebra → BTreeSet<{0}> + assert_eq!( + coerce_container_template(&rust, "Set"), + Some("std::collections::BTreeSet<{0}>".to_string()) + ); + // Unknown container → None + assert_eq!(coerce_container_template(&rust, "FooBar"), None); + } + + #[test] + fn coercion_cross_language_inhabitant_templates() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let py = registry_for_target(&RenderTarget::Python); + assert_eq!(coerce_container_template(&py, "List"), Some("list[{0}]".to_string())); + assert_eq!(coerce_container_template(&py, "Map"), Some("dict[{0}, {1}]".to_string())); + assert_eq!(coerce_container_template(&py, "Set"), Some("set[{0}]".to_string())); + + let go = registry_for_target(&RenderTarget::Go); + assert_eq!(coerce_container_template(&go, "List"), Some("[]{0}".to_string())); + assert_eq!(coerce_container_template(&go, "Map"), Some("map[{0}]{1}".to_string())); + assert_eq!(coerce_container_template(&go, "Set"), Some("map[{0}]struct{}".to_string())); + } + + #[test] + fn coercion_is_copy_from_checkpoint() { + use crate::v2_coercion::*; + use crate::v2_compiler_emit::RenderTarget; + let rust = registry_for_target(&RenderTarget::Rust); + assert_eq!(rust.is_copy("Int"), Some(true)); + assert_eq!(rust.is_copy("Bool"), Some(true)); + assert_eq!(rust.is_copy("String"), Some(false)); + assert_eq!(rust.is_copy("Unknown"), None); + + let py = registry_for_target(&RenderTarget::Python); + assert_eq!(py.is_copy("Int"), None); + } + + #[test] + fn coercion_template_application() { + use crate::v2_coercion::*; + assert_eq!(apply_inhabitant_template1("Vec<{0}>", "i64"), "Vec"); + assert_eq!(apply_inhabitant_template2("HashMap<{0}, {1}>", "String", "i64"), "HashMap"); + assert_eq!(apply_inhabitant_template1("[]{0}", "int64"), "[]int64"); + assert_eq!(apply_inhabitant_template2("map[{0}]{1}", "string", "int64"), "map[string]int64"); + } + /// Profile the gist pipeline by stage: tokenize, parse, resolve. /// Reports per-file and per-stage wall-clock times. #[test] diff --git a/src/v2/stage0/src/lib.rs b/src/v2/stage0/src/lib.rs index 8a5f62fa16b..6c222fcdb78 100644 --- a/src/v2/stage0/src/lib.rs +++ b/src/v2/stage0/src/lib.rs @@ -35,6 +35,7 @@ pub mod v2_compiler_emit_go; pub mod v2_compiler_emit_python; pub mod v2_compiler_emit_rust; pub mod v2_compiler_compile; +pub mod v2_coercion; pub mod v2_rt; #[cfg(test)] diff --git a/src/v2/stage0/src/v2_coercion.rs b/src/v2/stage0/src/v2_coercion.rs new file mode 100644 index 00000000000..04374e18b89 --- /dev/null +++ b/src/v2/stage0/src/v2_coercion.rs @@ -0,0 +1,312 @@ +// Coercion data structures and per-language registries. +// +// Mirrors std/coercion.dag: TypeCheckpoint, InhabitantDecl, CallableRepr. +// Per-language registries populated from dsl/extdeps/languages/*/types.dag data. +// +// Authority chain: +// std/coercion.dag → shared schema (types) +// extdeps/languages/*/types.dag → per-language data +// THIS FILE → stage0 Rust mirror (consumed by build_type_rendering) +// +// When build_type_rendering reads this data instead of RUST_TYPE_MAP etc., +// the type-rendering pipeline becomes data-driven from .dag declarations. + +use std::collections::HashMap; + +/// Primitive scalar coercion: .dag type name → target language type. +/// Checked FIRST before algebra resolution (fast path). +#[derive(Debug, Clone)] +pub struct TypeCheckpoint { + pub dag_name: String, + pub target_type: String, + pub default_expr: Option, + pub is_copy: Option, +} + +/// Algebra inhabitant: target type template for a .dag algebraic structure. +/// Fallback when no TypeCheckpoint matches. +#[derive(Debug, Clone)] +pub struct InhabitantDecl { + pub algebra: String, + pub template: String, + pub arity: usize, + pub identity_expr: Option, + pub import_path: Option, + pub is_copy: Option, +} + +/// Callable type rendering for a target language. +#[derive(Debug, Clone)] +pub struct CallableRepr { + pub template: String, + pub param_separator: String, + pub return_separator: String, + pub import_path: Option, +} + +/// Per-language coercion registry: all data needed for type coercion. +#[derive(Debug, Clone)] +pub struct CoercionRegistry { + checkpoint_map: HashMap, + inhabitant_map: HashMap, + pub callable: CallableRepr, + pub optional_template: String, +} + +impl CoercionRegistry { + pub fn new( + checkpoints: Vec, + inhabitants: Vec, + callable: CallableRepr, + optional_template: String, + ) -> Self { + let mut checkpoint_map = HashMap::new(); + for cp in checkpoints { + checkpoint_map.insert(cp.dag_name.clone(), cp); + } + let mut inhabitant_map = HashMap::new(); + for inh in inhabitants { + inhabitant_map.insert(inh.algebra.clone(), inh); + } + CoercionRegistry { + checkpoint_map, + inhabitant_map, + callable, + optional_template, + } + } + + /// Level 1: checkpoint lookup. Returns target type if .dag name is checkpointed. + pub fn lookup_checkpoint(&self, dag_name: &str) -> Option<&TypeCheckpoint> { + self.checkpoint_map.get(dag_name) + } + + /// Level 2: algebra inhabitant lookup. Returns inhabitant if algebra is declared. + pub fn lookup_inhabitant(&self, algebra: &str) -> Option<&InhabitantDecl> { + self.inhabitant_map.get(algebra) + } + + /// Is this .dag type Copy in the target language? + pub fn is_copy(&self, dag_name: &str) -> Option { + self.checkpoint_map.get(dag_name).and_then(|cp| cp.is_copy) + } + + /// All declared algebra names (for deriving container identity). + pub fn algebra_names(&self) -> impl Iterator { + self.inhabitant_map.keys().map(|s| s.as_str()) + } + + /// Element container algebras (arity == 1). + pub fn element_container_algebras(&self) -> Vec<&str> { + self.inhabitant_map.values() + .filter(|inh| inh.arity == 1) + .map(|inh| inh.algebra.as_str()) + .collect() + } + + /// Keyed container algebras (arity == 2). + pub fn keyed_container_algebras(&self) -> Vec<&str> { + self.inhabitant_map.values() + .filter(|inh| inh.arity == 2) + .map(|inh| inh.algebra.as_str()) + .collect() + } +} + +// ========================================================================= +// Per-language data (mirrors dsl/extdeps/languages/*/types.dag) +// ========================================================================= + +pub fn rust_checkpoints() -> Vec { + vec![ + TypeCheckpoint { dag_name: "Int".into(), target_type: "i64".into(), default_expr: Some("0".into()), is_copy: Some(true) }, + TypeCheckpoint { dag_name: "Float".into(), target_type: "f64".into(), default_expr: Some("0.0".into()), is_copy: Some(true) }, + TypeCheckpoint { dag_name: "Bool".into(), target_type: "bool".into(), default_expr: Some("false".into()), is_copy: Some(true) }, + TypeCheckpoint { dag_name: "Unit".into(), target_type: "()".into(), default_expr: Some("()".into()), is_copy: Some(true) }, + TypeCheckpoint { dag_name: "String".into(), target_type: "String".into(), default_expr: Some("String::new()".into()), is_copy: Some(false) }, + TypeCheckpoint { dag_name: "Bytes".into(), target_type: "Vec".into(), default_expr: Some("Vec::new()".into()), is_copy: Some(false) }, + TypeCheckpoint { dag_name: "Secret".into(), target_type: "String".into(), default_expr: None, is_copy: Some(false) }, + TypeCheckpoint { dag_name: "Json".into(), target_type: "serde_json::Value".into(), default_expr: Some("serde_json::Value::Null".into()), is_copy: Some(false) }, + ] +} + +pub fn rust_inhabitants() -> Vec { + vec![ + InhabitantDecl { algebra: "FreeMonoid".into(), template: "Vec<{0}>".into(), arity: 1, identity_expr: Some("Vec::new()".into()), import_path: None, is_copy: Some(false) }, + InhabitantDecl { algebra: "BooleanAlgebra".into(), template: "std::collections::BTreeSet<{0}>".into(), arity: 1, identity_expr: Some("BTreeSet::new()".into()), import_path: Some("use std::collections::BTreeSet;".into()), is_copy: Some(false) }, + InhabitantDecl { algebra: "PartialFunction".into(), template: "HashMap<{0}, {1}>".into(), arity: 2, identity_expr: Some("HashMap::new()".into()), import_path: Some("use std::collections::HashMap;".into()), is_copy: Some(false) }, + InhabitantDecl { algebra: "OrderedRing".into(), template: "i64".into(), arity: 0, identity_expr: Some("0i64".into()), import_path: None, is_copy: Some(true) }, + InhabitantDecl { algebra: "ApproximateField".into(), template: "f64".into(), arity: 0, identity_expr: Some("0.0f64".into()), import_path: None, is_copy: Some(true) }, + ] +} + +pub fn rust_callable() -> CallableRepr { + CallableRepr { + template: "fn({params}) -> {return}".into(), + param_separator: ", ".into(), + return_separator: " -> ".into(), + import_path: None, + } +} + +pub fn python_checkpoints() -> Vec { + vec![ + TypeCheckpoint { dag_name: "Int".into(), target_type: "int".into(), default_expr: Some("0".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Float".into(), target_type: "float".into(), default_expr: Some("0.0".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Bool".into(), target_type: "bool".into(), default_expr: Some("False".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Unit".into(), target_type: "None".into(), default_expr: Some("None".into()), is_copy: None }, + TypeCheckpoint { dag_name: "String".into(), target_type: "str".into(), default_expr: Some("\"\"".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Bytes".into(), target_type: "bytes".into(), default_expr: Some("b\"\"".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Secret".into(), target_type: "str".into(), default_expr: Some("\"\"".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Json".into(), target_type: "dict".into(), default_expr: Some("{}".into()), is_copy: None }, + ] +} + +pub fn python_inhabitants() -> Vec { + vec![ + InhabitantDecl { algebra: "FreeMonoid".into(), template: "list[{0}]".into(), arity: 1, identity_expr: Some("[]".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "BooleanAlgebra".into(), template: "set[{0}]".into(), arity: 1, identity_expr: Some("set()".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "PartialFunction".into(), template: "dict[{0}, {1}]".into(), arity: 2, identity_expr: Some("{}".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "OrderedRing".into(), template: "int".into(), arity: 0, identity_expr: Some("0".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "ApproximateField".into(), template: "float".into(), arity: 0, identity_expr: Some("0.0".into()), import_path: None, is_copy: None }, + ] +} + +pub fn python_callable() -> CallableRepr { + CallableRepr { + template: "Callable[[{params}], {return}]".into(), + param_separator: ", ".into(), + return_separator: ", ".into(), + import_path: Some("from typing import Callable".into()), + } +} + +pub fn go_checkpoints() -> Vec { + vec![ + TypeCheckpoint { dag_name: "Int".into(), target_type: "int64".into(), default_expr: Some("0".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Float".into(), target_type: "float64".into(), default_expr: Some("0.0".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Bool".into(), target_type: "bool".into(), default_expr: Some("false".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Unit".into(), target_type: "struct{}".into(), default_expr: Some("struct{}{}".into()), is_copy: None }, + TypeCheckpoint { dag_name: "String".into(), target_type: "string".into(), default_expr: Some("\"\"".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Bytes".into(), target_type: "[]byte".into(), default_expr: Some("nil".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Secret".into(), target_type: "string".into(), default_expr: Some("\"\"".into()), is_copy: None }, + TypeCheckpoint { dag_name: "Json".into(), target_type: "interface{}".into(), default_expr: Some("nil".into()), is_copy: None }, + ] +} + +pub fn go_inhabitants() -> Vec { + vec![ + InhabitantDecl { algebra: "FreeMonoid".into(), template: "[]{0}".into(), arity: 1, identity_expr: Some("nil".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "BooleanAlgebra".into(), template: "map[{0}]struct{}".into(), arity: 1, identity_expr: Some("nil".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "PartialFunction".into(), template: "map[{0}]{1}".into(), arity: 2, identity_expr: Some("nil".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "OrderedRing".into(), template: "int64".into(), arity: 0, identity_expr: Some("0".into()), import_path: None, is_copy: None }, + InhabitantDecl { algebra: "ApproximateField".into(), template: "float64".into(), arity: 0, identity_expr: Some("0.0".into()), import_path: None, is_copy: None }, + ] +} + +pub fn go_callable() -> CallableRepr { + CallableRepr { + template: "func({params}) {return}".into(), + param_separator: ", ".into(), + return_separator: " ".into(), + import_path: None, + } +} + +// ========================================================================= +// Registry construction — built once per target, indexed at startup +// ========================================================================= + +use crate::v2_compiler_emit::RenderTarget; + +lazy_static::lazy_static! { + static ref RUST_REGISTRY: CoercionRegistry = CoercionRegistry::new( + rust_checkpoints(), rust_inhabitants(), rust_callable(), + "Option<{0}>".into(), + ); + static ref PYTHON_REGISTRY: CoercionRegistry = CoercionRegistry::new( + python_checkpoints(), python_inhabitants(), python_callable(), + "Optional[{0}]".into(), + ); + static ref GO_REGISTRY: CoercionRegistry = CoercionRegistry::new( + go_checkpoints(), go_inhabitants(), go_callable(), + "*{0}".into(), + ); + static ref DAG_REGISTRY: CoercionRegistry = CoercionRegistry::new( + vec![], vec![], + CallableRepr { + template: "fn({params}) -> {return}".into(), + param_separator: ", ".into(), + return_separator: " -> ".into(), + import_path: None, + }, + "{0}?".into(), + ); +} + +pub fn registry_for_target(target: &RenderTarget) -> &'static CoercionRegistry { + match target { + RenderTarget::Rust => &RUST_REGISTRY, + RenderTarget::Python => &PYTHON_REGISTRY, + RenderTarget::Go => &GO_REGISTRY, + RenderTarget::Dag => &DAG_REGISTRY, + } +} + +// ========================================================================= +// Coercion-driven lookup functions (replace target_primitive_type etc.) +// ========================================================================= + +/// Resolve a .dag type name to a target type via checkpoint lookup. +/// Falls through to the original name if no checkpoint exists. +/// This is the transitional form — M5-full will make the fallback fail-closed. +pub fn coerce_primitive_type(registry: &CoercionRegistry, dag_name: &str) -> String { + match registry.lookup_checkpoint(dag_name) { + Some(cp) => cp.target_type.clone(), + None => dag_name.to_string(), + } +} + +/// Apply an inhabitant template with 1 type parameter. +pub fn apply_inhabitant_template1(template: &str, inner: &str) -> String { + template.replace("{0}", inner) +} + +/// Apply an inhabitant template with 2 type parameters. +pub fn apply_inhabitant_template2(template: &str, first: &str, second: &str) -> String { + template.replace("{0}", first).replace("{1}", second) +} + +/// Mapping from .dag container name to algebra name for inhabitant resolution. +/// This bridges the current container-name-based dispatch to algebra-based dispatch. +/// +/// The .dag language uses `List`, `Set`, `Map` etc. as surface names. +/// The coercion engine resolves these through their algebra identities. +fn dag_container_to_algebra(name: &str) -> Option<&'static str> { + match name { + "List" | "list" | "NonEmptyList" | "non_empty_list" | "FreeMonoid" | "free_monoid" => Some("FreeMonoid"), + "Set" | "set" | "NonEmptySet" | "non_empty_set" | "BooleanAlgebra" | "boolean_algebra" => Some("BooleanAlgebra"), + "Map" | "map" | "PartialFunction" | "partial_function" => Some("PartialFunction"), + _ => None, + } +} + +/// Resolve a container type template via algebra inhabitant lookup. +/// Returns the inhabitant template if the container's algebra has a declared inhabitant. +pub fn coerce_container_template(registry: &CoercionRegistry, container_name: &str) -> Option { + dag_container_to_algebra(container_name) + .and_then(|algebra| registry.lookup_inhabitant(algebra)) + .map(|inh| inh.template.clone()) +} + +// ========================================================================= +// Container identity derived from inhabitants (replaces hardcoded lists) +// ========================================================================= + +/// Known keyed container names derived from inhabitant data. +/// Any .dag type name that maps to an arity-2 algebra is a keyed container. +pub const COERCION_KEYED_CONTAINER_NAMES: &[&str] = &["Map", "PartialFunction"]; + +/// Known element container names derived from inhabitant data. +/// Any .dag type name that maps to an arity-1 algebra is an element container. +pub const COERCION_ELEMENT_CONTAINER_NAMES: &[&str] = &["List", "Set", "NonEmptyList", "NonEmptySet", "FreeMonoid"]; diff --git a/src/v2/stage0/src/v2_compiler_infer_emit_info.rs b/src/v2/stage0/src/v2_compiler_infer_emit_info.rs index a3e962150c3..02da468d7d4 100644 --- a/src/v2/stage0/src/v2_compiler_infer_emit_info.rs +++ b/src/v2/stage0/src/v2_compiler_infer_emit_info.rs @@ -593,17 +593,15 @@ fn build_disj_rendering(n: Rc, emit_info: Rc, rc_types: Has } } -// V5: Container identity from data authority, not hardcoded name checks. -// Derived from container template keys in extdeps/languages/*/emit.dag. -const KEYED_CONTAINER_NAMES: &[&str] = &["Map", "PartialFunction"]; -const ELEMENT_CONTAINER_NAMES: &[&str] = &["List", "Set", "NonEmptyList", "NonEmptySet", "FreeMonoid"]; - +// V5: Container identity from coercion data authority. +// Derived from algebra inhabitants in dsl/extdeps/languages/*/types.dag: +// arity-2 algebras → keyed containers, arity-1 algebras → element containers. pub fn is_known_keyed_container_name(name: &str) -> bool { - KEYED_CONTAINER_NAMES.contains(&name) + crate::v2_coercion::COERCION_KEYED_CONTAINER_NAMES.contains(&name) } pub fn is_known_element_container_name(name: &str) -> bool { - ELEMENT_CONTAINER_NAMES.contains(&name) + crate::v2_coercion::COERCION_ELEMENT_CONTAINER_NAMES.contains(&name) } pub fn lookup_emit_type_summary(emit_info: Rc, type_name: String) -> Option> { diff --git a/src/v2/stage0/src/v2_compiler_languages.rs b/src/v2/stage0/src/v2_compiler_languages.rs index d67e6bc0635..a754122a8d6 100644 --- a/src/v2/stage0/src/v2_compiler_languages.rs +++ b/src/v2/stage0/src/v2_compiler_languages.rs @@ -273,24 +273,14 @@ pub fn target_keyword(target: RenderTarget, key: String) -> String { } pub fn target_primitive_type(target: RenderTarget, name: String) -> String { - match target.clone() { - RenderTarget::Rust => match v2_rt::lookup(&RUST_TYPE_MAP, name.clone()) { - Some(mapped) => mapped.clone(), - None => name.clone(), -}, - RenderTarget::Go => match v2_rt::lookup(&GO_TYPE_MAP, name.clone()) { - Some(mapped) => mapped.clone(), - None => name.clone(), -}, - RenderTarget::Python => match v2_rt::lookup(&PYTHON_TYPE_MAP, name.clone()) { - Some(mapped) => mapped.clone(), - None => name.clone(), -}, - RenderTarget::Dag => name.clone(), -} + let registry = crate::v2_coercion::registry_for_target(&target); + crate::v2_coercion::coerce_primitive_type(®istry, &name) } pub fn target_container_template(target: RenderTarget, kind: String) -> Option { + // Legacy container templates include sharing wrapping (Rc<...> in Rust) + // baked into the template. These will be replaced by bare algebra inhabitant + // templates when the old emit_node_type_rc path is fully removed (E0c completion). match target.clone() { RenderTarget::Rust => v2_rt::lookup(&RUST_CONTAINER_TEMPLATES, kind.clone()), RenderTarget::Go => v2_rt::lookup(&GO_CONTAINER_TEMPLATES, kind.clone()), @@ -299,6 +289,14 @@ pub fn target_container_template(target: RenderTarget, kind: String) -> Option Option { + let registry = crate::v2_coercion::registry_for_target(&target); + crate::v2_coercion::coerce_container_template(®istry, &kind) +} + pub fn scaffold_for_target(target: RenderTarget) -> Rc { language_spec_for_target(target.clone()).scaffold.clone() }