diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 25d9d76131d..3a56a28bb83 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -1096,6 +1096,128 @@ mod compiler_tests { result.expect("shell_service_exit_error_arm_is_boxed panicked"); } + // REGRESSION CONTROL for a property that holds BY CONSTRUCTION today, and which nothing + // else would notice losing. DESIGN section 4b(4): the evidence stays enrolled. + // + // A function-VALUE realizes as `Rc`; a function-typed PARAMETER realizes as an + // `impl Fn(..) + Clone` bound, and `Rc` carries no blanket `impl Fn`, so passing the first + // into the second is an E0277 seam. rust_call_arg_function_value_adapt closes it by wrapping + // the argument in a forwarding closure, and it decides on + // `param_node_type_expr(n: param).params |> count > 0`. + // + // THAT IS THE SAME EXPRESSION ON THE SAME NODE that emit_param/emit_rust_param_type use to + // decide whether to emit the `impl Fn` bound at all. So the adapter fires exactly where the + // seam exists, and cannot fire where it does not -- the two predicates cannot disagree while + // they remain one expression. This control is what notices if they are ever forked. + // + // WHAT THIS ESTABLISHES, AT ITS DECLARED GRAIN. Emission SUCCEEDS on this fixture -- zero + // error diagnostics, asserted FIRST, so a refusal fails here rather than being matched past + // -- and the emitted bytes carry, for this fixture, all six of: + // the function-valued producer emitted as `Rc`; + // the declared-arrow formal emitted as `impl Fn(..) + Clone`; + // a forwarding closure emitted AT that formal; + // the bare type-variable formal carrying NO Fn bound; + // NO forwarding closure at that formal; + // and hence the two emitter decisions staying aligned on this fixture. + // Those are structural facts about the emitter, and substring matching can decide them. + // That list is the WHOLE claim. + // + // IT DOES NOT ESTABLISH THAT THE E0277 SEAM IS CLOSED, AND NOTHING EXECUTING TODAY DOES. + // Whether these bytes compile is a RUSTC verdict, and no amount of exact rendering is + // evidence about one; a substring assertion could not decide it at any lane membership. + // + // NO ENROLLED FAIL-CLOSED RUSTC CONSUMER COVERS THIS SYNTHETIC FIXTURE. That is a statement + // about the whole tree as it stands, not a pointer at some other test that would do it -- + // deliberately, because naming a candidate invites the next reader to read 'not yet' as + // 'once someone schedules it', and a candidate cited that way becomes coverage in the telling. + // + // NO PER-FIXTURE RUSTC CONSUMER IS BUILT HERE EITHER. probe.rs references the crate runtime + // (im::vector, v1_std_core, the artifact types) and does not stand alone, and a second + // per-fixture compile path is the parallel authority section 6 warns about. The standing + // execution half would be a real COMMITTED occurrence of this adapter shape joined BY + // IDENTITY to a rustc consumer that runs and REFUSES. That join does not exist today. + // + // WHY THE BARE TYPE VARIABLE IS THE DISCRIMINATING HALF. It was reported (by me, wrongly) as a + // silent defect: a parameter declared `T` has arity 0, so no adapter is emitted, and nothing + // refuses. The bytes below are the refutation -- `T` renders as a plain generic with NO Fn + // bound, so there is no seam to close, and adapting there would be a fabricated repair. + // Arity is invariant under substitution anyway: instantiation changes an arrow's type + // ARGUMENTS, never its parameter count. + #[test] + fn function_value_adapter_fires_exactly_where_the_impl_fn_bound_is_emitted() { + let result = std::thread::Builder::new() + .stack_size(32 * 1024 * 1024) + .spawn(|| { + let source = std::rc::Rc::new(crate::v1_compiler_compile::SourceFile { + path: "probe.dag".to_string(), + content: "module probe\nfn add_one(x: Int) -> Int { x + 1 }\nfn make_adder() -> fn(Int) -> Int { fn(n) { add_one(x: n) } }\nfn hold_tv(value: T) -> T { value }\nfn apply_arrow(f: fn(Int) -> Int, v: Int) -> Int { f(v) }\nfn through_type_variable() -> Int { apply_arrow(f: hold_tv(value: make_adder()), v: 1) }\nfn through_declared_arrow() -> Int { apply_arrow(f: make_adder(), v: 1) }\n".to_string(), + }); + let r = crate::v1_compiler_compile::compile_sources( + std::rc::Rc::new(im::vector![source]), + crate::v1_compiler_artifact::RenderTarget::Rust, + ); + let errors: Vec<_> = r + .diagnostics + .iter() + .filter(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())) + .collect(); + assert!( + errors.is_empty(), + "the fixture is ordinary admissible source; a refusal here is a compiler \ + defect rather than a defect in the fixture. Got: {:?}", + errors + ); + let emitted = r + .files + .iter() + .find(|f| f.path == "src/probe.rs") + .map(|f| f.content.clone()) + .expect("probe module must emit src/probe.rs"); + + // (1) the producer side of the seam: a function VALUE is the Rc carrier. + assert!( + emitted.contains("pub fn make_adder() -> Rc i64>"), + "a function-valued return must realize as the Rc carrier, got:\n{}", + emitted + ); + // (2) a DECLARED-ARROW parameter carries the impl Fn bound -- the seam exists here. + assert!( + emitted.contains("pub fn apply_arrow(f: impl Fn(i64) -> i64 + Clone, v: i64)"), + "a declared-arrow parameter must carry the impl Fn bound, got:\n{}", + emitted + ); + // (3) a BARE TYPE VARIABLE parameter carries NO Fn bound -- no seam exists here. + assert!( + emitted.contains("pub fn hold_tv(value: T) -> T"), + "a bare type-variable parameter must render as a plain generic with no Fn \ + bound -- if this ever gains one, the adapter predicate must move with it, \ + got:\n{}", + emitted + ); + // (4) THE ALIGNMENT, both directions. The adapter wraps the argument at the + // impl-Fn-bound parameter, and does NOT wrap it at the type-variable parameter -- + // whose argument is equally a call result, so shape alone does not explain it. + assert!( + emitted.contains( + "apply_arrow({ let __adapt_f = hold_tv(make_adder()); move |__adapt_a0| __adapt_f(__adapt_a0) }, 1)" + ), + "the forwarding adapter must be emitted at the impl Fn parameter, got:\n{}", + emitted + ); + assert!( + !emitted.contains("hold_tv({ let __adapt_f"), + "no adapter may be injected at a parameter that carries no Fn bound -- that \ + would be a fabricated repair of a seam that does not exist, got:\n{}", + emitted + ); + }) + .expect("failed to spawn thread") + .join(); + result.expect( + "function_value_adapter_fires_exactly_where_the_impl_fn_bound_is_emitted panicked", + ); + } + #[test] fn method_existence_wall_witness() { // DISCRIMINATING RED for method_existence_wall_note. Before the wall an @@ -2568,7 +2690,9 @@ mod compiler_tests { let shared = std::rc::Rc::new(im::OrdSet::new()); let generics = std::rc::Rc::new(im::Vector::new()); let variant_to_enum = std::rc::Rc::new(HashMap::new()); - let env = crate::v1_compiler_infer_env::empty_type_env(); + let mut env_value = (*crate::v1_compiler_infer_env::empty_type_env()).clone(); + env_value.unit_variant_index_observed = true; + let env = std::rc::Rc::new(env_value); let arg = named_type_node("Int"); let applied = shaped_type_node("std.algebra.FreeMonoid", vec![arg]); let rendered = crate::v1_compiler_emit_rust::render_rust_applied_type( @@ -2584,6 +2708,49 @@ mod compiler_tests { "applied-type base must not emit namespace dots in generic position" ); assert_eq!(rendered, "Vec"); + let marker = named_type_node("Time"); + let contribution = + std::rc::Rc::new(crate::v1_compiler_infer_env::UnitVariantContribution { + count: 1, + variant: marker, + }); + let by_parent = crate::v1_rt::rc_map_insert( + crate::v1_rt::rc_empty_map(), + "Quantity".to_string(), + contribution, + ); + let mut populated_env_value = (*crate::v1_compiler_infer_env::empty_type_env()).clone(); + populated_env_value.unit_variant_index = crate::v1_rt::rc_map_insert( + crate::v1_rt::rc_empty_map(), + "Time".to_string(), + by_parent, + ); + populated_env_value.unit_variant_index_observed = true; + let populated = crate::v1_compiler_emit_rust::render_rust_applied_type( + shaped_type_node("Box", vec![named_type_node("Time")]), + std::rc::Rc::new(im::Vector::new()), + std::rc::Rc::new(im::OrdSet::new()), + std::rc::Rc::new(HashMap::new()), + std::rc::Rc::new(HashMap::new()), + std::rc::Rc::new(populated_env_value), + ); + assert_eq!( + populated, "Box