diff --git a/dag/test/claim/emitted_lib_rs_module_declaration_witness_test.dag b/dag/test/claim/emitted_lib_rs_module_declaration_witness_test.dag index c281e9e4c1a..8bbd0caafe5 100644 --- a/dag/test/claim/emitted_lib_rs_module_declaration_witness_test.dag +++ b/dag/test/claim/emitted_lib_rs_module_declaration_witness_test.dag @@ -2,7 +2,7 @@ module test.claim.emitted_lib_rs_module_declaration_witness_test // Permanent regression control for the emitted `lib.rs` module list. // -// THE DEFECT, now repaired: v1.compiler.emit_rust emit_lib_rs_from_files derived +// THE DEFECT, now repaired: v1.compiler.emit_rust emit_lib_rs_from_paths derived // its module declarations from the files this emit actually produced, and then // appended `pub mod v1_interpreter_dispatch_generated;` UNCONDITIONALLY from a // hand-authored string. For the stage0 crate that module is real — a different diff --git a/src/v1/04_emit_info.dag b/src/v1/04_emit_info.dag index f9035712a2c..f18d5f6e2d2 100644 --- a/src/v1/04_emit_info.dag +++ b/src/v1/04_emit_info.dag @@ -568,4 +568,4 @@ fn add_emit_item_summary(state: EmitInfoBuildState, item: Node, source_indices: } } -data emit_dependency_registration_dissolve_on_note: DissolutionCondition = unbound_dissolution(description: "dissolve-on (FreeMonoid pass 2026-07-20): collect emitted cross-module type/path refs during emit into one authority; derive (a) use-lines via build_shared_types + emit_faithful_text_carrier_import_lines and (b) closure pub-mod membership via emit_lib_rs_from_files from that set. Instance-patch interim: faithful corpus emits carrier import lines per module unless locally defined (FreeMonoid/Char/NonEmptyStr/Int); closure projections (v1.compiler.closure_stub_v2_std_text_rust and closure_stub_v2_std_integer_rust) when refs exist but the real v2.std.text or v2.std.integer module is absent from narrow typed.modules — projections are NOT parallel modeling authorities (see each stub module dissolve_on_note); they dissolve when the ref set includes the real module in the closure.") +data emit_dependency_registration_dissolve_on_note: DissolutionCondition = unbound_dissolution(description: "dissolve-on (FreeMonoid pass 2026-07-20): collect emitted cross-module type/path refs during emit into one authority; derive (a) use-lines via build_shared_types + emit_faithful_text_carrier_import_lines and (b) closure pub-mod membership via emit_lib_rs_from_paths from that set. Instance-patch interim: faithful corpus emits carrier import lines per module unless locally defined (FreeMonoid/Char/NonEmptyStr/Int); closure projections (v1.compiler.closure_stub_v2_std_text_rust and closure_stub_v2_std_integer_rust) when refs exist but the real v2.std.text or v2.std.integer module is absent from narrow typed.modules — projections are NOT parallel modeling authorities (see each stub module dissolve_on_note); they dissolve when the ref set includes the real module in the closure.") diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index df7928fa52d..f3a2213b14a 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -52,7 +52,7 @@ import v1.std.core { transport_base_path, empty_intern_table } -import v1.compiler.artifact { RenderTarget, Rust } +import v1.compiler.artifact { RenderTarget, Rust, RustModuleRenderSelection, RenderEveryModule, RenderSelectedMirrors } import extdeps.languages.rust.emit { rust_trait_derive_attr_from_traits, rt_functions, rt_ref_map_functions, rt_wraps_result, rt_bridge_function_names, rust_container_templates, rust_method_wraps_result, rust_method_templates, @@ -2969,6 +2969,33 @@ fn emit_rust_reference_derived_census(typed: ResolvedGraph) -> ReferenceDerivedC // therefore joins the two diagnostic guards that already sit after the context, and the // preamble's binding order is untouched. fn emit_rust(typed: ResolvedGraph) -> EmitResult { + emit_rust_selected(typed: typed, selection: RenderEveryModule) +} + +// THE EMISSION, DENOMINATED IN THE CHANGE. +// +// Under RenderSelectedMirrors only the selected modules are RENDERED. Everything else this +// function returns is derived from the resolved graph or from paths, so it is identical under +// both arms of the selection -- which is what makes a scoped emission a subset of a whole one +// rather than a different answer: +// +// * the crate's module list (lib.rs) and the emitted-population manifest are computed from +// `module_paths`, one path per module in `typed.modules` whether or not it was rendered. +// Their inputs are `List`, so no rendered byte can reach them -- content +// independence is the parameter type rather than a claim about the body. +// * the two closure-stub decisions are asked of the RESOLVED GRAPH (see +// `closure_references_module_filename`), never of emitted text, so a module the selection +// declined to render still counts toward whether the crate needs a stub. +// * `import_refusals` are observed only for rendered modules, and that is exact rather than +// partial. `emit_resolved_for_target_selected` in `v1.compiler.compile` carries the +// argument: an emit with any error diagnostic returns NO files, so a committed tree is one +// whose whole closure emitted clean and the unrendered modules had nothing to say. +// +// The selection is a LIST of mirror basenames -- the exact derived closure of an edit. It is +// never "the one module that changed": a change reaches every mirror its reverse closure names, +// and a selector that could carry only one member would be a bound that cannot express its own +// answer. +fn emit_rust_selected(typed: ResolvedGraph, selection: RustModuleRenderSelection) -> EmitResult { let ctx = build_emit_rust_context(typed: typed) let emit_info = ctx.emit_info let registry = ctx.registry @@ -2992,14 +3019,24 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { return EmitResult { files: [], diagnostics: filename_collisions } } let test_projections = ctx.test_projections - let module_emissions = typed.modules |> map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules)) + let module_emissions = typed.modules + |> filter(tm => rust_module_render_selection_renders( + selection: selection, + module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module))) + |> map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules)) let module_files = module_emissions |> map(e => e.file) let import_refusals = module_emissions |> flat_map(e => e.import_refusals) - let test_files = typed.modules - |> map(tm => emit_test_file( - module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module), - projections: test_projections |> filter(p => p.module_name == authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)))) - |> filter(f => string_length(s: f.content) > 0) + let module_paths = typed.modules |> map(tm => rust_module_emit_path( + module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module))) + let test_module_names = typed.modules + |> map(tm => authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)) + |> filter(name => (test_projections |> filter(p => p.module_name == name)) |> count > 0) + let test_file_paths = test_module_names |> map(name => rust_test_file_path(module_name: name)) + let test_files = test_module_names + |> filter(name => rust_module_render_selection_renders(selection: selection, module_name: name)) + |> map(name => emit_test_file( + module_name: name, + projections: test_projections |> filter(p => p.module_name == name))) let has_services = has_service_items(typed: typed) let has_pipeline = typed.modules |> any(m => authored_name_at(source_indices: m.type_env.source_indices, node: m.module) == "v1.compiler.compile") let crate_name = if has_pipeline { "v1_compiler" } else { "v1_compiled" } @@ -3013,26 +3050,58 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { [] } - let v2_std_text_stub = if module_files_reference_v2_std_text(files: module_files) - && !module_files_include_v2_std_text(files: module_files) { + let v2_std_text_stub = if closure_needs_module_filename_stub(typed: typed, ctx: ctx, filename: "v2_std_text") { [emit_v2_std_text_closure_stub_module()] } else { [] } - let v2_std_integer_stub = if module_files_reference_v2_std_integer(files: module_files) - && !module_files_include_v2_std_integer(files: module_files) { + let v2_std_integer_stub = if closure_needs_module_filename_stub(typed: typed, ctx: ctx, filename: "v2_std_integer") { [emit_v2_std_integer_closure_stub_module()] } else { [] } let all_mod_files = concat(module_files, v2_std_text_stub, v2_std_integer_stub, [rt_file], dry_run_file) - let lib_file = emit_lib_rs_from_files(all_module_files: all_mod_files, has_compiler_tests: has_pipeline) + let all_mod_paths = concat( + module_paths, + v2_std_text_stub |> map(f => f.path), + v2_std_integer_stub |> map(f => f.path), + [rt_file.path], + dry_run_file |> map(f => f.path) + ) + let lib_file = emit_lib_rs_from_paths(all_module_paths: all_mod_paths, has_compiler_tests: has_pipeline) + let emitted_paths = concat( + [cargo.path, lib_file.path, main_file.path], + all_mod_paths, + compiler_tests_file |> map(f => f.path), + test_file_paths + ) + let population_manifest = emit_emitted_population_manifest(paths: emitted_paths) let emitted_files = concat([cargo, lib_file, main_file], all_mod_files, compiler_tests_file, test_files) - let population_manifest = emit_emitted_population_manifest(files: emitted_files) let files = concat(emitted_files, [population_manifest]) EmitResult { files: files, diagnostics: import_refusals } } +// THE ONE PLACE A MODULE'S EMITTED FILENAME IS DECIDED, read by the renderer and by every +// population-denominated aggregate that must name a module it did not render. `main` collides +// with the crate's own entry file, so it renders as `main_mod`; that rename used to live inside +// `emit_module_full` alone, where a second reader of the same fact had no way to reach it. +fn rust_module_emit_filename(module_name: String) -> String { + let raw = module_to_filename(name: module_name) + if raw == "main" { "main_mod" } else { raw } +} + +fn rust_module_emit_path(module_name: String) -> String { + concat(rust_source_root(), rust_module_emit_filename(module_name: module_name), rust_source_ext()) +} + +fn rust_module_render_selection_renders(selection: RustModuleRenderSelection, module_name: String) -> Bool { + match selection { + RenderEveryModule => true + RenderSelectedMirrors { basenames: basenames } => + basenames |> any(b => b == concat(rust_module_emit_filename(module_name: module_name), rust_source_ext())) + } +} + // THE EMITTER DECLARES THE POPULATION IT PRODUCED. This is the one place in the repository // where that fact exists as a value: `files` below is the complete emitted population, typed, // before anything reaches disk. It was then DISCARDED, and every downstream consumer rebuilt @@ -3058,8 +3127,8 @@ fn emitted_population_manifest_path() -> String { concat(rust_source_root(), emitted_population_manifest_basename) } -fn emit_emitted_population_manifest(files: List) -> TextFile { - let declared = concat(files |> map(f => f.path), [emitted_population_manifest_path()]) +fn emit_emitted_population_manifest(paths: List) -> TextFile { + let declared = concat(paths, [emitted_population_manifest_path()]) let lines = declared |> sort_by(path => path) |> map(path => concat(emitted_population_manifest_line_prefix, path)) @@ -3080,40 +3149,112 @@ fn emit_compiler_tests_module() -> TextFile { TextFile { path: concat(rust_source_root(), "compiler_tests", rust_source_ext()), content: compiler_tests_source() } } -// The v2 closure-stub triggers scan EMITTED TEXT for a module reference, and the emitter's own -// emitted file contains every such pattern as a STRING LITERAL -- both the pattern being searched -// for and the use line being generated. So the predicate matched itself, and any crate containing -// the emitter emitted stubs for modules nothing actually used. This was latent, not new: the -// deleted corpus_repr_is_faithful conjunct happened to be false on the seed regen closure and -// suppressed the self-match there, so removing the corpus mode surfaced it as a fail-closed -// emission-plan refusal rather than as wrong output. Anchoring on a leading newline discriminates a -// real emitted use line (line start) from the emitter's literal (always preceded by a quote). WHAT -// THIS DOES NOT DO: it does not climb the rung. The grain is still a text scan over emitted -// content, which cannot in general distinguish a program from the compiler that writes it; it only -// removes the one false-positive class that is decidable by anchoring. The stub's own dissolve-on -// stands unchanged -- derive the condition from the emitted reference SET, a produced structural -// fact, rather than re-scanning the output for it. - -fn emitted_files_carry_use_line(files: List, line: String) -> Bool { - files |> any(f => string_contains(s: f.content, pattern: concat("\n", line))) -} - -fn module_files_reference_v2_std_text(files: List) -> Bool { - emitted_files_carry_use_line(files: files, line: concat(rust_visibility_prefix(), "use crate::v2_std_text::")) -} - -fn module_files_reference_v2_std_integer(files: List) -> Bool { - emitted_files_carry_use_line(files: files, line: concat(rust_visibility_prefix(), "use crate::v2_std_integer::")) +// THE CLOSURE-STUB DECISION IS ASKED OF THE RESOLVED GRAPH, AND THAT IS WHAT MAKES IT TOTAL. +// +// It used to be a text scan over the files this emission had just rendered: does any emitted +// byte carry a line `pub use crate::v2_std_text::`. Two things are wrong with that, and only the +// second is new. +// +// The old one, recorded because it is the reason the scan grew its anchor: the emitter's OWN +// emitted file contains every such pattern as a string literal, both the pattern searched for and +// the use line generated, so the predicate matched itself and any crate containing the emitter +// stubbed modules nothing used. Anchoring on a leading newline removed that class without +// climbing the rung -- a text scan cannot in general distinguish a program from the compiler that +// writes it. +// +// The new one is decisive: a scan over rendered bytes cannot answer for a module that was not +// rendered. Under `RenderSelectedMirrors` the emitter renders a subset, so the old predicate +// would answer about the SUBSET and drop a stub the crate needs -- an E0583 in a crate nobody +// re-rendered, produced silently. The question is about the CLOSURE, so it is asked of the +// closure. +// +// WHAT IT ASKS. A use line `use crate::::` is produced in exactly one place -- +// `reference_derived_use_line_plan`, from a candidate name whose registry provider is the module +// with that filename. (The other producer, `emit_faithful_text_carrier_import_lines`, is the +// hardcoded carrier family; its gate `module_needs_faithful_carrier_imports` currently answers +// `false` unconditionally, so it emits nothing and contributes no reference at all.) So the +// question is: does any module in the closure carry a reference candidate whose provider is that +// filename. The candidates come from the SAME collectors the plan seeds itself from, through the +// same `repair_input_origin_candidates` authority -- not a second reading of them. +// +// IT IS A SUPERSET, COMPUTED AS THE ANSWER, AND THE DIRECTION IS THE SAFE ONE. The plan narrows +// its candidates by the module's own emitted tokens, by names already imported and by an export +// proof; those filters are downstream of rendering, so an exact predicate would cost the whole +// render and leave nothing to save. This omits them, so it can name a provider the plan would +// have dropped. Paired with an EXACT `includes` half, the only reachable error is one stub module +// too many -- an unused `pub mod`, which compiles -- while the error it forecloses is a missing +// module, which does not. This is a structural over-approximation standing as the answer, with no +// failure arm to widen: there is no input on which it declines to decide. +fn module_reference_candidate_names(typed_module: TypedModule, ctx: EmitRustContext) -> List { + let source_indices = typed_module.type_env.source_indices + let emit_info = ctx.emit_info + let this_module_name = authored_name_at(source_indices: source_indices, node: typed_module.module) + let items = typed_module.items + let value_names = unique_strings(items: items |> flat_map(item => collect_value_ref_names(n: item, source_indices: source_indices, type_summaries: emit_info.type_summaries, variant_to_enum: emit_info.variant_to_enum))) + let item_emit_surface_names = items |> flat_map(item => collect_item_emit_surface_names(item: item, source_indices: source_indices, emit_info: emit_info)) + let type_surface_names = unique_strings(items: item_emit_surface_names) + let field_surface_names = collect_items_field_import_surface_names(surface_names: item_emit_surface_names, emit_info: emit_info) + let variant_payload_structs = expand_variant_payload_struct_imports(names: type_surface_names, type_summaries: emit_info.type_summaries, variant_to_enum: emit_info.variant_to_enum) + let realized_surface_names = unique_strings(items: items |> flat_map(item => collect_item_realized_surface_names(item: item, shared_types: emit_info.shared_types, source_indices: source_indices, emit_info: emit_info))) + let unlisted_type_names = match map_get(ctx.unlisted_type_names_by_module, this_module_name) { + Present { value: v } => v + Absent => [] + } + let origin_candidates = repair_input_origin_candidates( + emitting_module: this_module_name, + unlisted_type_names: unlisted_type_names, + value_reference_names: value_names, + item_emit_surface_names: type_surface_names, + field_import_surface_names: field_surface_names, + variant_payload_surface_names: variant_payload_structs, + rendered_type_surface_names: realized_surface_names, + ) + unique_strings(items: repair_input_origin_compatibility_names(candidates: origin_candidates)) + |> filter(name => !reference_is_host_realized_builtin(name: name)) } -fn module_files_include_v2_std_text(files: List) -> Bool { - let expected = concat(rust_source_root(), "v2_std_text", rust_source_ext()) - files |> any(f => f.path == expected) +// THE NECESSARY CONDITION, ASKED FIRST BECAUSE IT IS THE CHEAP ONE AND IT IS EXACT. +// +// A `use crate::::` line exists only if `name` resolves through the item registry +// to a module with that filename. So a registry carrying no such provider admits no such line, +// whatever any module references -- this is an implication, not a heuristic, and answering `false` +// here is answering the question rather than declining it. +// +// IT IS ALSO THE WHOLE COST OF THIS PREDICATE. Without it the reference half walks every module in +// the closure through `repair_input_origin_candidates`, twice -- once per stub filename -- and that +// walk MEASURED 288 s on the seed regen closure, taking compile.emit from 258 s to 546 s. The +// modules it walks for are absent from the registry in every closure that is not a narrow one, so +// the walk was paid in full to answer `false` on the common path. The instrument is +// `claim_executor --regen-round-cost`, phase=compile.emit; the isolation was a probe that +// short-circuited this predicate and left emitted bytes unchanged (changed_paths=0). +fn registry_declares_module_filename(registry: Map, filename: String) -> Bool { + map_values(registry) |> any(info => module_to_filename(name: info.module_name) == filename) +} + +fn closure_references_module_filename(typed: ResolvedGraph, ctx: EmitRustContext, filename: String) -> Bool { + if !registry_declares_module_filename(registry: ctx.registry, filename: filename) { + return false + } + typed.modules |> any(tm => + module_reference_candidate_names(typed_module: tm, ctx: ctx) |> any(name => + match map_get(ctx.registry, name) { + Present { value: info } => module_to_filename(name: info.module_name) == filename + Absent => false + } + ) + ) } -fn module_files_include_v2_std_integer(files: List) -> Bool { - let expected = concat(rust_source_root(), "v2_std_integer", rust_source_ext()) - files |> any(f => f.path == expected) +// REFERENCED BUT ABSENT, and both halves read the resolved graph. `includes` is exact -- a module +// is in the closure or it is not -- so the superset lives entirely in the reference half and the +// conjunction inherits its one-sided direction. +fn closure_needs_module_filename_stub(typed: ResolvedGraph, ctx: EmitRustContext, filename: String) -> Bool { + closure_references_module_filename(typed: typed, ctx: ctx, filename: filename) + && !typed_closure_includes_module_filename( + typed_modules: typed.modules, + filename: filename, + source_indices: merged_module_source_indices(modules: typed.modules) + ) } fn emit_v2_std_integer_closure_stub_module() -> TextFile { @@ -3130,11 +3271,11 @@ fn emit_v2_std_text_closure_stub_module() -> TextFile { } } -fn lib_rs_mod_name_from_file(f: TextFile) -> String { +fn lib_rs_mod_name_from_path(path: String) -> String { let src_prefix_len = string_length(s: rust_source_root()) let ext_len = string_length(s: rust_source_ext()) - let path_len = string_length(s: f.path) - substring(s: f.path, start: src_prefix_len, end: path_len - ext_len) + let path_len = string_length(s: path) + substring(s: path, start: src_prefix_len, end: path_len - ext_len) } // v1_map_key_required_type_names discovers a declared type's map-key Hash+Eq requirement by @@ -3198,8 +3339,15 @@ fn order_partial_lib_rs_mod_names(mod_names: List) -> List { // which is E0583. It surfaced on the falsifier's wet witnesses, whose lib.rs is a // dozen lines: two of them died on the missing module before reaching the behaviour // they exist to measure. A partial crate now declares exactly what this emit produced. -fn emit_lib_rs_from_files(all_module_files: List, has_compiler_tests: Bool) -> TextFile { - let file_derived_names = all_module_files |> map(lib_rs_mod_name_from_file) +// +// PATH-ONLY, AND THE SIGNATURE IS THE PROOF. A crate's `mod` block is a statement about which +// FILES exist, never about what is in them, and this function once took the files themselves -- +// so a reader had to check the body to know that only `.path` was read, and a later edit could +// have reached for content without anything refusing. Taking `List` makes content +// independence structural: there is no content here to depend on. That is what lets a +// change-denominated emission declare modules it did not render. +fn emit_lib_rs_from_paths(all_module_paths: List, has_compiler_tests: Bool) -> TextFile { + let file_derived_names = all_module_paths |> map(lib_rs_mod_name_from_path) let mod_names = if has_compiler_tests { order_lib_rs_mod_names(mod_names: file_derived_names) } else { @@ -4663,8 +4811,7 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, else { concat("\n", local_enum_uses |> join(separator: "\n")) } let phantom_zst_markers = emit_module_phantom_zst_markers(items: typed_module.items, env: scope.type_env) let phantom_section = if phantom_zst_markers == "" { "" } else { concat("\n\n", phantom_zst_markers) } - let raw_filename = module_to_filename(name: authored_name(env: scope.type_env, node: m)) - let filename = if raw_filename == "main" { "main_mod" } else { raw_filename } + let module_emit_path = rust_module_emit_path(module_name: authored_name(env: scope.type_env, node: m)) let module_attrs = if authored_name(env: scope.type_env, node: m) == "std.error_primitives" { "#![allow(non_camel_case_types)]\n\n" } else { "" } @@ -4673,7 +4820,7 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, module_attrs, prelude, imports_section, svc_imports_str, local_uses_str, "\n\n", coproduct_wire_contract_validation_section, items_str, phantom_section, "\n") ModuleEmission { - file: TextFile { path: concat(rust_source_root(), filename, rust_source_ext()), content: content }, + file: TextFile { path: module_emit_path, content: content }, reference_rows: reference_plan.rows, import_refusals: reference_derived_row_diagnostics(rows: reference_plan.rows, module_span: m.span) } diff --git a/src/v1/artifact.dag b/src/v1/artifact.dag index 53c8d25e50d..17e0eaa28d7 100644 --- a/src/v1/artifact.dag +++ b/src/v1/artifact.dag @@ -11,12 +11,33 @@ type ArtifactKind | Frontend | GeneratedSupport +// WHICH MODULES THIS ARTIFACT RENDERS, and it is a property of the PLAN rather than of the +// emitter, because the plan is where "what is being built" is already decided. +// +// RenderEveryModule is the whole closure, byte for byte what the emitter always produced. +// RenderSelectedMirrors carries the exact derived closure of a change -- the basenames a regen +// round's affected-set bound selected -- and the emitter renders those and no others. +// +// THE SELECTION BOUNDS RENDERING AND NOTHING ELSE. Every population-denominated answer the +// emitter gives (the crate's module list, the emitted-population manifest, the closure-stub +// decision) is derived from the resolved graph or from paths, so it is identical under both +// arms. A selection that could change one of those answers would be a scope silently deciding +// what the artifact IS, rather than which of its files this round re-renders. +// +// There is no arm meaning "select nothing in particular": an empty basename list renders no +// modules, which is the honest answer for a round whose change touches no mirror, and is a +// different state from RenderEveryModule rather than a degenerate spelling of it. +type RustModuleRenderSelection + = RenderEveryModule + | RenderSelectedMirrors { basenames: List } + type Artifact { name: String kind: ArtifactKind target: RenderTarget entry_modules: List dependencies: List + module_selection: RustModuleRenderSelection } type BoundaryKind @@ -54,12 +75,20 @@ type ArtifactOutput { } fn default_artifact_plan(root_modules: List, target: RenderTarget) -> ArtifactPlan { + selected_artifact_plan(root_modules: root_modules, target: target, selection: RenderEveryModule) +} + +// ONE BODY, TWO NAMED ENTRY POINTS. `default_artifact_plan` is this call with the whole-closure +// arm supplied, so a caller states which question it asks by naming it rather than by passing a +// flag, and there is no second plan constructor to drift from this one. +fn selected_artifact_plan(root_modules: List, target: RenderTarget, selection: RustModuleRenderSelection) -> ArtifactPlan { plan_artifacts(rule: Explicit { artifacts: [Artifact { name: "default", kind: ServiceBinary, target: target, entry_modules: root_modules, - dependencies: [] + dependencies: [], + module_selection: selection }] }) } diff --git a/src/v1/closure_stub_v2_std_integer_rust.dag b/src/v1/closure_stub_v2_std_integer_rust.dag index ce5af758424..a8d25765e5f 100644 --- a/src/v1/closure_stub_v2_std_integer_rust.dag +++ b/src/v1/closure_stub_v2_std_integer_rust.dag @@ -2,7 +2,7 @@ module v1.compiler.closure_stub_v2_std_integer_rust // dissolve-on (FreeMonoid pass 2026-07-20): NOT a second v2.std.integer authority — a minimal // closure projection when narrow typed.modules omits v2.std.integer but emitted refs need Int. -// Dissolves when emit_lib_rs_from_files derives pub-mod membership from the emitted ref set and +// Dissolves when emit_lib_rs_from_paths derives pub-mod membership from the emitted ref set and // emits the real v2.std.integer module instead. fn closure_stub_v2_std_integer_source() -> String { diff --git a/src/v1/closure_stub_v2_std_text_rust.dag b/src/v1/closure_stub_v2_std_text_rust.dag index 915cb1b963b..359e5cef6ca 100644 --- a/src/v1/closure_stub_v2_std_text_rust.dag +++ b/src/v1/closure_stub_v2_std_text_rust.dag @@ -2,7 +2,7 @@ module v1.compiler.closure_stub_v2_std_text_rust // dissolve-on (FreeMonoid pass 2026-07-20): NOT a second v2.std.text authority — a minimal closure // projection when narrow typed.modules omits v2.std.text but emitted refs need -// crate::v2_std_text::. Dissolves when emit_lib_rs_from_files derives pub-mod membership from the +// crate::v2_std_text::. Dissolves when emit_lib_rs_from_paths derives pub-mod membership from the // emitted ref set and emits the real v2.std.text module instead. fn closure_stub_v2_std_text_source() -> String { diff --git a/src/v1/compile.dag b/src/v1/compile.dag index f1b07e18cf8..b892e3152be 100644 --- a/src/v1/compile.dag +++ b/src/v1/compile.dag @@ -65,7 +65,7 @@ import v1.compiler.infer_types { algebra_field_kind_name } import v1.compiler.emit_core_support { EmitResult, escape_json_string } import v1.compiler.emit { unmodeled_file_transport_diagnostics } -import v1.compiler.emit_rust { emit_rust } +import v1.compiler.emit_rust { emit_rust_selected } import v1.compiler.emit_python { emit_python } @@ -84,7 +84,7 @@ import std.termination { import v1.compiler.ownership { OwnershipProof, OwnershipDecision, SharedError, analyze_ownership } -import v1.compiler.artifact { ArtifactPlan, Artifact, RenderTarget, Dag, default_artifact_plan } +import v1.compiler.artifact { ArtifactPlan, Artifact, RenderTarget, Dag, default_artifact_plan, selected_artifact_plan, RustModuleRenderSelection, RenderEveryModule } import v1.compiler.dag_collect_support { DagCollectAcc, json_quote, connective_name } @@ -240,7 +240,7 @@ fn emit_artifact(typed: ResolvedGraph, artifact: Artifact) -> EmitResult { return EmitResult { files: [], diagnostics: unmodeled_transports } } match artifact.target { - Rust => emit_rust(typed: typed) + Rust => emit_rust_selected(typed: typed, selection: artifact.module_selection) Python => emit_python(typed: typed) Go => emit_go(typed: typed) Dag => emit_dag_artifact(typed: typed) @@ -1199,6 +1199,18 @@ fn compile_sources(sources: List, target: RenderTarget) -> PipelineR ) } +// THE CHANGE-DENOMINATED ENTRY. Same pipeline, same resolved graph, same diagnostics -- the +// selection reaches emission only, where it decides which modules are RENDERED. A caller that +// supplies RenderEveryModule here has written `compile_sources` the long way, which is why that +// wrapper stays the name for the whole-closure question. +fn compile_sources_selected(sources: List, target: RenderTarget, selection: RustModuleRenderSelection) -> PipelineResult { + emit_resolved_for_target_selected( + resolved: compile_to_resolved_with_options(sources: sources, options: default_compile_pipeline_options()), + target: target, + selection: selection + ) +} + fn compile_sources_with_options(sources: List, target: RenderTarget, options: CompilePipelineOptions) -> PipelineResult { emit_resolved_for_target( resolved: compile_to_resolved_with_options(sources: sources, options: options), @@ -1319,6 +1331,25 @@ fn compile_to_resolved_with_options(sources: List, options: CompileP } fn emit_resolved_for_target(resolved: ResolvedPipelineResult, target: RenderTarget) -> PipelineResult { + emit_resolved_for_target_selected(resolved: resolved, target: target, selection: RenderEveryModule) +} + +// WHY THE REFUSAL PRECONDITION A SCOPED ROUND RESTS ON LIVES HERE, one binding down. +// +// Under RenderSelectedMirrors the emitter observes import refusals only for the modules it +// rendered, so a reader is owed why that is EXACT rather than merely cheaper. It is exact +// because of `final_files` below: an emit carrying one error diagnostic returns NO FILES AT ALL. +// A committed mirror is therefore a file some emit returned, and no emit returns a file while +// any module in the closure refuses. So a tree that exists is a tree whose whole closure emitted +// clean, and the modules a scope declined to render had nothing to say. +// +// This is a construction rather than a claim about it: the `if` below is what makes the +// implication true, and `an_emit_that_refuses_hands_back_no_files` in +// `v1_compiler.required_regen_host` is its discriminating execution -- a fixture whose emit +// refuses, asserted to produce zero files rather than the ones that did render. It runs in the +// required unit step, so deleting the `if` goes red there, which is the only form this argument +// can take that a later reader can check. +fn emit_resolved_for_target_selected(resolved: ResolvedPipelineResult, target: RenderTarget, selection: RustModuleRenderSelection) -> PipelineResult { match emittable_graph(resolved: resolved) { Absent => PipelineResult { @@ -1331,9 +1362,10 @@ fn emit_resolved_for_target(resolved: ResolvedPipelineResult, target: RenderTarg } Present { value: emittable } => { let typed = emittable.graph - let artifact_plan = default_artifact_plan( + let artifact_plan = selected_artifact_plan( root_modules: typed.modules |> map(m => authored_name_at(source_indices: resolved.source_indices, node: m.module)), - target: target + target: target, + selection: selection ) let _ = trace_mark("compile.emit.begin") let emit_result = emit_from_artifact_plan(emittable: emittable, artifact_plan: artifact_plan) diff --git a/src/v1/stage0/src/required_regen_host.rs b/src/v1/stage0/src/required_regen_host.rs index 8573bc90046..1f022fdad55 100644 --- a/src/v1/stage0/src/required_regen_host.rs +++ b/src/v1/stage0/src/required_regen_host.rs @@ -12,9 +12,13 @@ use std::time::Instant; #[path = "bootstrap_stage0_crate_layout_generated.rs"] mod bootstrap_stage0_crate_layout_generated; use super::workspace_root; -use crate::v1_compiler_artifact::RenderTarget; +use crate::gunbc_stage0_emitted_population_manifest::{ + emitted_population_manifest_basename, emitted_population_manifest_line_prefix, + emitted_population_manifest_line_separator, +}; +use crate::v1_compiler_artifact::{RenderTarget, RustModuleRenderSelection}; use crate::v1_compiler_compile::{ - compile_sources, stage0_self_compile_refusal_message, SourceFile, + compile_sources_selected, stage0_self_compile_refusal_message, SourceFile, }; use crate::v1_rt; use bootstrap_stage0_crate_layout_generated::{ @@ -401,7 +405,18 @@ pub fn run_required_regen_scoped( // reward for agreeing with the committed tree. Authority for the ordering: // `v2.workflow.required_regen` `required_regen_run`, whose verdict arms cannot be spelled // without the tree they judged. - let (emitted, emitted_basenames) = match emit_generated_surface(&sources)? { + // THE EMISSION IS DENOMINATED IN THE CHANGE. `selected` is the intersection of the + // affected-set bound with the committed roster -- the exact derived closure of this edit, not + // "the module that changed" -- and it is what the emitter renders. Everything the emitter + // declares about the whole population (the crate module list, the manifest, the closure-stub + // decision) is derived from paths and from the resolved graph, so it is unchanged by this. + let render_selection = Rc::new(match scope { + RegenEmissionScope::WholePopulation => RustModuleRenderSelection::RenderEveryModule, + _ => RustModuleRenderSelection::RenderSelectedMirrors { + basenames: Rc::new(selected.iter().cloned().collect()), + }, + }); + let (emitted, emitted_basenames) = match emit_generated_surface(&sources, &render_selection)? { // EMIT PRODUCED NOTHING IS NOT A VERDICT ABOUT A TREE. A receipt here would name a // `candidate_artifact` no pass wrote -- the impersonation the receipt split ends, one field // over. `CandidateTreeUnproduced` in `v2.workflow.required_regen` is the modeled arm and @@ -632,12 +647,19 @@ pub fn run_required_regen_fixed_point( let formatter = ResolvedFormatter::admit()?.with_normalize_cache(&workspace)?; let sources = super::regen_input_sources(&workspace)?; let authority_digest = authority_digest_from_sources(&sources)?; - let emitted = compile_stage0(&sources)?; + // WHOLE POPULATION, DELIBERATELY. The fixed-point pass asks whether a seed rebuilt from the + // installed mirrors regenerates them unchanged; it compares digests over the COMMITTED + // roster, so it must render every member of that roster. A selection here would compare a + // digest over one population against pass 1's over another. + let emitted = compile_stage0( + &sources, + &Rc::new(RustModuleRenderSelection::RenderEveryModule), + )?; let committed_basenames = committed_generated_basenames(&workspace.join("src/v1/stage0/src"))?; if emitted.is_empty() { return Err("refusal: fixed-point emit produced zero files".to_string()); } - let emitted_basenames = generated_basenames_from_emit(&emitted); + let emitted_basenames = generated_basenames_from_emit(&emitted)?; let hand_dir_shadows = hand_maintained_dir_shadows(&workspace.join("src/v1/stage0/src"))?; if let Some(reason) = validate_compared_populations(&committed_basenames, &emitted_basenames, &hand_dir_shadows) @@ -816,14 +838,20 @@ fn scope_selection( } } -fn emit_generated_surface(sources: &[(String, String)]) -> Result { - let emitted = compile_stage0(sources)?; +/// `selection` bounds which modules are RENDERED, and nothing else. The emitted-population +/// roster below is read from the manifest, which the emitter derives from paths, so it stays +/// whole under every selection -- see `generated_basenames_from_emit`. +fn emit_generated_surface( + sources: &[(String, String)], + selection: &Rc, +) -> Result { + let emitted = compile_stage0(sources, selection)?; if emitted.is_empty() { return Ok(GeneratedSurfaceEmit::EmitRefused { reason: "refusal: emit produced zero files".to_string(), }); } - let emitted_basenames = generated_basenames_from_emit(&emitted); + let emitted_basenames = generated_basenames_from_emit(&emitted)?; Ok(GeneratedSurfaceEmit::Emitted { emitted, emitted_basenames, @@ -869,7 +897,10 @@ fn measure_generated_surface( sources: &[(String, String)], stage0_src: &Path, ) -> Result { - let (emitted, emitted_basenames) = match emit_generated_surface(sources)? { + let (emitted, emitted_basenames) = match emit_generated_surface( + sources, + &Rc::new(RustModuleRenderSelection::RenderEveryModule), + )? { GeneratedSurfaceEmit::EmitRefused { reason } => { return Ok(GeneratedSurfaceMeasured::Refused { reason }) } @@ -983,7 +1014,10 @@ fn is_declared_divergent(file_name: &str) -> bool { /// itself, so every regen read the corpus twice -- authority digest and here -- and the receipt /// priced the second walk at a quarter of the first phase (`regen.corpus_reload`, measured /// 2026-08-30: 24 s on srv1, 17 s on BuildBuddy). The caller reads once; both consumers share it. -fn compile_stage0(sources: &[(String, String)]) -> Result, String> { +fn compile_stage0( + sources: &[(String, String)], + selection: &Rc, +) -> Result, String> { let source_files: Vec> = sources .iter() .map(|(path, content)| { @@ -993,7 +1027,11 @@ fn compile_stage0(sources: &[(String, String)]) -> Result bool { basename.ends_with(".rs") } -fn generated_basenames_from_emit(emitted: &HashMap) -> Vec { +/// THE EMITTED ROSTER IS READ FROM THE EMITTER'S DECLARATION, NOT FROM WHAT IT HANDED BACK. +/// +/// This used to walk the keys of the returned map -- the files this emit RENDERED. That was the +/// same population under the only emission that existed, and it stops being so the moment an +/// emission is denominated in a change: under `RenderSelectedMirrors` the keys ARE the selection, +/// so every unselected mirror would read as `committed_not_emitted` and the population identity +/// join would be scoped by accident. That join is the one thing `v2.workflow.required_regen` +/// says may never be scoped, because it reads no bytes and so has nothing to save and everything +/// to hide. +/// +/// `emit_emitted_population_manifest` (`v1.compiler.emit_rust`) declares every path the emit +/// produced, derived from PATHS rather than from rendered content, so it is total under every +/// selection and identical to the rendered set when there is none. Reading it here is the +/// structural inverse of that write, taking both literals from the one authority the writer reads +/// (`gunbc.stage0_emitted_population_manifest`); the same inverse is spelled in the model at +/// `gunbc.stage0_rust_host_observation` `emitted_population_paths_from_manifest`, over the +/// committed artifact rather than an in-memory emission, and in `cssl_seed_linked_closure_assembly` +/// `declared_emitted_paths`, over a file on disk. Neither is reachable from here: the first is not +/// in the seed closure and the second lives in a separate binary's private module. +/// +/// A manifest the emit did not produce, or one carrying no declared line, REFUSES. It cannot be +/// silently replaced by the rendered keys: that fallback is exactly the widening this function +/// exists to remove, and it would be invisible because the two agree whenever the selection is +/// whole. +fn generated_basenames_from_emit(emitted: &HashMap) -> Result, String> { + let manifest_key = format!("src/{}", emitted_population_manifest_basename()); + let manifest = emitted.get(&manifest_key).ok_or_else(|| { + format!( + "refusal: emit declared no population -- {manifest_key} is absent from the emitted files, so the roster the population identity join needs does not exist" + ) + })?; + let prefix = emitted_population_manifest_line_prefix(); + let separator = emitted_population_manifest_line_separator(); let mut names: BTreeSet = BTreeSet::new(); - for path in emitted.keys() { + let mut declared = 0usize; + for line in manifest.split(separator.as_str()) { + let Some(path) = line.strip_prefix(prefix.as_str()) else { + continue; + }; + declared += 1; + // Basename, not the declared path: `committed_generated_basenames` keys on + // `file_name()`, and declared paths carry a `src/` prefix. Comparing the two + // key spaces made every file mismatch in both directions. if is_compared_generated_basename(emit_path_basename(path)) && !is_hand_maintained_path(path) { - // Basename, not the emit key: `committed_generated_basenames` keys on - // `file_name()`, and emit keys carry a `src/` prefix. Comparing the two - // key spaces made every file mismatch in both directions. names.insert(emit_path_basename(path).to_string()); } } - names.into_iter().collect() + if declared == 0 { + return Err(format!( + "refusal: {manifest_key} carries no declared path line -- the emitted population cannot be read from it" + )); + } + Ok(names.into_iter().collect()) } // Emit keys are the target-relative artifact path (e.g. "src/cli_run.rs" for @@ -2199,6 +2279,212 @@ mod tests { /// accepted control and the refusal differ by a single row. `"rs"` — the corrupt row a /// merge produced on integration/namespace-cut — is inert without this wall, because the /// copy loop skipped a declared row naming nothing. + // ------------------------------------------------------------------------------------ + // THE CHANGE-DENOMINATED EMISSION'S CONTROLS. + // + // Three modules, no imports, one selected. That is enough to discriminate every claim the + // scoped emission makes, and small enough to run in the required unit step rather than in a + // live-corpus lane -- the whole-tree version of the same comparison is the planted-edit + // control the regen round runs. + // ------------------------------------------------------------------------------------ + fn selection_fixture() -> Vec<(String, String)> { + ["alpha", "beta", "gamma"] + .iter() + .map(|name| { + ( + format!("fx_{name}.dag"), + format!("module fx.{name}\nfn {name}_add(a: Int, b: Int) -> Int {{ a + b }}\n"), + ) + }) + .collect() + } + + /// `selected_basenames` is `None` for the whole-closure arm and `Some(list)` for a selection. + /// The `RustModuleRenderSelection` itself is built INSIDE the worker thread: the emitter's + /// values are `Rc`-shaped and therefore not `Send`, so the selection cannot cross the thread + /// boundary the deep-recursion stack requires. What crosses is owned, thread-safe data in and + /// the emitted map out. + fn emit_fixture(selected_basenames: Option>) -> HashMap { + std::thread::Builder::new() + .stack_size(16 * 1024 * 1024) + .spawn(move || { + let selection = Rc::new(match selected_basenames { + None => RustModuleRenderSelection::RenderEveryModule, + Some(names) => RustModuleRenderSelection::RenderSelectedMirrors { + basenames: Rc::new(names.into_iter().collect()), + }, + }); + compile_stage0(&selection_fixture(), &selection).expect("fixture emits clean") + }) + .expect("spawn emit thread") + .join() + .expect("emit thread panicked") + } + + fn beta_only() -> Option> { + Some(vec!["fx_beta.rs".to_string()]) + } + + /// THE SELECTION IS THE RENDERED SET, and the rendered bytes are the whole round's bytes. + /// This is the unit-grain form of the planted-edit control: what a scoped round writes for a + /// selected mirror is byte-identical to what an unscoped round writes for it, and nothing + /// outside the selection is written at all. + #[test] + fn a_scoped_emission_renders_the_selection_and_nothing_else() { + let whole = emit_fixture(None); + let scoped = emit_fixture(beta_only()); + assert!( + whole.contains_key("src/fx_alpha.rs"), + "positive control: the unscoped emission renders every module" + ); + assert!( + !scoped.contains_key("src/fx_alpha.rs") && !scoped.contains_key("src/fx_gamma.rs"), + "a scoped emission must not render an unselected module" + ); + assert_eq!( + scoped.get("src/fx_beta.rs"), + whole.get("src/fx_beta.rs"), + "a selected mirror must be byte-identical to what the unscoped emission produced" + ); + } + + /// CONTENT INDEPENDENCE, DEMONSTRATED RATHER THAN ASSERTED. `emit_lib_rs_from_paths` and + /// `emit_emitted_population_manifest` take `List`, so no rendered byte can reach + /// them -- and this is what that buys: the two aggregates are the SAME BYTES under an + /// emission that rendered one module and one that rendered three. + /// + /// It is discriminating in both directions. Were either aggregate derived from the rendered + /// files, the scoped `lib.rs` would drop two `pub mod` lines and the manifest two paths, and + /// both halves below would go red -- which is exactly the E0583 a change-denominated + /// emission would otherwise ship silently, since the round never re-reads a mirror it did + /// not select. + #[test] + fn the_crate_module_list_and_the_manifest_are_content_independent() { + let whole = emit_fixture(None); + let scoped = emit_fixture(beta_only()); + assert_eq!( + scoped.get("src/lib.rs"), + whole.get("src/lib.rs"), + "lib.rs is derived from paths, so a selection cannot move it" + ); + assert_eq!( + scoped.get("src/emitted_population.rs"), + whole.get("src/emitted_population.rs"), + "the manifest is derived from paths, so a selection cannot move it" + ); + let lib = scoped + .get("src/lib.rs") + .expect("scoped emission still writes lib.rs"); + assert!( + lib.contains("pub mod fx_alpha;") && lib.contains("pub mod fx_gamma;"), + "the crate must declare modules this emission did not render: {lib}" + ); + let manifest = scoped + .get("src/emitted_population.rs") + .expect("scoped emission still writes the manifest"); + assert!( + manifest.contains("// src/fx_alpha.rs") && manifest.contains("// src/fx_gamma.rs"), + "the manifest must declare paths this emission did not render: {manifest}" + ); + } + + /// POPULATION IDENTITY IS NEVER SCOPED, read at the seam where a scope could have leaked into + /// it. `generated_basenames_from_emit` reads the emitter's declaration, so the roster a + /// scoped round hands the identity join is the roster an unscoped round hands it. + #[test] + fn the_emitted_roster_is_whole_under_a_scope() { + let whole = emit_fixture(None); + let scoped = emit_fixture(beta_only()); + let whole_roster = generated_basenames_from_emit(&whole).expect("whole roster"); + let scoped_roster = generated_basenames_from_emit(&scoped).expect("scoped roster"); + assert!( + whole_roster.contains(&"fx_alpha.rs".to_string()), + "positive control: the roster names every emitted mirror" + ); + assert_eq!( + scoped_roster, whole_roster, + "the roster the population identity join reads may not narrow with the selection" + ); + } + + /// AN EMPTY SELECTION RENDERS NOTHING AND STILL DECLARES EVERYTHING. It is an ordinary answer + /// -- an edit that touches no compared mirror -- and not a spelling of RenderEveryModule. + #[test] + fn an_empty_selection_renders_no_module_and_declares_them_all() { + let whole = emit_fixture(None); + let none = emit_fixture(Some(vec![])); + assert!( + !none.contains_key("src/fx_beta.rs"), + "an empty selection renders no module" + ); + assert_eq!( + none.get("src/lib.rs"), + whole.get("src/lib.rs"), + "an empty selection still declares the whole crate" + ); + } + + /// THE PRECONDITION `import_refusals` EXACTNESS RESTS ON, PUT ON THE EXECUTED PATH. + /// + /// A scoped emission observes refusals only for the modules it rendered. That is exact rather + /// than partial because an emit carrying an error diagnostic returns NO FILES -- so a + /// committed mirror is a file some clean emit produced, and the modules a selection declined + /// to render had nothing to say. The construction is `final_files` in + /// `v1.compiler.compile` `emit_resolved_for_target_selected`; this is its discriminating red. + /// + /// WHAT IT COVERS, STATED NARROWLY: the refusing class here is a module-filename collision, + /// which `emit_rust` refuses by returning an empty file list of its own. It establishes the + /// implication for a refusal reachable from a fixture, not for every refusing class -- an + /// import refusal is authored by a cross-module export failure this fixture cannot express. + #[test] + fn an_emit_that_refuses_hands_back_no_files() { + let (refused, files_empty) = std::thread::Builder::new() + .stack_size(16 * 1024 * 1024) + .spawn(|| { + let sources: Vec> = [ + ( + "fx_a.dag", + "module fx.alpha\nfn a_add(a: Int, b: Int) -> Int { a + b }\n", + ), + ( + "fx_b.dag", + "module fx_alpha\nfn b_add(a: Int, b: Int) -> Int { a + b }\n", + ), + ] + .iter() + .map(|(path, content)| { + Rc::new(SourceFile { + path: path.to_string(), + content: content.to_string(), + }) + }) + .collect(); + let result = compile_sources_selected( + Rc::new(sources.into()), + RenderTarget::Rust, + Rc::new(RustModuleRenderSelection::RenderEveryModule), + ); + ( + result + .diagnostics + .iter() + .any(|d| crate::v1_std_core::is_error_diagnostic(d.diagnostic.clone())), + result.files.is_empty(), + ) + }) + .expect("spawn refusal thread") + .join() + .expect("refusal thread panicked"); + assert!( + refused, + "the fixture must actually refuse, or this control proves nothing" + ); + assert!( + files_empty, + "an emit carrying an error diagnostic must hand back no files at all" + ); + } + #[test] fn declared_hand_maintained_row_must_name_an_existing_path() { let root = temp_dir("declared-row-wall"); diff --git a/src/v1/stage0/src/v1_compiler_artifact.rs b/src/v1/stage0/src/v1_compiler_artifact.rs index 48997d7535a..e4882c851c0 100644 --- a/src/v1/stage0/src/v1_compiler_artifact.rs +++ b/src/v1/stage0/src/v1_compiler_artifact.rs @@ -6,6 +6,7 @@ use self::BoundaryKind::*; use self::DagInferredRecord::*; use self::PartitionRule::*; use self::RenderTarget::*; +use self::RustModuleRenderSelection::*; pub use crate::std_types::SourceSpan; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; @@ -37,6 +38,23 @@ pub enum ArtifactKind { GeneratedSupport, } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum RustModuleRenderSelection { + RenderEveryModule, + RenderSelectedMirrors { basenames: Rc> }, +} +impl RustModuleRenderSelection { + pub fn basenames(&self) -> Rc> { + match self { + RustModuleRenderSelection::RenderEveryModule => panic!("no basenames on unit variant"), + RustModuleRenderSelection::RenderSelectedMirrors { + basenames: __val, .. + } => __val.clone(), + } + } +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct Artifact { pub name: String, @@ -44,6 +62,7 @@ pub struct Artifact { pub target: RenderTarget, pub entry_modules: Rc>, pub dependencies: Rc>, + pub module_selection: Rc, } #[derive( @@ -107,6 +126,18 @@ pub struct ArtifactOutput { pub fn default_artifact_plan( root_modules: Rc>, target: RenderTarget, +) -> Rc { + selected_artifact_plan( + root_modules.clone(), + target.clone(), + Rc::new(RustModuleRenderSelection::RenderEveryModule), + ) +} + +pub fn selected_artifact_plan( + root_modules: Rc>, + target: RenderTarget, + selection: Rc, ) -> Rc { plan_artifacts(Rc::new(PartitionRule::Explicit { artifacts: Rc::new(vec![Rc::new(Artifact { @@ -115,6 +146,7 @@ pub fn default_artifact_plan( target: target.clone(), entry_modules: root_modules.clone(), dependencies: Rc::new(vec![]), + module_selection: selection.clone(), })]), })) } diff --git a/src/v1/stage0/src/v1_compiler_compile.rs b/src/v1/stage0/src/v1_compiler_compile.rs index fbd42a92cc7..fba5df214e9 100644 --- a/src/v1/stage0/src/v1_compiler_compile.rs +++ b/src/v1/stage0/src/v1_compiler_compile.rs @@ -30,9 +30,12 @@ use crate::std_termination::ProportionalDivisor::{DivideByTwo, StrictlyLarger}; pub use crate::std_termination::{PositiveDescentAmount, ProportionalDivisor}; pub use crate::std_types::SourceSpan; pub use crate::v1_compiler_annotation_bind::admit_source_annotations; -pub use crate::v1_compiler_artifact::default_artifact_plan; use crate::v1_compiler_artifact::RenderTarget::Dag; -pub use crate::v1_compiler_artifact::{Artifact, ArtifactPlan, RenderTarget}; +use crate::v1_compiler_artifact::RustModuleRenderSelection::RenderEveryModule; +pub use crate::v1_compiler_artifact::{default_artifact_plan, selected_artifact_plan}; +pub use crate::v1_compiler_artifact::{ + Artifact, ArtifactPlan, RenderTarget, RustModuleRenderSelection, +}; pub use crate::v1_compiler_complexity::{build_complexity_report, empty_complexity_report}; pub use crate::v1_compiler_complexity::{ ComplexityReport, ComplexityViolation, FuncEntry, RecursionContext, @@ -45,7 +48,7 @@ pub use crate::v1_compiler_emit_core_support::escape_json_string; pub use crate::v1_compiler_emit_core_support::EmitResult; pub use crate::v1_compiler_emit_go::emit_go; pub use crate::v1_compiler_emit_python::emit_python; -pub use crate::v1_compiler_emit_rust::emit_rust; +pub use crate::v1_compiler_emit_rust::emit_rust_selected; pub use crate::v1_compiler_infer::{reconcile, reconcile_with_census_extra}; pub use crate::v1_compiler_infer_items::{ResolvedGraph, TypedModule}; pub use crate::v1_compiler_infer_types::algebra_field_kind_name; @@ -439,7 +442,10 @@ pub fn emit_artifact(typed: Rc, artifact: Rc) -> Rc crate::v1_compiler_emit_rust::emit_rust(typed.clone()), + RenderTarget::Rust => crate::v1_compiler_emit_rust::emit_rust_selected( + typed.clone(), + artifact.module_selection.clone(), + ), RenderTarget::Python => crate::v1_compiler_emit_python::emit_python(typed.clone()), RenderTarget::Go => crate::v1_compiler_emit_go::emit_go(typed.clone()), RenderTarget::Dag => emit_dag_artifact(typed.clone()), @@ -2754,6 +2760,18 @@ pub fn compile_sources( ) } +pub fn compile_sources_selected( + sources: Rc>>, + target: RenderTarget, + selection: Rc, +) -> Rc { + emit_resolved_for_target_selected( + compile_to_resolved_with_options(sources.clone(), default_compile_pipeline_options()), + target.clone(), + selection.clone(), + ) +} + pub fn compile_sources_with_options( sources: Rc>>, target: RenderTarget, @@ -2953,6 +2971,18 @@ pub fn compile_to_resolved_with_options( pub fn emit_resolved_for_target( resolved: Rc, target: RenderTarget, +) -> Rc { + emit_resolved_for_target_selected( + resolved.clone(), + target.clone(), + Rc::new(RustModuleRenderSelection::RenderEveryModule), + ) +} + +pub fn emit_resolved_for_target_selected( + resolved: Rc, + target: RenderTarget, + selection: Rc, ) -> Rc { match emittable_graph(resolved.clone()) { None => Rc::new(PipelineResult { @@ -2965,7 +2995,7 @@ pub fn emit_resolved_for_target( }), Some(emittable) => { let typed = emittable.graph(); - let artifact_plan = crate::v1_compiler_artifact::default_artifact_plan( + let artifact_plan = crate::v1_compiler_artifact::selected_artifact_plan( Rc::new({ let mut __result = Vec::new(); for m in typed.modules.clone().iter().cloned() { @@ -2977,6 +3007,7 @@ pub fn emit_resolved_for_target( __result }), target.clone(), + selection.clone(), ); let _ = v1_rt::trace_mark("compile.emit.begin".to_string()); let emit_result = emit_from_artifact_plan(emittable.clone(), artifact_plan.clone()); diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 5d9bcadd1e3..578ab3283aa 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -66,8 +66,11 @@ use crate::std_target_representation::ExactBindingResolution::{ }; pub use crate::std_types::SourceSpan; pub use crate::std_types::{container_template_algebra, is_container_type, is_kernel_type}; -pub use crate::v1_compiler_artifact::RenderTarget; use crate::v1_compiler_artifact::RenderTarget::Rust; +use crate::v1_compiler_artifact::RustModuleRenderSelection::{ + RenderEveryModule, RenderSelectedMirrors, +}; +pub use crate::v1_compiler_artifact::{RenderTarget, RustModuleRenderSelection}; pub use crate::v1_compiler_closure_stub_v2_std_integer_rust::closure_stub_v2_std_integer_source; pub use crate::v1_compiler_closure_stub_v2_std_text_rust::closure_stub_v2_std_text_source; pub use crate::v1_compiler_coercion::decl_identity_file; @@ -5906,6 +5909,16 @@ pub fn emit_rust_reference_derived_census(typed: Rc) -> Reference } pub fn emit_rust(typed: Rc) -> Rc { + emit_rust_selected( + typed.clone(), + Rc::new(RustModuleRenderSelection::RenderEveryModule), + ) +} + +pub fn emit_rust_selected( + typed: Rc, + selection: Rc, +) -> Rc { { let ctx = build_emit_rust_context(typed.clone()); let emit_info = ctx.emit_info.clone(); @@ -5944,7 +5957,24 @@ pub fn emit_rust(typed: Rc) -> Rc { let test_projections = ctx.test_projections.clone(); let module_emissions = Rc::new({ let mut __result = Vec::new(); - for tm in typed.modules.clone().iter().cloned() { + for tm in Rc::new({ + let mut __result = Vec::new(); + for tm in typed.modules.clone().iter().cloned() { + if rust_module_render_selection_renders( + selection.clone(), + crate::v1_std_core::authored_name_at( + tm.type_env.clone().source_indices.clone(), + tm.module.clone(), + ), + ) { + __result.push(tm); + } + } + __result + }) + .iter() + .cloned() + { __result.push(emit_module_full_with_dispositions( tm.clone(), ctx.clone(), @@ -5967,30 +5997,24 @@ pub fn emit_rust(typed: Rc) -> Rc { } __result }); - let test_files = Rc::new({ + let module_paths = Rc::new({ let mut __result = Vec::new(); - for f in Rc::new({ + for tm in typed.modules.clone().iter().cloned() { + __result.push(rust_module_emit_path(crate::v1_std_core::authored_name_at( + tm.type_env.clone().source_indices.clone(), + tm.module.clone(), + ))); + } + __result + }); + let test_module_names = Rc::new({ + let mut __result = Vec::new(); + for name in Rc::new({ let mut __result = Vec::new(); for tm in typed.modules.clone().iter().cloned() { - __result.push(emit_test_file( - crate::v1_std_core::authored_name_at( - tm.type_env.clone().source_indices.clone(), - tm.module.clone(), - ), - Rc::new({ - let mut __result = Vec::new(); - for p in test_projections.iter().cloned() { - if (p.module_name.clone() - == crate::v1_std_core::authored_name_at( - tm.type_env.clone().source_indices.clone(), - tm.module.clone(), - )) - { - __result.push(p); - } - } - __result - }), + __result.push(crate::v1_std_core::authored_name_at( + tm.type_env.clone().source_indices.clone(), + tm.module.clone(), )); } __result @@ -5998,12 +6022,59 @@ pub fn emit_rust(typed: Rc) -> Rc { .iter() .cloned() { - if (v1_rt::string_length(&f.content.clone()) > 0) { - __result.push(f); + if ((Rc::new({ + let mut __result = Vec::new(); + for p in test_projections.iter().cloned() { + if (p.module_name.clone() == name.clone()) { + __result.push(p); + } + } + __result + }) + .len() as i64) + > 0) + { + __result.push(name); } } __result }); + let test_file_paths = Rc::new({ + let mut __result = Vec::new(); + for name in test_module_names.iter().cloned() { + __result.push(rust_test_file_path(name.clone())); + } + __result + }); + let test_files = Rc::new({ + let mut __result = Vec::new(); + for name in Rc::new({ + let mut __result = Vec::new(); + for name in test_module_names.iter().cloned() { + if rust_module_render_selection_renders(selection.clone(), name.clone()) { + __result.push(name); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(emit_test_file( + name.clone(), + Rc::new({ + let mut __result = Vec::new(); + for p in test_projections.iter().cloned() { + if (p.module_name.clone() == name.clone()) { + __result.push(p); + } + } + __result + }), + )); + } + __result + }); let has_services = crate::v1_compiler_emit::has_service_items(typed.clone()); let has_pipeline = { let mut __found = false; @@ -6043,16 +6114,20 @@ pub fn emit_rust(typed: Rc) -> Rc { } else { Rc::new(vec![]) }; - let v2_std_text_stub = if (module_files_reference_v2_std_text(module_files.clone()) - && !module_files_include_v2_std_text(module_files.clone())) - { + let v2_std_text_stub = if closure_needs_module_filename_stub( + typed.clone(), + ctx.clone(), + "v2_std_text".to_string(), + ) { Rc::new(vec![emit_v2_std_text_closure_stub_module()]) } else { Rc::new(vec![]) }; - let v2_std_integer_stub = if (module_files_reference_v2_std_integer(module_files.clone()) - && !module_files_include_v2_std_integer(module_files.clone())) - { + let v2_std_integer_stub = if closure_needs_module_filename_stub( + typed.clone(), + ctx.clone(), + "v2_std_integer".to_string(), + ) { Rc::new(vec![emit_v2_std_integer_closure_stub_module()]) } else { Rc::new(vec![]) @@ -6067,7 +6142,59 @@ pub fn emit_rust(typed: Rc) -> Rc { ), dry_run_file.clone(), ); - let lib_file = emit_lib_rs_from_files(all_mod_files.clone(), has_pipeline.clone()); + let all_mod_paths = v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + module_paths.clone(), + Rc::new({ + let mut __result = Vec::new(); + for f in v2_std_text_stub.iter().cloned() { + __result.push(f.path.clone()); + } + __result + }), + ), + Rc::new({ + let mut __result = Vec::new(); + for f in v2_std_integer_stub.iter().cloned() { + __result.push(f.path.clone()); + } + __result + }), + ), + Rc::new(vec![rt_file.path.clone()]), + ), + Rc::new({ + let mut __result = Vec::new(); + for f in dry_run_file.iter().cloned() { + __result.push(f.path.clone()); + } + __result + }), + ); + let lib_file = emit_lib_rs_from_paths(all_mod_paths.clone(), has_pipeline.clone()); + let emitted_paths = v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + Rc::new(vec![ + cargo.path.clone(), + lib_file.path.clone(), + main_file.path.clone(), + ]), + all_mod_paths.clone(), + ), + Rc::new({ + let mut __result = Vec::new(); + for f in compiler_tests_file.iter().cloned() { + __result.push(f.path.clone()); + } + __result + }), + ), + test_file_paths.clone(), + ); + let population_manifest = emit_emitted_population_manifest(emitted_paths.clone()); let emitted_files = v1_rt::concat( v1_rt::concat( v1_rt::concat( @@ -6078,7 +6205,6 @@ pub fn emit_rust(typed: Rc) -> Rc { ), test_files.clone(), ); - let population_manifest = emit_emitted_population_manifest(emitted_files.clone()); let files = v1_rt::concat( emitted_files.clone(), Rc::new(vec![population_manifest.clone()]), @@ -6090,20 +6216,62 @@ pub fn emit_rust(typed: Rc) -> Rc { } } +pub fn rust_module_emit_filename(module_name: String) -> String { + { + let raw = crate::v1_compiler_emit_core_support::module_to_filename(module_name.clone()); + if (raw.clone() == "main".to_string()) { + "main_mod".to_string() + } else { + raw.clone() + } + } +} + +pub fn rust_module_emit_path(module_name: String) -> String { + v1_rt::concat( + v1_rt::concat( + rust_source_root(), + rust_module_emit_filename(module_name.clone()), + ), + rust_source_ext(), + ) +} + +pub fn rust_module_render_selection_renders( + selection: Rc, + module_name: String, +) -> bool { + match (*selection.clone()).clone() { + RustModuleRenderSelection::RenderEveryModule => true, + RustModuleRenderSelection::RenderSelectedMirrors { + basenames: basenames, + .. + } => { + let mut __found = false; + for b in basenames.iter().cloned() { + if (b.clone() + == v1_rt::concat( + rust_module_emit_filename(module_name.clone()), + rust_source_ext(), + )) + { + __found = true; + break; + } + } + __found + } + } +} + pub fn emitted_population_manifest_path() -> String { v1_rt::concat(rust_source_root(), emitted_population_manifest_basename()) } -pub fn emit_emitted_population_manifest(files: Rc>>) -> Rc { +pub fn emit_emitted_population_manifest(paths: Rc>) -> Rc { { let declared = v1_rt::concat( - Rc::new({ - let mut __result = Vec::new(); - for f in files.iter().cloned() { - __result.push(f.path.clone()); - } - __result - }), + paths.clone(), Rc::new(vec![emitted_population_manifest_path()]), ); let lines = Rc::new({ @@ -6159,14 +6327,122 @@ pub fn emit_compiler_tests_module() -> Rc { }) } -pub fn emitted_files_carry_use_line(files: Rc>>, line: String) -> bool { +pub fn module_reference_candidate_names( + typed_module: Rc, + ctx: Rc, +) -> Rc> { + { + let source_indices = typed_module.type_env.clone().source_indices.clone(); + let emit_info = ctx.emit_info.clone(); + let this_module_name = crate::v1_std_core::authored_name_at( + source_indices.clone(), + typed_module.module.clone(), + ); + let items = typed_module.items.clone(); + let value_names = crate::v1_compiler_emit_core_support::unique_strings(Rc::new({ + let mut __result = Vec::new(); + for item in items.iter().cloned() { + __result.extend( + (*collect_value_ref_names( + item.clone(), + source_indices.clone(), + emit_info.type_summaries.clone(), + emit_info.variant_to_enum.clone(), + )) + .iter() + .cloned(), + ); + } + __result + })); + let item_emit_surface_names = Rc::new({ + let mut __result = Vec::new(); + for item in items.iter().cloned() { + __result.extend( + (*collect_item_emit_surface_names( + item.clone(), + source_indices.clone(), + emit_info.clone(), + )) + .iter() + .cloned(), + ); + } + __result + }); + let type_surface_names = + crate::v1_compiler_emit_core_support::unique_strings(item_emit_surface_names.clone()); + let field_surface_names = collect_items_field_import_surface_names( + item_emit_surface_names.clone(), + emit_info.clone(), + ); + let variant_payload_structs = expand_variant_payload_struct_imports( + type_surface_names.clone(), + emit_info.type_summaries.clone(), + emit_info.variant_to_enum.clone(), + ); + let realized_surface_names = + crate::v1_compiler_emit_core_support::unique_strings(Rc::new({ + let mut __result = Vec::new(); + for item in items.iter().cloned() { + __result.extend( + (*collect_item_realized_surface_names( + item.clone(), + emit_info.shared_types.clone(), + source_indices.clone(), + emit_info.clone(), + )) + .iter() + .cloned(), + ); + } + __result + })); + let unlisted_type_names = match v1_rt::map_get( + &ctx.unlisted_type_names_by_module.clone(), + this_module_name.clone(), + ) { + Some(v) => v.clone(), + None => Rc::new(vec![]), + }; + let origin_candidates = crate::std_repair_input_origin::repair_input_origin_candidates( + this_module_name.clone(), + unlisted_type_names.clone(), + value_names.clone(), + type_surface_names.clone(), + field_surface_names.clone(), + variant_payload_structs.clone(), + realized_surface_names.clone(), + ); + Rc::new({ + let mut __result = Vec::new(); + for name in crate::v1_compiler_emit_core_support::unique_strings( + crate::std_repair_input_origin::repair_input_origin_compatibility_names( + origin_candidates.clone(), + ), + ) + .iter() + .cloned() + { + if !reference_is_host_realized_builtin(name.clone()) { + __result.push(name); + } + } + __result + }) + } +} + +pub fn registry_declares_module_filename( + registry: Rc>>, + filename: String, +) -> bool { { let mut __found = false; - for f in files.iter().cloned() { - if v1_rt::string_contains( - &f.content.clone(), - v1_rt::concat("\n".to_string(), line.clone()), - ) { + for info in Rc::new(v1_rt::map_values(®istry)).iter().cloned() { + if (crate::v1_compiler_emit_core_support::module_to_filename(info.module_name.clone()) + == filename.clone()) + { __found = true; break; } @@ -6175,36 +6451,38 @@ pub fn emitted_files_carry_use_line(files: Rc>>, line: String) } } -pub fn module_files_reference_v2_std_text(files: Rc>>) -> bool { - emitted_files_carry_use_line( - files.clone(), - v1_rt::concat( - rust_visibility_prefix(), - "use crate::v2_std_text::".to_string(), - ), - ) -} - -pub fn module_files_reference_v2_std_integer(files: Rc>>) -> bool { - emitted_files_carry_use_line( - files.clone(), - v1_rt::concat( - rust_visibility_prefix(), - "use crate::v2_std_integer::".to_string(), - ), - ) -} - -pub fn module_files_include_v2_std_text(files: Rc>>) -> bool { +pub fn closure_references_module_filename( + typed: Rc, + ctx: Rc, + filename: String, +) -> bool { { - let expected = v1_rt::concat( - v1_rt::concat(rust_source_root(), "v2_std_text".to_string()), - rust_source_ext(), - ); + if !registry_declares_module_filename(ctx.registry.clone(), filename.clone()) { + return false; + } { let mut __found = false; - for f in files.iter().cloned() { - if (f.path.clone() == expected.clone()) { + for tm in typed.modules.clone().iter().cloned() { + if { + let mut __found = false; + for name in module_reference_candidate_names(tm.clone(), ctx.clone()) + .iter() + .cloned() + { + if match v1_rt::map_get(&ctx.registry.clone(), name.clone()) { + Some(info) => { + (crate::v1_compiler_emit_core_support::module_to_filename( + info.module_name.clone(), + ) == filename.clone()) + } + None => false, + } { + __found = true; + break; + } + } + __found + } { __found = true; break; } @@ -6214,23 +6492,17 @@ pub fn module_files_include_v2_std_text(files: Rc>>) -> bool { } } -pub fn module_files_include_v2_std_integer(files: Rc>>) -> bool { - { - let expected = v1_rt::concat( - v1_rt::concat(rust_source_root(), "v2_std_integer".to_string()), - rust_source_ext(), - ); - { - let mut __found = false; - for f in files.iter().cloned() { - if (f.path.clone() == expected.clone()) { - __found = true; - break; - } - } - __found - } - } +pub fn closure_needs_module_filename_stub( + typed: Rc, + ctx: Rc, + filename: String, +) -> bool { + (closure_references_module_filename(typed.clone(), ctx.clone(), filename.clone()) + && !typed_closure_includes_module_filename( + typed.modules.clone(), + filename.clone(), + merged_module_source_indices(typed.modules.clone()), + )) } pub fn emit_v2_std_integer_closure_stub_module() -> Rc { @@ -6256,13 +6528,13 @@ pub fn emit_v2_std_text_closure_stub_module() -> Rc { }) } -pub fn lib_rs_mod_name_from_file(f: Rc) -> String { +pub fn lib_rs_mod_name_from_path(path: String) -> String { { let src_prefix_len = v1_rt::string_length(&rust_source_root()); let ext_len = v1_rt::string_length(&rust_source_ext()); - let path_len = v1_rt::string_length(&f.path.clone()); + let path_len = v1_rt::string_length(&path); v1_rt::substring( - &f.path.clone(), + &path, src_prefix_len.clone(), (path_len.clone() - ext_len.clone()), ) @@ -6340,16 +6612,16 @@ pub fn order_partial_lib_rs_mod_names(mod_names: Rc>) -> Rc>>, +pub fn emit_lib_rs_from_paths( + all_module_paths: Rc>, has_compiler_tests: bool, ) -> Rc { { let file_derived_names = Rc::new( - all_module_files + all_module_paths .iter() .cloned() - .map(lib_rs_mod_name_from_file) + .map(lib_rs_mod_name_from_path) .collect::>(), ); let mod_names = if has_compiler_tests.clone() { @@ -9475,14 +9747,10 @@ pub fn emit_module_full( } else { v1_rt::concat("\n\n".to_string(), phantom_zst_markers.clone()) }; - let raw_filename = crate::v1_compiler_emit_core_support::module_to_filename( - crate::v1_compiler_infer_env::authored_name(scope.type_env.clone(), m.clone()), - ); - let filename = if (raw_filename.clone() == "main".to_string()) { - "main_mod".to_string() - } else { - raw_filename.clone() - }; + let module_emit_path = rust_module_emit_path(crate::v1_compiler_infer_env::authored_name( + scope.type_env.clone(), + m.clone(), + )); let module_attrs = if (crate::v1_compiler_infer_env::authored_name(scope.type_env.clone(), m.clone()) == "std.error_primitives".to_string()) @@ -9494,10 +9762,7 @@ pub fn emit_module_full( let content = 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("// Generated by v1 compiler -- do not edit.\n".to_string(), "// Source module: ".to_string()), crate::v1_compiler_infer_env::authored_name(scope.type_env.clone(), m.clone())), "\n\n".to_string()), module_attrs.clone()), prelude.clone()), imports_section.clone()), svc_imports_str.clone()), local_uses_str.clone()), "\n\n".to_string()), coproduct_wire_contract_validation_section.clone()), items_str.clone()), phantom_section.clone()), "\n".to_string()); Rc::new(ModuleEmission { file: Rc::new(TextFile { - path: v1_rt::concat( - v1_rt::concat(rust_source_root(), filename.clone()), - rust_source_ext(), - ), + path: module_emit_path.clone(), content: content.clone(), }), reference_rows: reference_plan.rows.clone(), diff --git a/src/v1/stage0/src/v1_compiler_infer_emit_info.rs b/src/v1/stage0/src/v1_compiler_infer_emit_info.rs index 44827dd5881..3865b0c9300 100644 --- a/src/v1/stage0/src/v1_compiler_infer_emit_info.rs +++ b/src/v1/stage0/src/v1_compiler_infer_emit_info.rs @@ -1167,7 +1167,7 @@ pub fn add_emit_item_summary( pub fn emit_dependency_registration_dissolve_on_note() -> Rc { thread_local! { static CACHED: Rc = { - crate::std_dissolution::unbound_dissolution("dissolve-on (FreeMonoid pass 2026-07-20): collect emitted cross-module type/path refs during emit into one authority; derive (a) use-lines via build_shared_types + emit_faithful_text_carrier_import_lines and (b) closure pub-mod membership via emit_lib_rs_from_files from that set. Instance-patch interim: faithful corpus emits carrier import lines per module unless locally defined (FreeMonoid/Char/NonEmptyStr/Int); closure projections (v1.compiler.closure_stub_v2_std_text_rust and closure_stub_v2_std_integer_rust) when refs exist but the real v2.std.text or v2.std.integer module is absent from narrow typed.modules — projections are NOT parallel modeling authorities (see each stub module dissolve_on_note); they dissolve when the ref set includes the real module in the closure.".to_string()) + crate::std_dissolution::unbound_dissolution("dissolve-on (FreeMonoid pass 2026-07-20): collect emitted cross-module type/path refs during emit into one authority; derive (a) use-lines via build_shared_types + emit_faithful_text_carrier_import_lines and (b) closure pub-mod membership via emit_lib_rs_from_paths from that set. Instance-patch interim: faithful corpus emits carrier import lines per module unless locally defined (FreeMonoid/Char/NonEmptyStr/Int); closure projections (v1.compiler.closure_stub_v2_std_text_rust and closure_stub_v2_std_integer_rust) when refs exist but the real v2.std.text or v2.std.integer module is absent from narrow typed.modules — projections are NOT parallel modeling authorities (see each stub module dissolve_on_note); they dissolve when the ref set includes the real module in the closure.".to_string()) }; } CACHED.with(|c: &Rc| c.clone())