diff --git a/dag/gunbc/recurring_failure_mode/resolved_value_computed_then_discarded_for_the_authored_one.dag b/dag/gunbc/recurring_failure_mode/resolved_value_computed_then_discarded_for_the_authored_one.dag new file mode 100644 index 00000000000..28e4a0273b6 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/resolved_value_computed_then_discarded_for_the_authored_one.dag @@ -0,0 +1,30 @@ +module gunbc.recurring_failure_mode.resolved_value_computed_then_discarded_for_the_authored_one + +import std.types { NonEmptyStr } +import std.decl_ref { decl_ref } +import gunbc.recurring_failure_mode { RecurringFailureMode } + +data resolved_value_computed_then_discarded_for_the_authored_one: RecurringFailureMode = RecurringFailureMode { + identity: "resolved_value_computed_then_discarded_for_the_authored_one" as NonEmptyStr, + + receipts: [ + "**a phase computes the resolved form of a value, keeps only a by-product of it (its diagnostics), and rebuilds its output from the AUTHORED input** (the phase's result is structurally indistinguishable from a resolved one, so every consumer downstream reads an unresolved value as if the phase had run on it).", + + "**SPECIMEN, 2026-09-17 (CARRIER-KEY-1, tidy-bee-696).** `v1.compiler.infer_resolve` `resolve_item_types`, in its record (Conj) and coproduct (Disj) field arms, computes `tr = resolve_node(n: authored_type)` for each field. It then rebuilds the field with `children: child.children` and `inferred: child.inferred`, keeping only `tr.diagnostics` and `tr.resolved.properties`. The resolved type is thrown away. Its sibling `resolve_field`, which does carry the resolved type, has no callers.", + + "**THE CONSEQUENCE IS A MIS-KEYED IDENTITY, NOT A CRASH.** A field-position type reference reaches emission unresolved and carrying its own span. So `v1.std.core` `type_reference_provenance` keys it on the file the REFERENCE sits in. On the typed tree, the same kernel `String`, `Int` and `Bool` are keyed correctly as function parameters, which go through a path that keeps the resolved node, and wrongly as record fields. Re-derived by `v1.tests.claim.carrier_realization_census` over a closure: join `position_kind` to `split_identity`.", + + "**THE SYMPTOM WAS ALREADY PATCHED ONE LINK DOWNSTREAM**, which is `symptom_link_patched_without_the_earliest_unjustified_boundary` exactly. `v1.compiler.emit_rust` `type_reference_provenance_in_env` re-resolves an unresolved leaf through the environment because `field type exprs are not inferred`. That note states the upstream fact as a given instead of naming it as the defect.", + + "**RECOGNITION RULE.** A phase that calls the resolving function and reads only `.diagnostics` from its result, or rebuilds its output from the input it was handed. Ask what field of the result is dropped, and who downstream compensates for it. A second function that keeps the result and has no callers is the tell that the keeping version was written and then routed around.", + + "**NOT REPAIRED WHERE FOUND.** Carrying the resolved type into the field changes what every renderer sees at every field position, so it is measured under the step-3 renderer reroute of docs/plans/carrier-realization-arbiter-repair-design.md, not landed as a local fix. The interim is `gunbc.rung_drop` `type_reference_location_fallback_keys_production_realization`. Rung found at: outside the ladder (silent). Ceiling: structurally impossible, since the phase's output can carry the resolved node as its only type. Next trigger: the field arms keep `tr.resolved`, `resolve_field` is either their single route or deleted, and the downstream env re-resolution in `type_reference_provenance_in_env` is removed in the same change.", + ], + + evidence: [ + decl_ref(module_path: "v1.compiler.infer_resolve", decl_name: "resolve_item_types"), + decl_ref(module_path: "v1.compiler.infer_resolve", decl_name: "resolve_field"), + decl_ref(module_path: "v1.compiler.emit_rust", decl_name: "type_reference_provenance_in_env"), + decl_ref(module_path: "v1.std.core", decl_name: "type_reference_identity"), + ], +} diff --git a/dag/gunbc/recurring_failure_mode/state_space_conflation.dag b/dag/gunbc/recurring_failure_mode/state_space_conflation.dag index 1b82495247c..047266c4f05 100644 --- a/dag/gunbc/recurring_failure_mode/state_space_conflation.dag +++ b/dag/gunbc/recurring_failure_mode/state_space_conflation.dag @@ -213,6 +213,10 @@ data state_space_conflation: RecurringFailureMode = RecurringFailureMode { "**RECOGNITION RULE FOR THE FORM: when a consumer narrows a closed variant to an `Optional`, the narrowing is where the states are lost, and the arm that gives the collapsed case a permissive default is where the loss becomes a fail-open.** The tell is a refusal reason that is empty on some path: a refusal that cannot say why was never a refusal.", + "**RECEIPT (2026-09-17, CARRIER-KEY-1): THE REFERENCE-SITE FALLBACK ARM IS SPLIT, AND THE SPLIT ITSELF CORRECTED THE CONFLATION'S PROXY.** `v1.std.core` `type_reference_provenance` answered `this node IS the declaration` and `this reference's declaration was not recovered` with one arm keyed on the node's own span. The obvious proxy for telling them apart -- is a Resolved node bound -- is itself a conflation: on the typed tree, `v1.compiler.infer_resolve` resolves a LEAF reference by substituting the declaration (no Resolved node, correct span) and builds an APPLIED reference's Resolved node on the REFERENCE's span (a Resolved node, location key). `v1.std.core` `type_reference_identity` over `std.coercion` `TypeReferenceIdentity` therefore splits on the parser's `ModuleItemTypeDeclaration` mark and the kernel mint, and refuses the rest with a cause. Populations are re-derived by `v1.tests.claim.carrier_realization_census` column `split_identity`; the executing RED is `v1.compiler.compiler_tests_rust` `ct_type_reference_identity_split_test`. Production is unchanged and declared as `gunbc.rung_drop` `type_reference_location_fallback_keys_production_realization`.", + + "**RECOGNITION RULE ADDED BY THAT RECEIPT: a presence test on a carrier field is not a state test.** `inferred is Resolved` and `span is non-empty` each looked like the discriminator and each put both states on both sides of it. Ask what STRUCTURE only one state can have.", + ], evidence: [], diff --git a/dag/gunbc/rung_drop/roster.dag b/dag/gunbc/rung_drop/roster.dag index 63383bcfde0..03bd2478688 100644 --- a/dag/gunbc/rung_drop/roster.dag +++ b/dag/gunbc/rung_drop/roster.dag @@ -74,6 +74,7 @@ import gunbc.rung_drop.accumulator_copy_positive_controls_off_every_lane { accum import gunbc.rung_drop.seed_whole_corpus_demand_population_blind { seed_whole_corpus_demand_population_blind } import gunbc.rung_drop.roadmap_live_projection_new_witness_eval_step_cost { roadmap_live_projection_new_witness_eval_step_cost } import gunbc.rung_drop.seam_monolith_control_unmeasured_derived_root { seam_monolith_control_unmeasured_derived_root } +import gunbc.rung_drop.type_reference_location_fallback_keys_production_realization { type_reference_location_fallback_keys_production_realization } data rung_drop_roster: List = [ floor_cut_heal, @@ -131,6 +132,7 @@ data rung_drop_roster: List = [ seed_whole_corpus_demand_population_blind, roadmap_live_projection_new_witness_eval_step_cost, seam_monolith_control_unmeasured_derived_root, + type_reference_location_fallback_keys_production_realization, ] // THE DERIVATION THE PROJECTION USES, so "standing today" has one authority and not two. The diff --git a/dag/gunbc/rung_drop/type_reference_location_fallback_keys_production_realization.dag b/dag/gunbc/rung_drop/type_reference_location_fallback_keys_production_realization.dag new file mode 100644 index 00000000000..820561ace66 --- /dev/null +++ b/dag/gunbc/rung_drop/type_reference_location_fallback_keys_production_realization.dag @@ -0,0 +1,59 @@ +module gunbc.rung_drop.type_reference_location_fallback_keys_production_realization + +import std.types { NonEmptyStr } +import gunbc.rung_drop { RungDrop, Standing, TypedDeclaration, ReplacementStaged } +import gunbc.guarantee_rung { MechanicallyPreventable, OutsideTheLadder } + +// THE DECLARED DROP FOR THE REFERENCE-SITE IDENTITY KEY THAT REALIZATION IS DECIDED ON (DESIGN 4b(3)). +// +// WHAT IS DROPPED. v1.compiler.emit_rust checkpoint_table_bypasses_identity_note reports +// declaration-keyed realization as MECHANICALLY PREVENTABLE. That rung assumes the key names the +// DECLARING module. It does not: v1.std.core type_reference_provenance falls back to the node's own +// span whenever no Resolved node is bound, so a reference whose declaration was not recovered is +// keyed on the file the REFERENCE sits in. v1.compiler.coercion type_realization_decision then +// answers that location with total confidence. That is silent wrongness, which is outside the +// ladder, and the reported rung was never held on this path. +// +// WHY IT IS DECLARED RATHER THAN REPAIRED IN THE SAME CHANGE. std.coercion TypeReferenceIdentity and +// v1.std.core type_reference_identity split the collapsed arm and refuse where the key is a +// location. Pointing production at them is not a key edit. Mapping the refusal onto the existing +// DeclarationIdentityAbsent does not refuse: type_realization_decision answers that arm from the +// bare-name table. It also flips provenance_realizes_natively, which changes literal elaboration in +// v1.compiler.infer and String gating in v1.compiler.emit_rust. That is the renderer reroute, and +// docs/plans/carrier-realization-arbiter-repair-design.md forbids taking it before it is measured. +// So the query lands as an instrument and production keeps its answer. This row stops that answer +// being silent. +// +// THE POPULATION IS NAMED BY ITS INSTRUMENT, NOT TRANSCRIBED. It is every occurrence +// v1.tests.claim.carrier_realization_census reports with legacy key FALLBACK or resolved and a +// split identity of ReferenceIdentityUnavailable, re-derived by running that census's binary over +// a closure. Two causes carry it: record and coproduct FIELD type expressions, which +// v1.compiler.infer_resolve resolve_item_types resolves and then discards, and the Resolved node of an +// APPLIED reference, which resolve builds on the reference's own span. +// +// THE TRIGGER NAMES A CAPABILITY. Every production reader of a type reference's declaration asks +// type_reference_identity (or an identity authority keyed on the declaration node or env binding) +// and gives the refusal arm a typed, located behaviour rather than a location key. Deleting the +// fallback arm of type_reference_provenance is the observable retirement. Routing only the six +// text-carrier renderers does not discharge this row: v1.compiler.infer and the numeric realization +// gate read the same key. +data type_reference_location_fallback_keys_production_realization: RungDrop = RungDrop { + identity: "type_reference_location_fallback_keys_production_realization" as NonEmptyStr, + + subject: "the declaration identity key production realization decides on at a type reference (v1.std.core type_reference_provenance feeding v1.compiler.coercion type_realization_decision): a location stands in where no declaration was recovered", + + declared: "2026-09-17", + + standing: Standing, + + declaration: TypedDeclaration { + previous: MechanicallyPreventable, + temporary: OutsideTheLadder, + reason: ReplacementStaged { replacement: "v1.std.core type_reference_identity over std.coercion TypeReferenceIdentity, which refuses where the key would be a location; consumed today by v1.tests.claim.carrier_realization_census and v1.compiler.compiler_tests_rust ct_type_reference_identity_split_test only" }, + population: [ + "type-reference occurrences for which v1.tests.claim.carrier_realization_census reports split identity ReferenceIdentityUnavailable while production still answers a CorpusDeclared key: record and coproduct field type expressions (v1.compiler.infer_resolve resolve_item_types discards the resolved type)", + "applied type references whose Resolved node v1.compiler.infer_resolve builds on the reference's own span (split cause ResolvedNodeIsNotADeclaration)", + ], + restoration_trigger: "every production reader of a type reference's declaration identity -- v1.compiler.coercion type_reference_realization, the v1.compiler.infer native-numeric sites and the v1.compiler.emit_rust readers of type_reference_provenance -- asks type_reference_identity or a declaration-node or env-binding keyed authority and gives its refusal arm typed behaviour, so the own-span fallback arm of type_reference_provenance is deleted", + } +} diff --git a/dag/gunbc/type_reference_decl_file_occurrence_census.dag b/dag/gunbc/type_reference_decl_file_occurrence_census.dag index 2ae896fccd7..ce49a3bea3d 100644 --- a/dag/gunbc/type_reference_decl_file_occurrence_census.dag +++ b/dag/gunbc/type_reference_decl_file_occurrence_census.dag @@ -524,3 +524,54 @@ data type_reference_decl_file_census_discharge: List = [ decl_ref(module_path: "v1.compiler.coercion", decl_name: "type_reference_realization"), decl_ref(module_path: "v1.compiler.coercion", decl_name: "declaration_realization"), ] + +// THE FALLBACK ARM, SPLIT (CARRIER-KEY-1, 2026-09-17). The roster above counts CALL occurrences of the +// reference-site read. This record states how that read's own collapsed arm is now told apart, so +// the fallback population it produces can be scored per occurrence rather than read as one mass. +// It carries no count on purpose. The populations are occurrence facts about a closure, and the +// producer that re-derives them is named here instead of transcribed (DESIGN section 6). +// +// THE SPLIT IS BY STRUCTURE, AND THE RESOLVED ARM IS SPLIT TOO. Running the instrument on the typed +// tree showed the brief's premise was half right. A missing Resolved node does NOT mean inference +// was unavailable, because v1.compiler.infer_resolve resolves a leaf reference by SUBSTITUTING the +// declaration, span included. And a present Resolved node does NOT mean a declaration, because +// resolve builds an applied reference's Resolved node on the reference's own span. So neither +// "Resolved or not" nor "span present or not" separates the states. The parser's +// ModuleItemTypeDeclaration mark and the kernel mint do. +// +// WHAT THE SPLIT IS NOT, measured on its first run and stated so a refusal is not read as a wrong key. +// (1) IT OVER-REFUSES. A leaf reference substituted through an alias keeps the declaration's span +// but carries the alias target's item kind, so a reference whose old key DID name its declaration +// can land in Unavailable:NoResolutionBoundAtReference. Refusal means "not provable from the node", +// never "production was wrong here". Score a refusal right-or-wrong only by joining the census's +// environment column. (2) IT DOES NOT REFUSE THE KERNEL-MINTED ALIAS BOUNDARY. A substituted alias +// right-hand side minted on the kernel pseudo-file (e.g. std.algebra.FreeMonoid) answers +// ResolvedToDeclaration:KernelMinted while the environment names the corpus declaration. That +// inherits v1.std.core declaration_provenance_of's kernel recognizer and is state_space_conflation's +// third form, not a new defect. It shows up in the census as a ResolvedToDeclaration row whose +// identity column is DISAGREE. +// +// PRODUCTION IS NOT CHANGED BY THIS, and the record says so rather than implying a wall: +// gunbc.rung_drop type_reference_location_fallback_keys_production_realization declares the key +// production still decides on. +type FallbackArmSplit { + collapsed_read: DeclarationRef + split_query: DeclarationRef + outcome_type: DeclarationRef + refusal_causes: DeclarationRef + population_instrument: DeclarationRef + population_column: String + executing_witness: DeclarationRef + declared_drop: String +} + +data type_reference_decl_file_fallback_split: FallbackArmSplit = FallbackArmSplit { + collapsed_read: decl_ref(module_path: "v1.std.core", decl_name: "type_reference_provenance"), + split_query: decl_ref(module_path: "v1.std.core", decl_name: "type_reference_identity"), + outcome_type: decl_ref(module_path: "std.coercion", decl_name: "TypeReferenceIdentity"), + refusal_causes: decl_ref(module_path: "std.coercion", decl_name: "ReferenceIdentityUnavailableCause"), + population_instrument: decl_ref(module_path: "v1.tests.claim.carrier_realization_census", decl_name: "typed_census_from_sources"), + population_column: "split_identity, beside legacy_key: a FALLBACK or resolved legacy arm joined to IsTheDeclaration (legitimately keyed), ResolvedToDeclaration, or Unavailable: (production keyed a location), per occurrence", + executing_witness: decl_ref(module_path: "v1.compiler.compiler_tests_rust", decl_name: "ct_type_reference_identity_split_test"), + declared_drop: "gunbc.rung_drop type_reference_location_fallback_keys_production_realization", +} diff --git a/dag/std/coercion.dag b/dag/std/coercion.dag index 9395f50b105..d03f752097b 100644 --- a/dag/std/coercion.dag +++ b/dag/std/coercion.dag @@ -111,6 +111,42 @@ type TypeDeclarationProvenance | KernelMinted { minted_name: String } | DeclarationIdentityAbsent +// WHICH STATE A TYPE REFERENCE IS IN WHEN ITS DECLARATION IS ASKED FOR -- the split of the one arm +// the reference-site read used to answer for two states. v1.std.core type_reference_provenance +// falls back to the reference node's OWN span whenever no Resolved node is bound, so `this node IS +// a declaration` and `this reference's declaration was not recovered here` both answer +// CorpusDeclared on the file the node sits in. The second case is a location standing where an +// identity was owed: DESIGN section 5's absorbing fallback, and gunbc.recurring_failure_mode +// state_space_conflation's form for this carrier. +// +// Each arm is decided from STRUCTURE, never from span presence. A declaration node is one the parser +// marked ModuleItemTypeDeclaration, or the kernel mint for its own name. A Resolved node that is +// neither is NOT a declaration: the resolver builds the Resolved node of an applied reference on +// the REFERENCE's span, so reading its span is the same location key one hop later. Those arms +// refuse, and the cause says which of the two shapes refused. +// +// A TYPE-VARIABLE BINDER IS ITS OWN ARM. A generic parameter such as T is bound by the enclosing +// declaration and has no declaring module to key on. Filing it as a declaration would let the kernel +// span its binding is minted on pass for a KernelMinted type. Filing it as a refusal would count a +// correctly bound name as a missing identity. It is recognized only when the node itself carries +// the binding (inferred TypeVariable). An unresolved T sitting in a field still has no binding on the +// node, and refuses like any other unresolved reference. +// +// THIS IS NOT YET A PRODUCTION WALL. The production read is unchanged and still mis-keys, which is +// declared as gunbc.rung_drop type_reference_location_fallback_keys_production_realization. The +// consumer today is the census and the witness; the restoration trigger is the renderer reroute +// onto this query. +type TypeReferenceIdentity + = ReferenceResolvedToDeclaration { provenance: TypeDeclarationProvenance } + | ReferenceIsTheDeclaration { provenance: TypeDeclarationProvenance } + | ReferenceIsTypeVariableBinder { binder_name: String } + | ReferenceIdentityUnavailable { cause: ReferenceIdentityUnavailableCause } + +type ReferenceIdentityUnavailableCause + = NoResolutionBoundAtReference + | ResolvedNodeIsNotADeclaration + | DeclarationNodeCarriesNoSpan + type RealizationRefusalCause = DeclarationIdentityUnavailable | ExactSourceIdentityAbsent { detail: String } diff --git a/docs/design-rung-drops.md b/docs/design-rung-drops.md index c6c53ad09df..f9876ea2089 100644 --- a/docs/design-rung-drops.md +++ b/docs/design-rung-drops.md @@ -269,3 +269,7 @@ new-witness eval-step cost gate over four live-roadmap projection identities: th ### The cross-shard seam probe's monolith control as an executing whole-root compile of a derived root — declared 2026-09-13 The cross-shard seam probe's monolith control as an executing whole-root compile of a derived root: RUNG DROP, mechanically preventable -> mitigatable (deleted without replacement). Population: tools.dag_compile_clean_seam_transport run_cross_shard_seam_probe monolith control M (the stage-less cross_shard_seam_compile_args whole-root compile of the mutated dag copy). Restored when: THE MONOLITH CONTROL EXECUTES AGAIN ON ITS SUBJECT: either M is re-expressed at --entry grain against the seam it actually tests, so that an unsharded compile of the importer's closure over the mutated tree reds naming the seam class; or a measured-demand row for derived roots is recorded per base, keyed on the derived root's identity at that base and measured once with its receipt, so that whole-root admission admits M on a row. Never by a per-source or per-byte coefficient, and never by a bigger budget or a placement that skips admission. + +### the declaration identity key production realization decides on at a type reference (v1.std.core type_reference_provenance feeding v1.compiler.coercion type_realization_decision): a location stands in where no declaration was recovered — declared 2026-09-17 + +the declaration identity key production realization decides on at a type reference (v1.std.core type_reference_provenance feeding v1.compiler.coercion type_realization_decision): a location stands in where no declaration was recovered: RUNG DROP, mechanically preventable -> outside the ladder (replacement staged: v1.std.core type_reference_identity over std.coercion TypeReferenceIdentity, which refuses where the key would be a location; consumed today by v1.tests.claim.carrier_realization_census and v1.compiler.compiler_tests_rust ct_type_reference_identity_split_test only). Population: type-reference occurrences for which v1.tests.claim.carrier_realization_census reports split identity ReferenceIdentityUnavailable while production still answers a CorpusDeclared key: record and coproduct field type expressions (v1.compiler.infer_resolve resolve_item_types discards the resolved type), applied type references whose Resolved node v1.compiler.infer_resolve builds on the reference's own span (split cause ResolvedNodeIsNotADeclaration). Restored when: every production reader of a type reference's declaration identity -- v1.compiler.coercion type_reference_realization, the v1.compiler.infer native-numeric sites and the v1.compiler.emit_rust readers of type_reference_provenance -- asks type_reference_identity or a declaration-node or env-binding keyed authority and gives its refusal arm typed behaviour, so the own-span fallback arm of type_reference_provenance is deleted. diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index 3844ebd1839..22a30c731cd 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -8,7 +8,11 @@ import std.syntax { AlgebraFieldKind, AlgAdd, AlgMul, AlgReciprocal, AlgQuotient, AlgRemainder, AlgCompare, AlgMeet, AlgJoin } import std.algebra { CollectionSizeEffect, CostShape, AlgebraFieldTemplate } -import std.coercion { TypeDeclarationProvenance, CorpusDeclared, KernelMinted, DeclarationIdentityAbsent } +import std.coercion { + TypeDeclarationProvenance, CorpusDeclared, KernelMinted, DeclarationIdentityAbsent, + TypeReferenceIdentity, ReferenceResolvedToDeclaration, ReferenceIsTheDeclaration, ReferenceIsTypeVariableBinder, ReferenceIdentityUnavailable, + ReferenceIdentityUnavailableCause, NoResolutionBoundAtReference, ResolvedNodeIsNotADeclaration, DeclarationNodeCarriesNoSpan +} import std.dissolution { DissolutionCondition, unbound_dissolution } import std.literal_elaboration { LiteralElaboration } import std.operator_realization { OperandDeclaration } @@ -2527,6 +2531,10 @@ fn declaration_provenance_of(item: Node) -> TypeDeclarationProvenance { // its own declaration` from `this reference was not resolved here`, and in the second case answers // about the file the REFERENCE sits in. What changes is that its answer is no longer a String a // consumer may re-interpret: it is a provenance, and the only thing downstream of it is a decision. +// The two states are now told apart by type_reference_identity below. This read keeps its collapsed +// answer on purpose until the renderer reroute is measured, and that mis-keying is declared as +// gunbc.rung_drop type_reference_location_fallback_keys_production_realization rather than left +// silent. fn type_reference_provenance(n: Node) -> TypeDeclarationProvenance { match n.inferred { Present { value: Resolved { node: rt } } => declaration_provenance_of(item: rt) @@ -2534,6 +2542,75 @@ fn type_reference_provenance(n: Node) -> TypeDeclarationProvenance { } } +// THE SPLIT OF THE ARM ABOVE, as a separate query so the production answer stays byte-identical +// until the reroute is measured (std.coercion TypeReferenceIdentity says why each arm is decided +// from structure). Kernel identity is read through declaration_provenance_of, which already owns +// it by exact equality, so this adds no second kernel recognizer. +// EVERY ParsedModuleItemKind ARM IS SPELLED, no wildcard. A kind that should key as a declaration +// must be a compile-time decision here, never a silent `not a declaration` that reads as a refusal +// (the positive-authority rule stated on ParsedModuleItemKind itself; review 67150). +fn declaration_node_provenance(n: Node) -> TypeDeclarationProvenance? { + match declaration_provenance_of(item: n) { + KernelMinted { minted_name: nm } => Present { value: KernelMinted { minted_name: nm } } + CorpusDeclared { decl_file: f } => + match n.module_item_kind { + ModuleItemTypeDeclaration => Present { value: CorpusDeclared { decl_file: f } } + ModuleItemFunction => none + ModuleItemDataValue => none + ModuleItemService => none + ModuleItemResource => none + ModuleItemUnrecognized => none + NotAModuleItem => none + } + DeclarationIdentityAbsent => none + } +} + +fn declaration_node_unavailable_cause(n: Node, when_not_a_declaration: ReferenceIdentityUnavailableCause) -> ReferenceIdentityUnavailableCause { + match n.module_item_kind { + ModuleItemTypeDeclaration => DeclarationNodeCarriesNoSpan + ModuleItemFunction => when_not_a_declaration + ModuleItemDataValue => when_not_a_declaration + ModuleItemService => when_not_a_declaration + ModuleItemResource => when_not_a_declaration + ModuleItemUnrecognized => when_not_a_declaration + NotAModuleItem => when_not_a_declaration + } +} + +fn type_variable_binder_name(n: Node) -> String? { + match n.inferred { + Present { value: TypeVariable { id: tv } } => Present { value: tv } + _ => none + } +} + +fn type_reference_identity(n: Node) -> TypeReferenceIdentity { + match type_variable_binder_name(n: n) { + Present { value: tv } => ReferenceIsTypeVariableBinder { binder_name: tv } + Absent => type_reference_declaration_identity(n: n) + } +} + +fn type_reference_declaration_identity(n: Node) -> TypeReferenceIdentity { + match n.inferred { + Present { value: Resolved { node: rt } } => + match type_variable_binder_name(n: rt) { + Present { value: tv } => ReferenceIsTypeVariableBinder { binder_name: tv } + Absent => + match declaration_node_provenance(n: rt) { + Present { value: p } => ReferenceResolvedToDeclaration { provenance: p } + Absent => ReferenceIdentityUnavailable { cause: declaration_node_unavailable_cause(n: rt, when_not_a_declaration: ResolvedNodeIsNotADeclaration) } + } + } + _ => + match declaration_node_provenance(n: n) { + Present { value: p } => ReferenceIsTheDeclaration { provenance: p } + Absent => ReferenceIdentityUnavailable { cause: declaration_node_unavailable_cause(n: n, when_not_a_declaration: NoResolutionBoundAtReference) } + } + } +} + // FOR REPORTING ONLY -- an instrument or a diagnostic rendering a provenance as the path it stands // on. Nothing that DECIDES may consult this: reading a decision's input back out of a string is // precisely the round trip this construction removed, and the empty answer for an absent identity is diff --git a/src/v1/compiler_tests_rust.dag b/src/v1/compiler_tests_rust.dag index 8ce76b4ca66..1844b71db2b 100644 --- a/src/v1/compiler_tests_rust.dag +++ b/src/v1/compiler_tests_rust.dag @@ -1618,6 +1618,113 @@ fn ct_kernel_identity_single_authority_test() -> String { "\n") } +// CARRIER-KEY-1 split witness; the Rust it renders says what each row discriminates. +fn ct_type_reference_identity_split_test() -> String { + concat( + " // CARRIER-KEY-1: THE SPLIT OF THE REFERENCE-SITE FALLBACK ARM. v1.std.core\n", + " // type_reference_provenance answers the file the node SITS in whenever no Resolved node is bound,\n", + " // so a reference whose declaration was not recovered is keyed on a location.\n", + " // type_reference_identity splits that arm by STRUCTURE. These rows supply nodes shaped like the\n", + " // ones v1.tests.claim.carrier_realization_census observes on the typed tree, which is where the\n", + " // real producer's shapes are read.\n", + " //\n", + " // THE RED is the first row. An implementation that restores the own-span fallback answers\n", + " // ReferenceIsTheDeclaration on the reference's file and fails it. The legacy read is asserted on\n", + " // the same node so the row shows the fabricated key it refuses, not just that some refusal happens.\n", + " // The rows assert nothing about production, which still reads type_reference_provenance\n", + " // (gunbc.rung_drop type_reference_location_fallback_keys_production_realization).\n", + " #[test]\n", + " fn type_reference_identity_split_refuses_where_the_key_is_a_location() {\n", + " use crate::std_coercion::\{ReferenceIdentityUnavailableCause as Cause, TypeDeclarationProvenance as Prov, TypeReferenceIdentity as Id};\n", + " fn node(\n", + " name: &str,\n", + " file: &str,\n", + " kind: crate::v1_std_core::ParsedModuleItemKind,\n", + " resolved: Option>,\n", + " ) -> std::rc::Rc {\n", + " let base = shaped_type_node(name, Vec::new());\n", + " std::rc::Rc::new(crate::v1_std_core::Node {\n", + " ident_span: Some(std::rc::Rc::new(crate::v1_std_core::SourceSpan { file: file.to_string(), start: 0, end: 0 })),\n", + " module_item_kind: kind,\n", + " inferred: resolved.map(|rt| std::rc::Rc::new(crate::v1_std_core::InferredNode::Resolved { node: rt })),\n", + " ..(*base).clone()\n", + " })\n", + " }\n", + " use crate::v1_std_core::ParsedModuleItemKind::\{ModuleItemTypeDeclaration as Decl, NotAModuleItem as Ref};\n", + " let identity = |n: std::rc::Rc| (*crate::v1_std_core::type_reference_identity(n)).clone();\n", + " let legacy = |n: std::rc::Rc| (*crate::v1_std_core::type_reference_provenance(n)).clone();\n", + "\n", + " // (1) THE RED: a field-position reference with no resolution, sitting outside its declaring module.\n", + " let unresolved = node(\"String\", \"src/v2/compiler/01_tokenize.dag\", Ref, None);\n", + " assert!(\n", + " matches!(identity(unresolved.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::NoResolutionBoundAtReference)),\n", + " \"an unresolved reference must refuse, never key on the file it sits in\"\n", + " );\n", + " assert!(\n", + " matches!(legacy(unresolved), Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/compiler/01_tokenize.dag\"),\n", + " \"the legacy read keys that same reference on its own location -- the answer the split refuses\"\n", + " );\n", + "\n", + " // (2) A node that IS its declaration keeps its own span as a legitimate key.\n", + " let declaration = node(\"String\", \"src/v2/std/text.dag\", Decl, None);\n", + " assert!(\n", + " matches!(identity(declaration), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/std/text.dag\")),\n", + " \"a type declaration node answers its own declaring file\"\n", + " );\n", + "\n", + " // (3) The kernel mint for its own name is a declaration with no corpus file.\n", + " let kernel = node(\"String\", &crate::v1_std_core::kernel_span(\"String\".to_string()).file, Ref, None);\n", + " assert!(\n", + " matches!(identity(kernel), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::KernelMinted { minted_name } if minted_name == \"String\")),\n", + " \"a kernel-minted node answers KernelMinted through the existing kernel recognizer\"\n", + " );\n", + "\n", + " // (4) A reference resolved to a declaration node answers that declaration, not its own file.\n", + " let resolved = node(\"List\", \"src/v2/std/node.dag\", Ref, Some(node(\"List\", \"dag/std/types.dag\", Decl, None)));\n", + " assert!(\n", + " matches!(identity(resolved), Id::ReferenceResolvedToDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == \"dag/std/types.dag\")),\n", + " \"a Resolved declaration node answers its declaring file\"\n", + " );\n", + "\n", + " // (5) A Resolved node that is NOT a declaration (the applied-reference expansion built on the\n", + " // reference's own span) refuses; the legacy read keys it on the reference's file.\n", + " let expansion = node(\"FreeMonoid\", \"src/v2/std/algebra.dag\", Ref, None);\n", + " let applied = node(\"FreeMonoid\", \"src/v2/std/algebra.dag\", Ref, Some(expansion));\n", + " assert!(\n", + " matches!(identity(applied.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::ResolvedNodeIsNotADeclaration)),\n", + " \"a Resolved node that is not a declaration must refuse, not be read as one\"\n", + " );\n", + " assert!(\n", + " matches!(legacy(applied), Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/std/algebra.dag\"),\n", + " \"the legacy read keys the non-declaration Resolved node on the reference's location\"\n", + " );\n", + "\n", + " // (7) A type-variable binder is neither a declaration nor a refusal. Its binding is minted on a\n", + " // kernel span, so without this arm it would be misfiled as KernelMinted.\n", + " let binder = std::rc::Rc::new(crate::v1_std_core::Node {\n", + " inferred: Some(std::rc::Rc::new(crate::v1_std_core::InferredNode::TypeVariable { id: \"T\".to_string() })),\n", + " ..(*node(\"T\", &crate::v1_std_core::kernel_span(\"T\".to_string()).file, Ref, None)).clone()\n", + " });\n", + " assert!(\n", + " matches!(identity(binder.clone()), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == \"T\"),\n", + " \"a type-variable binding answers the binder arm, not KernelMinted\"\n", + " );\n", + " let bound_reference = node(\"T\", \"src/v2/std/optional.dag\", Ref, Some(binder));\n", + " assert!(\n", + " matches!(identity(bound_reference), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == \"T\"),\n", + " \"a reference resolved to a type-variable binding answers the binder arm\"\n", + " );\n", + "\n", + " // (6) A declaration node with no span is a refusal with its own cause, not a reference miss.\n", + " let spanless = node(\"Widget\", \"\", Decl, None);\n", + " assert!(\n", + " matches!(identity(spanless), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::DeclarationNodeCarriesNoSpan)),\n", + " \"a declaration without a span refuses with DeclarationNodeCarriesNoSpan\"\n", + " );\n", + " }\n", + "\n") +} + fn ct_coercion_tests() -> String { let entries = extract_coercion_tests() let test_fns = entries |> map(e => render_coercion_test_rust(entry: e)) @@ -1633,6 +1740,7 @@ fn ct_coercion_tests() -> String { ct_generic_param_declines_fail_closed_unwrap_test(), ct_witness_carrier_declines_non_witness_expected_type_test(), ct_kernel_identity_single_authority_test(), + ct_type_reference_identity_split_test(), ct_resource_item_is_not_a_type_item_test()) } diff --git a/src/v1/stage0/src/compiler_tests.rs b/src/v1/stage0/src/compiler_tests.rs index 9c93b7c330c..5911e4aa384 100644 --- a/src/v1/stage0/src/compiler_tests.rs +++ b/src/v1/stage0/src/compiler_tests.rs @@ -4461,6 +4461,143 @@ mod compiler_tests { ); } + // CARRIER-KEY-1: THE SPLIT OF THE REFERENCE-SITE FALLBACK ARM. v1.std.core + // type_reference_provenance answers the file the node SITS in whenever no Resolved node is bound, + // so a reference whose declaration was not recovered is keyed on a location. + // type_reference_identity splits that arm by STRUCTURE. These rows supply nodes shaped like the + // ones v1.tests.claim.carrier_realization_census observes on the typed tree, which is where the + // real producer's shapes are read. + // + // THE RED is the first row. An implementation that restores the own-span fallback answers + // ReferenceIsTheDeclaration on the reference's file and fails it. The legacy read is asserted on + // the same node so the row shows the fabricated key it refuses, not just that some refusal happens. + // The rows assert nothing about production, which still reads type_reference_provenance + // (gunbc.rung_drop type_reference_location_fallback_keys_production_realization). + #[test] + fn type_reference_identity_split_refuses_where_the_key_is_a_location() { + use crate::std_coercion::{ + ReferenceIdentityUnavailableCause as Cause, TypeDeclarationProvenance as Prov, + TypeReferenceIdentity as Id, + }; + fn node( + name: &str, + file: &str, + kind: crate::v1_std_core::ParsedModuleItemKind, + resolved: Option>, + ) -> std::rc::Rc { + let base = shaped_type_node(name, Vec::new()); + std::rc::Rc::new(crate::v1_std_core::Node { + ident_span: Some(std::rc::Rc::new(crate::v1_std_core::SourceSpan { + file: file.to_string(), + start: 0, + end: 0, + })), + module_item_kind: kind, + inferred: resolved.map(|rt| { + std::rc::Rc::new(crate::v1_std_core::InferredNode::Resolved { node: rt }) + }), + ..(*base).clone() + }) + } + use crate::v1_std_core::ParsedModuleItemKind::{ + ModuleItemTypeDeclaration as Decl, NotAModuleItem as Ref, + }; + let identity = |n: std::rc::Rc| { + (*crate::v1_std_core::type_reference_identity(n)).clone() + }; + let legacy = |n: std::rc::Rc| { + (*crate::v1_std_core::type_reference_provenance(n)).clone() + }; + + // (1) THE RED: a field-position reference with no resolution, sitting outside its declaring module. + let unresolved = node("String", "src/v2/compiler/01_tokenize.dag", Ref, None); + assert!( + matches!(identity(unresolved.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::NoResolutionBoundAtReference)), + "an unresolved reference must refuse, never key on the file it sits in" + ); + assert!( + matches!(legacy(unresolved), Prov::CorpusDeclared { decl_file } if decl_file == "src/v2/compiler/01_tokenize.dag"), + "the legacy read keys that same reference on its own location -- the answer the split refuses" + ); + + // (2) A node that IS its declaration keeps its own span as a legitimate key. + let declaration = node("String", "src/v2/std/text.dag", Decl, None); + assert!( + matches!(identity(declaration), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == "src/v2/std/text.dag")), + "a type declaration node answers its own declaring file" + ); + + // (3) The kernel mint for its own name is a declaration with no corpus file. + let kernel = node( + "String", + &crate::v1_std_core::kernel_span("String".to_string()).file, + Ref, + None, + ); + assert!( + matches!(identity(kernel), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::KernelMinted { minted_name } if minted_name == "String")), + "a kernel-minted node answers KernelMinted through the existing kernel recognizer" + ); + + // (4) A reference resolved to a declaration node answers that declaration, not its own file. + let resolved = node( + "List", + "src/v2/std/node.dag", + Ref, + Some(node("List", "dag/std/types.dag", Decl, None)), + ); + assert!( + matches!(identity(resolved), Id::ReferenceResolvedToDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == "dag/std/types.dag")), + "a Resolved declaration node answers its declaring file" + ); + + // (5) A Resolved node that is NOT a declaration (the applied-reference expansion built on the + // reference's own span) refuses; the legacy read keys it on the reference's file. + let expansion = node("FreeMonoid", "src/v2/std/algebra.dag", Ref, None); + let applied = node("FreeMonoid", "src/v2/std/algebra.dag", Ref, Some(expansion)); + assert!( + matches!(identity(applied.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::ResolvedNodeIsNotADeclaration)), + "a Resolved node that is not a declaration must refuse, not be read as one" + ); + assert!( + matches!(legacy(applied), Prov::CorpusDeclared { decl_file } if decl_file == "src/v2/std/algebra.dag"), + "the legacy read keys the non-declaration Resolved node on the reference's location" + ); + + // (7) A type-variable binder is neither a declaration nor a refusal. Its binding is minted on a + // kernel span, so without this arm it would be misfiled as KernelMinted. + let binder = std::rc::Rc::new(crate::v1_std_core::Node { + inferred: Some(std::rc::Rc::new( + crate::v1_std_core::InferredNode::TypeVariable { + id: "T".to_string(), + }, + )), + ..(*node( + "T", + &crate::v1_std_core::kernel_span("T".to_string()).file, + Ref, + None, + )) + .clone() + }); + assert!( + matches!(identity(binder.clone()), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == "T"), + "a type-variable binding answers the binder arm, not KernelMinted" + ); + let bound_reference = node("T", "src/v2/std/optional.dag", Ref, Some(binder)); + assert!( + matches!(identity(bound_reference), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == "T"), + "a reference resolved to a type-variable binding answers the binder arm" + ); + + // (6) A declaration node with no span is a refusal with its own cause, not a reference miss. + let spanless = node("Widget", "", Decl, None); + assert!( + matches!(identity(spanless), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::DeclarationNodeCarriesNoSpan)), + "a declaration without a span refuses with DeclarationNodeCarriesNoSpan" + ); + } + fn item_of_kind( name: &str, kind: crate::v1_std_core::ParsedModuleItemKind, diff --git a/src/v1/stage0/src/std_coercion.rs b/src/v1/stage0/src/std_coercion.rs index 0dd50f6e844..fe1e5ac2988 100644 --- a/src/v1/stage0/src/std_coercion.rs +++ b/src/v1/stage0/src/std_coercion.rs @@ -3,8 +3,10 @@ use self::RealizationGround::*; use self::RealizationRefusalCause::*; +use self::ReferenceIdentityUnavailableCause::*; use self::TypeDeclarationProvenance::*; use self::TypeRealizationDecision::*; +use self::TypeReferenceIdentity::*; use crate::v1_rt; use crate::v1_rt::{VecCompat, VecJoin}; use crate::NonEmptyBTreeSet; @@ -30,6 +32,33 @@ pub enum TypeDeclarationProvenance { DeclarationIdentityAbsent, } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +#[serde(tag = "_variant")] +pub enum TypeReferenceIdentity { + ReferenceResolvedToDeclaration { + provenance: Rc, + }, + ReferenceIsTheDeclaration { + provenance: Rc, + }, + ReferenceIsTypeVariableBinder { + binder_name: String, + }, + ReferenceIdentityUnavailable { + cause: ReferenceIdentityUnavailableCause, + }, +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(tag = "_variant")] +pub enum ReferenceIdentityUnavailableCause { + NoResolutionBoundAtReference, + ResolvedNodeIsNotADeclaration, + DeclarationNodeCarriesNoSpan, +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(tag = "_variant")] pub enum RealizationRefusalCause { @@ -162,6 +191,12 @@ pub fn is_dag_cast_domain_type(name: String) -> bool { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct NoResolutionBoundAtReference; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct ResolvedNodeIsNotADeclaration; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct DeclarationNodeCarriesNoSpan; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct GroundedByCheckpointRow; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs index d1758c8562d..8b4ef21469f 100644 --- a/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs +++ b/src/v1/stage0/src/v1_compiler_compiler_tests_rust.rs @@ -136,6 +136,10 @@ pub fn ct_kernel_identity_single_authority_test() -> String { v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(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(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(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(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(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(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(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(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(v1_rt::concat(v1_rt::concat(" // THE KERNEL-IDENTITY FACT HAS ONE AUTHORITY, AND THIS IS THE ROW THAT SEPARATES IT FROM THE\n".to_string(), " // PREFIX TEST IT REPLACED. `resolved_node_is_kernel_identity_for_name` moved from\n".to_string()), " // v1.compiler.infer_env -- where it was declared, never called, and sat ABOVE the module whose\n".to_string()), " // kernel_span mints the span it tests -- down to v1.std.core beside that minter, and\n".to_string()), " // declaration_provenance_of's KernelMinted arm now READS it instead of re-deriving the same\n".to_string()), " // comparison. The old infer_env body inlined `concat(\"\")`, reproducing\n".to_string()), " // kernel_span's output by hand, which made it a second authority for the span FORMAT too.\n".to_string()), " //\n".to_string()), " // WHAT EACH ROW HOLDS. The mismatch row is the discriminating one: a node whose ident_span is a\n".to_string()), " // kernel span for a DIFFERENT name is not the kernel identity for the queried name, so a\n".to_string()), " // substring or prefix test over \" std::rc::Rc {\n".to_string()), " let base = shaped_type_node(name, Vec::new());\n".to_string()), " std::rc::Rc::new(crate::v1_std_core::Node {\n".to_string()), " ident_span: Some(std::rc::Rc::new(crate::v1_std_core::SourceSpan {\n".to_string()), " file: ident_file,\n".to_string()), " start: 0,\n".to_string()), " end: 0,\n".to_string()), " })),\n".to_string()), " ..(*base).clone()\n".to_string()), " })\n".to_string()), " }\n".to_string()), "\n".to_string()), " // POSITIVE CONTROL -- the minter's own output, recognized for its own name.\n".to_string()), " let int_kernel = node_with_ident_file(\n".to_string()), " \"Int\",\n".to_string()), " crate::v1_std_core::kernel_span(\"Int\".to_string()).file.clone(),\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " crate::v1_std_core::resolved_node_is_kernel_identity_for_name(\n".to_string()), " int_kernel.clone(),\n".to_string()), " \"Int\".to_string()\n".to_string()), " ),\n".to_string()), " \"the recognizer must accept the span its own minter built for that name\"\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " matches!(\n".to_string()), " &*crate::v1_std_core::declaration_provenance_of(int_kernel.clone()),\n".to_string()), " crate::std_coercion::TypeDeclarationProvenance::KernelMinted { minted_name }\n".to_string()), " if minted_name == \"Int\"\n".to_string()), " ),\n".to_string()), " \"the provenance mint must reach KernelMinted through that same recognizer\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // THE DISCRIMINATOR -- exact equality against the minter, never a \"\"\n".to_string()), " ),\n".to_string()), " \"the mint must not upgrade a non-matching kernel-shaped span to KernelMinted\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // AGREEMENT -- one verdict, read twice. These cannot diverge while one authority answers.\n".to_string()), " for (name, file) in [\n".to_string()), " (\"Int\", crate::v1_std_core::kernel_span(\"Int\".to_string()).file.clone()),\n".to_string()), " (\"Nat\", crate::v1_std_core::kernel_span(\"Int\".to_string()).file.clone()),\n".to_string()), " (\"Widget\", \"dag/std/nat.dag\".to_string()),\n".to_string()), " ] {\n".to_string()), " let n = node_with_ident_file(name, file);\n".to_string()), " let recognized = crate::v1_std_core::resolved_node_is_kernel_identity_for_name(\n".to_string()), " n.clone(),\n".to_string()), " name.to_string(),\n".to_string()), " );\n".to_string()), " let minted = matches!(\n".to_string()), " &*crate::v1_std_core::declaration_provenance_of(n.clone()),\n".to_string()), " crate::std_coercion::TypeDeclarationProvenance::KernelMinted { .. }\n".to_string()), " );\n".to_string()), " assert_eq!(\n".to_string()), " recognized, minted,\n".to_string()), " \"recognizer and provenance mint must give one verdict for `{}`\",\n".to_string()), " name\n".to_string()), " );\n".to_string()), " }\n".to_string()), "\n".to_string()), " // THE TWO SURVIVING ARMS, unchanged by the relocation.\n".to_string()), " assert!(\n".to_string()), " matches!(\n".to_string()), " &*crate::v1_std_core::declaration_provenance_of(node_with_ident_file(\n".to_string()), " \"Widget\",\n".to_string()), " \"dag/std/nat.dag\".to_string()\n".to_string()), " )),\n".to_string()), " crate::std_coercion::TypeDeclarationProvenance::CorpusDeclared { decl_file }\n".to_string()), " if decl_file == \"dag/std/nat.dag\"\n".to_string()), " ),\n".to_string()), " \"a real declaring file must still mint CorpusDeclared\"\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " matches!(\n".to_string()), " &*crate::v1_std_core::declaration_provenance_of(node_with_ident_file(\n".to_string()), " \"Widget\",\n".to_string()), " \"\".to_string()\n".to_string()), " )),\n".to_string()), " crate::std_coercion::TypeDeclarationProvenance::DeclarationIdentityAbsent\n".to_string()), " ),\n".to_string()), " \"an empty ident file must still mint DeclarationIdentityAbsent\"\n".to_string()), " );\n".to_string()), " }\n".to_string()), "\n".to_string()) } +pub fn ct_type_reference_identity_split_test() -> String { + v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(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(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(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(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(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(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(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(" // CARRIER-KEY-1: THE SPLIT OF THE REFERENCE-SITE FALLBACK ARM. v1.std.core\n".to_string(), " // type_reference_provenance answers the file the node SITS in whenever no Resolved node is bound,\n".to_string()), " // so a reference whose declaration was not recovered is keyed on a location.\n".to_string()), " // type_reference_identity splits that arm by STRUCTURE. These rows supply nodes shaped like the\n".to_string()), " // ones v1.tests.claim.carrier_realization_census observes on the typed tree, which is where the\n".to_string()), " // real producer's shapes are read.\n".to_string()), " //\n".to_string()), " // THE RED is the first row. An implementation that restores the own-span fallback answers\n".to_string()), " // ReferenceIsTheDeclaration on the reference's file and fails it. The legacy read is asserted on\n".to_string()), " // the same node so the row shows the fabricated key it refuses, not just that some refusal happens.\n".to_string()), " // The rows assert nothing about production, which still reads type_reference_provenance\n".to_string()), " // (gunbc.rung_drop type_reference_location_fallback_keys_production_realization).\n".to_string()), " #[test]\n".to_string()), " fn type_reference_identity_split_refuses_where_the_key_is_a_location() {\n".to_string()), " use crate::std_coercion::{ReferenceIdentityUnavailableCause as Cause, TypeDeclarationProvenance as Prov, TypeReferenceIdentity as Id};\n".to_string()), " fn node(\n".to_string()), " name: &str,\n".to_string()), " file: &str,\n".to_string()), " kind: crate::v1_std_core::ParsedModuleItemKind,\n".to_string()), " resolved: Option>,\n".to_string()), " ) -> std::rc::Rc {\n".to_string()), " let base = shaped_type_node(name, Vec::new());\n".to_string()), " std::rc::Rc::new(crate::v1_std_core::Node {\n".to_string()), " ident_span: Some(std::rc::Rc::new(crate::v1_std_core::SourceSpan { file: file.to_string(), start: 0, end: 0 })),\n".to_string()), " module_item_kind: kind,\n".to_string()), " inferred: resolved.map(|rt| std::rc::Rc::new(crate::v1_std_core::InferredNode::Resolved { node: rt })),\n".to_string()), " ..(*base).clone()\n".to_string()), " })\n".to_string()), " }\n".to_string()), " use crate::v1_std_core::ParsedModuleItemKind::{ModuleItemTypeDeclaration as Decl, NotAModuleItem as Ref};\n".to_string()), " let identity = |n: std::rc::Rc| (*crate::v1_std_core::type_reference_identity(n)).clone();\n".to_string()), " let legacy = |n: std::rc::Rc| (*crate::v1_std_core::type_reference_provenance(n)).clone();\n".to_string()), "\n".to_string()), " // (1) THE RED: a field-position reference with no resolution, sitting outside its declaring module.\n".to_string()), " let unresolved = node(\"String\", \"src/v2/compiler/01_tokenize.dag\", Ref, None);\n".to_string()), " assert!(\n".to_string()), " matches!(identity(unresolved.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::NoResolutionBoundAtReference)),\n".to_string()), " \"an unresolved reference must refuse, never key on the file it sits in\"\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " matches!(legacy(unresolved), Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/compiler/01_tokenize.dag\"),\n".to_string()), " \"the legacy read keys that same reference on its own location -- the answer the split refuses\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (2) A node that IS its declaration keeps its own span as a legitimate key.\n".to_string()), " let declaration = node(\"String\", \"src/v2/std/text.dag\", Decl, None);\n".to_string()), " assert!(\n".to_string()), " matches!(identity(declaration), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/std/text.dag\")),\n".to_string()), " \"a type declaration node answers its own declaring file\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (3) The kernel mint for its own name is a declaration with no corpus file.\n".to_string()), " let kernel = node(\"String\", &crate::v1_std_core::kernel_span(\"String\".to_string()).file, Ref, None);\n".to_string()), " assert!(\n".to_string()), " matches!(identity(kernel), Id::ReferenceIsTheDeclaration { provenance } if matches!(&*provenance, Prov::KernelMinted { minted_name } if minted_name == \"String\")),\n".to_string()), " \"a kernel-minted node answers KernelMinted through the existing kernel recognizer\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (4) A reference resolved to a declaration node answers that declaration, not its own file.\n".to_string()), " let resolved = node(\"List\", \"src/v2/std/node.dag\", Ref, Some(node(\"List\", \"dag/std/types.dag\", Decl, None)));\n".to_string()), " assert!(\n".to_string()), " matches!(identity(resolved), Id::ReferenceResolvedToDeclaration { provenance } if matches!(&*provenance, Prov::CorpusDeclared { decl_file } if decl_file == \"dag/std/types.dag\")),\n".to_string()), " \"a Resolved declaration node answers its declaring file\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (5) A Resolved node that is NOT a declaration (the applied-reference expansion built on the\n".to_string()), " // reference's own span) refuses; the legacy read keys it on the reference's file.\n".to_string()), " let expansion = node(\"FreeMonoid\", \"src/v2/std/algebra.dag\", Ref, None);\n".to_string()), " let applied = node(\"FreeMonoid\", \"src/v2/std/algebra.dag\", Ref, Some(expansion));\n".to_string()), " assert!(\n".to_string()), " matches!(identity(applied.clone()), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::ResolvedNodeIsNotADeclaration)),\n".to_string()), " \"a Resolved node that is not a declaration must refuse, not be read as one\"\n".to_string()), " );\n".to_string()), " assert!(\n".to_string()), " matches!(legacy(applied), Prov::CorpusDeclared { decl_file } if decl_file == \"src/v2/std/algebra.dag\"),\n".to_string()), " \"the legacy read keys the non-declaration Resolved node on the reference's location\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (7) A type-variable binder is neither a declaration nor a refusal. Its binding is minted on a\n".to_string()), " // kernel span, so without this arm it would be misfiled as KernelMinted.\n".to_string()), " let binder = std::rc::Rc::new(crate::v1_std_core::Node {\n".to_string()), " inferred: Some(std::rc::Rc::new(crate::v1_std_core::InferredNode::TypeVariable { id: \"T\".to_string() })),\n".to_string()), " ..(*node(\"T\", &crate::v1_std_core::kernel_span(\"T\".to_string()).file, Ref, None)).clone()\n".to_string()), " });\n".to_string()), " assert!(\n".to_string()), " matches!(identity(binder.clone()), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == \"T\"),\n".to_string()), " \"a type-variable binding answers the binder arm, not KernelMinted\"\n".to_string()), " );\n".to_string()), " let bound_reference = node(\"T\", \"src/v2/std/optional.dag\", Ref, Some(binder));\n".to_string()), " assert!(\n".to_string()), " matches!(identity(bound_reference), Id::ReferenceIsTypeVariableBinder { binder_name } if binder_name == \"T\"),\n".to_string()), " \"a reference resolved to a type-variable binding answers the binder arm\"\n".to_string()), " );\n".to_string()), "\n".to_string()), " // (6) A declaration node with no span is a refusal with its own cause, not a reference miss.\n".to_string()), " let spanless = node(\"Widget\", \"\", Decl, None);\n".to_string()), " assert!(\n".to_string()), " matches!(identity(spanless), Id::ReferenceIdentityUnavailable { cause } if matches!(cause, Cause::DeclarationNodeCarriesNoSpan)),\n".to_string()), " \"a declaration without a span refuses with DeclarationNodeCarriesNoSpan\"\n".to_string()), " );\n".to_string()), " }\n".to_string()), "\n".to_string()) +} + pub fn ct_coercion_tests() -> String { { let entries = crate::v1_compiler_coercion::extract_coercion_tests(); @@ -146,7 +150,7 @@ pub fn ct_coercion_tests() -> String { } __result }); - 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(" // =========================================================================\n".to_string(), " // Coercion registry tests (auto-generated from data declarations)\n".to_string()), " // =========================================================================\n\n".to_string()), test_fns.clone().join(&"\n".to_string())), ct_rust_btree_set_ord_eligibility_test()), ct_diagnostics_carrier_grounding_test()), ct_render_rust_applied_type_qualified_base_test()), ct_renderer_hop_identity_keying_test()), ct_generic_param_declines_fail_closed_unwrap_test()), ct_witness_carrier_declines_non_witness_expected_type_test()), ct_kernel_identity_single_authority_test()), ct_resource_item_is_not_a_type_item_test()) + 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(" // =========================================================================\n".to_string(), " // Coercion registry tests (auto-generated from data declarations)\n".to_string()), " // =========================================================================\n\n".to_string()), test_fns.clone().join(&"\n".to_string())), ct_rust_btree_set_ord_eligibility_test()), ct_diagnostics_carrier_grounding_test()), ct_render_rust_applied_type_qualified_base_test()), ct_renderer_hop_identity_keying_test()), ct_generic_param_declines_fail_closed_unwrap_test()), ct_witness_carrier_declines_non_witness_expected_type_test()), ct_kernel_identity_single_authority_test()), ct_type_reference_identity_split_test()), ct_resource_item_is_not_a_type_item_test()) } } diff --git a/src/v1/stage0/src/v1_std_core.rs b/src/v1/stage0/src/v1_std_core.rs index c43736b34d3..c3773ebad1a 100644 --- a/src/v1/stage0/src/v1_std_core.rs +++ b/src/v1/stage0/src/v1_std_core.rs @@ -32,10 +32,19 @@ use self::VarBindingKind::*; use crate::std_algebra::CollectionSizeEffect::*; use crate::std_algebra::CostShape::*; pub use crate::std_algebra::{AlgebraFieldTemplate, CollectionSizeEffect, CostShape}; -pub use crate::std_coercion::TypeDeclarationProvenance; +use crate::std_coercion::ReferenceIdentityUnavailableCause::{ + DeclarationNodeCarriesNoSpan, NoResolutionBoundAtReference, ResolvedNodeIsNotADeclaration, +}; use crate::std_coercion::TypeDeclarationProvenance::{ CorpusDeclared, DeclarationIdentityAbsent, KernelMinted, }; +use crate::std_coercion::TypeReferenceIdentity::{ + ReferenceIdentityUnavailable, ReferenceIsTheDeclaration, ReferenceIsTypeVariableBinder, + ReferenceResolvedToDeclaration, +}; +pub use crate::std_coercion::{ + ReferenceIdentityUnavailableCause, TypeDeclarationProvenance, TypeReferenceIdentity, +}; pub use crate::std_dissolution::unbound_dissolution; pub use crate::std_dissolution::DissolutionCondition; use crate::std_dissolution::DissolutionCondition::*; @@ -4447,6 +4456,103 @@ pub fn type_reference_provenance(n: Rc) -> Rc { } } +pub fn declaration_node_provenance(n: Rc) -> Option> { + match (*declaration_provenance_of(n.clone())).clone() { + TypeDeclarationProvenance::KernelMinted { + minted_name: nm, .. + } => Some(Rc::new(TypeDeclarationProvenance::KernelMinted { + minted_name: nm.clone(), + })), + TypeDeclarationProvenance::CorpusDeclared { decl_file: f, .. } => { + match n.module_item_kind.clone() { + ParsedModuleItemKind::ModuleItemTypeDeclaration => { + Some(Rc::new(TypeDeclarationProvenance::CorpusDeclared { + decl_file: f.clone(), + })) + } + ParsedModuleItemKind::ModuleItemFunction => std::option::Option::None, + ParsedModuleItemKind::ModuleItemDataValue => std::option::Option::None, + ParsedModuleItemKind::ModuleItemService => std::option::Option::None, + ParsedModuleItemKind::ModuleItemResource => std::option::Option::None, + ParsedModuleItemKind::ModuleItemUnrecognized => std::option::Option::None, + ParsedModuleItemKind::NotAModuleItem => std::option::Option::None, + } + } + TypeDeclarationProvenance::DeclarationIdentityAbsent => std::option::Option::None, + } +} + +pub fn declaration_node_unavailable_cause( + n: Rc, + when_not_a_declaration: ReferenceIdentityUnavailableCause, +) -> ReferenceIdentityUnavailableCause { + match n.module_item_kind.clone() { + ParsedModuleItemKind::ModuleItemTypeDeclaration => { + ReferenceIdentityUnavailableCause::DeclarationNodeCarriesNoSpan + } + ParsedModuleItemKind::ModuleItemFunction => when_not_a_declaration, + ParsedModuleItemKind::ModuleItemDataValue => when_not_a_declaration, + ParsedModuleItemKind::ModuleItemService => when_not_a_declaration, + ParsedModuleItemKind::ModuleItemResource => when_not_a_declaration, + ParsedModuleItemKind::ModuleItemUnrecognized => when_not_a_declaration, + ParsedModuleItemKind::NotAModuleItem => when_not_a_declaration, + } +} + +pub fn type_variable_binder_name(n: Rc) -> Option { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::TypeVariable { id: tv, .. }) => Some(tv.clone()), + _ => std::option::Option::None, + } +} + +pub fn type_reference_identity(n: Rc) -> Rc { + match type_variable_binder_name(n.clone()) { + Some(tv) => Rc::new(TypeReferenceIdentity::ReferenceIsTypeVariableBinder { + binder_name: tv.clone(), + }), + std::option::Option::None => type_reference_declaration_identity(n.clone()), + } +} + +pub fn type_reference_declaration_identity(n: Rc) -> Rc { + match n.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: rt, .. }) => { + match type_variable_binder_name(rt.clone()) { + Some(tv) => Rc::new(TypeReferenceIdentity::ReferenceIsTypeVariableBinder { + binder_name: tv.clone(), + }), + std::option::Option::None => match declaration_node_provenance(rt.clone()) { + Some(p) => Rc::new(TypeReferenceIdentity::ReferenceResolvedToDeclaration { + provenance: p.clone(), + }), + std::option::Option::None => { + Rc::new(TypeReferenceIdentity::ReferenceIdentityUnavailable { + cause: declaration_node_unavailable_cause( + rt.clone(), + ReferenceIdentityUnavailableCause::ResolvedNodeIsNotADeclaration, + ), + }) + } + }, + } + } + _ => match declaration_node_provenance(n.clone()) { + Some(p) => Rc::new(TypeReferenceIdentity::ReferenceIsTheDeclaration { + provenance: p.clone(), + }), + std::option::Option::None => { + Rc::new(TypeReferenceIdentity::ReferenceIdentityUnavailable { + cause: declaration_node_unavailable_cause( + n.clone(), + ReferenceIdentityUnavailableCause::NoResolutionBoundAtReference, + ), + }) + } + }, + } +} + pub fn provenance_reported_file(p: Rc) -> String { match (*p.clone()).clone() { TypeDeclarationProvenance::CorpusDeclared { decl_file: f, .. } => f.clone(), diff --git a/src/v1/stage0/src/v1_tests_claim_carrier_realization_census.rs b/src/v1/stage0/src/v1_tests_claim_carrier_realization_census.rs index 204115722c3..72d754e4e86 100644 --- a/src/v1/stage0/src/v1_tests_claim_carrier_realization_census.rs +++ b/src/v1/stage0/src/v1_tests_claim_carrier_realization_census.rs @@ -4,9 +4,19 @@ use self::CensusOutcome::*; use self::DeclarationIdentityObservation::*; use self::LegacyKeyObservation::*; +use crate::std_coercion::ReferenceIdentityUnavailableCause::{ + DeclarationNodeCarriesNoSpan, NoResolutionBoundAtReference, ResolvedNodeIsNotADeclaration, +}; use crate::std_coercion::TypeDeclarationProvenance::DeclarationIdentityAbsent; use crate::std_coercion::TypeRealizationDecision::{RealizationRefused, Realized, Unrealized}; -pub use crate::std_coercion::{TypeDeclarationProvenance, TypeRealizationDecision}; +use crate::std_coercion::TypeReferenceIdentity::{ + ReferenceIdentityUnavailable, ReferenceIsTheDeclaration, ReferenceIsTypeVariableBinder, + ReferenceResolvedToDeclaration, +}; +pub use crate::std_coercion::{ + ReferenceIdentityUnavailableCause, TypeDeclarationProvenance, TypeRealizationDecision, + TypeReferenceIdentity, +}; use crate::std_types::Bool::*; pub use crate::std_types::{Bool, List, Map}; pub use crate::v1_compiler_artifact::RenderTarget; @@ -26,7 +36,7 @@ use crate::v1_std_core::Connective::Arrow; use crate::v1_std_core::InferredNode::Resolved; pub use crate::v1_std_core::{ authored_name_at, declaration_provenance_of, param_node_type_expr, provenance_reported_file, - type_reference_provenance, + type_reference_identity, type_reference_provenance, }; pub use crate::v1_std_core::{Connective, InferredNode, NewlineIndex, Node}; use crate::NonEmptyBTreeSet; @@ -78,6 +88,7 @@ pub struct TypedCarrierRow { pub authored_name: String, pub identity: Rc, pub legacy: Rc, + pub split: Rc, pub legacy_base: String, pub authority_base: String, pub outcome: CensusOutcome, @@ -247,6 +258,7 @@ pub fn typed_decision_row( (crate::v1_std_core::provenance_reported_file(inferred.clone()) != "".to_string()), legacy_key.clone(), ), + split: crate::v1_std_core::type_reference_identity(n.clone()), legacy_base: legacy_base_label(n.clone(), si.clone()), authority_base: authority_base_of(name.clone(), query_provenance.clone()), outcome: outcome_of( @@ -476,6 +488,41 @@ pub fn legacy_label(l: Rc) -> String { } } +pub fn split_cause_label(c: ReferenceIdentityUnavailableCause) -> String { + match c.clone() { + ReferenceIdentityUnavailableCause::NoResolutionBoundAtReference => { + "NoResolutionBoundAtReference".to_string() + } + ReferenceIdentityUnavailableCause::ResolvedNodeIsNotADeclaration => { + "ResolvedNodeIsNotADeclaration".to_string() + } + ReferenceIdentityUnavailableCause::DeclarationNodeCarriesNoSpan => { + "DeclarationNodeCarriesNoSpan".to_string() + } + } +} + +pub fn split_label(i: Rc) -> String { + match (*i.clone()).clone() { + TypeReferenceIdentity::ReferenceResolvedToDeclaration { provenance: p, .. } => { + v1_rt::concat( + "ResolvedToDeclaration:".to_string(), + crate::v1_std_core::provenance_reported_file(p.clone()), + ) + } + TypeReferenceIdentity::ReferenceIsTheDeclaration { provenance: p, .. } => v1_rt::concat( + "IsTheDeclaration:".to_string(), + crate::v1_std_core::provenance_reported_file(p.clone()), + ), + TypeReferenceIdentity::ReferenceIsTypeVariableBinder { binder_name: b, .. } => { + v1_rt::concat("TypeVariableBinder:".to_string(), b.clone()) + } + TypeReferenceIdentity::ReferenceIdentityUnavailable { cause: c, .. } => { + v1_rt::concat("Unavailable:".to_string(), split_cause_label(c.clone())) + } + } +} + pub fn outcome_label(o: CensusOutcome) -> String { match o.clone() { CensusOutcome::Agrees => "Agrees".to_string(), @@ -493,7 +540,7 @@ pub fn tsv_escape(v: String) -> String { } pub fn typed_census_header() -> String { - "module_file\tenclosing_decl\tposition_kind\tauthored_name\tidentity\tidentity_file\tlegacy_key\tlegacy_base\tauthority_base\toutcome".to_string() + "module_file\tenclosing_decl\tposition_kind\tauthored_name\tidentity\tidentity_file\tlegacy_key\tsplit_identity\tlegacy_base\tauthority_base\toutcome".to_string() } pub fn typed_row_tsv(r: Rc) -> String { @@ -505,6 +552,7 @@ pub fn typed_row_tsv(r: Rc) -> String { identity_label(r.identity.clone()), tsv_escape(identity_file_label(r.identity.clone())), tsv_escape(legacy_label(r.legacy.clone())), + tsv_escape(split_label(r.split.clone())), tsv_escape(r.legacy_base.clone()), tsv_escape(r.authority_base.clone()), outcome_label(r.outcome.clone()), diff --git a/src/v1/tests/claim/carrier_realization_census.dag b/src/v1/tests/claim/carrier_realization_census.dag index 784349f6486..654ef57acbe 100644 --- a/src/v1/tests/claim/carrier_realization_census.dag +++ b/src/v1/tests/claim/carrier_realization_census.dag @@ -8,8 +8,12 @@ import v1.compiler.coercion { type_realization_decision } import v1.compiler.artifact { Rust } import v1.compiler.infer_items { ResolvedGraph, TypedModule } import v1.compiler.infer_env { TypeEnv, lookup_type_for } -import v1.std.core { Node, NewlineIndex, authored_name_at, param_node_type_expr, Resolved, Arrow, declaration_provenance_of, type_reference_provenance, provenance_reported_file } -import std.coercion { TypeRealizationDecision, Realized, Unrealized, RealizationRefused, TypeDeclarationProvenance, DeclarationIdentityAbsent } +import v1.std.core { Node, NewlineIndex, authored_name_at, param_node_type_expr, Resolved, Arrow, declaration_provenance_of, type_reference_provenance, provenance_reported_file, type_reference_identity } +import std.coercion { + TypeRealizationDecision, Realized, Unrealized, RealizationRefused, TypeDeclarationProvenance, DeclarationIdentityAbsent, + TypeReferenceIdentity, ReferenceResolvedToDeclaration, ReferenceIsTheDeclaration, ReferenceIsTypeVariableBinder, ReferenceIdentityUnavailable, + ReferenceIdentityUnavailableCause, NoResolutionBoundAtReference, ResolvedNodeIsNotADeclaration, DeclarationNodeCarriesNoSpan +} import std.types { List, String, Map, Bool } // THE STEP-1 SHADOW CENSUS. For every type-reference occurrence the v1 emitter would render, @@ -79,6 +83,7 @@ type TypedCarrierRow { authored_name: String identity: DeclarationIdentityObservation legacy: LegacyKeyObservation + split: TypeReferenceIdentity legacy_base: String authority_base: String outcome: CensusOutcome @@ -196,6 +201,7 @@ fn typed_decision_row( authored_name: name, identity: identity, legacy: legacy_observation(had_inference: provenance_reported_file(p: inferred) != "", key: legacy_key), + split: type_reference_identity(n: n), legacy_base: legacy_base_label(n: n, si: si), authority_base: authority_base_of(name: name, provenance: query_provenance), outcome: outcome_of(claims_text: claims_text, realizes: authority_realizes(name: name, provenance: query_provenance), query_file: query_file) @@ -248,7 +254,7 @@ fn no_typed_rows() -> List { // The arrow test is a match rather than `n.connective == Arrow`, because a variant name followed // by `{` parses as a record construction, so the equality form makes the if-block's own brace read // as Arrow's constructor. v1.compiler.emit_rust rust_type_node_is_arrow is named for the same -// reason, and v1.compiler.resolve takes the same shape. +// reason, and v1.compiler.infer_resolve takes the same shape. fn typed_occurrence_rows( module_file: String, enclosing: String, position_kind: String, n: Node, env: TypeEnv, si: Map ) -> List { @@ -332,6 +338,29 @@ fn legacy_label(l: LegacyKeyObservation) -> String { } +// THE SPLIT COLUMN (CARRIER-KEY-1). The legacy column above says WHICH ARM production took; this one +// says which STATE the reference is in, from v1.std.core type_reference_identity. A FALLBACK row is +// scorable only once this column exists: a ReferenceIsTheDeclaration row is legitimately keyed, and a +// ReferenceIdentityUnavailable row is one where production keyed a location. The instrument reports +// the arm and its cause and nothing about whether production is fixed -- production still reads +// type_reference_provenance (gunbc.rung_drop type_reference_location_fallback_keys_production_realization). +fn split_cause_label(c: ReferenceIdentityUnavailableCause) -> String { + match c { + NoResolutionBoundAtReference => "NoResolutionBoundAtReference" + ResolvedNodeIsNotADeclaration => "ResolvedNodeIsNotADeclaration" + DeclarationNodeCarriesNoSpan => "DeclarationNodeCarriesNoSpan" + } +} + +fn split_label(i: TypeReferenceIdentity) -> String { + match i { + ReferenceResolvedToDeclaration { provenance: p } => concat("ResolvedToDeclaration:", provenance_reported_file(p: p)) + ReferenceIsTheDeclaration { provenance: p } => concat("IsTheDeclaration:", provenance_reported_file(p: p)) + ReferenceIsTypeVariableBinder { binder_name: b } => concat("TypeVariableBinder:", b) + ReferenceIdentityUnavailable { cause: c } => concat("Unavailable:", split_cause_label(c: c)) + } +} + fn outcome_label(o: CensusOutcome) -> String { match o { Agrees => "Agrees" @@ -345,7 +374,7 @@ fn tsv_escape(v: String) -> String { } fn typed_census_header() -> String { - "module_file\tenclosing_decl\tposition_kind\tauthored_name\tidentity\tidentity_file\tlegacy_key\tlegacy_base\tauthority_base\toutcome" + "module_file\tenclosing_decl\tposition_kind\tauthored_name\tidentity\tidentity_file\tlegacy_key\tsplit_identity\tlegacy_base\tauthority_base\toutcome" } fn typed_row_tsv(r: TypedCarrierRow) -> String { @@ -357,6 +386,7 @@ fn typed_row_tsv(r: TypedCarrierRow) -> String { identity_label(o: r.identity), tsv_escape(v: identity_file_label(o: r.identity)), tsv_escape(v: legacy_label(l: r.legacy)), + tsv_escape(v: split_label(i: r.split)), tsv_escape(v: r.legacy_base), tsv_escape(v: r.authority_base), outcome_label(o: r.outcome)