From 80b37b28a16fc30579cdbc2b6d4daeadae728a4e Mon Sep 17 00:00:00 2001 From: gunbc-ci-auto-heal Date: Thu, 17 Sep 2026 03:29:30 +0000 Subject: [PATCH] The v2-native CLI closure emits and builds clean: the seam refusal carries a type, and three carrier conversions were missing `gunbc test //gunbc/instruments:v2-native-cli` reports exit_status=0 warning_count=0 at 495s under RUSTFLAGS="-D warnings". The closure went from NINETEEN rustc errors to zero across three changes; this is the last two of them. C -- AN UNREALIZED HOST SEAM NOW CARRIES ITS OWN TYPE. The refusal itself was already right: a primitive whose body is a bare self-call has no behaviour on this target, and emitting that shape verbatim yields a function that compiles and returns to no caller (DESIGN section 5). What `panic!` alone cannot do is stand where its RESULT IS CONSUMED -- `!` coerces in value position but is not a method receiver, so `panic!("...").len()` answered E0599 and everything after it `unreachable`. The emitter now renders `v1_rt::unrealized_host_seam::(msg)` with T taken from the call's OWN INFERRED TYPE, which is how `empty_map` and `empty_set` in the same function already read `inferred`. Five errors in two modules, gone, with the divergence intact: the helper has no return path. WHERE THAT ARM MAY BE ASKED IS THE WHOLE LESSON, AND IT COST TWO FAILED ATTEMPTS. The predicate answers "runtime primitive, no bridge, no projected declaration", and that is the right question ONLY at the RESIDUE POSITION, after every earlier arm has declined -- because a family of primitives is rendered by DEDICATED ARMS carrying no bridge row: `with`, `concat` past two arguments, the collection folds, the pipelines. Attempt 1, body grain: refuse the whole FUNCTION when its body reaches a seam. Type-checks trivially. It called every bridgeless builtin unrealized, refused `v1.compiler.tokenize source_code_point` and then `v1.compiler.infer typecheck_module`, and the rebuilt seed panicked on its own first tokenize. Withdrawn. Attempt 2, one level too early: the typed refusal placed in emit_typed_call_expr, ahead of the delegation. It intercepted `with` in v1.compiler.infer, so `let typed_module = with(typed_base, ..)` took the seam's type instead of the record update's, and the SECOND GENERATION failed to compile (`expected Rc, found Rc>`). STAGE ONE BUILT CLEAN BOTH TIMES; only the fixed-point check caught it. The landed arm is the one that already sat after `is_rt` and after the `call_target` match, now taking `inferred` so it can type what it refuses. The constraint is recorded on the predicate, because it is a fact about where the question may be asked rather than about what it computes. B -- THREE CARRIER CONVERSIONS THAT WERE SIMPLY MISSING, AND NOT THE FORK THEY LOOKED LIKE. `v2.std.compilers.lexing` declares `LiteralPattern { text: v2.std.text.String }` -- the QUALIFIED spelling, so the field is the free-monoid char list on purpose -- and its own constructor converts with `chars`. Two other constructors handed over an ordinary string unconverted, and `semantic_decl_string_to_bundle_node` folded one through `from_code_point` without taking the char-list view. Adding `chars(s: ..)` at the three sites is the fix. THAT ATTRIBUTION WAS WRONG BEFORE IT WAS MEASURED, and the correction matters more than the fix. The error signature (`expected Rc>, found String`) matches the String meaning fork, so these were reported as instances of it and briefed to the self-host lane as blocked behind docs/plans/carrier-realization-arbiter-repair-design.md -- 52 sites, arm A's 25 generic-carrier errors, a location-valued identity key, two refuted repairs. None of that was load-bearing here. These are that document's ARM B, the arm it states is closable constructor-side, and they close. Arm A is real, unsolved, and lives in `01_tokenize`'s closure rather than this one. EVIDENCE, all on the converged compiler rather than the one that produced the first reading. `//gunbc/instruments:v2-native-cli` exit_status=0 warning_count=0 wall_s=495. `//gunbc/instruments:self-host` exit_status=0 warning_count=0 wall_s=586 -- the corpus-wide control, because this changes how EVERY unrealized-seam call emits. The regen round converges with one mirror changed. EVERY FIGURE ABOVE IS MEASURED ON THIS TREE, after #11474 squash-merged and this commit was rebased onto it. Two earlier zeroes are SUPERSEDED rather than cited beside these: the first was measured against a binary still carrying attempt 2's `with` defect, and the second against the pre-rebase tree. The convergence run that discriminated attempt 2 reported convergence_stages=2 [stage-1, stage-2] -- stage two is where that attempt failed, so its clean build is the evidence that the residue placement is right, and it is recorded here as history of the check rather than as a property of this tree. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/05_emit_rust.dag | 49 +++++++++++++++++-- src/v1/runtime_rust.dag | 26 ++++++++++ src/v1/stage0/src/v1_compiler_emit_rust.rs | 15 +++++- src/v1/stage0/src/v1_compiler_runtime_rust.rs | 2 +- src/v1/stage0/src/v1_rt.rs | 4 ++ src/v2/extdeps/languages/rust.dag | 7 ++- .../std/compilers/semantic_decl_emission.dag | 15 +++++- 7 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index d30fe924cb7..42eea92e1f5 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -11216,6 +11216,25 @@ fn call_target_is_runtime_primitive(target: CallTargetIdentity) -> Bool { // question the call site answers positionally today -- at which point a body walk can consume it and // the whole-function refusal becomes decidable. // +// THE PREDICATE IS ONLY TRUE AT THE RESIDUE POSITION, AND THAT IS A FACT ABOUT WHERE IT MAY BE +// ASKED RATHER THAN ABOUT WHAT IT COMPUTES. It answers "runtime primitive, no bridge, no projected +// declaration" -- which is the right question ONLY once every earlier arm has declined, because the +// emitter renders a family of primitives through DEDICATED ARMS that carry no bridge row: `with` +// (record update), `concat` past two arguments, the collection folds, the pipelines. Asked before +// those arms, it calls every one of them unrealized. +// +// MEASURED TWICE, both times by the seed failing on itself rather than by reading the dispatch. A +// body-grain consumer refused `v1.compiler.tokenize source_code_point` and `v1.compiler.infer +// typecheck_module`, and the rebuilt seed panicked on its own first tokenize. Then a call-expression +// consumer placed one level too early intercepted `with` in `v1.compiler.infer` -- the emitted +// `let typed_module = with(typed_base, ..)` took the seam refusal's type instead of the record +// update's, and the SECOND GENERATION failed to compile with `expected Rc, found +// Rc>`. Stage one built clean both times; only the fixed-point check caught it. +// +// So the only admissible consumer is the arm in `emit_typed_call` that already sits after `is_rt` +// and after the `call_target` match. A caller that wants this answer earlier does not want this +// question. +// // THE BRIDGE TEST IS PART OF THE PREDICATE, NOT A GUARD ITS CALLERS ADD, and that placement was // bought the hard way. A runtime primitive WITH a `v1_rt` row is realized -- the bridge IS its // realization -- so "runtime primitive with no projected declaration" is not the question; it @@ -11225,6 +11244,15 @@ fn call_target_is_runtime_primitive(target: CallTargetIdentity) -> Bool { // `v1.compiler.tokenize source_code_point` -- an ordinary bridged function -- and the rebuilt binary // panicked on its own first tokenize. A predicate that is only correct when every caller remembers // to pre-filter it is a wall with a hole per consumer, so the filter moved inside. +// The seam's own name, for the refusal text. Empty when the target is not a runtime primitive at +// all, which the predicate beside this one has already excluded at every call site that asks. +fn call_target_unrealized_primitive_name(target: CallTargetIdentity) -> String { + match call_target_runtime_primitive_name(target: target) { + Present { value: primitive_name } => primitive_name + Absent => "" + } +} + fn call_target_is_unrealized_primitive(target: CallTargetIdentity) -> Bool { match call_target_runtime_primitive_name(target: target) { Absent => false @@ -11641,7 +11669,7 @@ fn emit_typed_call_expr(func: String, args: List, inferred: InferredNode?, _ => "v1_rt::rc_empty_set::<_>()" } } else { - emit_typed_call(func: func, args: args, call_semantics: call_semantics, callee_is_function_value: callee_is_function_value, call_target: call_target, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) + emit_typed_call(func: func, args: args, inferred: inferred, call_semantics: call_semantics, callee_is_function_value: callee_is_function_value, call_target: call_target, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) } let wraps_in_rc = match call_target_runtime_primitive_name(target: call_target) { @@ -11818,7 +11846,7 @@ fn lambda_argument_scope(arg: Node, scope: InferScope) -> InferScope { // because a BARE leaf can name two modules, which is precisely the state a resolved owner rules // out. An undetermined target has no identifier to emit and gets a located refusal rather than // a fall back to the authored spelling, which is the re-lookup this carrier exists to remove. -fn emit_typed_call(func: String, args: List, call_semantics: CallSemantics?, callee_is_function_value: Bool, call_target: CallTargetIdentity, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { +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) } @@ -11972,7 +12000,20 @@ fn emit_typed_call(func: String, args: List, call_semantics: CallSemantics } } let func_name = if callee_self_capture { concat(func_ident, ".clone()") } else { func_ident } let call_str = if unrealized_primitive_call { - func_ident + match inferred { + Present { value: Resolved { node: ret_type } } => + concat( + "v1_rt::unrealized_host_seam::<", + render_rust_type(n: ret_type, shared_types: shared_types, source_indices: si, emit_info: emit_info), + ">(\"", + escape_string_literal_body(s: concat( + concat("primitive ", call_target_unrealized_primitive_name(target: call_target)), + " has no v1_rt realization and no declaration to emit: give it a row in extdeps.languages.rust.emit rt_function_registry and a body in v1.runtime_rust, or delete its consumers" + )), + "\")" + ) + _ => func_ident + } } else { if is_rt && func == "concat" && all_args |> count > 2 { emit_nested_rt_concat(remaining: all_args, acc: "", shared_types: shared_types) @@ -14430,7 +14471,7 @@ fn emit_rust_tco_non_self_call(frame: TcoFrame, registry: Map, match frame.expr.expr_data { ExprCall { call_semantics: cs, descent_evidence: _ } => let f = expr_call_func_at(texpr: frame.expr, source_indices: frame.scope.type_env.source_indices) - let call_str = emit_typed_call(func: f, args: frame.expr.children, call_semantics: cs, callee_is_function_value: call_semantics_is_function_value(cs: cs), call_target: call_semantics_target(cs: cs), registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info) + let call_str = emit_typed_call(func: f, args: frame.expr.children, inferred: frame.expr.inferred, call_semantics: cs, callee_is_function_value: call_semantics_is_function_value(cs: cs), call_target: call_semantics_target(cs: cs), registry: registry, scope: frame.scope, depth: frame.depth, shared_types: shared_types, emit_info: emit_info) concat("break ", call_str, ";") _ => emit_error_expr(message: "emit_rust_tco_non_self_call expected ExprCall", target: Rust) } diff --git a/src/v1/runtime_rust.dag b/src/v1/runtime_rust.dag index 4a1495bed14..60d33e0d79c 100644 --- a/src/v1/runtime_rust.dag +++ b/src/v1/runtime_rust.dag @@ -589,6 +589,29 @@ fn rt_rc_container_ops() -> String { // runtime is mirrored into the partition's foundation crate, so `v1_rt::` resolves from a // partitioned crate as well as from the monolithic one. A crate-root definition would not: the // partition crates re-export only what stage0_crates names explicitly. +// THE DIVERGING SEAM HELPER, AND WHY IT IS GENERIC RATHER THAN A BARE `panic!`. +// +// An unrealized host seam must refuse, and `panic!` is the refusal: a primitive whose body is a bare +// self-call has no behaviour on this target, and emitting that shape verbatim produces a function +// that compiles, runs and returns to no caller (DESIGN section 5 silent wrongness). What `panic!` +// alone cannot do is stand where its RESULT IS CONSUMED. `!` coerces to any type in value position, +// but it is not a method receiver, so `panic!("...").len()` refuses with E0599 and every argument +// after it with `unreachable`. Measured on the v2-native CLI closure: two seams, five errors, none +// of which named a seam. +// +// `unrealized_host_seam::` is the same divergence with a NAME FOR ITS TYPE. The emitter renders +// the turbofish from the call's own inferred type, so the expression is concrete, method resolution +// works, and the program still cannot get past it. Nothing is fabricated: the function has no return +// path. +// +// WHY NOT REFUSE THE WHOLE FUNCTION INSTEAD, which type-checks trivially because `!` coerces in +// return position. That was built and withdrawn: deciding "does this body reach an unrealized seam" +// needs a realizability predicate the emitter does not have -- `rt_functions()` is not the set of +// realizable primitives, since collection folds, pipelines and the operators are rendered by +// dedicated arms with no bridge row -- so a body walk refused `v1.compiler.tokenize +// source_code_point` and `v1.compiler.infer typecheck_module`, and the rebuilt seed panicked on its +// own first tokenize. Typing the expression needs no such predicate: the call site already knows +// what the call was supposed to return. fn rt_filesystem() -> String { concat( rust_file_create_new_canonical_block(), "\n", @@ -596,6 +619,9 @@ fn rt_filesystem() -> String { "pub struct FilesystemReadResult {\n", " pub content: String,\n", "}\n\n", + "pub fn unrealized_host_seam(seam: &str) -> T {\n", + " panic!(\"{}\", seam);\n", + "}\n\n", "pub fn filesystem_read(path: String) -> FilesystemReadResult {\n", " let content = std::fs::read_to_string(&path)\n", " .unwrap_or_else(|e| panic!(\"failed to read {}: {}\", path, e));\n", diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 84a4199ed1d..3b50862dc4a 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -23828,6 +23828,13 @@ pub fn call_target_is_runtime_primitive(target: Rc) -> bool (call_target_runtime_primitive_name(target.clone()) != std::option::Option::None) } +pub fn call_target_unrealized_primitive_name(target: Rc) -> String { + match call_target_runtime_primitive_name(target.clone()) { + Some(primitive_name) => primitive_name.clone(), + std::option::Option::None => "".to_string(), + } +} + pub fn call_target_is_unrealized_primitive(target: Rc) -> bool { match call_target_runtime_primitive_name(target.clone()) { std::option::Option::None => false, @@ -24908,6 +24915,7 @@ pub fn emit_typed_call_expr( emit_typed_call( func.clone(), args.clone(), + inferred.clone(), call_semantics.clone(), callee_is_function_value.clone(), call_target.clone(), @@ -25142,6 +25150,7 @@ pub fn lambda_argument_scope(arg: Rc, scope: Rc) -> Rc>>, + inferred: Option>, call_semantics: Option>, callee_is_function_value: bool, call_target: Rc, @@ -25666,7 +25675,10 @@ pub fn emit_typed_call( func_ident.clone() }; let call_str = if unrealized_primitive_call.clone() { - func_ident.clone() + match inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: ret_type, .. }) => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("v1_rt::unrealized_host_seam::<".to_string(), render_rust_type(ret_type.clone(), shared_types.clone(), si.clone(), emit_info.clone())), ">(\"".to_string()), crate::v1_compiler_emit_core_support::escape_string_literal_body(v1_rt::concat(v1_rt::concat("primitive ".to_string(), call_target_unrealized_primitive_name(call_target.clone())), " has no v1_rt realization and no declaration to emit: give it a row in extdeps.languages.rust.emit rt_function_registry and a body in v1.runtime_rust, or delete its consumers".to_string()))), "\")".to_string()), + _ => func_ident.clone(), +} } else { if ((is_rt.clone() && (func.clone() == "concat".to_string())) && ((all_args.clone().len() as i64) > 2)) @@ -33013,6 +33025,7 @@ pub fn emit_rust_tco_non_self_call( let call_str = emit_typed_call( f.clone(), frame.expr.clone().children.clone(), + frame.expr.clone().inferred.clone(), cs.clone(), call_semantics_is_function_value(cs.clone()), crate::v1_std_core::call_semantics_target(cs.clone()), diff --git a/src/v1/stage0/src/v1_compiler_runtime_rust.rs b/src/v1/stage0/src/v1_compiler_runtime_rust.rs index a896f8061ca..4c8322c0227 100644 --- a/src/v1/stage0/src/v1_compiler_runtime_rust.rs +++ b/src/v1/stage0/src/v1_compiler_runtime_rust.rs @@ -65,7 +65,7 @@ pub fn rt_rc_container_ops() -> String { } pub fn rt_filesystem() -> String { - v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(crate::extdeps_filesystem_rust_realization::rust_file_create_new_canonical_block(), "\n".to_string()), "#[derive(Debug, Clone)]\n".to_string()), "pub struct FilesystemReadResult {\n".to_string()), " pub content: String,\n".to_string()), "}\n\n".to_string()), "pub fn filesystem_read(path: String) -> FilesystemReadResult {\n".to_string()), " let content = std::fs::read_to_string(&path)\n".to_string()), " .unwrap_or_else(|e| panic!(\"failed to read {}: {}\", path, e));\n".to_string()), " FilesystemReadResult { content }\n".to_string()), "}\n".to_string()) + v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(crate::extdeps_filesystem_rust_realization::rust_file_create_new_canonical_block(), "\n".to_string()), "#[derive(Debug, Clone)]\n".to_string()), "pub struct FilesystemReadResult {\n".to_string()), " pub content: String,\n".to_string()), "}\n\n".to_string()), "pub fn unrealized_host_seam(seam: &str) -> T {\n".to_string()), " panic!(\"{}\", seam);\n".to_string()), "}\n\n".to_string()), "pub fn filesystem_read(path: String) -> FilesystemReadResult {\n".to_string()), " let content = std::fs::read_to_string(&path)\n".to_string()), " .unwrap_or_else(|e| panic!(\"failed to read {}: {}\", path, e));\n".to_string()), " FilesystemReadResult { content }\n".to_string()), "}\n".to_string()) } pub fn rt_hash_ops() -> String { diff --git a/src/v1/stage0/src/v1_rt.rs b/src/v1/stage0/src/v1_rt.rs index fcaf6366b67..af8b0738ec0 100644 --- a/src/v1/stage0/src/v1_rt.rs +++ b/src/v1/stage0/src/v1_rt.rs @@ -1192,6 +1192,10 @@ pub struct FilesystemReadResult { pub content: String, } +pub fn unrealized_host_seam(seam: &str) -> T { + panic!("{}", seam); +} + pub fn filesystem_read(path: String) -> FilesystemReadResult { let content = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("failed to read {}: {}", path, e)); diff --git a/src/v2/extdeps/languages/rust.dag b/src/v2/extdeps/languages/rust.dag index eb549ef4c5b..27e6beabb40 100644 --- a/src/v2/extdeps/languages/rust.dag +++ b/src/v2/extdeps/languages/rust.dag @@ -2261,10 +2261,15 @@ fn rust_target_model() -> TargetModel { } } +// `LexPattern` declares `LiteralPattern { text: v2.std.text.String }` -- the QUALIFIED spelling, so +// the field is the free-monoid char list on purpose, not the target's native string. The constructor +// in `v2.std.compilers.lexing` converts with `chars` when it builds one from a fragment; this one +// took an ordinary string and handed it over unconverted, which rustc answered as a carrier mismatch +// at the field rather than at the missing conversion. fn rust_lex_rule(token_class: Symbol, text: String) -> LexRule { TokenRule { token_class: token_class, - pattern: LiteralPattern { text: text } + pattern: LiteralPattern { text: chars(s: text) } } } diff --git a/src/v2/std/compilers/semantic_decl_emission.dag b/src/v2/std/compilers/semantic_decl_emission.dag index 5125e58c2e3..560644674c2 100644 --- a/src/v2/std/compilers/semantic_decl_emission.dag +++ b/src/v2/std/compilers/semantic_decl_emission.dag @@ -131,9 +131,20 @@ fn semantic_decl_emission_bundle_malformed_diagnostic(bundle: Node) -> Diagnosti } } +// THE FREE-MONOID ENCODING READS CODE POINTS, SO THE STRING IS VIEWED AS ONE HERE. +// +// The parameter is an ordinary string and its callers are right to pass one -- `semantic_decl_emit` +// hands it a `text` and a `spelling`. What the BODY needs is the char-list view, because it folds +// each element through `from_code_point`. `chars` is the conversion between the two, and it is the +// same one `v2.std.compilers.lexing` already uses when it builds a `LiteralPattern`. +// +// WITHOUT IT THE FOLD HAS NO ELEMENT TYPE TO INFER: `String` renders as the target's native string, +// `fold_list` is generic over `FreeMonoid`, and rustc answers E0282 rather than naming the +// missing view. That diagnostic is what sent this to the `String`-fork program on first reading; it +// is a missing conversion at one site. fn semantic_decl_string_to_bundle_node(s: String) -> Node { fold_list( - xs: s, + xs: chars(s: s), empty: target_model_type_atom_node(identity: ^free_monoid_variant_empty), cons: fn(tail, c) { target_model_type_conj_node( @@ -284,7 +295,7 @@ fn decode_semantic_decl_lex_rule_row(row: Node) -> Outcome { outcome_accepted( value: TokenRule { token_class: token_class, - pattern: LiteralPattern { text: text } + pattern: LiteralPattern { text: chars(s: text) } } ) }