From 76fc1feb896169286fbd65c2bbf06b621cced020 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 01:28:19 +0000 Subject: [PATCH 1/9] Emit algebra length on host String as string_length. The method form already remapped .length on a string receiver; the function form called the generic FreeMonoid body and rustc reported E0308. One decision now covers both spellings. Co-authored-by: Cursor --- dag/gunbc/auth/approval_store_receipt.dag | 4 +- .../instruments/native_emission_controls.dag | 22 ++++++- src/v1/05_emit_rust.dag | 28 +++++++++ src/v1/stage0/src/v1_compiler_emit_rust.rs | 63 +++++++++++++++++++ 4 files changed, 113 insertions(+), 4 deletions(-) diff --git a/dag/gunbc/auth/approval_store_receipt.dag b/dag/gunbc/auth/approval_store_receipt.dag index 170782e2bdb..fcd4dcea471 100644 --- a/dag/gunbc/auth/approval_store_receipt.dag +++ b/dag/gunbc/auth/approval_store_receipt.dag @@ -2,7 +2,7 @@ module gunbc.auth.approval_store_receipt import std.logic { Bool } import std.types { NonEmptyStr, Timestamp, String, Int, List } -import v2.std.algebra { skip } +import v2.std.algebra { skip, length } import std.optional { Present, Absent } import std.scoped_authorization { AttemptIdentity } import std.durable_compare_and_set { cas_generation_first } @@ -249,7 +249,7 @@ fn receipt_line_at(lines: List, i: Int) -> String { fn request_field(line: String, key: String) -> String { let prefix = join([key, "="], "") - if starts_with(s: line, prefix: prefix) { substring(s: line, start: string_length(s: prefix), end: string_length(s: line)) } else { "" } + if starts_with(s: line, prefix: prefix) { substring(s: line, start: length(xs: prefix), end: length(xs: line)) } else { "" } } // THE WIRE A RECEIPT TRAVELS AS: its signing input, then its tag, one line each. The signing input diff --git a/dag/gunbc/instruments/native_emission_controls.dag b/dag/gunbc/instruments/native_emission_controls.dag index 2a2ba507dbb..501a7803ba4 100644 --- a/dag/gunbc/instruments/native_emission_controls.dag +++ b/dag/gunbc/instruments/native_emission_controls.dag @@ -1,7 +1,7 @@ module gunbc.instruments.native_emission_controls import std.types { Bool, Int, List, NonEmptyStr, Set, String } -import v2.std.algebra { filter } +import v2.std.algebra { filter, length } import std.list { duplicated_by_key } import gunbc.instruments.native_emission_controls_homonym { ControlBoxed, narrow_slot_value, narrow_slots_equal } import std.resource_contract { Observation, Read } @@ -452,6 +452,21 @@ fn control_outcome_is_bad(o: ControlOutcome) -> Bool { } } +// RULE: `v2.std.algebra` `length` over host text lowers to `v1_rt::string_length`, the same +// decision `.length` on a string receiver already takes (v1.compiler.emit_rust emit_typed_call). +// Without it the generic FreeMonoid body is called at a `String` (E0308 at request_field). +fn control_algebra_length_of_string(s: String) -> Int { + length(xs: s) +} + +fn algebra_length_on_string_cases() -> List { + [ + NativeEmissionCase { identity: "algebra_length_of_a_string_counts_scalars", held: control_algebra_length_of_string(s: "abc") == 3 }, + NativeEmissionCase { identity: "algebra_length_of_a_string_red_is_not_another_count", held: control_algebra_length_of_string(s: "abc") != 4 }, + NativeEmissionCase { identity: "algebra_length_of_a_list_is_unchanged", held: length(xs: ["a", "b"]) == 2 } + ] +} + fn fold_accumulator_formals_cases() -> List { [ NativeEmissionCase { identity: "fold_constructor_init_sums_all_nonnegative", held: control_outcome_good_value(o: control_sum_until_negative(xs: [1, 2, 3])) == 6 }, @@ -490,6 +505,9 @@ data native_emission_expected_identities: List = [ "effectful_fold_step_red_empty_keeps_init", "capability_alias_output_octet_in_range", "capability_alias_output_red_octet_out_of_range", + "algebra_length_of_a_string_counts_scalars", + "algebra_length_of_a_string_red_is_not_another_count", + "algebra_length_of_a_list_is_unchanged", "fold_constructor_init_sums_all_nonnegative", "fold_constructor_init_red_negative_switches_arm", "fold_constructor_init_red_empty_keeps_init", @@ -507,7 +525,7 @@ data native_emission_expected_identities: List = [ ] fn native_emission_cases() -> List { - concat(concat(concat(concat(same_leaf_declaration_cases(), concat(concat(concat(concat(concat(concat(header_bound_cases(), fn_value_bound_cases()), filter_in_branch_condition_cases()), claim_driver_filesystem_cases()), effectful_fold_step_cases()), capability_alias_output_cases()), fold_accumulator_formals_cases())), list_index_cases()), effectful_generic_return_cases()), generic_empty_map_turbofish_cases()) + concat(concat(concat(concat(concat(same_leaf_declaration_cases(), concat(concat(concat(concat(concat(concat(header_bound_cases(), fn_value_bound_cases()), filter_in_branch_condition_cases()), claim_driver_filesystem_cases()), effectful_fold_step_cases()), capability_alias_output_cases()), fold_accumulator_formals_cases())), list_index_cases()), effectful_generic_return_cases()), generic_empty_map_turbofish_cases()), algebra_length_on_string_cases()) } fn case_row(c: NativeEmissionCase) -> String { diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 87ac7976dcf..38791affbe9 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12629,6 +12629,30 @@ fn lambda_argument_scope(arg: Node, scope: InferScope) -> InferScope { // emit_func_def names the declaration it renders on the body scope (caller_decl_name) so that read // has a subject. +// THE SAME LENGTH DECISION THE METHOD PATH ALREADY ASKS. AlgebraMethodSemantics remaps `.length` +// on a host-string receiver to `string_length`. The function form `v2.std.algebra` `length` over +// `FreeMonoid` is that operation spelled as a call: `String` inhabits the monoid, so +// `length(xs: s)` is legal source. Emitting it as the generic FreeMonoid body (`Rc>`) is +// E0308 against the host `String` at `request_field` and at every `length` of a `String` in +// `std.scoped_authorization`. Both spellings ask whether the operand is host text, and both lower +// to `v1_rt::string_length`. +fn rust_emit_length_on_host_string(func: String, args: List, call_semantics: CallSemantics?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String? { + if func != "length" { none } else { + let length_args = order_typed_call_args_from_semantics(args: args, func: func, call_semantics: call_semantics, scope: scope) + match length_args |> first { + Absent => none + Present { value: xs_arg } => + if is_string_typed_expr(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { + Present { value: concat( + "v1_rt::string_length(&", + emit_typed_expr_base(texpr: arg_value(n: xs_arg), registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info), + ")" + ) } + } else { none } + } + } +} + fn emit_typed_call(func: String, args: List, inferred: InferredNode?, call_semantics: CallSemantics?, callee_is_function_value: Bool, call_target: CallTargetIdentity, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { if callee_is_function_value { return emit_typed_function_value_call(func: func, args: args, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) @@ -12709,6 +12733,10 @@ fn emit_typed_call(func: String, args: List, inferred: InferredNode?, call } return disc_result } + match rust_emit_length_on_host_string(func: func, args: args, call_semantics: call_semantics, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) { + Present { value: lowered } => return lowered + Absent => {} + } let ordered_args = order_typed_call_args_from_semantics(args: args, func: func, call_semantics: call_semantics, scope: scope) let application_plan = call_semantics_supplied_application_plan(cs: call_semantics) let callee = match call_target { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 052115df96b..7b1bb8cda2a 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -26715,6 +26715,56 @@ pub fn lambda_argument_scope(arg: Rc, scope: Rc) -> Rc>>, + call_semantics: Option>, + registry: Rc>>, + scope: Rc, + depth: i64, + shared_types: Rc>, + emit_info: Rc, +) -> Option { + if (func.clone() != "length".to_string()) { + std::option::Option::None + } else { + { + let length_args = crate::v1_compiler_emit::order_typed_call_args_from_semantics( + args.clone(), + func.clone(), + call_semantics.clone(), + scope.clone(), + ); + match length_args.clone().first().cloned() { + std::option::Option::None => std::option::Option::None, + Some(xs_arg) => { + if is_string_typed_expr( + crate::v1_std_core::arg_value(xs_arg.clone()), + scope.type_env.clone().source_indices.clone(), + ) { + Some(v1_rt::concat( + v1_rt::concat( + "v1_rt::string_length(&".to_string(), + emit_typed_expr_base( + crate::v1_std_core::arg_value(xs_arg.clone()), + registry.clone(), + scope.clone(), + depth.clone(), + shared_types.clone(), + emit_info.clone(), + ), + ), + ")".to_string(), + )) + } else { + std::option::Option::None + } + } + } + } + } +} + pub fn emit_typed_call( func: String, args: Rc>>, @@ -26994,6 +27044,19 @@ pub fn emit_typed_call( return disc_result; } } + match rust_emit_length_on_host_string( + func.clone(), + args.clone(), + call_semantics.clone(), + registry.clone(), + scope.clone(), + depth.clone(), + shared_types.clone(), + emit_info.clone(), + ) { + Some(lowered) => return lowered.clone(), + std::option::Option::None => {} + } let ordered_args = crate::v1_compiler_emit::order_typed_call_args_from_semantics( args.clone(), func.clone(), From 77eea99ef5ab9522daabe85ffd4555bc9bde22e9 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 02:00:09 +0000 Subject: [PATCH 2/9] Gate algebra-length String lowering on the resolved declaration. A leaf-name rewrite would replace any function called length whose first argument is String. The homonym control (constant 99) stays red if that happens. Co-authored-by: Cursor --- .../instruments/native_emission_controls.dag | 6 ++-- .../native_emission_controls_homonym.dag | 10 +++++- src/v1/05_emit_rust.dag | 27 ++++++++++++-- src/v1/stage0/src/v1_compiler_emit_rust.rs | 35 ++++++++++++++++++- 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/dag/gunbc/instruments/native_emission_controls.dag b/dag/gunbc/instruments/native_emission_controls.dag index 501a7803ba4..ad055187a3b 100644 --- a/dag/gunbc/instruments/native_emission_controls.dag +++ b/dag/gunbc/instruments/native_emission_controls.dag @@ -3,7 +3,7 @@ module gunbc.instruments.native_emission_controls import std.types { Bool, Int, List, NonEmptyStr, Set, String } import v2.std.algebra { filter, length } import std.list { duplicated_by_key } -import gunbc.instruments.native_emission_controls_homonym { ControlBoxed, narrow_slot_value, narrow_slots_equal } +import gunbc.instruments.native_emission_controls_homonym { ControlBoxed, narrow_slot_value, narrow_slots_equal, homonym_length_of } import std.resource_contract { Observation, Read } import std.integer { UInt8, UInt8Ready, UInt8OutOfRange, uint8_of_int } import extdeps.filesystem.filesystem_io { Filesystem } @@ -463,7 +463,8 @@ fn algebra_length_on_string_cases() -> List { [ NativeEmissionCase { identity: "algebra_length_of_a_string_counts_scalars", held: control_algebra_length_of_string(s: "abc") == 3 }, NativeEmissionCase { identity: "algebra_length_of_a_string_red_is_not_another_count", held: control_algebra_length_of_string(s: "abc") != 4 }, - NativeEmissionCase { identity: "algebra_length_of_a_list_is_unchanged", held: length(xs: ["a", "b"]) == 2 } + NativeEmissionCase { identity: "algebra_length_of_a_list_is_unchanged", held: length(xs: ["a", "b"]) == 2 }, + NativeEmissionCase { identity: "algebra_length_red_a_homonym_length_is_not_scalar_count", held: homonym_length_of(s: "abc") == 99 } ] } @@ -508,6 +509,7 @@ data native_emission_expected_identities: List = [ "algebra_length_of_a_string_counts_scalars", "algebra_length_of_a_string_red_is_not_another_count", "algebra_length_of_a_list_is_unchanged", + "algebra_length_red_a_homonym_length_is_not_scalar_count", "fold_constructor_init_sums_all_nonnegative", "fold_constructor_init_red_negative_switches_arm", "fold_constructor_init_red_empty_keeps_init", diff --git a/dag/gunbc/instruments/native_emission_controls_homonym.dag b/dag/gunbc/instruments/native_emission_controls_homonym.dag index b5e42e22776..6ccaf6c879a 100644 --- a/dag/gunbc/instruments/native_emission_controls_homonym.dag +++ b/dag/gunbc/instruments/native_emission_controls_homonym.dag @@ -1,6 +1,6 @@ module gunbc.instruments.native_emission_controls_homonym -import std.types { Bool, String } +import std.types { Bool, Int, String } // THE SECOND DECLARER OF A LEAF, for gunbc.instruments.native_emission_controls. That module declares // ControlSlot; this one declares ControlSlot. One closure, one leaf, two declarations with @@ -32,3 +32,11 @@ fn narrow_slot_value(value: String) -> String { fn narrow_slots_equal(left: String, right: String) -> Bool { narrow_slot(value: left) == narrow_slot(value: right) } + +// A `length` that is NOT `v2.std.algebra` `length`. Its body is a constant, so a leaf-name rewrite +// to `v1_rt::string_length` would return 3 for "abc" and this control would go red. +fn length(xs: String) -> Int { 99 } + +fn homonym_length_of(s: String) -> Int { + length(xs: s) +} diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 38791affbe9..b8b9c5c3bf8 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12636,8 +12636,29 @@ fn lambda_argument_scope(arg: Node, scope: InferScope) -> InferScope { // E0308 against the host `String` at `request_field` and at every `length` of a `String` in // `std.scoped_authorization`. Both spellings ask whether the operand is host text, and both lower // to `v1_rt::string_length`. -fn rust_emit_length_on_host_string(func: String, args: List, call_semantics: CallSemantics?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String? { - if func != "length" { none } else { +// THE CALLEE IS THE DECLARATION, NOT THE LEAF. A call spelled `length` that resolves to another +// module's function (or a local) must emit that function; rewriting by the authored name would +// replace a byte-count or a constant with a scalar count and answer wrongly in silence. +fn rust_call_target_is_v2_algebra_length(target: CallTargetIdentity) -> Bool { + match rust_call_target_declared_length(target: target) { + Absent => false + Present { value: id } => + id.decl_name == "length" && id.owner_module_path == "v2.std.algebra" + } +} + +fn rust_call_target_declared_length(target: CallTargetIdentity) -> DeclaredCallableIdentity? { + match target { + SourceDeclarationCall { owner_module_path: owner, decl_name: decl } => + Present { value: DeclaredCallableIdentity { owner_module_path: owner, decl_name: decl } } + RuntimePrimitiveCall { primitive_name: _, projected_from: projected } => projected + LocallyBoundCall { name: _ } => none + CallableTargetUndetermined => none + } +} + +fn rust_emit_length_on_host_string(call_target: CallTargetIdentity, func: String, args: List, call_semantics: CallSemantics?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String? { + if rust_call_target_is_v2_algebra_length(target: call_target) == false { none } else { let length_args = order_typed_call_args_from_semantics(args: args, func: func, call_semantics: call_semantics, scope: scope) match length_args |> first { Absent => none @@ -12733,7 +12754,7 @@ fn emit_typed_call(func: String, args: List, inferred: InferredNode?, call } return disc_result } - match rust_emit_length_on_host_string(func: func, args: args, call_semantics: call_semantics, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) { + match rust_emit_length_on_host_string(call_target: call_target, func: func, args: args, call_semantics: call_semantics, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) { Present { value: lowered } => return lowered Absent => {} } diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 7b1bb8cda2a..07c2b6c9d2e 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -26715,7 +26715,39 @@ pub fn lambda_argument_scope(arg: Rc, scope: Rc) -> Rc) -> bool { + match rust_call_target_declared_length(target.clone()) { + std::option::Option::None => false, + Some(id) => { + ((id.decl_name.clone() == "length".to_string()) + && (id.owner_module_path.clone() == "v2.std.algebra".to_string())) + } + } +} + +pub fn rust_call_target_declared_length( + target: Rc, +) -> Option> { + match (*target.clone()).clone() { + CallTargetIdentity::SourceDeclarationCall { + owner_module_path: owner, + decl_name: decl, + .. + } => Some(Rc::new(DeclaredCallableIdentity { + owner_module_path: owner.clone(), + decl_name: decl.clone(), + })), + CallTargetIdentity::RuntimePrimitiveCall { + projected_from: projected, + .. + } => projected.clone(), + CallTargetIdentity::LocallyBoundCall { name: _, .. } => std::option::Option::None, + CallTargetIdentity::CallableTargetUndetermined => std::option::Option::None, + } +} + pub fn rust_emit_length_on_host_string( + call_target: Rc, func: String, args: Rc>>, call_semantics: Option>, @@ -26725,7 +26757,7 @@ pub fn rust_emit_length_on_host_string( shared_types: Rc>, emit_info: Rc, ) -> Option { - if (func.clone() != "length".to_string()) { + if (rust_call_target_is_v2_algebra_length(call_target.clone()) == false) { std::option::Option::None } else { { @@ -27045,6 +27077,7 @@ pub fn emit_typed_call( } } match rust_emit_length_on_host_string( + call_target.clone(), func.clone(), args.clone(), call_semantics.clone(), From 4e65642af29da4f78aeba2ebb5de11b2e6eb5b9a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 03:28:09 +0000 Subject: [PATCH 3/9] Lower algebra length to string_length only for host text. Leaf-name String also matches v2.std.text.String (structural FreeMonoid), which made emit-build E0308 at string_split_step. Co-authored-by: Cursor --- .../instruments/native_emission_controls.dag | 7 +++-- src/v1/05_emit_rust.dag | 28 +++++++++++++------ src/v1/stage0/src/v1_compiler_emit_rust.rs | 20 ++++++++++++- 3 files changed, 43 insertions(+), 12 deletions(-) diff --git a/dag/gunbc/instruments/native_emission_controls.dag b/dag/gunbc/instruments/native_emission_controls.dag index ad055187a3b..cc529d309e1 100644 --- a/dag/gunbc/instruments/native_emission_controls.dag +++ b/dag/gunbc/instruments/native_emission_controls.dag @@ -452,9 +452,10 @@ fn control_outcome_is_bad(o: ControlOutcome) -> Bool { } } -// RULE: `v2.std.algebra` `length` over host text lowers to `v1_rt::string_length`, the same -// decision `.length` on a string receiver already takes (v1.compiler.emit_rust emit_typed_call). -// Without it the generic FreeMonoid body is called at a `String` (E0308 at request_field). +// RULE: `v2.std.algebra` `length` over HOST text (kernel `String`) lowers to +// `v1_rt::string_length`. Corpus `v2.std.text.String` is structural FreeMonoid and keeps the +// generic body. Without the host arm, kernel `String` at `request_field` is E0308; with a +// leaf-name arm, structural text at `string_split_step` is E0308 the other way. fn control_algebra_length_of_string(s: String) -> Int { length(xs: s) } diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index b8b9c5c3bf8..fcb176f20d7 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12629,13 +12629,15 @@ fn lambda_argument_scope(arg: Node, scope: InferScope) -> InferScope { // emit_func_def names the declaration it renders on the body scope (caller_decl_name) so that read // has a subject. -// THE SAME LENGTH DECISION THE METHOD PATH ALREADY ASKS. AlgebraMethodSemantics remaps `.length` -// on a host-string receiver to `string_length`. The function form `v2.std.algebra` `length` over -// `FreeMonoid` is that operation spelled as a call: `String` inhabits the monoid, so -// `length(xs: s)` is legal source. Emitting it as the generic FreeMonoid body (`Rc>`) is -// E0308 against the host `String` at `request_field` and at every `length` of a `String` in -// `std.scoped_authorization`. Both spellings ask whether the operand is host text, and both lower -// to `v1_rt::string_length`. +// THE SAME LENGTH DECISION THE HOST-TEXT RENDERER ALREADY ASKS. AlgebraMethodSemantics remaps +// `.length` on a host-string receiver to `string_length`. The function form `v2.std.algebra` +// `length` over `FreeMonoid` is that operation spelled as a call: kernel `String` inhabits +// the monoid, so `length(xs: s)` is legal source. Emitting it as the generic FreeMonoid body +// (`Rc>`) is E0308 against the host `String` at `request_field`. `v2.std.text.String` is +// the other carrier: the structural alias `FreeMonoid` (`Rc>` here). Rewriting +// THAT call to `v1_rt::string_length` is E0308 the other way (self-host `v2_std_text.rs` +// `string_split_step`). The discriminator is `is_host_text_carrier_type`, the same provenance +// gate the type renderer uses — leaf name `String` is not enough. // THE CALLEE IS THE DECLARATION, NOT THE LEAF. A call spelled `length` that resolves to another // module's function (or a local) must emit that function; rewriting by the authored name would // replace a byte-count or a constant with a scalar count and answer wrongly in silence. @@ -12663,7 +12665,7 @@ fn rust_emit_length_on_host_string(call_target: CallTargetIdentity, func: String match length_args |> first { Absent => none Present { value: xs_arg } => - if is_string_typed_expr(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { + if is_host_text_typed_expr(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { Present { value: concat( "v1_rt::string_length(&", emit_typed_expr_base(texpr: arg_value(n: xs_arg), registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info), @@ -15823,6 +15825,16 @@ fn is_string_typed_expr(e: Node, source_indices: Map) -> B } } +fn is_host_text_typed_expr(e: Node, source_indices: Map) -> Bool { + match e.inferred { + Present { value: Resolved { node: rt } } => + let is_optional = rt.return_cardinality == CardOptional + let inner = if is_optional { with_required_cardinality(n: rt) } else { rt } + is_host_text_carrier_type(n: inner, source_indices: source_indices) + _ => false + } +} + fn emit_typed_string_interp(parts: List, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { let has_interpolations = parts |> any(p => match p { Interpolation { expr: _ } => true _ => false }) let fmt_parts = parts |> map(p => typed_interp_format_part(part: p, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info, has_interpolations: has_interpolations)) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 07c2b6c9d2e..749a64740fc 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -26770,7 +26770,7 @@ pub fn rust_emit_length_on_host_string( match length_args.clone().first().cloned() { std::option::Option::None => std::option::Option::None, Some(xs_arg) => { - if is_string_typed_expr( + if is_host_text_typed_expr( crate::v1_std_core::arg_value(xs_arg.clone()), scope.type_env.clone().source_indices.clone(), ) { @@ -35379,6 +35379,24 @@ pub fn is_string_typed_expr( } } +pub fn is_host_text_typed_expr( + e: Rc, + source_indices: Rc>>, +) -> bool { + match e.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => { + let is_optional = (rt.return_cardinality.clone() == Cardinality::CardOptional); + let inner = if is_optional.clone() { + crate::v1_std_core::with_required_cardinality(rt.clone()) + } else { + rt.clone() + }; + is_host_text_carrier_type(inner.clone(), source_indices.clone()) + } + _ => false, + } +} + pub fn emit_typed_string_interp( parts: Rc>>, registry: Rc>>, From 62b81047b73fde1aca898502e813a54e6d0dcc9a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 05:00:03 +0000 Subject: [PATCH 4/9] Do not declare a second corpus fn length. A homonym named length made every bare length an unimported two-declarer and failed the floor. The control still returns 99 from a uniquely named body so a blanket String rewrite would go red. Co-authored-by: Cursor --- .../instruments/native_emission_controls_homonym.dag | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/dag/gunbc/instruments/native_emission_controls_homonym.dag b/dag/gunbc/instruments/native_emission_controls_homonym.dag index 6ccaf6c879a..31cc03f4441 100644 --- a/dag/gunbc/instruments/native_emission_controls_homonym.dag +++ b/dag/gunbc/instruments/native_emission_controls_homonym.dag @@ -33,10 +33,13 @@ fn narrow_slots_equal(left: String, right: String) -> Bool { narrow_slot(value: left) == narrow_slot(value: right) } -// A `length` that is NOT `v2.std.algebra` `length`. Its body is a constant, so a leaf-name rewrite -// to `v1_rt::string_length` would return 3 for "abc" and this control would go red. -fn length(xs: String) -> Int { 99 } +// A String-in, Int-out body that is NOT `v2.std.algebra` `length`. A second corpus declaration +// named `length` makes every bare `length` an unimported two-declarer (floor). The identity +// gate on the algebra call is what keeps a leaf-name rewrite from eating this body; if emit +// rewrote every host-String call to `v1_rt::string_length`, "abc" would answer 3 and the +// control would go red. +fn length_constant_ninety_nine(xs: String) -> Int { 99 } fn homonym_length_of(s: String) -> Int { - length(xs: s) + length_constant_ninety_nine(xs: s) } From 6bc35c38e2f0077734b9f2cd29b38ad2698cbed2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 14:39:14 +0000 Subject: [PATCH 5/9] Restore a same-leaf length control outside the accepted corpus. A second fn length under dag/ fails the floor. The specimen is a fixtures/ module compiled by an isolated infer_ emit-check: identity keeps the constant 99; the old leaf predicate would have emitted string_length. Co-authored-by: Cursor --- .../instruments/native_emission_controls.dag | 6 +- .../native_emission_controls_homonym.dag | 13 +---- ...er_algebra_length_homonym_witness_test.dag | 57 +++++++++++++++++++ fixtures/algebra_length_homonym/probe.dag | 13 +++++ 4 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 dag/test/claim/infer_algebra_length_homonym_witness_test.dag create mode 100644 fixtures/algebra_length_homonym/probe.dag diff --git a/dag/gunbc/instruments/native_emission_controls.dag b/dag/gunbc/instruments/native_emission_controls.dag index cc529d309e1..3639b2de51e 100644 --- a/dag/gunbc/instruments/native_emission_controls.dag +++ b/dag/gunbc/instruments/native_emission_controls.dag @@ -3,7 +3,7 @@ module gunbc.instruments.native_emission_controls import std.types { Bool, Int, List, NonEmptyStr, Set, String } import v2.std.algebra { filter, length } import std.list { duplicated_by_key } -import gunbc.instruments.native_emission_controls_homonym { ControlBoxed, narrow_slot_value, narrow_slots_equal, homonym_length_of } +import gunbc.instruments.native_emission_controls_homonym { ControlBoxed, narrow_slot_value, narrow_slots_equal } import std.resource_contract { Observation, Read } import std.integer { UInt8, UInt8Ready, UInt8OutOfRange, uint8_of_int } import extdeps.filesystem.filesystem_io { Filesystem } @@ -464,8 +464,7 @@ fn algebra_length_on_string_cases() -> List { [ NativeEmissionCase { identity: "algebra_length_of_a_string_counts_scalars", held: control_algebra_length_of_string(s: "abc") == 3 }, NativeEmissionCase { identity: "algebra_length_of_a_string_red_is_not_another_count", held: control_algebra_length_of_string(s: "abc") != 4 }, - NativeEmissionCase { identity: "algebra_length_of_a_list_is_unchanged", held: length(xs: ["a", "b"]) == 2 }, - NativeEmissionCase { identity: "algebra_length_red_a_homonym_length_is_not_scalar_count", held: homonym_length_of(s: "abc") == 99 } + NativeEmissionCase { identity: "algebra_length_of_a_list_is_unchanged", held: length(xs: ["a", "b"]) == 2 } ] } @@ -510,7 +509,6 @@ data native_emission_expected_identities: List = [ "algebra_length_of_a_string_counts_scalars", "algebra_length_of_a_string_red_is_not_another_count", "algebra_length_of_a_list_is_unchanged", - "algebra_length_red_a_homonym_length_is_not_scalar_count", "fold_constructor_init_sums_all_nonnegative", "fold_constructor_init_red_negative_switches_arm", "fold_constructor_init_red_empty_keeps_init", diff --git a/dag/gunbc/instruments/native_emission_controls_homonym.dag b/dag/gunbc/instruments/native_emission_controls_homonym.dag index 31cc03f4441..b5e42e22776 100644 --- a/dag/gunbc/instruments/native_emission_controls_homonym.dag +++ b/dag/gunbc/instruments/native_emission_controls_homonym.dag @@ -1,6 +1,6 @@ module gunbc.instruments.native_emission_controls_homonym -import std.types { Bool, Int, String } +import std.types { Bool, String } // THE SECOND DECLARER OF A LEAF, for gunbc.instruments.native_emission_controls. That module declares // ControlSlot; this one declares ControlSlot. One closure, one leaf, two declarations with @@ -32,14 +32,3 @@ fn narrow_slot_value(value: String) -> String { fn narrow_slots_equal(left: String, right: String) -> Bool { narrow_slot(value: left) == narrow_slot(value: right) } - -// A String-in, Int-out body that is NOT `v2.std.algebra` `length`. A second corpus declaration -// named `length` makes every bare `length` an unimported two-declarer (floor). The identity -// gate on the algebra call is what keeps a leaf-name rewrite from eating this body; if emit -// rewrote every host-String call to `v1_rt::string_length`, "abc" would answer 3 and the -// control would go red. -fn length_constant_ninety_nine(xs: String) -> Int { 99 } - -fn homonym_length_of(s: String) -> Int { - length_constant_ninety_nine(xs: s) -} diff --git a/dag/test/claim/infer_algebra_length_homonym_witness_test.dag b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag new file mode 100644 index 00000000000..da918f1696e --- /dev/null +++ b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag @@ -0,0 +1,57 @@ +module test.claim.infer_algebra_length_homonym_witness + +import extdeps.filesystem.filesystem_io +import std.types { Bool, String } +import v2.std.live_tree { LiveTreeDisposition, ReadsLiveTree } + +data live_tree_disposition: LiveTreeDisposition = ReadsLiveTree + +// THE SAME-LEAF CONTROL THE ACCEPTED CORPUS CANNOT CARRY. A second `fn length` under dag/ or +// src/v2 makes every bare `length` an unimported two-declarer (floor). The specimen is therefore +// a fixture outside those roots, compiled here through compile_dag_rust_emit_check — the same +// isolated nested-compile shape #13459 used for a kernel-name collision that must not be +// ingestible. The file is the authority; this module reads it. +// +// WHAT WOULD GO RED UNDER THE OLD LEAF PREDICATE. That predicate rewrote any host-String call +// whose callee *leaf* was `length` to `v1_rt::string_length`. This fixture's `probe` calls a +// `length` whose decl_name is `length` and whose owner is `algebra_length_homonym_probe`. The +// identity gate leaves the constant body; the leaf predicate would emit `v1_rt::string_length` +// and this exclude-list would fail. + +data algebra_length_homonym_probe_path: String = "fixtures/algebra_length_homonym/probe.dag" + +fn algebra_length_homonym_probe_source() -> String { + filesystem_read(path: algebra_length_homonym_probe_path).content +} + +fn length_rewrite_by_authored_leaf(func: String) -> Bool { + func == "length" +} + +fn length_rewrite_by_declaration(owner: String, decl: String) -> Bool { + decl == "length" && owner == "v2.std.algebra" +} + +test fn a_user_length_on_host_string_is_not_emitted_as_string_length() -> Bool { + compile_dag_rust_emit_check( + algebra_length_homonym_probe_source(), + "src/algebra_length_homonym_probe.rs", + ["99"], + ["v1_rt::string_length"] + ) +} + +test fn the_old_leaf_predicate_would_rewrite_the_fixture_length() -> Bool { + length_rewrite_by_authored_leaf(func: "length") + && !length_rewrite_by_declaration(owner: "algebra_length_homonym_probe", decl: "length") + && length_rewrite_by_declaration(owner: "v2.std.algebra", decl: "length") +} + +test fn algebra_length_on_host_string_still_lowers_to_string_length() -> Bool { + compile_dag_rust_emit_check( + "module algebra_length_host_probe\nimport std.types { String, Int }\nimport v2.std.algebra { length }\nfn probe(s: String) -> Int { length(xs: s) }\n", + "src/algebra_length_host_probe.rs", + ["v1_rt::string_length(&"], + ["v2_std_algebra::length("] + ) +} diff --git a/fixtures/algebra_length_homonym/probe.dag b/fixtures/algebra_length_homonym/probe.dag new file mode 100644 index 00000000000..76c634ea902 --- /dev/null +++ b/fixtures/algebra_length_homonym/probe.dag @@ -0,0 +1,13 @@ +module algebra_length_homonym_probe + +import std.types { String, Int } + +// A user `length` whose leaf is the algebra op's leaf and whose owner is not v2.std.algebra. +// Lives under fixtures/ so the accepted corpus (dag + src/v2) never grows a second `fn length` +// (floor: every bare `length` would then be an unimported two-declarer). Compiled only by +// test.claim.infer_algebra_length_homonym_witness. +fn length(xs: String) -> Int { 99 } + +fn probe() -> Int { + length(xs: "abc") +} From 383eaa3ca715f57b41bab14c25f50c59e8545678 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 16:54:48 +0000 Subject: [PATCH 6/9] Use one host-text gate for algebra length call and method. The method arm still asked is_string_typed_expr (leaf name). Both spellings now share rust_algebra_length_lowers_on_host_text so structural String is count, not string_length. Co-authored-by: Cursor --- ...er_algebra_length_homonym_witness_test.dag | 9 +++++++ src/v1/05_emit_rust.dag | 25 +++++++++++-------- src/v1/stage0/src/v1_compiler_emit_rust.rs | 11 ++++++-- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/dag/test/claim/infer_algebra_length_homonym_witness_test.dag b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag index da918f1696e..7403f48e248 100644 --- a/dag/test/claim/infer_algebra_length_homonym_witness_test.dag +++ b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag @@ -55,3 +55,12 @@ test fn algebra_length_on_host_string_still_lowers_to_string_length() -> Bool { ["v2_std_algebra::length("] ) } + +test fn host_text_receiver_length_lowers_to_string_length() -> Bool { + compile_dag_rust_emit_check( + "module host_text_length_method_probe\nimport std.types { String, Int }\nfn probe(s: String) -> Int { s.length() }\n", + "src/host_text_length_method_probe.rs", + ["string_length"], + [] + ) +} diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index fcb176f20d7..85319ed6374 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12629,15 +12629,14 @@ fn lambda_argument_scope(arg: Node, scope: InferScope) -> InferScope { // emit_func_def names the declaration it renders on the body scope (caller_decl_name) so that read // has a subject. -// THE SAME LENGTH DECISION THE HOST-TEXT RENDERER ALREADY ASKS. AlgebraMethodSemantics remaps -// `.length` on a host-string receiver to `string_length`. The function form `v2.std.algebra` -// `length` over `FreeMonoid` is that operation spelled as a call: kernel `String` inhabits -// the monoid, so `length(xs: s)` is legal source. Emitting it as the generic FreeMonoid body -// (`Rc>`) is E0308 against the host `String` at `request_field`. `v2.std.text.String` is -// the other carrier: the structural alias `FreeMonoid` (`Rc>` here). Rewriting -// THAT call to `v1_rt::string_length` is E0308 the other way (self-host `v2_std_text.rs` -// `string_split_step`). The discriminator is `is_host_text_carrier_type`, the same provenance -// gate the type renderer uses — leaf name `String` is not enough. +// ONE HOST-TEXT DISCRIMINATOR FOR BOTH SPELLINGS. AlgebraMethodSemantics remaps `.length` on a +// host-string receiver to `string_length`; the function form `v2.std.algebra` `length` over +// `FreeMonoid` is that operation spelled as a call. Kernel `String` inhabits the monoid, so +// `length(xs: s)` is legal source. Emitting it as the generic FreeMonoid body (`Rc>`) is +// E0308 against the host `String` at `request_field`. `v2.std.text.String` is the other carrier: +// the structural alias `FreeMonoid` (`Rc>` here). Rewriting THAT call (or +// `.length` on it) to `v1_rt::string_length` is E0308 the other way (self-host `v2_std_text.rs` +// `string_split_step`). Both arms ask `is_host_text_typed_expr` — leaf name `String` is not enough. // THE CALLEE IS THE DECLARATION, NOT THE LEAF. A call spelled `length` that resolves to another // module's function (or a local) must emit that function; rewriting by the authored name would // replace a byte-count or a constant with a scalar count and answer wrongly in silence. @@ -12659,13 +12658,17 @@ fn rust_call_target_declared_length(target: CallTargetIdentity) -> DeclaredCalla } } +fn rust_algebra_length_lowers_on_host_text(e: Node, source_indices: Map) -> Bool { + is_host_text_typed_expr(e: e, source_indices: source_indices) +} + fn rust_emit_length_on_host_string(call_target: CallTargetIdentity, func: String, args: List, call_semantics: CallSemantics?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String? { if rust_call_target_is_v2_algebra_length(target: call_target) == false { none } else { let length_args = order_typed_call_args_from_semantics(args: args, func: func, call_semantics: call_semantics, scope: scope) match length_args |> first { Absent => none Present { value: xs_arg } => - if is_host_text_typed_expr(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { + if rust_algebra_length_lowers_on_host_text(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { Present { value: concat( "v1_rt::string_length(&", emit_typed_expr_base(texpr: arg_value(n: xs_arg), registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info), @@ -13746,7 +13749,7 @@ fn emit_typed_method_call(method_call_node: Node, receiver: Node, method: String let method_name_raw = authored_name_at(source_indices: scope.type_env.source_indices, node: method_def) let method_name = if method_name_raw == "length" { - if is_string_typed_expr(e: receiver, source_indices: scope.type_env.source_indices) { "string_length" } else { "count" } + if rust_algebra_length_lowers_on_host_text(e: receiver, source_indices: scope.type_env.source_indices) { "string_length" } else { "count" } } else { method_name_raw } let algebra_field_call = rust_callable_field_method_call(method_name: method_name, receiver: receiver, args: args, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) if algebra_field_call != none { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 749a64740fc..bcd019337b9 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -26746,6 +26746,13 @@ pub fn rust_call_target_declared_length( } } +pub fn rust_algebra_length_lowers_on_host_text( + e: Rc, + source_indices: Rc>>, +) -> bool { + is_host_text_typed_expr(e.clone(), source_indices.clone()) +} + pub fn rust_emit_length_on_host_string( call_target: Rc, func: String, @@ -26770,7 +26777,7 @@ pub fn rust_emit_length_on_host_string( match length_args.clone().first().cloned() { std::option::Option::None => std::option::Option::None, Some(xs_arg) => { - if is_host_text_typed_expr( + if rust_algebra_length_lowers_on_host_text( crate::v1_std_core::arg_value(xs_arg.clone()), scope.type_env.clone().source_indices.clone(), ) { @@ -30140,7 +30147,7 @@ pub fn emit_typed_method_call( method_def.clone(), ); let method_name = if (method_name_raw.clone() == "length".to_string()) { - if is_string_typed_expr( + if rust_algebra_length_lowers_on_host_text( receiver.clone(), scope.type_env.clone().source_indices.clone(), ) { From 06aefae46f878714cbbbd086ee39533ecc339eb0 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 17:09:29 +0000 Subject: [PATCH 7/9] Delete the self-confirming leaf-predicate claim. It restated the emitter's rewrite rule on hand-picked strings and would stay green if emit matched on the leaf. The fixture emit-check already fails that regression. Co-authored-by: Cursor --- .../infer_algebra_length_homonym_witness_test.dag | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/dag/test/claim/infer_algebra_length_homonym_witness_test.dag b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag index 7403f48e248..eab9bc8d9e5 100644 --- a/dag/test/claim/infer_algebra_length_homonym_witness_test.dag +++ b/dag/test/claim/infer_algebra_length_homonym_witness_test.dag @@ -24,14 +24,6 @@ fn algebra_length_homonym_probe_source() -> String { filesystem_read(path: algebra_length_homonym_probe_path).content } -fn length_rewrite_by_authored_leaf(func: String) -> Bool { - func == "length" -} - -fn length_rewrite_by_declaration(owner: String, decl: String) -> Bool { - decl == "length" && owner == "v2.std.algebra" -} - test fn a_user_length_on_host_string_is_not_emitted_as_string_length() -> Bool { compile_dag_rust_emit_check( algebra_length_homonym_probe_source(), @@ -41,12 +33,6 @@ test fn a_user_length_on_host_string_is_not_emitted_as_string_length() -> Bool { ) } -test fn the_old_leaf_predicate_would_rewrite_the_fixture_length() -> Bool { - length_rewrite_by_authored_leaf(func: "length") - && !length_rewrite_by_declaration(owner: "algebra_length_homonym_probe", decl: "length") - && length_rewrite_by_declaration(owner: "v2.std.algebra", decl: "length") -} - test fn algebra_length_on_host_string_still_lowers_to_string_length() -> Bool { compile_dag_rust_emit_check( "module algebra_length_host_probe\nimport std.types { String, Int }\nimport v2.std.algebra { length }\nfn probe(s: String) -> Int { length(xs: s) }\n", From 0e1a1c0770666a775f3184c8b2f41387c8d00b35 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 17:19:55 +0000 Subject: [PATCH 8/9] Call is_host_text_typed_expr instead of an alias wrapper. Co-authored-by: Cursor --- src/v1/05_emit_rust.dag | 8 ++------ src/v1/stage0/src/v1_compiler_emit_rust.rs | 11 ++--------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 85319ed6374..75b64807ab2 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -12658,17 +12658,13 @@ fn rust_call_target_declared_length(target: CallTargetIdentity) -> DeclaredCalla } } -fn rust_algebra_length_lowers_on_host_text(e: Node, source_indices: Map) -> Bool { - is_host_text_typed_expr(e: e, source_indices: source_indices) -} - fn rust_emit_length_on_host_string(call_target: CallTargetIdentity, func: String, args: List, call_semantics: CallSemantics?, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String? { if rust_call_target_is_v2_algebra_length(target: call_target) == false { none } else { let length_args = order_typed_call_args_from_semantics(args: args, func: func, call_semantics: call_semantics, scope: scope) match length_args |> first { Absent => none Present { value: xs_arg } => - if rust_algebra_length_lowers_on_host_text(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { + if is_host_text_typed_expr(e: arg_value(n: xs_arg), source_indices: scope.type_env.source_indices) { Present { value: concat( "v1_rt::string_length(&", emit_typed_expr_base(texpr: arg_value(n: xs_arg), registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info), @@ -13749,7 +13745,7 @@ fn emit_typed_method_call(method_call_node: Node, receiver: Node, method: String let method_name_raw = authored_name_at(source_indices: scope.type_env.source_indices, node: method_def) let method_name = if method_name_raw == "length" { - if rust_algebra_length_lowers_on_host_text(e: receiver, source_indices: scope.type_env.source_indices) { "string_length" } else { "count" } + if is_host_text_typed_expr(e: receiver, source_indices: scope.type_env.source_indices) { "string_length" } else { "count" } } else { method_name_raw } let algebra_field_call = rust_callable_field_method_call(method_name: method_name, receiver: receiver, args: args, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) if algebra_field_call != none { diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index bcd019337b9..1d9e2f31b58 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -26746,13 +26746,6 @@ pub fn rust_call_target_declared_length( } } -pub fn rust_algebra_length_lowers_on_host_text( - e: Rc, - source_indices: Rc>>, -) -> bool { - is_host_text_typed_expr(e.clone(), source_indices.clone()) -} - pub fn rust_emit_length_on_host_string( call_target: Rc, func: String, @@ -26777,7 +26770,7 @@ pub fn rust_emit_length_on_host_string( match length_args.clone().first().cloned() { std::option::Option::None => std::option::Option::None, Some(xs_arg) => { - if rust_algebra_length_lowers_on_host_text( + if is_host_text_typed_expr( crate::v1_std_core::arg_value(xs_arg.clone()), scope.type_env.clone().source_indices.clone(), ) { @@ -30147,7 +30140,7 @@ pub fn emit_typed_method_call( method_def.clone(), ); let method_name = if (method_name_raw.clone() == "length".to_string()) { - if rust_algebra_length_lowers_on_host_text( + if is_host_text_typed_expr( receiver.clone(), scope.type_env.clone().source_indices.clone(), ) { From 51c57520c3856e1e3f26720ec5a70d0ef669b511 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Thu, 8 Oct 2026 23:17:30 +0000 Subject: [PATCH 9/9] Regen the emit_rust stage0 mirror after merging main. Co-authored-by: Cursor --- src/v1/stage0/src/v1_compiler_emit_rust.rs | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 1d9e2f31b58..3d912ba42da 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -40091,34 +40091,37 @@ pub fn emit_capability_method( } } -pub fn data_value_has_cross_refs(value: Rc) -> bool { +pub fn data_value_is_json_literal_tree(value: Rc) -> bool { stacker::maybe_grow(512 * 1024, 2 * 1024 * 1024, || { match (*value.expr_data.clone()).clone() { - ExprData::ExprVar { - binding_kind: _, .. - } => true, - ExprData::ExprCall { .. } => true, + ExprData::ExprLiteral { value: _, .. } => true, ExprData::ExprListLit => { - let mut __found = false; + let mut __all = true; for c in value.children.clone().iter().cloned() { - if data_value_has_cross_refs(c.clone()) { - __found = true; + if !(data_value_is_json_literal_tree(c.clone())) { + __all = false; break; } } - __found + __all } ExprData::ExprRecordLit { parent_enum: _, .. } => { - let mut __found = false; + let mut __all = true; for f in value.children.clone().iter().cloned() { - if data_value_has_cross_refs(crate::v1_std_core::field_init_node_value( - f.clone(), + if !(data_value_is_json_literal_tree( + crate::v1_std_core::field_init_node_value(f.clone()), )) { - __found = true; + __all = false; break; } } - __found + __all + } + ExprData::ExprUnaryOp { + op: UnaryOpKind::Neg, + .. + } => { + data_value_is_json_literal_tree(crate::v1_std_core::unaryop_operand(value.clone())) } _ => false, } @@ -40541,7 +40544,7 @@ pub fn emit_data_def_body( if ((crate::v1_compiler_emit::has_nested_records_node( type_node.clone(), scope.type_env.clone().source_indices.clone(), - ) && !data_value_has_cross_refs(value.clone())) + ) && data_value_is_json_literal_tree(value.clone())) && !data_row_type_forbids_deserialize( type_node.clone(), emit_info.clone(),