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) } } ) }