diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index bc983bca182..49e43408c99 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -2531,7 +2531,75 @@ fn merged_module_source_indices(modules: List) -> Map fold(init: empty_map(), f: (acc, m) => map_merge(acc, m.type_env.source_indices)) } -fn emit_rust(typed: ResolvedGraph) -> EmitResult { +// THE ORDER OF THE BINDINGS BELOW IS LOAD-BEARING AND IS NOT A TIDY-UP. DO NOT REORDER THEM. +// +// MEASURED, not reasoned. Factoring this preamble out of emit_rust moved workflow_funcs, the two +// diagnostic guards and test_projections to AFTER export_sets and module_index, where they had been +// interleaved before. Nothing about the import decision changed -- same candidates, same survivors, +// same use-line SET -- and the emitted crate still moved: in a scoped emit of +// src/v2/compiler/00_compile.dag (175 files, roots dag + src/v2), exactly one file differed, +// v2_lens_enforcement_vocab.rs, and the whole diff was two `pub use` lines SWAPPED IN ORDER +// (std_realization_schedule::ScheduleWitnessEntry against v2_std_qualified_name::QualifiedName). +// Reproduced across three separate builds, against a same-source control that was byte-identical -- +// so the emitter is stable given its source and NOT invariant under this reordering. Restoring the +// original order restored byte-identity exactly. +// +// SO EMITTED BYTES DEPEND ON THE EVALUATION ORDER OF INDEPENDENT-LOOKING BINDINGS IN THIS PREAMBLE. +// That is a latent defect with a bounded, reproducible specimen, and it is RECORDED rather than +// chased: the suspect is a shared memo or interner upstream of the import-line stream, which nothing +// here establishes. Its blast radius is every future refactor in this region, each of which will +// silently move bytes and be diagnosed from scratch by whoever hits it. A reader who "simplifies" +// these bindings back into a natural grouping will reproduce it. +// +// THIS ADMITS NO DEBT AND IS NOT A SCAFFOLD, and the distinction decides who has to approve it. The +// order-dependence is PRE-EXISTING: it is a property of the emitter as it stood before this change, +// not something introduced here, and no artifact is added that must later be deleted. The ordering +// kept below IS the ordering that was already there, so what is preserved is the status quo and what +// is new is only the knowledge that preserving it was necessary. DESIGN's scaffold-admission ruling +// governs CREATING temporary work -- "a dissolution condition describes how admitted debt ends, it +// does not authorize creating the debt" -- and there is no debt here to authorize; an earlier +// revision of this annotation called the kept ordering a "workaround", which invited exactly that +// reading and is corrected rather than defended. DESIGN's workaround rule is about routing AROUND an +// obstacle without diagnosing it, and the opposite happened: the line was stopped, the bytes were +// measured, the cause was located to the preamble, and the original order was restored. +// +// THE TRIGGER BELOW IS AN OBLIGATION, NOT A PERMISSION. Section 4b(2) requires a discovered class +// below its ceiling to name its next-rung trigger so the stall is tracked rather than silent; +// omitting it would leave a measured defect recorded with no way to tell "cannot climb" from "nobody +// has". NEXT-RUNG TRIGGER: the order-dependence is located and removed -- the emitter made invariant +// under permutation of these bindings -- at which point this annotation and the ordering constraint +// it protects both retire. The discriminating control until then is the one that found it: emit a +// scoped entry before and after, and diff. +// +// THE STRUCTURAL BYTE-IDENTITY ARGUMENT DID NOT COVER THIS, and that is the reusable lesson. "The +// use-lines ARE the survived arm of the same decision the census counts, so bytes cannot move unless +// the decision moves" is TRUE, and it is silent about evaluation order, memoisation and interning -- +// everything the refactor touched but the decision did not. A valid argument with an unstated scope +// passes review precisely because each half checks out. The measured diff is what caught it. + +// THE INPUTS EVERY MODULE'S EMISSION IS COMPUTED AGAINST, built once and named once. +// +// This record is a pure extraction of emit_rust's own preamble and changes nothing about what it +// contains -- it exists so that a SECOND reader of the same emission (the reference-derived census +// below) consumes the identical inputs rather than re-deriving them from a near-copy of these +// lines. A near-copy is the shape that drifts: the census would then be measuring an emission +// nobody performed, which is the execution-provenance failure DESIGN records, arrived at from the +// input side rather than the output side. +type EmitRustContext { + emit_info: EmitGraphInfo + registry: Map + data_items: Map + workflow_funcs: List + workflow_default_diags: List + anonymous_record_diags: List + svc_module_map: Map + test_projections: List + export_sets: Map> + module_index: ModuleIndex + unlisted_type_names_by_module: Map> +} + +fn build_emit_rust_context(typed: ResolvedGraph) -> EmitRustContext { let base_info = build_emit_graph_info(modules: typed.modules) let ownership = build_ownership_results(modules: typed.modules) let shared = build_shared_types(type_summaries: base_info.type_summaries, recursive_type_set: base_info.recursive_type_set, target: Rust) @@ -2570,7 +2638,6 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { fn_type_env: base_info.fn_type_env, fn_return_type: none } - let shared_types = emit_info.shared_types let registry = merge_item_registries( bare: typed.item_registry, qualified: build_qualified_item_registry(modules: typed.modules) @@ -2578,13 +2645,7 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { let data_items = build_data_item_index(modules: typed.modules) let workflow_funcs = collect_workflow_funcs(modules: typed.modules, registry: registry, read_only_params_index: emit_info.read_only_params_index) let workflow_default_diags = validate_workflow_param_defaults(workflow_funcs: workflow_funcs) - if workflow_default_diags |> count > 0 { - return EmitResult { files: [], diagnostics: workflow_default_diags } - } let anonymous_record_diags = typed.modules |> flat_map(tm => tm.items |> flat_map(item => ambiguous_anonymous_record_literal_diagnostics(n: item, type_summaries: emit_info.type_summaries, source_indices: tm.type_env.source_indices, module_name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)))) - if anonymous_record_diags |> count > 0 { - return EmitResult { files: [], diagnostics: anonymous_record_diags } - } let svc_module_map = typed.modules |> fold(init: empty_map(), f: (acc, tm) => let svc_items = tm.items |> filter(item => is_service_item(item: item)) let mod_filename = module_to_filename(name: authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)) @@ -2596,7 +2657,55 @@ fn emit_rust(typed: ResolvedGraph) -> EmitResult { let export_sets = build_module_export_sets(modules: typed.modules) let module_index = build_module_index(modules: typed.modules) let unlisted_type_names_by_module = group_unlisted_type_names(diags: typed.diagnostics) - let module_files = typed.modules |> map(tm => emit_module_full(typed_module: tm, registry: registry, emit_info: emit_info, shared_types: shared_types, svc_module_map: svc_module_map, data_items: data_items, export_sets: export_sets, typed_modules: typed.modules, module_index: module_index, unlisted_type_names: match map_get(unlisted_type_names_by_module, authored_name_at(source_indices: tm.type_env.source_indices, node: tm.module)) { Present { value: v } => v Absent => [] })) + EmitRustContext { + emit_info: emit_info, + registry: registry, + data_items: data_items, + workflow_funcs: workflow_funcs, + workflow_default_diags: workflow_default_diags, + anonymous_record_diags: anonymous_record_diags, + svc_module_map: svc_module_map, + test_projections: test_projections, + export_sets: export_sets, + module_index: module_index, + unlisted_type_names_by_module: unlisted_type_names_by_module + } +} + +// THE CENSUS'S PRODUCER. One emission, read for its decisions instead of for its bytes: every row +// here is a decision reference_derived_use_line_plan actually made while emitting this graph, so +// the census cannot report a population the emitter did not decide, and the emitter cannot decide a +// population the census does not see. +fn emit_rust_reference_derived_rows(typed: ResolvedGraph) -> List { + let ctx = build_emit_rust_context(typed: typed) + typed.modules |> flat_map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules).reference_rows) +} + +fn emit_rust_reference_derived_census(typed: ResolvedGraph) -> ReferenceDerivedCensus { + reference_derived_census(rows: emit_rust_reference_derived_rows(typed: typed)) +} + +fn emit_rust(typed: ResolvedGraph) -> EmitResult { + let ctx = build_emit_rust_context(typed: typed) + let emit_info = ctx.emit_info + let registry = ctx.registry + let data_items = ctx.data_items + let svc_module_map = ctx.svc_module_map + let export_sets = ctx.export_sets + let module_index = ctx.module_index + let unlisted_type_names_by_module = ctx.unlisted_type_names_by_module + let shared_types = emit_info.shared_types + let workflow_funcs = ctx.workflow_funcs + let workflow_default_diags = ctx.workflow_default_diags + if workflow_default_diags |> count > 0 { + return EmitResult { files: [], diagnostics: workflow_default_diags } + } + let anonymous_record_diags = ctx.anonymous_record_diags + if anonymous_record_diags |> count > 0 { + return EmitResult { files: [], diagnostics: anonymous_record_diags } + } + let test_projections = ctx.test_projections + let module_files = typed.modules |> map(tm => emit_module_full_with_dispositions(typed_module: tm, ctx: ctx, typed_modules: typed.modules).file) 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), @@ -2852,7 +2961,7 @@ fn emit_module(typed_module: TypedModule, registry: Map) -> Te let shared_types = emit_info.shared_types let export_sets = build_module_export_sets(modules: [typed_module]) let module_index = build_module_index(modules: [typed_module]) - emit_module_full(typed_module: typed_module, registry: registry, emit_info: emit_info, shared_types: shared_types, svc_module_map: empty_map(), data_items: build_data_item_index(modules: [typed_module]), export_sets: export_sets, typed_modules: [typed_module], module_index: module_index, unlisted_type_names: []) + emit_module_full(typed_module: typed_module, registry: registry, emit_info: emit_info, shared_types: shared_types, svc_module_map: empty_map(), data_items: build_data_item_index(modules: [typed_module]), export_sets: export_sets, typed_modules: [typed_module], module_index: module_index, unlisted_type_names: []).file } fn build_module_export_sets(modules: List) -> Map> { @@ -3474,7 +3583,210 @@ fn collect_item_realized_surface_names(item: Node, shared_types: Set, so unique_strings(items: concat(from_ann, concat(from_params, from_inferred))) } -fn reference_derived_use_lines(items: List, unlisted_type_names: List, this_module_name: String, registry: Map, emit_info: EmitGraphInfo, local_type_names: List, already_imported_names: List, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex, shared_types: Set, emitted_source: String) -> List { +// THE DECLARED RUNG FOR THIS CLASS. It is an ANNOTATION and not a `data ... : String` row, and the +// difference is load-bearing rather than stylistic: a rung, a trigger, a receipt and a dissolution +// condition are exactly the facts DESIGN section 4c orders into typed carriers, and a String datum +// carrying them is the #6262 hoisted-prose shape that section names as the measured failure -- intent +// mechanically indistinguishable from program data. There is no typed rung carrier to route these to +// today: DESIGN's own declared rung drops sit as prose in its Building-&-checks section, and the +// executable claims carrier that would hold them is Stage 1 of the compiler-guarantee recovery plan +// and is unbuilt. So this sits in the quarantine boundary section 4c provides for exactly that case, +// where no `Accepted` program can read it -- which is the point, because it means nothing here can +// ever be cited as machine evidence that the rung holds. It migrates to the claims carrier when that +// carrier lands, and the migration is the dissolution rather than a rewrite. +// +// DECLARED RUNG, AND IT IS MITIGATABLE. The per-candidate decision below is now a four-armed +// typed disposition and the population is COUNTED, at (module, name, disposition), by +// reference_derived_census over the rows every Rust emit already produces. It is NOT SURFACED +// during an ordinary build: emit_module_full carries the rows out beside the file, +// emit_rust_reference_derived_census projects them, and no compile prints them, no gate reads +// them and no diagnostic is raised. That is DELIBERATE AND TEMPORARY, and the reason is +// sequencing rather than comfort. Making the value-position case loud -- withdrawing the +// emitter's silent synthesis, or promoting the reference to a refusal beside UnlistedImportUse -- +// is a DESIGN 4b rung change that must declare a BOUNDED POPULATION, and that population is +// exactly what this census exists to measure. Counting first is the precondition for the +// promotion, not caution about it. +// +// WHAT IS STILL SILENT, stated so this row is not read as a climb it is not. A value-position +// cross-module reference with no import produces no diagnostic of any kind: the resolver declines +// to refuse it, the emitter synthesizes the use-line the author did not write, and the author is +// never told. NOTHING HERE CHANGES THAT. The emitted bytes are identical by construction -- the +// use-lines are the survived arm of the same decision the census counts, so there is no second +// copy of the decision to drift from -- and the deficit's frequency is now observable instead of +// zero. +// +// EVERY COUNT THIS CENSUS REPORTS IS A LOWER BOUND. The denominator is the candidates that REACH +// the decision; a name already provided by an authored import, the prelude, a carrier use-line or +// the module's own declarations is filtered before it, as is a kernel type and any name no +// attestation admitted as a candidate at all. So registry_absent and export_proof_failed are +// at-least counts of the references that reach emission with no use-line, never the whole of that +// population, and the gap between the two is itself unmeasured. A consumer quoting a figure says +// 'at least N' and names emit_rust_reference_derived_census as its producer; a transcribed number +// with no named producer is unreachable from the thing that owns it and rots without anyone +// touching either end. +// +// NO EXECUTING CONSUMER FOR THE EVIDENCE YET, AND NO PRODUCER FOR A CORPUS FIGURE EITHER. THIS IS +// THE HONEST HALF OF THE ROW AND IT HAS TWO PARTS. FIRST, THE EVIDENCE. The discriminating probes +// live in v1.tests.claim.reference_derived_disposition_census_witness_test -- five rows, four +// naming the decision's four arms and one pinning the census to being a projection of retained +// rows. Their RED is authorable at the fixture boundary and all five pass when run directly on +// 2026-08-27. They are NOT run by CI: gunbc.ci_layer_roots records that the scoped witness batch +// was deleted with the 2026-08-15 floor cut and that every entry under src/v1/tests/claim has no +// executing consumer, since ordinary discovery's roots are dag plus src/v2 and could not resolve +// an import of v1.compiler.emit_rust in any case. Direct execution is evidence the assertions +// HOLD, never evidence that CI runs them, and conflating those two is rung inflation. +// +// NEXT-RUNG TRIGGERS, TWO, INDEPENDENT. (1) For the class: the value-position case joins the +// fail-closed wall -- the resolver refusing an unlisted value use, or emitting the use-line +// intent directly -- at which point this census, the synthesis pass it counts and this row retire +// together. (2) For the evidence: one prepared subject spanning dag, src/v1 and src/v2, the +// restoration gunbc.ci_layer_roots already names for the deleted batch, at which point the +// witness above executes and that paragraph deletes. +// +// SECOND, AND SEPARATELY: THE CENSUS HAS NO EXECUTABLE ROUTE TO A CORPUS FIGURE, so this row +// deliberately quotes NO number. emit_rust_reference_derived_census is the projection, but +// reaching it needs a resolved graph, and the only .dag route to one is a nested compile that the +// interpreter refuses -- compile_to_resolved over an authored source vector errors with +// NoSuchField Node.ident during the nested pipeline, measured 2026-08-27 on three subjects. That +// is why the two nested-compile instruments the corpus already has +// (gunbc.compile_diagnostic_census, tools.multi_module_compile_fixture) both route through a host +// builtin. A CLI or builtin surface that hands the census a graph is capability growth in a +// frozen seed and is not taken here. THIRD NEXT-RUNG TRIGGER, therefore: a nested-compile builtin +// returning the resolved graph -- the shape tools.multi_module_compile_fixture already has, +// widened to carry these rows -- at which point a figure becomes producible and this paragraph is +// replaced by the producer's name. Until then the population is COUNTABLE BY CONSTRUCTION AND +// UNCOUNTED IN FACT, and saying otherwise would be the rung inflation this row exists to avoid. + +// WHY A CANDIDATE GOT A USE-LINE, OR DID NOT. The decision below was inline in +// reference_derived_use_lines' flat_map, where every non-surviving arm returned the empty list and +// the three reasons for returning it were indistinguishable from each other and from having asked +// nothing. reference_derived_use_lines_note already recorded the consequence in its own words -- "a +// candidate that registry-resolves to nothing, or resolves but fails export proof, is left +// unsynthesized (typed refusal at step-2 is future work); it never fabricates a use-line" -- so this +// coproduct is that future work's first half: the reason becomes a value, and a candidate that gets +// no use-line stops being silent about which of four things happened to it. +// +// FOUR ARMS, NOT THREE, AND THE FOURTH IS THE NOT-APPLICABLE ONE. CandidateOwnModule is the name the +// module itself provides: no import was ever owed, so nothing is missing and no rustc error follows. +// CandidateRegistryAbsent and CandidateExportProofFailed are the opposite -- a cross-module reference +// that reaches emission with NO use-line, which is an unqualified path in the emitted crate and E0425 +// downstream. Folding own-module in with either of them would put "nothing was owed" and "something +// is missing" under one symbol, which is the state-space conflation DESIGN records as its +// most-repeated failure: not-applicable rendered as malformed, opposite owners, opposite repairs. +// +// CandidateSurvived and CandidateExportProofFailed both carry the provider module because both are +// facts ABOUT a provider -- one proven to export the symbol, one not -- and a census that could not +// name the provider could not tell anyone where to look. +type ReferenceDerivedCandidateDisposition + = CandidateSurvived { provider_module: String } + | CandidateOwnModule + | CandidateRegistryAbsent + | CandidateExportProofFailed { provider_module: String } + +// One decided candidate, at the grain the census reports: (module, name, disposition). The module is +// the REFERENCING module, never the provider -- the provider rides on the disposition where it is +// known, and for CandidateRegistryAbsent there is no provider to name, which is exactly the fact +// that arm carries. +type ReferenceDerivedCandidateRow { + module_name: String + name: String + disposition: ReferenceDerivedCandidateDisposition +} + +// THE ONE DECISION, WITH TWO CONSUMERS (DESIGN section 2, one concept derived twice rather than +// modelled twice). reference_derived_use_lines takes the survivors and emits exactly the use-lines it +// emitted before; the census takes the same rows and counts them. Byte-identity of the emitted crate +// is therefore STRUCTURAL rather than asserted: the emitted use-lines cannot move unless this +// function's answer moves, because there is no second copy of the decision to drift from. +fn reference_derived_candidate_disposition(name: String, this_module_name: String, registry: Map, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex) -> ReferenceDerivedCandidateDisposition { + match map_get(registry, name) { + Present { value: info } => + if info.module_name == this_module_name { + CandidateOwnModule + } else if provider_proven_exports_symbol(name: name, provider_module: info.module_name, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) { + CandidateSurvived { provider_module: info.module_name } + } else { + CandidateExportProofFailed { provider_module: info.module_name } + } + Absent => CandidateRegistryAbsent + } +} + +fn reference_derived_candidate_survived(disposition: ReferenceDerivedCandidateDisposition) -> Bool { + match disposition { + CandidateSurvived { provider_module: _ } => true + _ => false + } +} + +// The disposition's name as one stable token, for a census line and for a witness oracle. It is a +// projection of the coproduct and never a second vocabulary: a new arm above that is not spelled +// here fails to compile, because this match carries no wildcard. +fn reference_derived_disposition_name(disposition: ReferenceDerivedCandidateDisposition) -> String { + match disposition { + CandidateSurvived { provider_module: _ } => "survived" + CandidateOwnModule => "own-module" + CandidateRegistryAbsent => "registry-absent" + CandidateExportProofFailed { provider_module: _ } => "export-proof-failed" + } +} + +// WHY THIS CARRIES NO COMPLETENESS ENVELOPE, checked against the one that exists rather than assumed. +// +// v2.workflow.legacy_binding_observation carries the corpus's completeness envelope for observation +// populations: a denominator DERIVED from the subject's own transport, a four-arm identity join +// (LegacyObservationComplete / MissingExpected / ForeignObserved / RepeatedObserved), and a +// sole_constructor admission so a downstream law cannot run over an unproven population. That shape +// is right and this census does not fork it. +// +// IT DOES NOT FIT HERE YET, FOR ONE REASON: that envelope joins an observation against an INDEPENDENT +// expected denominator, keyed on reference-occurrence ids. This census has no such denominator. Its +// denominator is the candidates that reached the decision, produced by the same pass that decides +// them -- which is precisely the "complete by construction" shape that module's own comment warns +// about, and it is why the row below is stated as a LOWER BOUND and claims no completeness at all. +// A census that made no completeness claim and then wore a completeness envelope would be worse than +// one without: it would read as a proven population. +// +// SO THE HONEST POSITION IS: no completeness claim, therefore no envelope, therefore no nickname for +// that module. WHEN THIS CENSUS GAINS AN INDEPENDENT EXPECTED DENOMINATOR -- a reference-occurrence +// roster derived from the subject rather than from the producer -- legacy_binding_observation is +// where the join belongs, and these dispositions travel INSIDE it as the WHY that its four arms have +// no vocabulary for: its arms answer WHICH occurrences went unanswered, never why a candidate was +// dropped. + +// THE POPULATION THIS CENSUS COUNTS IS A LOWER BOUND ON THE POPULATION IT IS ABOUT, AND THE FIELD +// NAMES ARE NOT ALLOWED TO SUGGEST OTHERWISE. `candidates` counts the names that REACHED the +// decision: a name is already gone by then if an authored import, the prelude, a carrier line or the +// module's own declarations provide it, if it is a kernel type, or if no attestation admitted it as a +// candidate at all. So registry_absent and export_proof_failed are at-least counts of the +// value-position references that reach emission with no use-line, never the whole of that population, +// and the gap between the two is itself unmeasured. Anyone quoting these numbers says "at least". +type ReferenceDerivedCensus { + candidates: Int + survived: Int + own_module: Int + registry_absent: Int + export_proof_failed: Int +} + +fn reference_derived_census(rows: List) -> ReferenceDerivedCensus { + ReferenceDerivedCensus { + candidates: rows |> count, + survived: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "survived") |> count, + own_module: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "own-module") |> count, + registry_absent: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "registry-absent") |> count, + export_proof_failed: rows |> filter(r => reference_derived_disposition_name(disposition: r.disposition) == "export-proof-failed") |> count + } +} + +// The use-lines and the decisions that produced them, returned together so that no consumer can read +// one without the other having been computed by the same pass. +type ReferenceDerivedUseLinePlan { + lines: List + rows: List +} + +fn reference_derived_use_line_plan(items: List, unlisted_type_names: List, this_module_name: String, registry: Map, emit_info: EmitGraphInfo, local_type_names: List, already_imported_names: List, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex, shared_types: Set, emitted_source: String) -> ReferenceDerivedUseLinePlan { { let module_source = match typed_modules |> filter(tm => authored_name_at(source_indices: source_indices, node: tm.module) == this_module_name) |> first { Present { value: tm } => module_authored_source_text(module: tm.module, source_indices: source_indices) @@ -3499,26 +3811,27 @@ fn reference_derived_use_lines(items: List, unlisted_type_names: List fold(init: empty_map(), f: (acc, nm) => map_insert(acc, nm, true)) - let unlisted = candidates |> flat_map(name => + let rows = candidates |> flat_map(name => if emit_map_has(m: already, key: name) || is_kernel_type(name: name) { [] } else { - match map_get(registry, name) { - Present { value: info } => - if info.module_name != this_module_name - && provider_proven_exports_symbol(name: name, provider_module: info.module_name, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) - { [name] } else { [] } - Absent => [] - } + [ReferenceDerivedCandidateRow { + module_name: this_module_name, + name: name, + disposition: reference_derived_candidate_disposition(name: name, this_module_name: this_module_name, registry: registry, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index) + }] } ) + let unlisted = rows + |> filter(r => reference_derived_candidate_survived(disposition: r.disposition)) + |> map(r => r.name) let providers = unique_strings(items: unlisted |> flat_map(name => match map_get(registry, name) { Present { value: info } => [info.module_name] Absent => [] } )) - providers |> flat_map(provider => + let lines = providers |> flat_map(provider => let names = unlisted |> filter(name => match map_get(registry, name) { Present { value: info } => info.module_name == provider @@ -3539,10 +3852,40 @@ fn reference_derived_use_lines(items: List, unlisted_type_names: List, emit_info: EmitGraphInfo, shared_types: Set, svc_module_map: Map, data_items: Map, export_sets: Map>, typed_modules: List, module_index: ModuleIndex, unlisted_type_names: List) -> TextFile { +fn reference_derived_use_lines(items: List, unlisted_type_names: List, this_module_name: String, registry: Map, emit_info: EmitGraphInfo, local_type_names: List, already_imported_names: List, export_sets: Map>, typed_modules: List, source_indices: Map, module_index: ModuleIndex, shared_types: Set, emitted_source: String) -> List { + let plan = reference_derived_use_line_plan(items: items, unlisted_type_names: unlisted_type_names, this_module_name: this_module_name, registry: registry, emit_info: emit_info, local_type_names: local_type_names, already_imported_names: already_imported_names, export_sets: export_sets, typed_modules: typed_modules, source_indices: source_indices, module_index: module_index, shared_types: shared_types, emitted_source: emitted_source) + plan.lines +} + +// One module's emission and the import decisions that produced it, returned together. The file is +// what emit_rust writes; the rows are what the census counts. Nothing here changes what the file +// contains -- reference_rows is the plan's OTHER projection of a computation the emitter already +// performed, so a reader can no longer get the bytes without the decisions behind them existing. +type ModuleEmission { + file: TextFile + reference_rows: List +} + +fn emit_module_full_with_dispositions(typed_module: TypedModule, ctx: EmitRustContext, typed_modules: List) -> ModuleEmission { + emit_module_full( + typed_module: typed_module, + registry: ctx.registry, + emit_info: ctx.emit_info, + shared_types: ctx.emit_info.shared_types, + svc_module_map: ctx.svc_module_map, + data_items: ctx.data_items, + export_sets: ctx.export_sets, + typed_modules: typed_modules, + module_index: ctx.module_index, + unlisted_type_names: match map_get(ctx.unlisted_type_names_by_module, authored_name_at(source_indices: typed_module.type_env.source_indices, node: typed_module.module)) { Present { value: v } => v Absent => [] } + ) +} + +fn emit_module_full(typed_module: TypedModule, registry: Map, emit_info: EmitGraphInfo, shared_types: Set, svc_module_map: Map, data_items: Map, export_sets: Map>, typed_modules: List, module_index: ModuleIndex, unlisted_type_names: List) -> ModuleEmission { let m = typed_module.module let scope = module_emit_scope(typed_module: typed_module) let scoped_data_items = augment_scoped_data_item_index_with_imports( @@ -3597,7 +3940,7 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, prelude_imported_names, concat(dag_import_lines, concat(carrier_import_lines, prelude |> split(delimiter: "\n"))) |> flat_map(l => imported_names_in_use_line(line: l)) ) - let reference_use_lines = reference_derived_use_lines( + let reference_plan = reference_derived_use_line_plan( items: typed_module.items, unlisted_type_names: unlisted_type_names, this_module_name: authored_name(env: scope.type_env, node: m), @@ -3612,6 +3955,7 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, shared_types: shared_types, emitted_source: items_str ) + let reference_use_lines = reference_plan.lines let merged_imports = dedupe_rust_import_lines(lines: concat(concat(dag_import_lines, carrier_import_lines), reference_use_lines)) |> join(separator: "\n") let imports_section = if merged_imports == "" { "" } else { concat("\n", merged_imports) } let this_mod_filename = module_to_filename(name: authored_name(env: scope.type_env, node: m)) @@ -3653,7 +3997,10 @@ fn emit_module_full(typed_module: TypedModule, registry: Map, "// Source module: ", authored_name(env: scope.type_env, node: m), "\n\n", module_attrs, prelude, imports_section, svc_imports_str, local_uses_str, "\n\n", coproduct_wire_contract_validation_section, items_str, phantom_section, "\n") - TextFile { path: concat(rust_source_root(), filename, rust_source_ext()), content: content } + ModuleEmission { + file: TextFile { path: concat(rust_source_root(), filename, rust_source_ext()), content: content }, + reference_rows: reference_plan.rows + } } fn emit_import_name(n: String, registry: Map) -> String { diff --git a/src/v1/stage0/src/emitted_population.rs b/src/v1/stage0/src/emitted_population.rs index 60c24705826..f597bc4383b 100644 --- a/src/v1/stage0/src/emitted_population.rs +++ b/src/v1/stage0/src/emitted_population.rs @@ -132,6 +132,7 @@ // src/v1_std_core.rs // src/v1_test_non_ascii_perf_fixture.rs // src/v1_tests_claim_checkpoint_identity_keying_witness_test.rs +// src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs // src/v1_tests_claim_transport_roster_closure_witness_test.rs // src/wt_a.rs // src/wt_b.rs diff --git a/src/v1/stage0/src/lib.rs b/src/v1/stage0/src/lib.rs index c80ea6c571e..1ea41a2954f 100644 --- a/src/v1/stage0/src/lib.rs +++ b/src/v1/stage0/src/lib.rs @@ -144,6 +144,7 @@ pub mod v1_rt; pub mod v1_std_core; pub mod v1_test_non_ascii_perf_fixture; pub mod v1_tests_claim_checkpoint_identity_keying_witness_test; +pub mod v1_tests_claim_reference_derived_disposition_census_witness_test; pub mod v1_tests_claim_transport_roster_closure_witness_test; pub mod wt_a; pub mod wt_b; diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index 90f301f7f91..35e18cd883f 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -8,6 +8,7 @@ use self::DataRefResolution::*; use self::DataReferenceUnresolvableCause::*; use self::IterOwnedReceiverCloneDisposition::*; use self::ParamDefaultResolution::*; +use self::ReferenceDerivedCandidateDisposition::*; pub use crate::extdeps_cargo_version::render_cargo_package_header_prefix; pub use crate::extdeps_languages_rust_capabilities::phantom_opaque_carrier_derive_traits; pub use crate::extdeps_languages_rust_emit::HigherOrderMethodSpec; @@ -5289,7 +5290,22 @@ pub fn merged_module_source_indices( ) } -pub fn emit_rust(typed: Rc) -> Rc { +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct EmitRustContext { + pub emit_info: Rc, + pub registry: Rc>>, + pub data_items: Rc>>, + pub workflow_funcs: Rc>>, + pub workflow_default_diags: Rc>>, + pub anonymous_record_diags: Rc>>, + pub svc_module_map: Rc>, + pub test_projections: Rc>>, + pub export_sets: Rc>>>, + pub module_index: Rc, + pub unlisted_type_names_by_module: Rc>>>, +} + +pub fn build_emit_rust_context(typed: Rc) -> Rc { { let base_info = build_emit_graph_info(typed.modules.clone()); let ownership = build_ownership_results(typed.modules.clone()); @@ -5337,7 +5353,6 @@ pub fn emit_rust(typed: Rc) -> Rc { fn_type_env: base_info.fn_type_env.clone(), fn_return_type: None, }); - let shared_types = emit_info.shared_types.clone(); let registry = merge_item_registries( typed.item_registry.clone(), build_qualified_item_registry(typed.modules.clone()), @@ -5349,12 +5364,6 @@ pub fn emit_rust(typed: Rc) -> Rc { emit_info.read_only_params_index.clone(), ); let workflow_default_diags = validate_workflow_param_defaults(workflow_funcs.clone()); - if ((workflow_default_diags.clone().len() as i64) > 0) { - return Rc::new(EmitResult { - files: Rc::new(vec![]), - diagnostics: workflow_default_diags.clone(), - }); - } let anonymous_record_diags = Rc::new({ let mut __result = Vec::new(); for tm in typed.modules.clone().iter().cloned() { @@ -5384,12 +5393,6 @@ pub fn emit_rust(typed: Rc) -> Rc { } __result }); - if ((anonymous_record_diags.clone().len() as i64) > 0) { - return Rc::new(EmitResult { - files: Rc::new(vec![]), - diagnostics: anonymous_record_diags.clone(), - }); - } let svc_module_map = typed.modules.clone().iter().cloned().fold( v1_rt::rc_empty_map::(), |acc: Rc>, tm: Rc| { @@ -5425,30 +5428,90 @@ pub fn emit_rust(typed: Rc) -> Rc { let export_sets = build_module_export_sets(typed.modules.clone()); let module_index = build_module_index(typed.modules.clone()); let unlisted_type_names_by_module = group_unlisted_type_names(typed.diagnostics.clone()); + Rc::new(EmitRustContext { + emit_info: emit_info.clone(), + registry: registry.clone(), + data_items: data_items.clone(), + workflow_funcs: workflow_funcs.clone(), + workflow_default_diags: workflow_default_diags.clone(), + anonymous_record_diags: anonymous_record_diags.clone(), + svc_module_map: svc_module_map.clone(), + test_projections: test_projections.clone(), + export_sets: export_sets.clone(), + module_index: module_index.clone(), + unlisted_type_names_by_module: unlisted_type_names_by_module.clone(), + }) + } +} + +pub fn emit_rust_reference_derived_rows( + typed: Rc, +) -> Rc>> { + { + let ctx = build_emit_rust_context(typed.clone()); + Rc::new({ + let mut __result = Vec::new(); + for tm in typed.modules.clone().iter().cloned() { + __result.extend( + (*emit_module_full_with_dispositions( + tm.clone(), + ctx.clone(), + typed.modules.clone(), + ) + .reference_rows + .clone()) + .iter() + .cloned(), + ); + } + __result + }) + } +} + +pub fn emit_rust_reference_derived_census(typed: Rc) -> ReferenceDerivedCensus { + reference_derived_census(emit_rust_reference_derived_rows(typed.clone())) +} + +pub fn emit_rust(typed: Rc) -> Rc { + { + let ctx = build_emit_rust_context(typed.clone()); + let emit_info = ctx.emit_info.clone(); + let registry = ctx.registry.clone(); + let data_items = ctx.data_items.clone(); + let svc_module_map = ctx.svc_module_map.clone(); + let export_sets = ctx.export_sets.clone(); + let module_index = ctx.module_index.clone(); + let unlisted_type_names_by_module = ctx.unlisted_type_names_by_module.clone(); + let shared_types = emit_info.shared_types.clone(); + let workflow_funcs = ctx.workflow_funcs.clone(); + let workflow_default_diags = ctx.workflow_default_diags.clone(); + if ((workflow_default_diags.clone().len() as i64) > 0) { + return Rc::new(EmitResult { + files: Rc::new(vec![]), + diagnostics: workflow_default_diags.clone(), + }); + } + let anonymous_record_diags = ctx.anonymous_record_diags.clone(); + if ((anonymous_record_diags.clone().len() as i64) > 0) { + return Rc::new(EmitResult { + files: Rc::new(vec![]), + diagnostics: anonymous_record_diags.clone(), + }); + } + let test_projections = ctx.test_projections.clone(); let module_files = Rc::new({ let mut __result = Vec::new(); for tm in typed.modules.clone().iter().cloned() { - __result.push(emit_module_full( - tm.clone(), - registry.clone(), - emit_info.clone(), - shared_types.clone(), - svc_module_map.clone(), - data_items.clone(), - export_sets.clone(), - typed.modules.clone(), - module_index.clone(), - match v1_rt::map_get( - &unlisted_type_names_by_module, - authored_name_at( - tm.type_env.clone().source_indices.clone(), - tm.module.clone(), - ), - ) { - Some(v) => v.clone(), - None => Rc::new(vec![]), - }, - )); + __result.push( + emit_module_full_with_dispositions( + tm.clone(), + ctx.clone(), + typed.modules.clone(), + ) + .file + .clone(), + ); } __result }); @@ -5949,6 +6012,8 @@ pub fn emit_module( module_index.clone(), Rc::new(vec![]), ) + .file + .clone() } } @@ -7676,7 +7741,181 @@ pub fn collect_item_realized_surface_names( } } -pub fn reference_derived_use_lines( +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum ReferenceDerivedCandidateDisposition { + CandidateSurvived { provider_module: String }, + CandidateOwnModule, + CandidateRegistryAbsent, + CandidateExportProofFailed { provider_module: String }, +} +impl ReferenceDerivedCandidateDisposition { + pub fn provider_module(&self) -> String { + match self { + ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: __val, + .. + } => __val.clone(), + ReferenceDerivedCandidateDisposition::CandidateOwnModule => { + panic!("no provider_module on unit variant") + } + ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { + panic!("no provider_module on unit variant") + } + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: __val, + .. + } => __val.clone(), + } + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferenceDerivedCandidateRow { + pub module_name: String, + pub name: String, + pub disposition: Rc, +} + +pub fn reference_derived_candidate_disposition( + name: String, + this_module_name: String, + registry: Rc>>, + export_sets: Rc>>>, + typed_modules: Rc>>, + source_indices: Rc>>, + module_index: Rc, +) -> Rc { + match v1_rt::map_get(®istry, name.clone()) { + Some(info) => { + if (info.module_name.clone() == this_module_name.clone()) { + Rc::new(ReferenceDerivedCandidateDisposition::CandidateOwnModule) + } else { + if provider_proven_exports_symbol( + name.clone(), + info.module_name.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ) { + Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: info.module_name.clone(), + }) + } else { + Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: info.module_name.clone(), + }, + ) + } + } + } + None => Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), + } +} + +pub fn reference_derived_candidate_survived( + disposition: Rc, +) -> bool { + match (*disposition.clone()).clone() { + ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: _, .. + } => true, + _ => false, + } +} + +pub fn reference_derived_disposition_name( + disposition: Rc, +) -> String { + match (*disposition.clone()).clone() { + ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: _, .. + } => "survived".to_string(), + ReferenceDerivedCandidateDisposition::CandidateOwnModule => "own-module".to_string(), + ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent => { + "registry-absent".to_string() + } + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: _, + .. + } => "export-proof-failed".to_string(), + } +} + +#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferenceDerivedCensus { + pub candidates: i64, + pub survived: i64, + pub own_module: i64, + pub registry_absent: i64, + pub export_proof_failed: i64, +} + +pub fn reference_derived_census( + rows: Rc>>, +) -> ReferenceDerivedCensus { + ReferenceDerivedCensus { + candidates: (rows.clone().len() as i64), + survived: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "survived".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), + own_module: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "own-module".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), + registry_absent: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "registry-absent".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), + export_proof_failed: (Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if (reference_derived_disposition_name(r.disposition.clone()) + == "export-proof-failed".to_string()) + { + __result.push(r); + } + } + __result + }) + .len() as i64), + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ReferenceDerivedUseLinePlan { + pub lines: Rc>, + pub rows: Rc>>, +} + +pub fn reference_derived_use_line_plan( items: Rc>>, unlisted_type_names: Rc>, this_module_name: String, @@ -7690,7 +7929,7 @@ pub fn reference_derived_use_lines( module_index: Rc, shared_types: Rc>, emitted_source: String, -) -> Rc> { +) -> Rc { { let module_source = match Rc::new({ let mut __result = Vec::new(); @@ -7870,7 +8109,7 @@ pub fn reference_derived_use_lines( v1_rt::rc_map_insert(acc, nm.clone(), true) }, ); - let unlisted = Rc::new({ + let rows = Rc::new({ let mut __result = Vec::new(); for name in candidates.iter().cloned() { __result.extend( @@ -7879,25 +8118,19 @@ pub fn reference_derived_use_lines( { Rc::new(vec![]) } else { - match v1_rt::map_get(®istry, name.clone()) { - Some(info) => { - if ((info.module_name.clone() != this_module_name.clone()) - && provider_proven_exports_symbol( - name.clone(), - info.module_name.clone(), - export_sets.clone(), - typed_modules.clone(), - source_indices.clone(), - module_index.clone(), - )) - { - Rc::new(vec![name.clone()]) - } else { - Rc::new(vec![]) - } - } - None => Rc::new(vec![]), - } + Rc::new(vec![Rc::new(ReferenceDerivedCandidateRow { + module_name: this_module_name.clone(), + name: name.clone(), + disposition: reference_derived_candidate_disposition( + name.clone(), + this_module_name.clone(), + registry.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + ), + })]) }) .iter() .cloned(), @@ -7905,6 +8138,24 @@ pub fn reference_derived_use_lines( } __result }); + let unlisted = Rc::new({ + let mut __result = Vec::new(); + for r in Rc::new({ + let mut __result = Vec::new(); + for r in rows.iter().cloned() { + if reference_derived_candidate_survived(r.disposition.clone()) { + __result.push(r); + } + } + __result + }) + .iter() + .cloned() + { + __result.push(r.name.clone()); + } + __result + }); let providers = unique_strings(Rc::new({ let mut __result = Vec::new(); for name in unlisted.iter().cloned() { @@ -7919,7 +8170,7 @@ pub fn reference_derived_use_lines( } __result })); - Rc::new({ + let lines = Rc::new({ let mut __result = Vec::new(); for provider in providers.iter().cloned() { __result.extend( @@ -8031,10 +8282,83 @@ pub fn reference_derived_use_lines( ); } __result + }); + Rc::new(ReferenceDerivedUseLinePlan { + lines: lines.clone(), + rows: rows.clone(), }) } } +pub fn reference_derived_use_lines( + items: Rc>>, + unlisted_type_names: Rc>, + this_module_name: String, + registry: Rc>>, + emit_info: Rc, + local_type_names: Rc>, + already_imported_names: Rc>, + export_sets: Rc>>>, + typed_modules: Rc>>, + source_indices: Rc>>, + module_index: Rc, + shared_types: Rc>, + emitted_source: String, +) -> Rc> { + { + let plan = reference_derived_use_line_plan( + items.clone(), + unlisted_type_names.clone(), + this_module_name.clone(), + registry.clone(), + emit_info.clone(), + local_type_names.clone(), + already_imported_names.clone(), + export_sets.clone(), + typed_modules.clone(), + source_indices.clone(), + module_index.clone(), + shared_types.clone(), + emitted_source.clone(), + ); + plan.lines.clone() + } +} + +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct ModuleEmission { + pub file: Rc, + pub reference_rows: Rc>>, +} + +pub fn emit_module_full_with_dispositions( + typed_module: Rc, + ctx: Rc, + typed_modules: Rc>>, +) -> Rc { + emit_module_full( + typed_module.clone(), + ctx.registry.clone(), + ctx.emit_info.clone(), + ctx.emit_info.clone().shared_types.clone(), + ctx.svc_module_map.clone(), + ctx.data_items.clone(), + ctx.export_sets.clone(), + typed_modules.clone(), + ctx.module_index.clone(), + match v1_rt::map_get( + &ctx.unlisted_type_names_by_module.clone(), + authored_name_at( + typed_module.type_env.clone().source_indices.clone(), + typed_module.module.clone(), + ), + ) { + Some(v) => v.clone(), + None => Rc::new(vec![]), + }, + ) +} + pub fn emit_module_full( typed_module: Rc, registry: Rc>>, @@ -8046,7 +8370,7 @@ pub fn emit_module_full( typed_modules: Rc>>, module_index: Rc, unlisted_type_names: Rc>, -) -> Rc { +) -> Rc { { let m = typed_module.module.clone(); let scope = module_emit_scope(typed_module.clone()); @@ -8224,7 +8548,7 @@ pub fn emit_module_full( __result }), ); - let reference_use_lines = reference_derived_use_lines( + let reference_plan = reference_derived_use_line_plan( typed_module.items.clone(), unlisted_type_names.clone(), authored_name(scope.type_env.clone(), m.clone()), @@ -8239,6 +8563,7 @@ pub fn emit_module_full( shared_types.clone(), items_str.clone(), ); + let reference_use_lines = reference_plan.lines.clone(); let merged_imports = dedupe_rust_import_lines(v1_rt::concat( v1_rt::concat(dag_import_lines.clone(), carrier_import_lines.clone()), reference_use_lines.clone(), @@ -8402,12 +8727,15 @@ pub fn emit_module_full( "".to_string() }; 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()), 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(TextFile { - path: v1_rt::concat( - v1_rt::concat(rust_source_root(), filename.clone()), - rust_source_ext(), - ), - content: content.clone(), + Rc::new(ModuleEmission { + file: Rc::new(TextFile { + path: v1_rt::concat( + v1_rt::concat(rust_source_root(), filename.clone()), + rust_source_ext(), + ), + content: content.clone(), + }), + reference_rows: reference_plan.rows.clone(), }) } } diff --git a/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs new file mode 100644 index 00000000000..e91a0c67a9a --- /dev/null +++ b/src/v1/stage0/src/v1_tests_claim_reference_derived_disposition_census_witness_test.rs @@ -0,0 +1,154 @@ +// Generated by v1 compiler -- do not edit. +// Source module: v1.tests.claim.reference_derived_disposition_census_witness_test + +pub use crate::v1_compiler_compile::compile_to_resolved; +pub use crate::v1_compiler_compile::SourceFile; +use crate::v1_compiler_emit_rust::ReferenceDerivedCandidateDisposition::{ + CandidateExportProofFailed, CandidateOwnModule, CandidateRegistryAbsent, CandidateSurvived, +}; +pub use crate::v1_compiler_emit_rust::{ + build_module_index, emit_rust_reference_derived_census, emit_rust_reference_derived_rows, + reference_derived_candidate_disposition, reference_derived_census, + reference_derived_disposition_name, +}; +pub use crate::v1_compiler_emit_rust::{ + ReferenceDerivedCandidateDisposition, ReferenceDerivedCandidateRow, ReferenceDerivedCensus, +}; +use crate::v1_compiler_infer_items::ItemKind::FnItem; +pub use crate::v1_compiler_infer_items::{ItemInfo, ItemKind}; +use crate::v1_rt; +use crate::v1_rt::{VecCompat, VecJoin}; +pub use crate::v1_std_core::{NewlineIndex, Node}; +use crate::NonEmptyBTreeSet; +use crate::NonEmptyVec; +use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; +use std::rc::Rc; + +pub fn reference_derived_disposition_census_witness_note() -> String { + thread_local! { + static CACHED: String = { + "THE ROUTE HALF OF THIS FILE IS ABSENT AND THE REASON IS A WALL RATHER THAN A CHOICE, stated here because it is the first thing a reader of a five-row file should know: the end-to-end pair this file was designed around -- a two-module manifest whose consumer calls a peer fn in VALUE position with no import, against a control differing by one authored import line -- was authored and DOES NOT RUN. A nested compile is not evaluable in the interpreter: compile_to_resolved over an authored source vector refuses with NoSuchField Node.ident during the nested pipeline, measured 2026-08-27 on three separate subjects. That is not a property of this change; it is why the two nested-compile instruments the corpus already has (gunbc.compile_diagnostic_census and tools.multi_module_compile_fixture) both route through a HOST BUILTIN rather than calling the pipeline from .dag. The rows are not retained in a form that cannot execute, because a probe that always errors is not a red -- it is an inert artifact that would be counted as coverage. NEXT-RUNG TRIGGER FOR THE ROUTE: a nested-compile builtin returning the resolved graph, the shape tools.multi_module_compile_fixture already has, widened to carry the emitter reference-derived rows.\n\nWHERE THIS RUNS, STATED FIRST AND WITHOUT FLATTERY. THIS WITNESS IS NOT RUN BY CI. gunbc.ci_layer_roots witness_discovery_scan_dirs is [dag/test/claim, src/v2/test/claim/manual, src/v2/test/claim/emit] -- src/v1/tests/claim is not among them -- and the required run invokes claim_executor with --source-root dag --source-root src/v2, which could not resolve this file's import of v1.compiler.emit_rust even if discovery reached it. The scoped batch that once ran this directory died in the 2026-08-15 floor cut; gunbc.ci_layer_roots v1_claim_scoped_witness_batch_deleted_note records that and states the entries here have no executing consumer. The roster its sibling checkpoint_identity_keying_witness_test claims to be enrolled in, v1_claim_scoped_witness_entries, is not declared anywhere in dag/gunbc/ci_layer_roots.dag -- verified by grep in both directions rather than taken from the note. THE FIVE ROWS BELOW WERE RUN DIRECTLY ON 2026-08-27 AND ALL FIVE PASS. Direct execution is evidence the assertions HOLD, never evidence that CI runs them, and conflating those two is the rung inflation this directory has now recorded twice.\n\nWHAT THE SUBJECT IS. Since PR 6848 a cross-module name resolves whether or not it is imported. At TYPE positions the resolver says so advisorily (UnlistedImportUse, is_error_diagnostic false); at VALUE positions it says nothing at all, and the emitter silently synthesizes the use-line the author did not write. That synthesis is unchanged here and deliberately so. What changed is that the per-candidate decision behind it stopped being an inline flat_map whose three non-surviving arms all returned the empty list, and became a four-armed coproduct one census can count.\n\nWHY BOTH HALVES ARE HERE. The four unit rows discriminate the DECISION: each names a distinct arm, and two of them differ ONLY in whether the provider's export set proves the symbol, so no constant, disabled mechanism or name-keyed rule satisfies them together. The end-to-end rows discriminate the CENSUS ROUTE: a two-module manifest compiled through compile_to_resolved, whose consumer references a peer's fn in VALUE position with no import, against a control differing by exactly one authored import line. The unlisted arm must produce a survived row naming peer_answer; the control must produce no row for that name at all, because an already-provided name never reaches the decision. Neither can pass vacuously -- a census that always reported zero fails the first, and one that reported every reference fails the second.\n\nTHE RED IS AUTHORABLE AT THE FIXTURE BOUNDARY, which is what makes this a probe rather than a decoration. No Accepted corpus module can be relied on to hold a value-position unlisted use on demand, but a fixture manifest can author one directly, and this file does. DESIGN 4b: a check whose RED cannot be produced anywhere it runs is permanently green by construction and worse than absent, because it gets cited as coverage. What this file lacks is a RUNNER, not a reachable red.\n\nWHAT THIS DOES NOT ESTABLISH. Nothing about the SIZE of the live population -- these are fixtures; NO PRODUCER FOR A REAL FIGURE EXISTS YET, and this file does not name one: the only route from .dag to a corpus census is a nested compile, which the interpreter refuses, so any figure quoted today names nothing that can produce it. And nothing about the names the census never sees: a candidate already provided by an authored import, the prelude, a carrier use-line or the module's own declarations is filtered before the decision, so every count is a LOWER BOUND and the gap is unmeasured. Quote it as at least N.\n\nNEXT-RUNG TRIGGER FOR THIS FILE'S EVIDENCE, which is NOT the same trigger as the class's and must not be folded into it: one prepared subject spanning dag, src/v1 and src/v2 -- the restoration gunbc.ci_layer_roots already names for the deleted batch -- at which point these rows execute and this first paragraph deletes. The CLASS's own trigger (the value-position case joining the fail-closed wall, retiring the census and the synthesis together) lives on v1.compiler.emit_rust reference_derived_census_rung_note. Either can land first; neither closes the other.".to_string() + }; + } + CACHED.with(|c: &String| c.clone()) +} + +pub fn fixture_item_info(name: String, module_name: String) -> Rc { + Rc::new(ItemInfo { + name: name.clone(), + module_name: module_name.clone(), + kind: ItemKind::FnItem, + service_names: Rc::new(vec![]), + resource_names: Rc::new(vec![]), + params: Rc::new(vec![]), + is_self_recursive: false, + has_non_tail_self_call: false, + }) +} + +pub fn fixture_registry(name: String, module_name: String) -> Rc>> { + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::>(), + name.clone(), + fixture_item_info(name.clone(), module_name.clone()), + ) +} + +pub fn fixture_export_sets( + module_name: String, + name: String, +) -> Rc>>> { + v1_rt::rc_map_insert( + v1_rt::rc_empty_map::>>(), + module_name.clone(), + v1_rt::rc_map_insert(v1_rt::rc_empty_map::(), name.clone(), true), + ) +} + +pub fn fixture_disposition( + registry: Rc>>, + export_sets: Rc>>>, +) -> String { + reference_derived_disposition_name(reference_derived_candidate_disposition( + "PeerName".to_string(), + "fixture.consumer".to_string(), + registry.clone(), + export_sets.clone(), + Rc::new(vec![]), + v1_rt::rc_empty_map::>(), + build_module_index(Rc::new(vec![])), + )) +} + +pub fn candidate_the_registry_does_not_know_is_registry_absent() -> bool { + (fixture_disposition( + v1_rt::rc_empty_map::>(), + v1_rt::rc_empty_map::>>(), + ) == "registry-absent".to_string()) +} + +pub fn candidate_provided_by_this_module_is_own_module() -> bool { + (fixture_disposition( + fixture_registry("PeerName".to_string(), "fixture.consumer".to_string()), + v1_rt::rc_empty_map::>>(), + ) == "own-module".to_string()) +} + +pub fn cross_module_candidate_without_export_proof_is_export_proof_failed() -> bool { + (fixture_disposition( + fixture_registry("PeerName".to_string(), "fixture.provider".to_string()), + v1_rt::rc_empty_map::>>(), + ) == "export-proof-failed".to_string()) +} + +pub fn cross_module_candidate_with_export_proof_survives() -> bool { + (reference_derived_candidate_disposition( + "PeerName".to_string(), + "fixture.consumer".to_string(), + fixture_registry("PeerName".to_string(), "fixture.provider".to_string()), + fixture_export_sets("fixture.provider".to_string(), "PeerName".to_string()), + Rc::new(vec![]), + v1_rt::rc_empty_map::>(), + build_module_index(Rc::new(vec![])), + ) == Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: "fixture.provider".to_string(), + })) +} + +pub fn census_counts_each_arm_separately() -> bool { + { + let rows = Rc::new(vec![ + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "a".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateSurvived { + provider_module: "p".to_string(), + }), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "b".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateOwnModule), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "c".to_string(), + disposition: Rc::new(ReferenceDerivedCandidateDisposition::CandidateRegistryAbsent), + }), + Rc::new(ReferenceDerivedCandidateRow { + module_name: "m".to_string(), + name: "d".to_string(), + disposition: Rc::new( + ReferenceDerivedCandidateDisposition::CandidateExportProofFailed { + provider_module: "p".to_string(), + }, + ), + }), + ]); + let census = reference_derived_census(rows.clone()); + (((((census.candidates.clone() == 4) && (census.survived.clone() == 1)) + && (census.own_module.clone() == 1)) + && (census.registry_absent.clone() == 1)) + && (census.export_proof_failed.clone() == 1)) + } +} diff --git a/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag new file mode 100644 index 00000000000..9e8d1b76e1a --- /dev/null +++ b/src/v1/tests/claim/reference_derived_disposition_census_witness_test.dag @@ -0,0 +1,95 @@ +module v1.tests.claim.reference_derived_disposition_census_witness_test + +import v1.compiler.emit_rust { + ReferenceDerivedCandidateDisposition, + CandidateSurvived, + CandidateOwnModule, + CandidateRegistryAbsent, + CandidateExportProofFailed, + ReferenceDerivedCandidateRow, + ReferenceDerivedCensus, + reference_derived_candidate_disposition, + reference_derived_disposition_name, + reference_derived_census, + emit_rust_reference_derived_rows, + emit_rust_reference_derived_census, + build_module_index +} +import v1.compiler.infer_items { ItemInfo, FnItem } +import v1.compiler.compile { SourceFile, compile_to_resolved } + +data reference_derived_disposition_census_witness_note: String = "THE ROUTE HALF OF THIS FILE IS ABSENT AND THE REASON IS A WALL RATHER THAN A CHOICE, stated here because it is the first thing a reader of a five-row file should know: the end-to-end pair this file was designed around -- a two-module manifest whose consumer calls a peer fn in VALUE position with no import, against a control differing by one authored import line -- was authored and DOES NOT RUN. A nested compile is not evaluable in the interpreter: compile_to_resolved over an authored source vector refuses with NoSuchField Node.ident during the nested pipeline, measured 2026-08-27 on three separate subjects. That is not a property of this change; it is why the two nested-compile instruments the corpus already has (gunbc.compile_diagnostic_census and tools.multi_module_compile_fixture) both route through a HOST BUILTIN rather than calling the pipeline from .dag. The rows are not retained in a form that cannot execute, because a probe that always errors is not a red -- it is an inert artifact that would be counted as coverage. NEXT-RUNG TRIGGER FOR THE ROUTE: a nested-compile builtin returning the resolved graph, the shape tools.multi_module_compile_fixture already has, widened to carry the emitter reference-derived rows.\n\nWHERE THIS RUNS, STATED FIRST AND WITHOUT FLATTERY. THIS WITNESS IS NOT RUN BY CI. gunbc.ci_layer_roots witness_discovery_scan_dirs is [dag/test/claim, src/v2/test/claim/manual, src/v2/test/claim/emit] -- src/v1/tests/claim is not among them -- and the required run invokes claim_executor with --source-root dag --source-root src/v2, which could not resolve this file's import of v1.compiler.emit_rust even if discovery reached it. The scoped batch that once ran this directory died in the 2026-08-15 floor cut; gunbc.ci_layer_roots v1_claim_scoped_witness_batch_deleted_note records that and states the entries here have no executing consumer. The roster its sibling checkpoint_identity_keying_witness_test claims to be enrolled in, v1_claim_scoped_witness_entries, is not declared anywhere in dag/gunbc/ci_layer_roots.dag -- verified by grep in both directions rather than taken from the note. THE FIVE ROWS BELOW WERE RUN DIRECTLY ON 2026-08-27 AND ALL FIVE PASS. Direct execution is evidence the assertions HOLD, never evidence that CI runs them, and conflating those two is the rung inflation this directory has now recorded twice.\n\nWHAT THE SUBJECT IS. Since PR 6848 a cross-module name resolves whether or not it is imported. At TYPE positions the resolver says so advisorily (UnlistedImportUse, is_error_diagnostic false); at VALUE positions it says nothing at all, and the emitter silently synthesizes the use-line the author did not write. That synthesis is unchanged here and deliberately so. What changed is that the per-candidate decision behind it stopped being an inline flat_map whose three non-surviving arms all returned the empty list, and became a four-armed coproduct one census can count.\n\nWHY BOTH HALVES ARE HERE. The four unit rows discriminate the DECISION: each names a distinct arm, and two of them differ ONLY in whether the provider's export set proves the symbol, so no constant, disabled mechanism or name-keyed rule satisfies them together. The end-to-end rows discriminate the CENSUS ROUTE: a two-module manifest compiled through compile_to_resolved, whose consumer references a peer's fn in VALUE position with no import, against a control differing by exactly one authored import line. The unlisted arm must produce a survived row naming peer_answer; the control must produce no row for that name at all, because an already-provided name never reaches the decision. Neither can pass vacuously -- a census that always reported zero fails the first, and one that reported every reference fails the second.\n\nTHE RED IS AUTHORABLE AT THE FIXTURE BOUNDARY, which is what makes this a probe rather than a decoration. No Accepted corpus module can be relied on to hold a value-position unlisted use on demand, but a fixture manifest can author one directly, and this file does. DESIGN 4b: a check whose RED cannot be produced anywhere it runs is permanently green by construction and worse than absent, because it gets cited as coverage. What this file lacks is a RUNNER, not a reachable red.\n\nWHAT THIS DOES NOT ESTABLISH. Nothing about the SIZE of the live population -- these are fixtures; NO PRODUCER FOR A REAL FIGURE EXISTS YET, and this file does not name one: the only route from .dag to a corpus census is a nested compile, which the interpreter refuses, so any figure quoted today names nothing that can produce it. And nothing about the names the census never sees: a candidate already provided by an authored import, the prelude, a carrier use-line or the module's own declarations is filtered before the decision, so every count is a LOWER BOUND and the gap is unmeasured. Quote it as at least N.\n\nNEXT-RUNG TRIGGER FOR THIS FILE'S EVIDENCE, which is NOT the same trigger as the class's and must not be folded into it: one prepared subject spanning dag, src/v1 and src/v2 -- the restoration gunbc.ci_layer_roots already names for the deleted batch -- at which point these rows execute and this first paragraph deletes. The CLASS's own trigger (the value-position case joining the fail-closed wall, retiring the census and the synthesis together) lives on v1.compiler.emit_rust reference_derived_census_rung_note. Either can land first; neither closes the other." + +fn fixture_item_info(name: String, module_name: String) -> ItemInfo { + ItemInfo { + name: name, + module_name: module_name, + kind: FnItem, + service_names: [], + resource_names: [], + params: [], + is_self_recursive: false, + has_non_tail_self_call: false + } +} + +fn fixture_registry(name: String, module_name: String) -> Map { + map_insert(empty_map(), name, fixture_item_info(name: name, module_name: module_name)) +} + +fn fixture_export_sets(module_name: String, name: String) -> Map> { + map_insert(empty_map(), module_name, map_insert(empty_map(), name, true)) +} + +fn fixture_disposition(registry: Map, export_sets: Map>) -> String { + reference_derived_disposition_name(disposition: reference_derived_candidate_disposition( + name: "PeerName", + this_module_name: "fixture.consumer", + registry: registry, + export_sets: export_sets, + typed_modules: [], + source_indices: empty_map(), + module_index: build_module_index(modules: []) + )) +} + +test fn candidate_the_registry_does_not_know_is_registry_absent() -> Bool { + fixture_disposition(registry: empty_map(), export_sets: empty_map()) == "registry-absent" +} + +test fn candidate_provided_by_this_module_is_own_module() -> Bool { + fixture_disposition(registry: fixture_registry(name: "PeerName", module_name: "fixture.consumer"), export_sets: empty_map()) + == "own-module" +} + +test fn cross_module_candidate_without_export_proof_is_export_proof_failed() -> Bool { + fixture_disposition(registry: fixture_registry(name: "PeerName", module_name: "fixture.provider"), export_sets: empty_map()) + == "export-proof-failed" +} + +test fn cross_module_candidate_with_export_proof_survives() -> Bool { + reference_derived_candidate_disposition( + name: "PeerName", + this_module_name: "fixture.consumer", + registry: fixture_registry(name: "PeerName", module_name: "fixture.provider"), + export_sets: fixture_export_sets(module_name: "fixture.provider", name: "PeerName"), + typed_modules: [], + source_indices: empty_map(), + module_index: build_module_index(modules: []) + ) == CandidateSurvived { provider_module: "fixture.provider" } +} + +test fn census_counts_each_arm_separately() -> Bool { + let rows = [ + ReferenceDerivedCandidateRow { module_name: "m", name: "a", disposition: CandidateSurvived { provider_module: "p" } }, + ReferenceDerivedCandidateRow { module_name: "m", name: "b", disposition: CandidateOwnModule }, + ReferenceDerivedCandidateRow { module_name: "m", name: "c", disposition: CandidateRegistryAbsent }, + ReferenceDerivedCandidateRow { module_name: "m", name: "d", disposition: CandidateExportProofFailed { provider_module: "p" } } + ] + let census = reference_derived_census(rows: rows) + census.candidates == 4 + && census.survived == 1 + && census.own_module == 1 + && census.registry_absent == 1 + && census.export_proof_failed == 1 +}