diff --git a/dag/gunbc/fabric/fabric_event_log.dag b/dag/gunbc/fabric/fabric_event_log.dag index 877a4368837..befbe02c4b5 100644 --- a/dag/gunbc/fabric/fabric_event_log.dag +++ b/dag/gunbc/fabric/fabric_event_log.dag @@ -6,7 +6,7 @@ import std.measure { Count, One, Measure } import product.placement_supply { HostIdentity } import std.process { ProcessExit, ExitSuccess, exit_failure } import std.fabric_storage { - FabricObject, FabricObjectRef, fabric_object_ref_of, fabric_object_ref_wire, fabric_object_ref_of_wire, + FabricObject, FabricObjectRef, fabric_object_ref_wire, fabric_object_ref_of_wire, FabricHeadAbsent, FabricHeadAt, ExpectHeadAbsent, ExpectHeadAt, FabricHeadExpectation, FabricStorageFault, fabric_storage_fault_wire, FabricObjectStored, FabricPutRefused, FabricHeadObserved, FabricHeadReadRefused, diff --git a/dag/gunbc/fabric/fabric_storage_file_store.dag b/dag/gunbc/fabric/fabric_storage_file_store.dag index 8d725e6ff0e..9db8e3bf309 100644 --- a/dag/gunbc/fabric/fabric_storage_file_store.dag +++ b/dag/gunbc/fabric/fabric_storage_file_store.dag @@ -9,7 +9,7 @@ import std.durable_compare_and_set { CasSlotObservationRefused, CasGenerationPublicationRefused, CasUnreadableMalformed, CasUnreadableReadRefused, } import std.fabric_storage { - FabricObjectRef, FabricObject, FabricStoredObject, fabric_object_ref_of, fabric_object_ref_wire, fabric_object_ref_of_wire, fabric_object_ref_eq, + FabricObjectRef, FabricObject, FabricStoredObject, FabricAddressedObject, fabric_object_addressed, fabric_object_ref_wire, fabric_object_ref_of_wire, fabric_object_ref_eq, fabric_object_preimage, fabric_object_decode, fabric_object_verified, FabricObjectVerified, FabricObjectVerificationFailed, FabricHeadReading, FabricHeadAbsent, FabricHeadAt, FabricHeadExpectation, fabric_head_admits, FabricStorageFault, FabricStoreRefused, FabricObjectMissing, FabricObjectCorrupt, FabricHeadNameRefused, @@ -104,11 +104,19 @@ fn fabric_storage_file_get(root: FabricStorageFileRoot, object: FabricObjectRef) // PUTTING AN OBJECT. Its ref is derived from its bytes, so a put names nothing the caller chose. A // put is idempotent -- the name already holding THESE bytes is the object already stored -- and an -// occupied name holding OTHER bytes is a digest collision, refused as corruption rather than read -// through: the digest family is non-cryptographic (std.content_hash content_hash_of_value), and this -// arm is what keeps a collision loud. -fn fabric_storage_put_from_create(object: FabricObject, created: FilesystemCreateNew, existing: FabricObjectGet) -> FabricPut { - let r = fabric_object_ref_of(object: object) +// occupied name holding OTHER bytes is refused as corruption rather than read through. +// +// THE COLLISION ARM STAYS. The digest family is non-cryptographic (gunbc.recurring_failure_mode +// fabric_object_identity_is_a_structural_locator), so a collision is a thing an ordinary workload +// can actually meet and the arm is a wall against an accident. It must not be removed when the +// family becomes cryptographic either: under that family a collision stops being noise and becomes +// an ATTACK, which is exactly when the check earns its keep. +// +// THE REF IS RECEIVED, NOT RE-DERIVED: the object and its ref arrive as ONE carrier, minted once at +// the entry below, so this function cannot be handed a ref for some other object's bytes. +fn fabric_storage_put_from_create(addressed: FabricAddressedObject, created: FilesystemCreateNew, existing: FabricObjectGet) -> FabricPut { + let r = addressed.ref + let object = addressed.object match created { FilesystemCreated { path: _ } => FabricObjectStored { object: r } FilesystemCreateTargetOccupied { path: _ } => @@ -125,16 +133,21 @@ fn fabric_storage_put_from_create(object: FabricObject, created: FilesystemCreat } } +// BOTH HALVES OF THE ADDRESSED OBJECT COME OFF THE CARRIER, and the write below reads a.object +// rather than the outer `object` deliberately. They are the same VALUE; the difference is SHAPE. +// Reading the outer binding is the spelling that lets a later edit change one side without the +// other, which is how the peer-parameter defect FabricAddressedObject exists to close gets back in. fn fabric_storage_file_put(root: FabricStorageFileRoot, object: FabricObject) -> FabricPut { - let r = fabric_object_ref_of(object: object) + let a = fabric_object_addressed(object: object) + let r = a.ref let path = fabric_storage_object_path(root: root, object: r) - let write = Filesystem.WriteCreateNew(path: path, content: fabric_object_preimage(object: object) as String) + let write = Filesystem.WriteCreateNew(path: path, content: fabric_object_preimage(object: a.object) as String) let created = filesystem_create_new(path: write.path, success: write.success, error: write.error, error_kind: write.error_kind) match created { - FilesystemCreateTargetOccupied { path: _ } => fabric_storage_put_from_create(object: object, created: created, existing: fabric_storage_file_get(root: root, object: r)) - FilesystemCreated { path: _ } => fabric_storage_put_from_create(object: object, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) - FilesystemCreateRefused { path: _, kind: _, error: _ } => fabric_storage_put_from_create(object: object, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) - FilesystemCreateKindUnrecognized { path: _, observed: _, error: _ } => fabric_storage_put_from_create(object: object, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) + FilesystemCreateTargetOccupied { path: _ } => fabric_storage_put_from_create(addressed: a, created: created, existing: fabric_storage_file_get(root: root, object: r)) + FilesystemCreated { path: _ } => fabric_storage_put_from_create(addressed: a, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) + FilesystemCreateRefused { path: _, kind: _, error: _ } => fabric_storage_put_from_create(addressed: a, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) + FilesystemCreateKindUnrecognized { path: _, observed: _, error: _ } => fabric_storage_put_from_create(addressed: a, created: created, existing: FabricObjectGetRefused { fault: FabricObjectMissing { object: r } }) } } diff --git a/dag/gunbc/kind_reflection_seed_growth.dag b/dag/gunbc/kind_reflection_seed_growth.dag new file mode 100644 index 00000000000..1df94645ad7 --- /dev/null +++ b/dag/gunbc/kind_reflection_seed_growth.dag @@ -0,0 +1,68 @@ +module gunbc.kind_reflection_seed_growth + +import gunbc.roadmap_model { RoadmapNodeId } +import gunbc.seed_growth { SeedGrowthJustification } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +// FORWARD-FREEZE RECEIPT for the hand Rust carried into the seed by the kind-reflection work +// merged from gunbc#11819. Authored because review 69733 found the growth undeclared, and it was +// right: gunbc.seed_growth_admission makes unenumerated hand growth in src/v1 a stop-line, and +// prose in a // annotation cannot discharge it. DESIGN section 4c is the reason the annotations +// those declarations already carry are not enough -- a dissolution condition is a typed carrier's +// job, and "replaced by generated bytes on the next regen" living only in a comment is +// uncountable by the roster that exists to count it. +// +// THE POPULATION SPLITS IN TWO, AND ONLY ONE HALF IS REAL GROWTH. Sixteen declarations were added +// or modified across v1_compiler_infer_resolve, v1_compiler_parse, v1_std_core, +// std_machine_constraints and v1_compiler_infer. FIFTEEN of them resolve in the .dag authority -- +// type_arg_kind_inhabitance, KindInhabitance, kind_admissible_inhabitant_name, +// kind_names_admissible_inhabitant, kind_lookup_trace, kind_decl_for_message, +// node_authored_or_own_name, type_arg_display_spelling, type_param_kind_diagnostics, +// type_arg_name_is_bound_generic_parameter, parse_optional_type_param_kind, +// with_type_param_kind_property, TypeParamKindResult, type_param_kind_property_name, and the +// WidthResolution arms -- so they are generated bytes carried BY HAND AHEAD OF A REGEN, which is +// ExistingSeedItemModified rather than addition. ONE does not: binding_resolves_to_type_parameter +// occurs ZERO times in every .dag under src/v1. That one is the growth, and it is the only +// declaration this row cites. +// +// WHAT IT IS: A STUB WHOSE BODY IS THE LITERAL false, AND SAYING SO IS THE POINT. It stands where +// v1.compiler.infer declares name_is_enclosing_declared_type_parameter, which asks the DECLARED +// type-parameter roster carried on InferScope as enclosing_declared_type_param_names. The seed's +// InferScope HAS NO SUCH FIELD. Implementing the predicate by hand therefore means adding a struct +// field and plumbing it through every InferScope construction site in generated Rust -- which is +// cementing compiler logic into the seed to make a check go green, the direction DESIGN section 7 +// and this repository's standing instruction both refuse. The stub is the smaller, more honest +// debt, and it is declared here rather than left silent. +// +// THE CURRENT BOUNDARY IS A DEAD BRANCH AND AN INERT WALL, MEASURED RATHER THAN ASSERTED. Because +// the predicate is false, both call sites in v1_compiler_infer take their else arm always, so the +// net behavioural delta of all three added items is ZERO -- and the consequence is that +// TypeParameterInValuePosition, which v1.std.core declares with SeverityError and +// GateBlocking and v1.compiler.infer constructs at two sites, CANNOT FIRE. A module whose entire +// content is a generic function returning its own type parameter compiles with exit 0, zero +// blocking errors, and emits. That is not a side note about this row; it is what this row is +// declaring, and DESIGN section 4b names it: the tier where the machinery exists but nothing gates +// on it, and an inert lens is itself a lie. +// +// THE SILENCE IS WITNESSED RATHER THAN DESCRIBED. +// test.claim.type_argument_kind_inhabitance_witness_test pins it as a SILENCE assertion with an +// adequacy control on the same harness and the same run, so the zero is the compiler's and not a +// harness that never reached the judgment. It goes red the day the wall works, and that red means +// flip the assertion, not relax it. The class is +// gunbc.recurring_failure_mode.a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles. +// +// WHY THE STUB IS NOT SIMPLY DELETED, which is the first repair anyone should reach for. Deleting +// it means deleting the two call sites with it, and those two branches DO resolve in the .dag -- +// v1.compiler.infer constructs the diagnostic at exactly those points. Removing them would put the +// seed further from the authority rather than closer, and the next regen would re-add them. The +// divergence worth removing is the predicate, and the way to remove it is the regen, not a second +// hand edit. +data kind_reflection_seed_growth_justification: SeedGrowthJustification = SeedGrowthJustification { + hand_authored_declarations: [ + DeclarationRef { module_path: "v1_compiler.v1_compiler_infer", decl_name: "binding_resolves_to_type_parameter", field: WholeDeclaration } + ], + reason: "A stub standing where v1.compiler.infer declares name_is_enclosing_declared_type_parameter. The declared predicate reads InferScope's enclosing_declared_type_param_names roster; the seed's InferScope carries no such field, so honouring it by hand would mean adding a struct field and threading it through every construction site in generated Rust -- hand-authoring compiler logic into the seed to make a wall go green, which DESIGN section 7 refuses. Its body is the literal false, so both call sites take their else arm always and the behavioural delta of the whole kind-reflection seed carry is zero.\n\nWHAT IT COSTS, STATED AND NOT NETTED AWAY: TypeParameterInValuePosition is declared SeverityError/GateBlocking and constructed at two sites, and it cannot fire. A generic function returning its own type parameter compiles clean and emits. The silence is pinned by test.claim.type_argument_kind_inhabitance_witness_test as an explicit hole assertion with an adequacy control on the same run, and filed as gunbc.recurring_failure_mode.a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles.\n\nSCOPE, ENUMERATED RATHER THAN COUNTED. Fifteen further declarations in this change resolve in the .dag authority and are generated bytes carried ahead of a regen (ExistingSeedItemModified per gunbc.seed_growth_admission SeedGrowthChangeDisposition), so they are not counted as additions; they are named in this file's header rather than omitted. TWO of them diverge in SHAPE and both are disclosed for the same reason; an earlier revision of this row said ONE and was off by one on its own stated axis (review 69986, BLOCKING). FIRST: the seed's type_arg_kind_inhabitance takes three parameters where v1.compiler.infer_resolve declares four, because the seed lacks the kind_inhabitant_matches_resolved arm. SECOND: src/v1/stage0/src/std_machine_constraints.rs carries `pub type WidthResolution = PointerWidth;`, an ALIAS, where std.machine_constraints now declares the two-arm coproduct StaticWidthIndex | PointerWidth -- the emission convention for such a coproduct is one enum plus a marker struct per arm, visible in this same change at v1_compiler_infer_resolve's KindInhabitance, and StaticWidthIndex occurs nowhere under src/. So the mirror is not the bytes a regen would produce, and the seed cannot express the literal arm at all. Neither divergence is repaired by hand: hand-carrying either is the cementing DESIGN section 7 refuses, and both close on the same regen this row's trigger names. The behaviour they leave is measured rather than assumed -- the kind wall's reds and its positive control are witnessed on the BUILT compiler by test.claim.type_argument_kind_inhabitance_witness_test, and the literal axis is admitted unconditionally by the language-level rule in either tree, so the alias costs no refusal the coproduct would have made today.", + owning_dissolution_lane: "v1-hand-queue-drain" as RoadmapNodeId, + trigger: "Regenerate the stage0 mirror from v1.compiler.infer and v1.compiler.infer_resolve -- SUFFICIENT FOR the built compiler to consult the declared type-parameter roster and to carry the four-parameter kind-inhabitance fold, so that a type name in value position refuses at its own location rather than at rustc's, and the seed's checking predicates are the authority's rather than hand-carried approximations of them. The regen is what deletes this row; a further hand patch to either declaration would not, because it would leave the seed and the authority disagreeing about which predicate decides.", + current_boundary: "Both call sites are dead branches: the predicate is false, so the net behavioural change is zero and no program is judged differently than before this change. The cost is not a wrong answer but an absent one -- the value-position wall is unreachable by construction, and gunbc#11819's claim that it fires is retracted in gunbc#11996 rather than carried. The kind-inhabitance wall it sits beside DOES fire and is witnessed in both directions on one run, so the two must not be read together: one wall discriminates, the other is inert, and the seed is why." +} diff --git a/dag/gunbc/recurring_failure_mode/a_carrier_swap_moves_the_mint_and_strands_the_parser.dag b/dag/gunbc/recurring_failure_mode/a_carrier_swap_moves_the_mint_and_strands_the_parser.dag new file mode 100644 index 00000000000..ca577d819c5 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/a_carrier_swap_moves_the_mint_and_strands_the_parser.dag @@ -0,0 +1,27 @@ +module gunbc.recurring_failure_mode.a_carrier_swap_moves_the_mint_and_strands_the_parser + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data a_carrier_swap_moves_the_mint_and_strands_the_parser: RecurringFailureMode = RecurringFailureMode { + identity: "a_carrier_swap_moves_the_mint_and_strands_the_parser" as NonEmptyStr, + + receipts: [ + "A carrier swap moves the MINT and strands the PARSER. INVALID STATE: one value has a producer and an inverse -- a digest and the reader of its wire form, an id and its parser, an encoder and its decoder -- and a migration retypes the producer while the inverse keeps admitting only the OLD form. The two halves are separate declarations, so nothing relates them, and the type system is satisfied on both sides: each is internally consistent with the type it names, and only their COMPOSITION is broken.", + + "WHY IT IS WORSE THAN AN ORDINARY BREAK, AND THIS IS THE WHOLE ROW: the failure is READ-PATH ONLY, and the two paths exercise different halves. A write exercises the mint, which is correct. A read exercises the parser, which is not. So the system accepts work, the corpus fills with values written under the new form, and every attempt to read one refuses -- the damage accumulates during exactly the period in which everything looks healthy. A put-only smoke test is GREEN BY CONSTRUCTION and cannot detect it, which is why the usual first instrument is the one that will not fire.", + + "RECEIPT, 2026-09-21, gunbc#11996, found by reading the module rather than by a red. std.fabric_storage fabric_object_ref_of was moved from the fnv1a64 structural digest onto a computed SHA-256, so fabric_object_ref_wire began serializing the sha256-prefixed wire form. Its inverse, fabric_object_ref_of_wire, read content_hash_from_structural_digest, which admits sixteen hex digits and refuses everything else. Left alone it would have refused every ref the module mints. The brief that directed the migration named the mint and did not name the inverse; neither did the module's own note, which describes ref-minting and wire-parsing as the two halves of one closed loop and still did not couple them at the point of change.", + + "THE TELL, STATED SO IT CAN BE LOOKED FOR BEFORE THE READ FAILS: a migration diff that touches a producer and not its inverse, where the inverse is discoverable by name -- of_wire beside to_wire, parse beside serialize, decode beside encode. The corpus already carries the coupling as prose in the same modules; what it does not carry is a mechanism that makes changing one without the other refuse. The second tell is a round-trip claim that passes because it round-trips through the NEW form on both sides while the STORED population is in the old one -- agreement between two halves that moved together is not evidence that either matches what is already written down.", + + "RUNG FOUND AT: below the ladder -- silent wrongness on the read path, with no diagnostic at the change site and a discriminating red available only from a read of a value written before the swap. ATTAINABLE CEILING: structurally guaranteed. A serializer and its parser over one carrier are an inverse PAIR, and DESIGN section 4 already names this shape -- one grammar read in both directions -- so a substrate that derived the parser from the same rows as the serializer would make the two unable to disagree, exactly as it does for emission and ingestion. NEXT-RUNG TRIGGER: the wire grammar of a carrier declared once and read in both directions -- SUFFICIENT FOR no module to be able to author a serializer and a parser that admit different languages, so a family change propagates to both halves or refuses at the declaration. MECHANICALLY PREVENTABLE is reachable sooner and is not the ceiling: a round-trip claim pinned against a SUPPLIED wire literal from the old population, rather than against a value this run just serialized, goes red when the parser is stranded.", + ], + + evidence: [ + DeclarationRef { module_path: "std.fabric_storage", decl_name: "fabric_object_ref_of_wire", field: WholeDeclaration }, + DeclarationRef { module_path: "std.fabric_storage", decl_name: "fabric_object_ref_wire", field: WholeDeclaration }, + DeclarationRef { module_path: "std.content_hash", decl_name: "parse_content_hash", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles.dag b/dag/gunbc/recurring_failure_mode/a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles.dag new file mode 100644 index 00000000000..2a96a5cfa50 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles.dag @@ -0,0 +1,29 @@ +module gunbc.recurring_failure_mode.a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles: RecurringFailureMode = RecurringFailureMode { + identity: "a_wall_declared_in_dag_is_inert_in_the_seed_that_compiles" as NonEmptyStr, + + receipts: [ + "A wall declared in .dag is inert in the seed that compiles. INVALID STATE: a refusal is authored in the .dag authority -- the diagnostic variant declared, its severity and gate set to blocking, its construction sites written, its admitting predicate defined -- while the SEED, which is the binary that actually compiles the corpus, carries an earlier or stub version of that predicate. The .dag says the wall exists. The compiler does not have it. Both statements are true of different artifacts, and every ordinary reading conflates them because the .dag IS the authority everywhere else.", + + "WHY IT IS NOT AN ORDINARY STALENESS BUG: THE SELF-HOST LOOP MAKES THE GAP SELF-CONCEALING. The seed is regenerated BY the compiler, so a change to a checking rule only reaches the binary through a regen -- and a regen is itself a compile performed by the CURRENT seed, which does not have the rule. A hand-patch into the seed closes it for ONE rule and silently leaves every other rule in the same change unpatched, which is exactly the state that produces a partially-live wall: some refusals fire, some do not, and nothing distinguishes them from outside.", + + "RECEIPT, 2026-09-21, gunbc#11996, on a compiler built from the tree at e20e8c5004 with a must-fail control passing (a bogus flag refused, exit 2). v1.std.core declares TypeParameterInValuePosition with SeverityError and GateBlocking; v1.compiler.infer constructs it at two sites; v1.compiler.infer also declares the corrected admitting predicate name_is_enclosing_declared_type_parameter, which asks the DECLARED type-parameter roster. That predicate occurs fourteen times in the .dag and ZERO times in the seed. What the seed carries instead is binding_resolves_to_type_parameter, whose entire body is the literal `false`. A module whose complete content is `fn any_param(x: T) -> Int { T }` therefore compiles with exit 0, ZERO blocking errors, and EMITS. v1.compiler.infer's own note records the consequence: it renders `{ t }` into a crate that then fails rustc E0425 -- so the silence is not harmless, it is a located refusal traded for an unlocated one in a later toolchain.", + + "THE TELL, AND IT IS AVAILABLE WITHOUT A BUILD: a predicate the .dag defines and the seed does not mention, at a name the .dag references many times. A stub whose body is a bare `true` or `false` is the sharpest form, because it type-checks, it satisfies every caller, and it makes the enclosing branch unreachable rather than wrong. The SECOND tell is a PR reporting several refusals firing where only some were hand-patched into the seed: the report is honest about what was observed and silent about which artifact observed it.", + + "WHY A GREEN WITNESS DOES NOT CATCH IT, WHICH IS THE PART WORTH CARRYING. A witness asserting the wall REFUSES goes red and cannot merge, so the pressure is to delete it -- and deleting it removes the only thing that would have noticed. A witness asserting the wall ADMITS is green, merges, and reads to every later reader as coverage of a working wall. The honest instrument is neither: pin the hole explicitly, assert the CURRENT silence, state in the claim that it must not be read as a guarantee, and ship an adequacy control on the same harness and the same run that DOES count a blocking row, so a zero is the compiler's silence rather than the harness never reaching the judgment.", + + "RUNG FOUND AT: below the ladder -- silent acceptance of a program the declared rules refuse, with the authority and the realization disagreeing and nothing reporting it. ATTAINABLE CEILING: mechanically preventable. Whether a declaration the .dag references is ABSENT FROM THE SEED is decidable from the two trees with no build, so a gate can refuse a change that adds a checking predicate the mirror does not carry. STRUCTURALLY GUARANTEED would need the seed to stop being a separately-edited artifact at all, which is the self-host program's own terminal state and not this row's to claim. NEXT-RUNG TRIGGER: a gate comparing declared checking predicates against the stage0 mirror -- SUFFICIENT FOR a change that authors a refusal in .dag to refuse merging while the seed cannot perform it, so no wall can be reported as firing on the strength of the authority alone.", + ], + + evidence: [ + DeclarationRef { module_path: "v1.compiler.infer", decl_name: "name_is_enclosing_declared_type_parameter", field: WholeDeclaration }, + DeclarationRef { module_path: "v1.std.core", decl_name: "TypeParameterInValuePosition", field: WholeDeclaration }, + DeclarationRef { module_path: "test.claim.type_argument_kind_inhabitance_witness_test", decl_name: "a_type_parameter_in_value_position_does_not_yet_refuse_and_this_pins_that_hole", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/emitted_field_accessor_panics_on_a_variant_without_the_field.dag b/dag/gunbc/recurring_failure_mode/emitted_field_accessor_panics_on_a_variant_without_the_field.dag new file mode 100644 index 00000000000..7d4b3c12ad5 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/emitted_field_accessor_panics_on_a_variant_without_the_field.dag @@ -0,0 +1,26 @@ +module gunbc.recurring_failure_mode.emitted_field_accessor_panics_on_a_variant_without_the_field + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data emitted_field_accessor_panics_on_a_variant_without_the_field: RecurringFailureMode = RecurringFailureMode { + identity: "emitted_field_accessor_panics_on_a_variant_without_the_field" as NonEmptyStr, + + receipts: [ + "emitted field accessor panics on a variant without the field. INVALID STATE: a coproduct whose arms do not all carry a field name, for which the Rust emitter renders one shared accessor over every arm, giving the arms that lack the field a `panic!` body. HARM: the emitted crate COMPILES CLEAN and aborts at runtime when that arm is reached. That is a fail-open arm authored by the compiler into the program it emits -- the absorbing fallback DESIGN section 5 forbids -- and nothing at the .dag layer says it happened.", + + "DISTINGUISHED FROM ITS NEAR NEIGHBOUR, and the distinction is the reason this is its own class rather than a receipt on accepted_source_emits_uncompilable_target. That class is source the TARGET COMPILER REFUSES: loud, at build, before anything runs. This one passes the target compiler and is silent until the arm is reached, so the two sit at different rungs and have different detection stories. A class that folded them together would report the louder one's rung for both.", + + "RECEIPT, 2026-09-20, session gentle-seal-490, measured on the committed stage0 mirror rather than reasoned about. Occurrences of `panic!(\"no on unit variant\")`: 137 across 43 files, 69 distinct accessor names; the sibling positional-payload arm renders the same way. Highest-count files: v1_compiler_parse.rs (23), std_realization_schedule.rs (11), v1_std_core.rs (10), std_workspace_artifact.rs (7), std_measure.rs (7). PRE-EXISTING rather than introduced by the lane that found it: v1_std_core.rs on HEAD carries 10. So the seed's own emitted realization contains 137 compiler-authored fail-open arms today.", + + "HOW IT WAS FOUND, because the path matters for whoever meets it next: a lane declaring a mixed-arity coproduct (one arm carrying a field, one bare) read the emitted module and found the accessor's panic arm sitting beside an E0573 it was actually chasing. The panic was not what broke the build -- it would have shipped. A lane that writes such a coproduct and does not READ THE EMITTED BYTES has no signal at all.", + + "RUNG FOUND AT: below the ladder -- silent wrongness, deferred to runtime, in generated code. ATTAINABLE CEILING: structurally guaranteed. The emitter has the fact it needs at the moment it renders the arm (it is iterating the arms and knows which lack the field), so it can refuse to render a shared accessor over a coproduct whose arms do not all carry that field, with a located diagnostic naming the coproduct, the field and the arms that lack it, instead of rendering a panic. NEXT-RUNG TRIGGER: v1.compiler.emit_rust refuses the shared-accessor render for a coproduct not every arm of which declares the accessed field -- SUFFICIENT FOR no Accepted program to emit a compiler-authored panic arm for a missing field, so the 137 become either declared per-arm accessors or located refusals. STRUCTURALLY IMPOSSIBLE would additionally require the language to make a field access over a coproduct arm that cannot carry it unwritable at the .dag layer, which is a separate modeling obligation and is NOT this row's trigger.", + ], + + evidence: [ + DeclarationRef { module_path: "v1.compiler.emit_rust", decl_name: "emit_enum_shared_accessors", field: WholeDeclaration }, + DeclarationRef { module_path: "std.machine_constraints", decl_name: "MachineWidth", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/fabric_object_identity_is_a_structural_locator.dag b/dag/gunbc/recurring_failure_mode/fabric_object_identity_is_a_structural_locator.dag new file mode 100644 index 00000000000..c81fc5652da --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/fabric_object_identity_is_a_structural_locator.dag @@ -0,0 +1,31 @@ +module gunbc.recurring_failure_mode.fabric_object_identity_is_a_structural_locator + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data fabric_object_identity_is_a_structural_locator: RecurringFailureMode = RecurringFailureMode { + identity: "fabric_object_identity_is_a_structural_locator" as NonEmptyStr, + + receipts: [ + "Fabric object identity is a structural locator. INVALID STATE: a fabric object ref (std.fabric_storage FabricObjectRef) is minted by content_hash_of_value, the Fnv1a64Structural family -- sixty-four non-cryptographic bits. Two distinct preimages can share a ref at birthday work no adversary would notice paying, and the store has no way to tell them apart: the ref is the only name an object has.", + + "HARM: a read verified against the asked ref can return the wrong bytes with no refusal. fabric_object_verified re-mints the ref of the bytes it holds and compares; under this family the comparison holds for a second preimage, so the verified-cache guarantee the storage program rests on -- every read verified against the asked ref, a failed verification discards the cache copy -- is a statement about a locator, and calling it verification would be rung inflation (DESIGN 4b(1)). The module says so at the mint and does not call it that.", + + "RUNG FOUND AT: 1, mitigatable. The verify-on-read route exists and refuses every mismatch it can see -- a corrupt file, a substituted tip, a reply one object short -- so the harm is contained to what a 64-bit fingerprint cannot distinguish. ATTAINABLE CEILING: 3, structurally guaranteed, once refs are minted and verified through a cryptographic family -- the COMPUTATION (extdeps.crypto.sha2 sha256_hex, FIPS 180-4) already exists, is pure, and is witnessed against the standard's vectors by test.claim.sha256_fips_witness_test. What does not exist is a mint over it: gunbc#11996 authored one and withdrew it, because interpreted SHA-256 is unaffordable both at this module's consumers and at the mint's own inhabitance claim.", + + "THE RECEIPT THAT THIS IS A STALL AND NOT A CHOICE. gunbc#11996 bound fabric_object_ref_of to the cryptographic mint and ran it on the module's real consumers. Under the interpreter every put, every verified get and every closure step pays an interpreted SHA-256, and the spark pair_serving authority-log and d0 real-execution claims -- which append and re-read event chains through gunbc.fabric.fabric_storage_file_store -- ran two to three hundred times slower than on main, cancelling the required floor at its cap (workflow run 35670506917). The differential that pins it to the mint's family and nothing else -- the merge-base binary and the PR binary on the base tree equal, the PR tree slow, the PR tree with only the mint's family flipped back fast -- is on that PR at issuecomment-5770994507. The binding was withdrawn in the same PR and the family stays structural. THE SECOND MEASUREMENT WITHDREW THE MINT ITSELF, and it is the one that decides the shape rather than the speed: the mint's inhabitance claim -- one interpreted SHA-256 over a single-block preimage -- performed 244,396 eval steps against the required floor's 72,300-step new-witness budget (v2.workflow.required_floor claim_eval_step_budget_for_identity). One compression block is the floor of that cost, so no smaller preimage buys it back, and cost-debt enrolment is shrink-only and not an author's to rule. A mint whose only real-path claim cannot be enrolled is a declaration with no executing consumer, which DESIGN 3c refuses, so the mint was withdrawn with its binding and std.content_hash is unchanged by that PR.", + + "WHY THE OBVIOUS REPAIRS ARE REFUSED. A seed intrinsic for SHA-256 would cement Rust into the substrate to buy a number, which the brief and DESIGN 7 forbid: the closure is emit-blocked only because bounded-natural arithmetic is refused at emission (gunbc.recurring_failure_mode bounded_natural_arithmetic_evaluated_as_unbounded_int), and that is a language capability, not a missing bridge. A shell-out to sha256sum takes a PATH, so it would write every in-memory preimage to a file before hashing it, re-opening the custody gap verification exists to close, and would make ref-minting an effect. Raising the floor's cap or narrowing its scope is the escape hatch DESIGN 5 names.", + + "CLASS: can climb after one capability lands. NEXT-RUNG TRIGGER, at capability grain (DESIGN 4b(3)): native emission of the extdeps.crypto.sha2 closure sufficient for every consumer of gunbc.fabric.fabric_storage_file_store to run at floor rate -- measured as the spark pair_serving authority-log claims completing inside main's floor envelope with fabric_object_ref_of minting through a cryptographic family, AND the mint's inhabitance claim enrolling inside the new-witness budget rather than over it. An emitted closure that is still slow does not fire it. When it fires, fabric_object_ref_of and fabric_object_ref_of_wire change family together (gunbc.recurring_failure_mode a_carrier_swap_moves_the_mint_and_strands_the_parser), and this row's discriminating control is the floor run that completes.", + ], + + evidence: [ + DeclarationRef { module_path: "std.fabric_storage", decl_name: "fabric_object_ref_of", field: WholeDeclaration }, + DeclarationRef { module_path: "std.fabric_storage", decl_name: "fabric_object_verified", field: WholeDeclaration }, + DeclarationRef { module_path: "extdeps.crypto.sha2", decl_name: "sha256_hex", field: WholeDeclaration }, + DeclarationRef { module_path: "gunbc.fabric_storage_file_store", decl_name: "fabric_storage_file_get", field: WholeDeclaration }, + DeclarationRef { module_path: "test.claim.sha256_fips_witness_test", decl_name: "sha256_of_the_empty_message_is_the_published_value", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/single_arm_sum_parses_as_an_alias_and_becomes_unreadable.dag b/dag/gunbc/recurring_failure_mode/single_arm_sum_parses_as_an_alias_and_becomes_unreadable.dag new file mode 100644 index 00000000000..652bcb242c6 --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/single_arm_sum_parses_as_an_alias_and_becomes_unreadable.dag @@ -0,0 +1,26 @@ +module gunbc.recurring_failure_mode.single_arm_sum_parses_as_an_alias_and_becomes_unreadable + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data single_arm_sum_parses_as_an_alias_and_becomes_unreadable: RecurringFailureMode = RecurringFailureMode { + identity: "single_arm_sum_parses_as_an_alias_and_becomes_unreadable" as NonEmptyStr, + + receipts: [ + "single-arm sum parses as an alias and becomes unreadable. INVALID STATE: an author writes a one-member roster as `type K = A`, intending a sum with one arm, and the ARM COUNT SILENTLY DECIDES WHAT KIND OF DECLARATION IT IS -- one arm parses as a TYPE ALIAS, two or more as a coproduct. HARM: the alias reaches a consumer as a bare nominal node carrying NO resolved target and NO children, so any consumer that reads a declaration's members reads nothing, and there is no diagnostic anywhere saying the declaration is not the shape that was written.", + + "RECEIPT, 2026-09-20, session gentle-seal-490, printed by the refusal itself rather than inferred. A kind annotation `type MachineWidth` was given a one-arm roster, `type WidthResolution = PointerWidth`. The wall that checks kind inhabitance then refused the kind's OWN declared inhabitant, twice, on the real corpus, and the self-diagnosing message carried the cause: `'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=0]'`. Changing the roster to two arms (`= StaticWidthIndex | PointerWidth`) made the same declaration a coproduct with readable children and the same check correct.", + + "THE SECOND HALF, because the obvious rescue does not work: resolving the declaration instead of reading it does NOT recover the target. The kind node a parser synthesizes for an annotation carries no `ident`, and lookup_type_for keys on `node.ident`, so node-keyed resolution returns the node unchanged. Both the read route and the resolve route fail, for two different reasons, and neither reports anything.", + + "WHY READING THE SOURCE DID NOT PREDICT IT, which is the part worth carrying: v1.compiler.infer local_binding_for_item's alias arm carries `inferred: item.inferred`, and v1.compiler.infer_env lookup_type_by_name returns `binding.resolved`. BOTH STATEMENTS ARE TRUE. Neither predicts this call site, because the single-arm parse has already made the declaration an alias before either applies. TRUE-BUT-NOT-PREDICTIVE is the failure, not a misreading, and it cost three build cycles before an instrument built into the refusal settled it in one. A related note already existed saying a single-arm coproduct is a type alias; it was filed against breakage at the IMPORT site, was read as being about that consumer, and the single-arm form was written anyway -- a note correct about the mechanism but filed under one consumer reads as covered and is worse than no note.", + + "RUNG FOUND AT: below the ladder -- silent wrongness at the declaration, with the author's intent and the parsed shape diverging and nothing reporting it. ATTAINABLE CEILING: structurally guaranteed. The parser knows the arm count and the `=` form at the moment it chooses between alias and coproduct, so it can refuse a one-arm sum outright, or admit it as a one-arm coproduct, rather than silently reclassifying it. NEXT-RUNG TRIGGER: v1.compiler.parse distinguishes an intended one-arm sum from an alias -- SUFFICIENT FOR no declaration to change kind as a function of its arm count without a located diagnostic, so a consumer reading a roster either reads it or is told why it cannot. STRUCTURALLY IMPOSSIBLE would additionally require the surface to make the two forms unconfusable, which is a language-design question this row does not decide.", + ], + + evidence: [ + DeclarationRef { module_path: "std.machine_constraints", decl_name: "WidthResolution", field: WholeDeclaration }, + DeclarationRef { module_path: "v1.compiler.infer_resolve", decl_name: "type_arg_kind_inhabitance", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/recurring_failure_mode/type_variable_means_both_declared_generic_and_inference_variable.dag b/dag/gunbc/recurring_failure_mode/type_variable_means_both_declared_generic_and_inference_variable.dag new file mode 100644 index 00000000000..676c150261e --- /dev/null +++ b/dag/gunbc/recurring_failure_mode/type_variable_means_both_declared_generic_and_inference_variable.dag @@ -0,0 +1,28 @@ +module gunbc.recurring_failure_mode.type_variable_means_both_declared_generic_and_inference_variable + +import std.types { NonEmptyStr } +import gunbc.recurring_failure_mode { RecurringFailureMode } +import std.decl_ref { DeclarationRef, WholeDeclaration } + +data type_variable_means_both_declared_generic_and_inference_variable: RecurringFailureMode = RecurringFailureMode { + identity: "type_variable_means_both_declared_generic_and_inference_variable" as NonEmptyStr, + + receipts: [ + "TypeVariable means both declared generic and inference variable. INVALID STATE: one name, `v1.std.core` InferredNode `TypeVariable`, carries TWO materially different facts -- (1) a DECLARED GENERIC TYPE PARAMETER, inserted into the environment by v1.compiler.infer_env env_with_type_variable_bindings, and (2) an UNRESOLVED INFERENCE VARIABLE standing for a value whose type is not yet pinned. HARM: a consumer that asks a binding whether its inferred is a TypeVariable cannot tell which fact it just learned, and the two have opposite consequences. This is the DESIGN section 3 meaning fork -- one name, two meanings -- inside the seed's own vocabulary.", + + "RECEIPT, 2026-09-20, session gentle-seal-490. A new floor wall refusing a TYPE PARAMETER in value position keyed on `is_type_variable(binding.resolved.inferred)`. Because a lambda binder carries meaning (2) mid-inference, the wall refused 24 ORDINARY VALUE BINDERS across 11 corpus modules on CI: `e` in a fold lambda (gunbc.fabric.fabric_cell_effect x4), `item` in a value binder (gunbc.host.host_converge x2), plus evidence x3, s x2, p x2, and key, first, report, o, paths, path, line, seg, text, t. The wall intended to close a silent-wrongness class committed that class itself.", + + "THE DISCRIMINATOR ALREADY EXISTED AND IS NOT A SPLIT OF THE FORK. v1.compiler.infer_resolve fn_type_param_names is the authority for a declaration's DECLARED type-parameter names -- it is what env_with_type_variable_bindings is handed in the first place. Asking whether a name is in the ENCLOSING DECLARATION's roster is identity-keyed against a declared list and cannot be satisfied by a binder's mid-inference provenance. The roster is carried to the check site on v1.compiler.infer InferScope, which already holds locals, body_locals, match_bound_names, lambda_param_provenance and in_flight_lambda_param_names -- the same family of scope-bound naming facts.", + + "THE FIX ROUTES AROUND THE FORK AND DOES NOT CLOSE IT, stated so this row is not read as retired. TypeVariable still carries both meanings after the repair; what changed is that one consumer stopped asking it the ambiguous question. Every other consumer that asks a TypeVariable what it means is still exposed, and this row stays open until the two facts are separately representable.", + + "REJECTED REPAIRS, recorded so they are not re-walked. Keying on SPELLING -- the 24 refused names are lowercase while this corpus's declared generics are N, T, R, C, U -- is a stringly proxy that fails silently the day a declaration writes `fn f`. Keying on v1.compiler.infer InferScope caller_decl_name is a fail-open: that field is set from a caller's decl_name in one arm and to the literal `` in another, so a wall keyed on it silently admits everything reaching the second arm.", + + "RUNG FOUND AT: mitigatable -- the ambiguity is live and consumers must each avoid it by construction. ATTAINABLE CEILING: structurally impossible, by giving the two facts separate constructors so a consumer cannot ask one question and receive the other. NEXT-RUNG TRIGGER: InferredNode distinguishes a declared generic parameter from an unresolved inference variable -- SUFFICIENT FOR no consumer to be able to conflate them, so the discriminating question is unwritable rather than merely avoided.", + ], + + evidence: [ + DeclarationRef { module_path: "v1.compiler.infer_env", decl_name: "env_with_type_variable_bindings", field: WholeDeclaration }, + DeclarationRef { module_path: "v1.compiler.infer_resolve", decl_name: "fn_type_param_names", field: WholeDeclaration }, + ], +} diff --git a/dag/gunbc/scm/commit_closure.dag b/dag/gunbc/scm/commit_closure.dag index 7ec33d93584..a2b75474b17 100644 --- a/dag/gunbc/scm/commit_closure.dag +++ b/dag/gunbc/scm/commit_closure.dag @@ -75,7 +75,24 @@ module gunbc.scm.commit_closure // AND THE RUNG OF THE DIGEST ITSELF IS NOT AUTHENTICATION. ObjectId is fnv1a64 structural. It // establishes structural consistency and refuses collisions at the current local rung; it is not an // adversarial or cross-tenant cryptographic commitment, and calling this "Merkle" must not import -// that stronger claim. dissolve-on: a computing cryptographic digest reachable from .dag. +// that stronger claim. +// +// DISSOLVE-ON, CORRECTED 2026-09-21 (gunbc#11996) -- THE SECOND ROW CARRYING THIS TRIGGER. It read +// "a computing cryptographic digest reachable from .dag", the same artifact-shaped condition +// gunbc.scm.object_store carried, and that condition is NOW SATISFIED: extdeps.crypto.sha2 computes +// FIPS 180-4 SHA-256 in the substrate, witnessed by test.claim.sha256_fips_witness_test (the +// COMPUTATION; no mint over it survives that change, and both the mint and its binding were +// withdrawn on measurement). Read literally this row retires today while ObjectId is still fnv1a64 and the sentence +// above -- that this is not an adversarial commitment -- is still exactly true. A trigger naming +// less than the capability it restores will be satisfied while the capability stays dead +// (DESIGN 4b(3)). +// +// THE TRIGGER IS THE SAME CAPABILITY object_store NAMES, AND IT IS NOT A SECOND ROW ABOUT A SECOND +// FACT: this module's digest IS ObjectId, so the two dissolve together or neither does. Native +// emission of the extdeps.crypto.sha2 closure -- SUFFICIENT FOR v2 to emit and build itself with +// node identity minted from a cryptographic family. When ObjectId changes family, this paragraph +// and object_store's declared rung delete in one motion; until then "Merkle" here continues to mean +// structural consistency and not authentication. import std.types { Bool, Int, List, list_length } import std.algebra { Cons, Empty, FreeSemigroup } diff --git a/dag/gunbc/scm/object_store.dag b/dag/gunbc/scm/object_store.dag index e4740594719..9d5ac94155f 100644 --- a/dag/gunbc/scm/object_store.dag +++ b/dag/gunbc/scm/object_store.dag @@ -45,10 +45,44 @@ import v2.std.node { // about stability, reconstruction and identity-free combination, and it is NOT adequate as a durable // intersubjective content identity for cross-party agreement: a 64-bit non-cryptographic digest is a // locator, and treating a locator as an identity witness is a defect class this repository has already -// met elsewhere. No stronger option exists in .dag today -- the sha256 surfaces in std.content_hash and -// extdeps.crypto.hash VALIDATE hex rather than computing it, and the v1 seed's builtin registry is -// frozen against new rows. dissolve-on: a computing cryptographic digest reachable from .dag, at which -// point ObjectId changes family and this note deletes. +// met elsewhere. +// +// CORRECTED 2026-09-21 (gunbc#11996). This note said "No stronger option exists in .dag today -- the +// sha256 surfaces in std.content_hash and extdeps.crypto.hash VALIDATE hex rather than computing it". +// That was true when written and is now FALSE: extdeps.crypto.sha2 computes FIPS 180-4 SHA-256 over +// List in the substrate, witnessed by test.claim.sha256_fips_witness_test. A std.content_hash +// mint over it, and std.fabric_storage's object refs bound to that mint, were both authored in that +// change and both withdrawn in it. Interpreted SHA-256 made every store consumer two to three hundred +// times slower and cancelled the required floor, and the mint's own inhabitance claim performed +// 244,396 eval steps against a 72,300-step budget, so the seam could not carry an executing claim +// either (gunbc.recurring_failure_mode fabric_object_identity_is_a_structural_locator carries both +// receipts and the trigger). What is available today is the COMPUTATION, not a mint over it. +// +// AND THE OLD TRIGGER HAD TO BE REPLACED, NOT JUST THE REASON -- this is the DESIGN 4b(3) defect the +// row was one merge away from committing. The dissolve-on read "a computing cryptographic digest +// reachable from .dag", and that condition is NOW SATISFIED. Read literally it retires this row +// today, while the capability it exists to protect is still completely dead: ObjectId would remain +// fnv1a64, and nothing would be left saying so. A trigger must name the CAPABILITY, never an +// artifact that would contribute to one. +// +// WHY THE CAPABILITY IS NATIVE EMISSION AND NOT THROUGHPUT, derived rather than assumed. ObjectId is +// v2.std.node's Hash by alias, for the measured reason stated below, and that Hash is the COMPILER'S +// OWN structural node identity -- it is computed for every node of every compile, including the +// compiles by which v2 emits itself (DESIGN section 7). extdeps.crypto.sha2 does not emit: +// UInt32 is Compose> over the Phantom carrier, so it is a STRUCTURAL operand, +// and std.operator_realization operator_realization_for routes Add/Sub/Mul/Div/Mod on a structural +// operand to structural_arithmetic_refusal -- a typed located refusal, not a slow path. So retyping +// Hash onto sha2 today does not yield a slower compiler, it yields one that cannot be emitted at +// all. That is a capability boundary and not a cost tradeoff, which is why no throughput measurement +// decides it. The class is gunbc.recurring_failure_mode +// bounded_natural_arithmetic_evaluated_as_unbounded_int, whose own prerequisite is MachineWidth +// reified as a value. +// +// DISSOLVE-ON, RESTATED AS THE CAPABILITY: native emission of the extdeps.crypto.sha2 closure -- +// SUFFICIENT FOR v2 to emit and build itself with node identity minted from a cryptographic family, +// so that the digest carrying cross-party agreement is the same digest the compiler computes. When +// that holds, ObjectId changes family with Hash and this note deletes. A SHA-256 bridge existing, +// or std.fabric_storage consuming one, does not satisfy it and neither did satisfy it. // // ObjectId IS v2.std.node's Hash rather than a parallel alias over the same underlying digest type. // The typechecker treats two aliases of one primitive as distinct, so the first cut -- ObjectId diff --git a/dag/gunbc/seed_growth_admission.dag b/dag/gunbc/seed_growth_admission.dag index c56ac00ffd5..f23c539e743 100644 --- a/dag/gunbc/seed_growth_admission.dag +++ b/dag/gunbc/seed_growth_admission.dag @@ -24,6 +24,7 @@ import gunbc.decl_facts_authored_string_attach_seed_growth { } import gunbc.keyed_declaration_read_seed_growth { keyed_declaration_read_seed_growth_justification } import gunbc.census_memo_seed_growth { census_memo_seed_growth_justification } +import gunbc.kind_reflection_seed_growth { kind_reflection_seed_growth_justification } import gunbc.typed_module_store_seed_growth { typed_module_store_seed_growth_justification } import gunbc.claim_unwind_seed_growth { claim_unwind_seed_growth_justification } import gunbc.claim_batch_function_preflight_seed_growth { claim_batch_function_preflight_seed_growth_justification } @@ -266,6 +267,7 @@ fn seed_growth_justification_roster() -> List { shared_fill_attribution_seed_growth_justification, source_root_eval_driver_seed_growth_justification, census_memo_seed_growth_justification, + kind_reflection_seed_growth_justification, typed_module_store_seed_growth_justification, data_eval_counters_seed_growth_justification, declaration_index_seed_growth_justification, diff --git a/dag/std/fabric_storage.dag b/dag/std/fabric_storage.dag index 7a3e8a98b9b..9cec51f1595 100644 --- a/dag/std/fabric_storage.dag +++ b/dag/std/fabric_storage.dag @@ -53,14 +53,70 @@ fn fabric_object_preimage(object: FabricObject) -> NonEmptyStr { join(["fabric_storage.object;", link_words, "B", to_string(length(object.body as String)), ":", object.body as String], "") as NonEmptyStr } +// THE DIGEST IS THE STRUCTURAL FAMILY, AND THAT IS A DECLARED STALL, NOT A CHOICE. +// +// Fnv1a64Structural is sixty-four non-cryptographic bits. Against an adversary that is a LOCATOR, +// not an identity -- a second preimage is findable at birthday work no attacker would notice paying +// -- so a store built on it can say "these are the bytes I have" and cannot say "these are the +// bytes you asked for". The verified-cache guarantee this module exists to support is exactly the +// second sentence, so minting a locator and calling the check verification would be rung inflation +// in the DESIGN 4b(1) sense, and this module does not call it that. +// +// THE COMPUTATION EXISTS AND A MINT OVER IT WAS TRIED HERE AND WITHDRAWN, BOTH HALVES MEASURED. +// extdeps.crypto.sha2 sha256_hex reaches the FIPS 180-4 digest with neither a write nor a spawn, and +// gunbc#11996 authored a std.content_hash mint over it and bound it to this function. Two independent +// measurements withdrew it. FIRST, at this module's real consumers: interpreted SHA-256 on every put, +// every verified get and every closure step made each consumer of gunbc.fabric.fabric_storage_file_store +// two to three hundred times slower and cancelled the required floor at its cap. SECOND, at the mint's +// own inhabitance claim: one interpreted SHA-256 of a single-block preimage performed 244,396 eval steps +// against the required floor's 72,300-step new-witness budget, so the seam could not carry an executing +// claim at floor rate either -- a mint no claim can run is a declaration DESIGN 3c refuses. The row is +// gunbc.recurring_failure_mode +// fabric_object_identity_is_a_structural_locator: rung, ceiling, the receipt, and the trigger -- +// native emission of the sha2 closure SUFFICIENT FOR those consumers to run at floor rate. Until +// that capability lands, this ref stays structural and the refusal to call it verification stays +// in force. The structural mint is pure, which is what keeps fabric_object_verified a pure check; the shell arm +// (extdeps.crypto.hash sha256sum over a PATH) was refused for the same reason under either family: +// it would write every in-memory preimage to a file before hashing it, re-opening the custody gap +// verification exists to close. fn fabric_object_ref_of(object: FabricObject) -> FabricObjectRef { FabricObjectRef { digest: content_hash_of_value(value: fabric_object_preimage(object: object)) } } +// BYTES TOGETHER WITH THE REF THAT NAMES THEM, AND THE ONLY WAY TO BUILD ONE IS TO MINT IT. +// +// This exists because the alternative was measured and is a filed defect class: a function taking +// (object, ref) as PEER PARAMETERS admits a ref for some OTHER object, and every downstream +// guarantee citing that ref then describes a different value while reading exactly like a guarantee +// about this one (gunbc.recurring_failure_mode subject_and_its_digest_as_independent_parameters). +// sole_constructor on FabricObjectRef does not close it -- that governs how a ref is MINTED, not +// whether these bytes are the bytes it names. +// +// sole_constructor here is what closes it: no module can assemble a FabricAddressedObject, and the +// one producer derives the ref FROM the object it stores beside it. So a mismatched pair has no +// constructor rather than being refused by a check somebody has to remember to write +// (DESIGN 5: construction over validation). Consumers that need both halves take this one value, +// which is why the store's put path no longer passes them separately. It stands on its own and +// does not depend on which family the ref is minted in. +type FabricAddressedObject sole_constructor { + object: FabricObject + ref: FabricObjectRef +} + +fn fabric_object_addressed(object: FabricObject) -> FabricAddressedObject { + FabricAddressedObject { object: object, ref: fabric_object_ref_of(object: object) } +} + fn fabric_object_ref_wire(object: FabricObjectRef) -> NonEmptyStr { serialize_content_hash(hash: object.digest) } +// The inverse of fabric_object_ref_wire for a READ. It admits exactly the family this module mints +// -- sixteen hex digits of the structural family -- so a ref this module could not have produced +// is refused at the wire rather than parsed into a value fabric_object_verified would then reject as +// cross-family. When the mint changes family (the row named above), this parse changes with it: the +// two are one fact read in two directions, and gunbc#11996 recorded the failure mode of moving one +// without the other (gunbc.recurring_failure_mode a_carrier_swap_moves_the_mint_and_strands_the_parser). fn fabric_object_ref_of_wire(wire: String) -> FabricObjectRef? { match content_hash_from_structural_digest(digest: wire) { Present { value: h } => Present { value: FabricObjectRef { digest: h } } @@ -152,6 +208,16 @@ type FabricClosureRead | FabricClosureBudgetExhausted { at: FabricObjectRef, bound: Int } | FabricClosureRefused { fault: FabricStorageFault } +// THE COMPARISON IS INLINE, AND EXTRACTING IT IS A DEFECT RATHER THAN A TIDY-UP. An earlier +// revision of this change split the equality arm into a helper taking (asked, content, actual) -- +// the bytes and a digest of those bytes as PEER PARAMETERS with no arm relating them. That signature +// admits a digest of something OTHER than the value beside it: called with A's ref and B's bytes it +// reports B verified under A's name, having never hashed B. The class is already in this tree as +// gunbc.recurring_failure_mode subject_and_its_digest_as_independent_parameters, and sole_constructor +// on FabricObjectRef does not close it -- that walls how a ref is MINTED, not whether these bytes are +// the bytes it names. Keeping the comparison inside the arm that just derived `actual` FROM `content` +// means the relation is carried by the binding rather than by a caller's discipline, so the bad call +// has no expression (DESIGN 5: construction over validation). // VERIFYING FETCHED BYTES AGAINST THE REF THEY WERE ASKED FOR. The single authority every handler // routes a fetched object through: a store that answered with other bytes is corrupt, never trusted. type FabricObjectVerification diff --git a/dag/std/machine_constraints.dag b/dag/std/machine_constraints.dag index 4c865d84032..3c7a693c531 100644 --- a/dag/std/machine_constraints.dag +++ b/dag/std/machine_constraints.dag @@ -1,9 +1,79 @@ module std.machine_constraints import std.constructors { Phantom } +import std.types { NonEmptyStr } +import std.dissolution { DissolutionCondition, unbound_dissolution } -type MachineWidth +// A TYPE ARGUMENT IS NOT A QUANTITY AND NOT A VALUE. What stands in MachineWidth's parameter +// position is WHICH width this type is -- a type-level index. std.measure BitWidth +// (Measure) is the measured amount of bits and is what REIFYING a width +// PRODUCES, not what the argument denotes; putting it here was this declaration's first draft and a +// category error. A value-carrying coproduct was its second: see the enumeration below. +// +// WHAT THIS POSITION ADMITS: a Nat literal, or the named type PointerWidth. The wall checks the +// NAMED half against this declared kind; literal admissibility is a language-level rule rather than +// a per-kind fact, which is a DECLARED FRONTIER stated at machine_width_literal_roster_frontier +// below rather than a silent gap. +// +// FIVE CARRIERS WERE TRIED FOR A PER-KIND LITERAL ROSTER AND EVERY ONE FAILED ON THE SAME AXIS. +// Recorded so the next reader does not re-walk it: +// payload (StaticWidth { bits: Nat }) -- value-level in a type-level position: E0573 at the 4 +// live MachineWidth sites, a generated panic! arm on the field accessor, and a +// record nothing ever constructs because the corpus writes a bare 32. +// arm named for the type -- a coproduct arm PointerWidth beside `type PointerWidth` puts two +// claimants on one name in one module; which wins decides the emission, so it is +// resolution-order dependent. +// alias -- carries named inhabitants cleanly but cannot carry Nat without +// minting arms, which is the collision above. +// data row -- no resolver reads data initializers: ModuleItemDataValue appears +// nowhere in v1.compiler.infer_resolve, infer_env or infer, and the one precedent that consumes +// a symbol-index hit reads decl.inferred, the declared TYPE, not the initializer. +// bare unit variants -- EMITS AND COMPILES, verified cross-module with markers minted and +// a correctly qualified use line, and STILL cannot express per-kind literal admissibility: a +// bare 32 is a literal, not a name, so admitting it needs a compiler-held convention mapping +// literals to an arm spelled StaticWidth. That is the stringly proxy this wall exists to remove. +// The last entry is the one worth reading: it is the option that looks viable and is not. -type PointerWidth +// THE KIND, AND ITS ARMS ARE THE ROSTER. The check asks whether the argument is one of these +// DECLARED arms, so a kind naming PointerWidth cannot be satisfied by an unrelated declaration that +// happens to have the right shape -- which is the difference between this and the variant-shape +// proxy it replaced. +// IT IS A COPRODUCT RATHER THAN AN ALIAS FOR A MEASURED REASON. `type WidthResolution = PointerWidth` +// is a SINGLE-ARM form, which this language parses as a TYPE ALIAS; the declaration then reaches the +// kind check as a bare nominal node carrying neither a resolved target nor children, so the check +// could read nothing and refused its own declared inhabitant. The refusal printed it: +// 'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=0]' +// Two arms make it a real coproduct whose children ARE readable. Unit variants in type-argument +// position are minted as zero-sized markers and compile, verified cross-module with a correctly +// generated qualified use line, so PointerWidth keeps working at the 4 live MachineWidth sites. +// StaticWidthIndex names the literal arm so the roster is complete rather than implicit; a Nat +// literal is admitted by the language-level rule described above, not by matching this arm. +type WidthResolution + = StaticWidthIndex + | PointerWidth + +type MachineWidth + +// THE DECLARED FRONTIER THIS FILE'S HEADER CITES, NOW ACTUALLY DECLARED (review 69766). The header +// said literal admissibility was "a DECLARED FRONTIER stated at machine_width_literal_roster_frontier +// below" and no such declaration existed -- the name resolved to exactly one occurrence, the comment +// claiming it. That is the DESIGN 3 citation defect in its purest form, and DESIGN 4c is why the +// repair is a carrier rather than a reworded sentence: a dissolution condition belongs in a typed +// carrier, and one living only in an annotation is unreachable by the census that exists to find it. +// +// WHAT IS ACTUALLY OPEN. The kind above rosters the NAMED inhabitants, and the wall checks an +// argument against them -- witnessed in both directions by +// test.claim.type_argument_kind_inhabitance_witness_test. A NAT LITERAL is admitted in any kinded +// position by a LANGUAGE-LEVEL rule, not by matching StaticWidthIndex or any other arm, so this kind +// cannot say WHICH literals it admits: MachineWidth<32> and MachineWidth<7> are equally accepted and +// only the first names a real machine width. The five carriers tried for a per-kind literal roster +// are enumerated in this file's header, and every one fails on the same axis. +// +// THE RUNG IS SET BY THIS AXIS AND NOT BY THE NAMED ONE (DESIGN 4b(1): a class's rung is the minimum +// across its paths). The named half is walled; the literal half is not, so this position is NOT +// fully grounded and must not be cited as such. +data machine_width_literal_roster_frontier: DissolutionCondition = unbound_dissolution( + description: "Per-kind literal admissibility at a kinded type-argument position. A Nat literal is admitted by a language-level rule rather than by the kind's declared arms, so MachineWidth cannot express WHICH literals it admits and a nonsensical width typechecks exactly as a real one does. DISSOLVES ON a kind being able to declare its admissible literal domain -- SUFFICIENT FOR the wall to refuse a literal outside it at the same position and with the same located diagnostic it already gives a named non-inhabitant, so the literal and named halves reach one rung. Five carriers were tried and enumerated at this module's header; each fails because a literal is not a name, so admitting one needs a compiler-held convention mapping literals to an arm -- the stringly proxy this kind exists to remove. Not dissolved by adding an arm, and not by widening the wall to refuse every literal, which would refuse the four live MachineWidth sites the corpus writes as bare integers." as NonEmptyStr +) type Compose = Phantom diff --git a/dag/test/claim/type_argument_kind_inhabitance_witness_test.dag b/dag/test/claim/type_argument_kind_inhabitance_witness_test.dag new file mode 100644 index 00000000000..ea8e2ea152b --- /dev/null +++ b/dag/test/claim/type_argument_kind_inhabitance_witness_test.dag @@ -0,0 +1,199 @@ +module test.claim.type_argument_kind_inhabitance_witness_test + +import gunbc.compile_diagnostic_census { + CompileDiagnosticCensusRow, + CensusObserved, + CensusNotRunnable, + census_blocking_rows, + census_rows_of_class, + census_total_count, + censuses_differ_by_exactly_the_target_key, +} +import std.types { String, Bool, Int } + +// THE WALL THAT MAKES A KINDED TYPE PARAMETER MEAN SOMETHING, AND ITS POSITIVE CONTROL. +// Class rows: gunbc.recurring_failure_mode.single_arm_sum_parses_as_an_alias_and_becomes_unreadable +// and .type_variable_means_both_declared_generic_and_inference_variable. +// +// WHY THIS FILE EXISTS AT ALL. gunbc#11819 recorded three refusals firing with located +// diagnostics AND the kind's own declared inhabitant refusing alongside them. A wall that refuses +// everything has meaningless reds, so none of those three were citable -- the PR said so itself. +// The discriminating pair is therefore the whole point: the same wall must refuse a non-inhabitant +// and ADMIT the declared one, on one run, or it has established nothing in either direction. +// +// THE ORACLE NAMES THE CLASS, NOT THE OUTCOME, for the reason +// test.claim.optional_at_required_position_witness_test states at its own oracle: a +// compiles/does-not-compile boolean stays red through ANY refusal, so a RED here would keep +// reporting this wall while TypeArgumentKindMismatch never fired. Each RED counts blocking rows of +// the named class; each control asserts ZERO of that class. -1 on the not-runnable arm keeps +// could-not-measure out of both >= 1 and == 0. +// +// THE SOURCES DECLARE THEIR OWN KIND AND DO NOT IMPORT std.machine_constraints. That is +// deliberate and it is what lets this file run: compile_dag_diagnostic_census resolves synthetic +// sources against the live checkout, and a module that leans on live-tree CONTENT must declare +// ReadsLiveTree, which the required floor declines before the fold sees it -- authored evidence +// rather than enrolled. A self-contained kind tests the MECHANISM (does a kinded position consult +// the kind's declared arms) rather than one corpus declaration, so it is both stronger evidence +// and executable. The corpus-level reading -- that std.integer's four live +// MachineWidth sites compile -- is integration evidence reported in the PR, not a +// second copy of this claim. +// +// TWO ARMS, NOT ONE, AND DO NOT SIMPLIFY IT. WidthResolution is a two-arm coproduct here for the +// same measured reason std.machine_constraints gives: a single-arm `type K = A` parses as a TYPE +// ALIAS, reaches the check as a bare nominal with no resolved target and no children, and the wall +// then refuses its own inhabitant with no diagnostic saying why. +// +// dissolve-on: none -- permanent regression control per DESIGN 4b(4). + +fn kind_mismatch_blocking_count(source: String) -> Int { + match compile_dag_diagnostic_census(source) { + CensusObserved { rows: rows } => + census_total_count(rows: census_blocking_rows(rows: census_rows_of_class(rows: rows, wanted: "TypeArgumentKindMismatch"))) + CensusNotRunnable { cause: _ } => -1 + } +} + +fn type_param_value_position_blocking_count(source: String) -> Int { + match compile_dag_diagnostic_census(source) { + CensusObserved { rows: rows } => + census_total_count(rows: census_blocking_rows(rows: census_rows_of_class(rows: rows, wanted: "TypeParameterInValuePosition"))) + CensusNotRunnable { cause: _ } => -1 + } +} + +fn kind_named_non_inhabitant_source() -> String { + "module kindprobe.neg_named\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\ntype Use = Width\n\n" +} + +// A plain String where the kind admits StaticWidthIndex or PointerWidth. +test fn a_named_non_inhabitant_at_a_kinded_position_must_refuse() -> Bool { + kind_mismatch_blocking_count(source: kind_named_non_inhabitant_source()) >= 1 +} + +fn kind_unrelated_type_source() -> String { + "module kindprobe.neg_unrelated\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\ntype UnrelatedType\ntype Use = Width\n\n" +} + +// THE RED THAT SEPARATES THIS WALL FROM A SHAPE PROXY. UnrelatedType is bodyless and therefore +// STRUCTURALLY IDENTICAL to the arms it is being offered against. Only a check consulting the +// kind's DECLARED arms can refuse it; a check reading variant shape admits it. This is the +// property five candidate carriers were chasing, and it is the reason the kind is a coproduct. +test fn a_bodyless_type_of_the_same_shape_as_an_arm_must_still_refuse() -> Bool { + kind_mismatch_blocking_count(source: kind_unrelated_type_source()) >= 1 +} + +fn type_param_in_value_position_source() -> String { + "module kindprobe.neg_valuepos\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\nfn any_param(x: T) -> Int {\n T\n}\n\n" +} + +// THIS PINS A DEFECT, NOT A GUARANTEE. READ THE ASSERTION BACKWARDS. +// +// A type parameter is not a value: `T` in the body of `fn any_param(x: T) -> Int` names a TYPE +// where a value is required. The compiler HAS the diagnostic for it -- v1.compiler.core declares +// TypeParameterInValuePosition and gives it SeverityError/GateBlocking, and v1.compiler.infer +// constructs it at two sites. It still never fires, and the reason is not subtle: the seed's +// binding_resolves_to_type_parameter is a literal `false` +// (src/v1/stage0/src/v1_compiler_infer.rs), so the guard admitting the refusal is unreachable BY +// CONSTRUCTION. The corrected predicate v1.compiler.infer declares -- +// name_is_enclosing_declared_type_parameter, asking the DECLARED type-parameter roster rather than +// a binding's inferred provenance -- occurs fourteen times in the .dag and ZERO times in the seed. +// +// MEASURED 2026-09-21 on a compiler built from this tree at e20e8c5004 (control: a bogus flag +// refuses with exit 2): a module whose entire content is that function compiles with exit 0, ZERO +// blocking errors, and EMITS. v1.compiler.infer's own note records the consequence -- it renders +// `{ t }` into a crate that then fails rustc E0425 -- so the silence is not harmless, it is +// deferred to a later toolchain with the location lost. +// +// SO THIS IS AN INERT WALL, which DESIGN 4b names directly: the tier where the machinery exists but +// nothing gates on it, and an inert lens is itself a lie. gunbc#11819's body lists this refusal as +// FIRING. It does not fire on a built compiler, and that claim is retracted here rather than +// carried: what that PR hand-patched into the seed was the KIND check, not this one, and the +// stage0 regen that would carry the rest has not run. +// +// WHY == 0 AND NOT >= 1: a claim asserting the refusal would be red until the regen lands, and a +// red witness cannot merge, so the choice is to pin the hole or to delete the evidence. This is the +// corpus's existing shape for exactly that (gunbc.guarantee_probe_corpus ExpectSilentAcceptanceHole: +// a hole that climbs is rewritten as the blocking-refusal assertion and stays as a permanent +// regression control). DO NOT READ THIS GREEN AS A GUARANTEE. It goes RED the day the wall starts +// working, and that red is the signal to flip the assertion to >= 1, not to relax it. +// +// PROBE ADEQUACY, so a zero here is the compiler's silence and never this harness failing to reach +// the judgment: a_named_non_inhabitant_at_a_kinded_position_must_refuse runs on this same harness, +// in this same file, on this same run, and DOES count a blocking row. The harness reaches +// judgments; this one is simply not made. +// +// NEXT-RUNG TRIGGER: the stage0 mirror regenerated from v1.compiler.infer -- SUFFICIENT FOR the +// built compiler to consult the declared type-parameter roster, so a type name in value position +// refuses at its own location rather than at rustc's. +test fn a_type_parameter_in_value_position_does_not_yet_refuse_and_this_pins_that_hole() -> Bool { + type_param_value_position_blocking_count(source: type_param_in_value_position_source()) == 0 +} + +fn kind_declared_inhabitant_source() -> String { + "module kindprobe.pos_inhabitant\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\ntype Use = Width\n\n" +} + +// ── WHY THE POSITIVES ALSO REQUIRE EMISSION, AND WHY A DIFFERENTIAL DOES NOT WORK HERE ─────── +// +// AN EXTERNAL REVIEW RAISED A REAL HOLE: a positive control asserting ZERO rows of ONE named class +// stays green when its fixture is rejected for some OTHER blocking reason. It measures the absence +// of one diagnostic, not admission. That is this PR's own subject one layer out. +// +// THE REPAIR IS compile_dag_rust_emit_check, the same instrument +// test.claim.optional_at_required_position_witness_test uses on its own positives. Emission cannot +// succeed over ANY blocking diagnostic, so a fixture rejected for an unrelated reason now fails +// instead of passing as a control -- and it is the stronger reading besides, since it establishes +// that the admitted program EMITS rather than merely that one diagnostic is absent. +// +// THE REVIEW'S OTHER SUGGESTION -- require zero TOTAL blocking diagnostics -- IS REFUSED, in +// gunbc.compile_diagnostic_census's own words: the row set is the whole compile's, closure +// included, so "a witness reading census_total_count == 0 as 'the fixture still compiles' is +// asserting a property of the corpus, and on any importing fixture that assertion is permanently +// false: a check whose RED is unauthorable, which DESIGN 4b names a decoration rather than a weak +// wall." These fixtures import std.types, so they import a closure. +// +// A DIFFERENTIAL WAS TRIED AND DOES NOT HOLD HERE. RECORDED SO THE NEXT READER DOES NOT RE-WALK IT. +// That module names a second exact form: a differential between two censuses whose sources differ +// on one axis, where the shared closure cancels. Two sources identical but for the type argument +// were built and compared with censuses_differ_by_exactly_the_target_key. It FAILED, and bisecting +// it by execution showed the comparator was right and the claim was wrong: +// red_has_the_key_once PASS (class, subject 'Width' and blocking are scoped right) +// green_has_the_key_zero PASS (the wall genuinely admits the inhabitant) +// blocking_totals_differ_by_exactly_one FAIL +// total_row_counts_differ_by_exactly_one FAIL +// So the substitution changes MORE than the one key: a refused type argument leaves the declaration +// that used it unresolved, and that cascades further rows in the same compile. The refusal is not +// its own only consequence, which is precisely the condition a one-axis differential requires. +// Anchoring the unused declaration in both sources did not fix it, because the cascade is +// downstream of the refusal rather than of the spelling. The differential was removed rather than +// widened -- a comparator tuned until it agrees is the defect this file exists to catch. +// THE POSITIVE CONTROL, AND THE DEFECT gunbc#11819 LEFT OPEN. Until this passes, every red above +// is meaningless: a wall that refuses its own declared inhabitant refuses everything, and refusing +// everything is not discrimination. This claim is what makes the other three citable. +test fn the_kinds_own_declared_inhabitant_must_be_admitted() -> Bool { + kind_mismatch_blocking_count(source: kind_declared_inhabitant_source()) == 0 + && compile_dag_rust_emit_check(kind_declared_inhabitant_source(), "src/kindprobe_pos_inhabitant.rs", [], []) +} + +fn kind_second_arm_source() -> String { + "module kindprobe.pos_other_arm\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\ntype Use = Width\n\n" +} + +// THE SECOND ARM, because admitting exactly ONE name is indistinguishable from a hardcoded +// special case for that name. Both declared arms being admitted is what shows the check reads the +// kind's children rather than matching a spelling. +test fn the_kinds_other_arm_is_admitted_too_so_the_wall_is_not_pinned_to_one_name() -> Bool { + kind_mismatch_blocking_count(source: kind_second_arm_source()) == 0 + && compile_dag_rust_emit_check(kind_second_arm_source(), "src/kindprobe_pos_other_arm.rs", [], []) +} + +fn generic_fn_positive_source() -> String { + "module kindprobe.pos_generic_fn\n\nimport std.types { Int, String }\n\ntype WidthResolution\n = StaticWidthIndex\n | PointerWidth\n\ntype Phantom\ntype Width = Phantom\n\nfn passes_through(x: T) -> T {\n x\n}\n\n" +} + +// The value-position wall's own control: a generic function that uses T only as a TYPE is +// ordinary and must not be caught. +test fn a_generic_function_not_misusing_its_parameter_still_compiles() -> Bool { + type_param_value_position_blocking_count(source: generic_fn_positive_source()) == 0 + && compile_dag_rust_emit_check(generic_fn_positive_source(), "src/kindprobe_pos_generic_fn.rs", [], []) +} diff --git a/docs/plans/fabric-storage.md b/docs/plans/fabric-storage.md index cffaac6dc79..70c8e1a1242 100644 --- a/docs/plans/fabric-storage.md +++ b/docs/plans/fabric-storage.md @@ -67,5 +67,6 @@ The backend binds 127.0.0.1 and is reached only through `tailscale serve` on srv ## Known limits of the first realization -- **Weak digest:** object refs use the non-cryptographic structural digest (`std.content_hash` `content_hash_of_value`). A collision is refused at put (`FabricObjectCorrupt`), never read through. Moving to SHA-256 needs a SHA-256 over bytes the substrate can compute; at present it can only parse SHA-256 hex. +- **Weak digest, and the strong one is built and measured, not missing:** object refs mint through the non-cryptographic structural family (`std.content_hash` `content_hash_of_value`). A collision is refused at put (`FabricObjectCorrupt`), never read through. The computing SHA-256 exists in the substrate — `extdeps.crypto.sha2` `sha256_hex`, FIPS 180-4, witnessed by `test.claim.sha256_fips_witness_test`. What does **not** exist is a mint over it: gunbc#11996 authored one in `std.content_hash`, bound `fabric_object_ref_of` to it, and **withdrew both on measurement**. Interpreted, every put, every verified get and every closure step pays a SHA-256, and the spark pair_serving real-execution claims that append and re-read chains through `gunbc.fabric.fabric_storage_file_store` ran two to three hundred times slower than on main, cancelling the required floor at its cap. The mint's own inhabitance claim then failed the same way for a second, independent reason: one interpreted SHA-256 over a single-block preimage performed 244,396 eval steps against the required floor's 72,300-step new-witness budget (`v2.workflow.required_floor` `claim_eval_step_budget_for_identity`), and one compression block is the floor of that cost — so the seam could not carry an executing claim either, and a declaration with no executing consumer is what DESIGN §3c refuses. The row is `gunbc.recurring_failure_mode` `fabric_object_identity_is_a_structural_locator` (rung 1, ceiling 3, the run and the differential cited by id). **The blocker is native emission, not strength:** `UInt32` is `Compose>`, a structural operand, and `std.operator_realization` `operator_realization_for` routes arithmetic on a structural operand to `structural_arithmetic_refusal`, so the closure runs only under the interpreter (`gunbc.recurring_failure_mode` `bounded_natural_arithmetic_evaluated_as_unbounded_int`). The trigger is stated at capability grain: native emission of that closure sufficient for every consumer of the file store to run at floor rate. The shell-out alternative was refused for a **structural** reason under either family: `extdeps.crypto.hash` `crypto.Sha256Sum.File` takes a *path*, while `fabric_object_preimage` produces bytes **in memory**, so reaching it means a temp-file write per object — the custody gap (hash one file, store another) that verification exists to close — and makes minting an effect. Minting stays pure, so `fabric_object_verified` stays a pure check. A collision remains a **content-identity** failure that can enable an isolation failure given authorization and sharing; tenant isolation stands on its own and never on the digest. +- **When the family does change, the parse changes with it and the stored population is measured first.** `fabric_object_ref_of_wire` admits exactly the family the mint produces, so the two move together (`gunbc.recurring_failure_mode` `a_carrier_swap_moves_the_mint_and_strands_the_parser`). Moving refs from fnv1a64 to SHA-256 makes any object *already stored* under the old family unreadable, and not loudly: `fabric_object_verified` re-derives identity with the new mint, `fabric_object_ref_eq` maps `ContentHashCrossFamilyIncomparable` to `false`, and the read returns `FabricObjectCorrupt` for bytes that are perfectly intact. **Observed 2026-09-21 on srv1** (`gunbc.roadmap_dashboard_instance` `dashboard_instance_fabric_storage_root`): the store root was empty, so the population was zero at that reading; it must be re-measured when the trigger fires, and a conversion could not rewrite links alone — `gunbc.fabric_event_log` encodes the parent id **both** in the object's links and inside the serialized event body, so it has to go through the owning event codec and preserve the logical chain. - **Head probe bound:** a head's generations are probed upward from 1 (`gunbc.durable_cas_file_store` `cas_max_generation_probe`). A partition with more appends than that bound refuses its head read, as `FabricStoreRefused` carrying `CasUnreadableObservationBoundExceeded`. The chain walk has the same bound (4096). Compaction, meaning a snapshot object and a head that starts from it, is the remedy for both, and it belongs to step 5. diff --git a/src/v1/00_core.dag b/src/v1/00_core.dag index a98053bbf50..4d248482951 100644 --- a/src/v1/00_core.dag +++ b/src/v1/00_core.dag @@ -451,6 +451,8 @@ type CompilerDiagnostic | EqualityOnFunctionMember { type_name: String, member: String, span: SourceSpan } | EqualityMemberUnjudgeable { type_name: String, member: String, span: SourceSpan } | TypeArgumentArityMismatch { type_name: String, supplied: Int, declared: Int, span: SourceSpan } + | TypeArgumentKindMismatch { type_name: String, param_name: String, kind_name: String, supplied: String, span: SourceSpan } + | TypeParameterInValuePosition { name: String, span: SourceSpan } | OccurrenceTransportViolation { refusal: OccurrenceTransportRefusal } | ContainerSpellingUnrecognized { name: String, container_leaf: String, span: SourceSpan } | ServiceConfigReferenceJudgmentDeferred { @@ -676,6 +678,8 @@ fn diagnostic_to_span(d: CompilerDiagnostic) -> SourceSpan { EqualityOnFunctionMember { type_name: _, member: _, span: s } => s EqualityMemberUnjudgeable { type_name: _, member: _, span: s } => s TypeArgumentArityMismatch { type_name: _, supplied: _, declared: _, span: s } => s + TypeArgumentKindMismatch { type_name: _, param_name: _, kind_name: _, supplied: _, span: s } => s + TypeParameterInValuePosition { name: _, span: s } => s OccurrenceTransportViolation { refusal: refusal } => match occurrence_transport_refusal_diagnostic_span(refusal: refusal) { Present { value: span } => span @@ -785,6 +789,10 @@ fn diagnostic_to_message(d: CompilerDiagnostic) -> String { concat("equality admission for '", t, "' cannot be judged: ", m, " — '==' is refused rather than admitted on an unjudged member") TypeArgumentArityMismatch { type_name: t, supplied: s, declared: d, span: _ } => concat("type argument arity mismatch applying '", t, "': ", to_string(value: s), " type argument(s) supplied, ", to_string(value: d), " type parameter(s) declared") + TypeArgumentKindMismatch { type_name: t, param_name: p, kind_name: k, supplied: sup, span: _ } => + concat("type argument does not inhabit the declared kind applying '", t, "': parameter '", p, "' is declared '", k, "', and '", sup, "' is not one of its inhabitants") + TypeParameterInValuePosition { name: n, span: _ } => + concat("'", n, "' is a type parameter, not a value: a name bound as a type may not stand in an expression position") OccurrenceTransportViolation { refusal: refusal } => occurrence_transport_refusal_diagnostic_message(refusal: refusal) ContainerSpellingUnrecognized { name: n, container_leaf: leaf, span: _ } => @@ -912,6 +920,8 @@ fn diagnostic_disposition(d: CompilerDiagnostic) -> DiagnosticDisposition { EqualityOnFunctionMember { type_name: _, member: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } EqualityMemberUnjudgeable { type_name: _, member: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } TypeArgumentArityMismatch { type_name: _, supplied: _, declared: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } + TypeArgumentKindMismatch { type_name: _, param_name: _, kind_name: _, supplied: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } + TypeParameterInValuePosition { name: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } OccurrenceTransportViolation { refusal: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } ContainerSpellingUnrecognized { name: _, container_leaf: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateBlocking } ServiceConfigReferenceJudgmentDeferred { field: _, referenced_name: _, trigger: _, span: _ } => DiagnosticDisposition { severity: SeverityError, gate: GateAdvisoryTypecheck } @@ -1998,6 +2008,14 @@ fn file_transport_node(occurrence_identity: NodeOccurrenceIdentity, base_path: N make_transport_node(occurrence_identity: occurrence_identity, properties: props, children: [], body: none, span: span) } +// The property channel a type parameter's KIND annotation rides on. Named once here because the +// parser writes it (v1.compiler.parse collect_type_param_names) and the resolver reads it +// (v1.compiler.resolve), and a property key spelled at both ends is the same literal standing in +// two authorities -- DESIGN section 3. +fn type_param_kind_property_name() -> String { + "__param_kind" +} + fn find_property(props: List, prop_name: String, source_indices: Map) -> Node? { match props |> filter(p => field_init_node_name_at(n: p, source_indices: source_indices) == prop_name) |> first() { Present { value: fi } => Present { value: field_init_node_value(n: fi) } diff --git a/src/v1/02_parse.dag b/src/v1/02_parse.dag index b1fafde3dc9..31eb10ae908 100644 --- a/src/v1/02_parse.dag +++ b/src/v1/02_parse.dag @@ -7,7 +7,7 @@ import v1.std.core { Unmarked, TestMarked, Cardinality, Required, CardOptional, make_param_node, param_node_type_expr, param_node_default_value, make_field_node, field_node_name_at, field_node_type_expr, field_node_cardinality, field_node_default_value, field_node_from_key, - field_from_key_property_name, + field_from_key_property_name, type_param_kind_property_name, make_variant_node, variant_node_name_at, variant_node_fields, leaf_node_with_span, ExprData, NoExprData, ExprLiteral, ExprVar, ExprFieldAccess, ExprCall, ExprMethodCall, @@ -3097,26 +3097,80 @@ fn parse_optional_type_params(tokens: TokenStream, ctx: ParseContext) -> TypePar } } +// A KIND ANNOTATION IS CARRIED AS A PROPERTY, NEVER AS THE PARAMETER TYPE-EXPR. The type-expr of a +// generic parameter is a leaf bearing the parameter's own name, and that self-naming IS the marker +// every downstream consumer keys on to recognise a generic declaration (v1.compiler.infer +// param_is_generic_decl requires pname == tname). Writing the kind there would make `bits: Width` +// stop being a generic parameter at all, so the kind rides the existing property channel and the +// parameter keeps its identity. fn collect_type_param_names(tokens: TokenStream, ctx: ParseContext, params: List) -> TypeParamsResult { if tok_is_ident(tok: token_stream_first(stream: tokens)) { let r = expect_ident(tokens: tokens) let span = r.span let type_mint = mint_parsed_node_identity(ctx: ctx) let type_expr = leaf_type_node(occurrence_identity: type_mint.identity, name: r.name, span: span) - let param_mint = mint_parsed_node_identity(ctx: type_mint.ctx) - let param = make_param_node(occurrence_identity: param_mint.identity, name: r.name, type_expr: type_expr, default_value: none, span: span, name_span: span) + let kind_result = parse_optional_type_param_kind(tokens: r.tokens, ctx: type_mint.ctx) + let param_mint = mint_parsed_node_identity(ctx: kind_result.ctx) + let bare_param = make_param_node(occurrence_identity: param_mint.identity, name: r.name, type_expr: type_expr, default_value: none, span: span, name_span: span) + let param = with_type_param_kind_property(param: bare_param, kind: kind_result.kind) let next_params = list_push(params, param) - match eat(tokens: r.tokens, expected: ExpectComma) { + match eat(tokens: kind_result.tokens, expected: ExpectComma) { EatConsumed { token: _, tokens: __ec } => collect_type_param_names(tokens: __ec, ctx: param_mint.ctx, params: next_params) EatUnchanged { tokens: __eu } => - TypeParamsResult { params: next_params, tokens: r.tokens, ctx: param_mint.ctx } + TypeParamsResult { params: next_params, tokens: kind_result.tokens, ctx: param_mint.ctx } } } else { TypeParamsResult { params: params, tokens: tokens, ctx: ctx } } } +type TypeParamKindResult { + kind: Node? + tokens: TokenStream + ctx: ParseContext +} + +fn parse_optional_type_param_kind(tokens: TokenStream, ctx: ParseContext) -> TypeParamKindResult { + match eat(tokens: tokens, expected: ExpectColon) { + EatConsumed { token: _, tokens: __ec } => + let r = expect_ident(tokens: __ec) + let kind_mint = mint_parsed_node_identity(ctx: ctx) + TypeParamKindResult { + kind: Present { value: leaf_type_node(occurrence_identity: kind_mint.identity, name: r.name, span: r.span) }, + tokens: r.tokens, + ctx: kind_mint.ctx + } + EatUnchanged { tokens: __eu } => + TypeParamKindResult { kind: none, tokens: tokens, ctx: ctx } + } +} + +// THE KIND PROPERTY'S name_span MUST BE no_span(), AND THE REASON IS A TRAP WORTH NAMING. Property +// lookup goes through find_property -> field_init_node_name_at -> authored_name_at, which reads the +// SOURCE TEXT at the property node's ident_span and falls back to the node's `name` field ONLY when +// that span's file is absent from source_indices. Passing the kind token's own span therefore made +// this property answer its own name as "WidthResolution" rather than "__param_kind", every lookup +// missed, and the kind wall compiled, shipped and never fired -- green on every probe including the +// ones written to go red. The existing sole_constructor property passes no_span() for exactly this +// reason. A synthetic property is keyed by what its span reads, not by what it is called. +fn with_type_param_kind_property(param: Node, kind: Node?) -> Node { + match kind { + Absent => param + Present { value: k } => + let prop = make_field_init_node(occurrence_identity: OccurrenceSynthetic, name: type_param_kind_property_name(), value: k, span: k.span, name_span: no_span()) + Node { + occurrence_identity: param.occurrence_identity, name: param.name, span: param.span, ident_span: param.ident_span, + children: param.children, connective: param.connective, params: param.params, inferred: param.inferred, + return_cardinality: param.return_cardinality, uses: param.uses, body: param.body, transport: param.transport, + properties: list_push(param.properties, prop), type_annotation: param.type_annotation, + is_self_recursive: param.is_self_recursive, has_non_tail_self_call: param.has_non_tail_self_call, + match_pattern: param.match_pattern, module_item_kind: param.module_item_kind, + declaration_marker: param.declaration_marker, expr_data: param.expr_data + } + } +} + type TypeArgsResult { args: List tokens: TokenStream diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 69a639aac0f..bceb3ab45c6 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -51,7 +51,7 @@ import v1.std.core { Unmarked, ErrorNode, make_error_node, is_error_diagnostic, is_interpreter_blocking_diagnostic, diagnostic_to_span, AlgebraApplicationEvidenceUnavailable, FrontierOccurrenceKey, diagnostic_frontier_occurrence_key, - InternalError, TypeMismatch, VariantCollision, SoleConstructorViolation, OptionalCastNotEliminated, BareNoneNotAdmittedByFieldType, ConstructorCallAdmissionRefused, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, ServiceConfigReferenceJudgmentDeferred, UnlistedVariantValueUse, + InternalError, TypeMismatch, TypeParameterInValuePosition, VariantCollision, SoleConstructorViolation, OptionalCastNotEliminated, BareNoneNotAdmittedByFieldType, ConstructorCallAdmissionRefused, UnresolvedType, FieldNotFound, MethodNotFound, MethodExistenceUndecided, MethodExistenceFrontierAdmitted, ReceiverTypeUnestablished, FrontierOccurrenceBudgetExceeded, MissingField, AmbiguousReference, ServiceConfigReferenceJudgmentDeferred, UnlistedVariantValueUse, CallArgumentNameUnknown, CallPositionalSurplus, CallPositionalDeficit, CallNamedArgOnFunctionValue, CallArgumentDuplicate, EqualityOnFunctionMember, EqualityMemberUnjudgeable, TypeArgumentArityMismatch, @@ -281,6 +281,7 @@ type InferScope { lambda_param_provenance: Map caller_decl_name: String in_flight_lambda_param_names: List + enclosing_declared_type_param_names: List } type InferResult { @@ -5770,7 +5771,8 @@ fn build_params_scope(scope: InferScope, params: List) -> InferScope { item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: empty_map(), - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } } @@ -5805,7 +5807,8 @@ fn extend_scope(scope: InferScope, name: String, resolved: Node, provenance: Sub item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: scope.lambda_param_provenance, - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } } @@ -5821,7 +5824,8 @@ fn extend_scope_match_bound(scope: InferScope, name: String, resolved: Node, pro item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: scope.lambda_param_provenance, - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } } @@ -5843,7 +5847,8 @@ fn extend_scope_with_params(scope: InferScope, params: List) -> InferSco item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: scope.lambda_param_provenance, - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } } @@ -5997,7 +6002,8 @@ fn infer_method_args_with_fold(method_name: String?, method_args: List, fo item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: prov_map, - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } let ar = infer_expr(texpr: lam_value, scope: fold_scope, expected: Present { value: fold_callable }) ArgInferResult { typed_arg: make_arg_node(occurrence_identity: a.occurrence_identity, name: arg_name_at(n: a, source_indices: scope.type_env.source_indices), value: ar.typed, span: a.span, name_span: a.span), diagnostics: ar.diagnostics } @@ -6019,7 +6025,8 @@ fn infer_method_args_with_fold(method_name: String?, method_args: List, fo item_registry: scope.item_registry, caller_decl_name: scope.caller_decl_name, lambda_param_provenance: nf_prov_map, - in_flight_lambda_param_names: scope.in_flight_lambda_param_names + in_flight_lambda_param_names: scope.in_flight_lambda_param_names, + enclosing_declared_type_param_names: scope.enclosing_declared_type_param_names } } else { scope } match arg_contract { @@ -6544,6 +6551,19 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu diagnostics: variant_value_reference_diagnostics(scope: scope, name: name, span: span, owner: variant_owner_node(scope: scope, name: name)) } Absent => + if name_is_enclosing_declared_type_parameter(name: name, scope: scope) { + InferResult { + typed: make_named_expr_node(occurrence_identity: texpr.occurrence_identity, + name: name, + expr_data: ExprVar { binding_kind: none }, + children: [], + inferred: Present { value: Resolved { node: error_type } }, + span: span, + name_span: span + ), + diagnostics: [make_error_node(diagnostic: TypeParameterInValuePosition { name: name, span: span }, module_name: scope.module_name)] + } + } else { let binding_kind = infer_var_binding_kind(scope: scope, name: name) ok_infer(texpr: make_named_expr_node(occurrence_identity: texpr.occurrence_identity, name: name, @@ -6553,6 +6573,7 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu span: span, name_span: span )) + } } Absent => match lookup_func_sig(func_env: scope.func_env, type_env: scope.type_env, name: name) { @@ -6613,6 +6634,19 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu diagnostics: variant_value_reference_diagnostics(scope: scope, name: name, span: span, owner: Present { value: exp_enum }) } Absent => + if name_is_enclosing_declared_type_parameter(name: name, scope: scope) { + InferResult { + typed: make_named_expr_node(occurrence_identity: texpr.occurrence_identity, + name: name, + expr_data: ExprVar { binding_kind: none }, + children: [], + inferred: Present { value: Resolved { node: error_type } }, + span: span, + name_span: span + ), + diagnostics: [make_error_node(diagnostic: TypeParameterInValuePosition { name: name, span: span }, module_name: scope.module_name)] + } + } else { let binding_kind = infer_var_binding_kind(scope: scope, name: name) InferResult { typed: make_named_expr_node(occurrence_identity: texpr.occurrence_identity, @@ -6625,6 +6659,7 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu ), diagnostics: bare_product_reference_missing_field_diagnostics(scope: scope, name: name, span: span) } + } } } Absent => @@ -7843,7 +7878,8 @@ fn infer_expr_body(texpr: Node, scope: InferScope, expected: Node?) -> InferResu match_bound_names: lam_scope.match_bound_names, module_name: lam_scope.module_name, service_registry: lam_scope.service_registry, item_registry: lam_scope.item_registry, caller_decl_name: lam_scope.caller_decl_name, lambda_param_provenance: empty_map(), - in_flight_lambda_param_names: concat(lam_scope.in_flight_lambda_param_names, lam_params) + in_flight_lambda_param_names: concat(lam_scope.in_flight_lambda_param_names, lam_params), + enclosing_declared_type_param_names: lam_scope.enclosing_declared_type_param_names } let body_result = infer_expr(texpr: lam_body, scope: body_scope, expected: body_expected) let body_typed = body_result.typed @@ -11494,7 +11530,8 @@ fn infer_item(item: Node, scope: InferScope) -> TypedItemResult { item_registry: fn_scope.item_registry, caller_decl_name: fn_decl_name, lambda_param_provenance: fn_scope.lambda_param_provenance, - in_flight_lambda_param_names: [] + in_flight_lambda_param_names: [], + enclosing_declared_type_param_names: fn_type_param_names(item: item, source_indices: scope.type_env.source_indices) } let fn_return_expected = if item.inferred != none { Present { value: resolved_type(n: item) } } else { none } let body_result = infer_expr(texpr: item.body.value, scope: fn_scope, expected: fn_return_expected) @@ -11648,6 +11685,30 @@ fn split_sig_params(params: List, source_indices: Map Int { ZZZ }` refuses with one blocking error while +// `fn any_param(x: T) -> Int { T }` compiles with ZERO and emits `{ t }` into a crate that fails +// E0425. The wall was never missing; it was DEFEATED by the binding, one link earlier than the +// undefined-name check, which is doing its job correctly. +// +// IT ASKS THE DECLARED ROSTER, NOT THE INFERRED TYPE, AND THE FIRST VERSION DID THE OPPOSITE. That +// version tested `is_type_variable(binding.resolved.inferred)`, which conflates TWO MEANINGS +// TypeVariable carries in this compiler: a DECLARED GENERIC PARAMETER, and an UNRESOLVED INFERENCE +// VARIABLE standing for a value whose type is not yet pinned. A lambda binder carries the second +// mid-inference, so the wall refused 24 ordinary VALUE binders across 11 modules on the real corpus +// -- `e` in a fold lambda, `item` in a value binder, `evidence`, `key`, `s`, `first`. That is the +// same defect class this wall exists to close, committed by the wall. The roster is the authority +// env_with_type_variable_bindings was itself handed, so asking it is identity-keyed against a +// DECLARED list rather than inferred from a binder's provenance. +// THE FORK IS ROUTED AROUND, NOT CLOSED: TypeVariable still carries both meanings, and this wall is +// not its only consumer. Filed as gunbc.recurring_failure_mode +// type_variable_means_both_declared_generic_and_inference_variable. +fn name_is_enclosing_declared_type_parameter(name: String, scope: InferScope) -> Bool { + scope.enclosing_declared_type_param_names |> any(tp => tp == name) +} + fn param_is_generic_decl(p: Node, source_indices: Map) -> Bool { let pt = param_node_type_expr(n: p) let pname = param_node_name_at(n: p, source_indices: source_indices) @@ -14046,7 +14107,8 @@ fn typecheck_module(resolved: ResolvedModule, parent_index: Map tir.item) diff --git a/src/v1/04_resolve.dag b/src/v1/04_resolve.dag index fa4989fa158..ddaa9af3ae0 100644 --- a/src/v1/04_resolve.dag +++ b/src/v1/04_resolve.dag @@ -15,7 +15,7 @@ import v1.std.core { Unmarked, field_from_key_property_name, InferredNode, Resolved, CompilerError, TypeVariable, is_compiler_error, ErrorNode, make_error_node, - InternalError, ArityMismatch, UnresolvedType, UnlistedImportUse, + InternalError, ArityMismatch, UnresolvedType, UnlistedImportUse, TypeArgumentKindMismatch, Cardinality, Required, StringPart, Text, Interpolation, MatchPattern, Wildcard, @@ -29,6 +29,7 @@ import v1.std.core { Unmarked, make_expr_node, make_named_expr_node, make_expr_error_node, map_children, expr_call_func_at, expr_method_name_at, let_binding_name_at, foreach_variable_at, make_arg_node, arg_name_at, arg_value, make_arm_node, arm_pattern, arm_guard, arm_body, make_field_init_node, field_init_node_name_at, field_init_node_value, make_resource_use_node, resource_use_name_at, resource_use_resource, make_text_part_node, make_interp_part_node, + find_property, expr_literal_int_optional, type_param_kind_property_name, make_transport_node, local_transport_node, is_local_transport, transport_request_body, is_kernel_type, is_container_type, @@ -84,6 +85,201 @@ fn lookup_unit_variant_phantom_type(env: TypeEnv, variant_name: String) -> UnitV } } +// KIND INHABITANCE AT AN APPLIED TYPE ARGUMENT -- the wall that makes a kinded parameter position +// mean something. It sits beside the arity check in resolve_node_bounded's generic-use-site branch +// because that is the ONE place an applied user generic is judged, so every occurrence is reached +// rather than only the field-access route alias_chain_arity_diagnostics covers. +// +// THE ADMITTING ARMS, AND THE HONEST CEILING OF EACH; the arms themselves are read from +// type_arg_kind_inhabitance below rather than counted here, because a count in prose rots the first +// time an arm is added. TWO of them are admitted WITHOUT consulting the kind at all. A type variable +// or an in-scope generic parameter is admitted and judged at INSTANTIATION instead -- this position +// cannot know the argument yet, and refusing it would refuse every generic function over a kinded +// parameter. A NAT LITERAL is admitted UNCONDITIONALLY, before the kind declaration is looked up, so +// no variant, field count or kind identity is ever consulted for it. Everything else is judged +// against the KIND ITSELF: the kind's declared alias target and its declared children are the roster +// of admissible named inhabitants, matched by identity rather than by shape. +// +// THE LITERAL ARM IS THE WEAK ONE AND IS STATED RATHER THAN DRESSED UP, AND AN EARLIER REVISION OF +// THIS PARAGRAPH OVERSTATED IT (review 69986, BLOCKING). It said the literal was admitted only where +// the kind declares a one-field variant, and priced the residual risk on that reading. The code does +// no such thing and never did at this head: `expr_literal_int_optional(expr: arg) != none` admits any +// integer literal in any kinded position. Literal admissibility is a LANGUAGE-LEVEL rule here rather +// than a per-kind fact, the frontier is declared at std.machine_constraints +// machine_width_literal_roster_frontier, and the same weaker account is written on +// type_arg_kind_inhabitance itself. RUNG HONESTY (DESIGN 4b(1)): a class's rung is the minimum across +// its paths, so this position's rung is set by the LITERAL axis and not by the named one, and an +// annotation crediting the arm with a per-kind judgment it does not perform is rung inflation in +// prose -- in a change whose whole subject is refusing exactly that. +fn type_param_kind_diagnostics(carrier: Node, decl: Node, type_name: String, env: TypeEnv, module_name: String) -> List { + decl.params |> enumerate |> flat_map(pair => + match find_property(props: pair.second.properties, prop_name: type_param_kind_property_name(), source_indices: env.source_indices) { + Absent => [] + Present { value: kind_node } => + match carrier.children |> skip(pair.first) |> first { + Absent => [] + Present { value: arg } => + let kind_name = authored_name(env: env, node: kind_node) + let param_name = authored_name_at(source_indices: env.source_indices, node: pair.second) + match type_arg_kind_inhabitance(arg: arg, kind_node: kind_node, env: env, module_name: module_name) { + KindInhabited => [] + KindNotInhabited => + [make_error_node( + diagnostic: TypeArgumentKindMismatch { + type_name: type_name, param_name: param_name, kind_name: kind_name, + supplied: concat(type_arg_display_spelling(n: arg, env: env), " [", kind_lookup_trace(kind_decl: kind_decl_for_message(kind_node: kind_node, env: env), env: env), "]"), span: arg.span + }, + module_name: module_name + )] + KindDeclarationUnresolved => + [make_error_node( + diagnostic: TypeArgumentKindMismatch { + type_name: type_name, param_name: param_name, kind_name: kind_name, + supplied: concat("(kind declaration '", kind_name, "' did not resolve, so inhabitance could not be established)"), + span: arg.span + }, + module_name: module_name + )] + } + } + } + ) +} + +fn type_arg_display_spelling(n: Node, env: TypeEnv) -> String { + match expr_literal_int_optional(expr: n) { + Present { value: v } => to_string(value: v) + Absent => authored_name(env: env, node: n) + } +} + +// THREE ANSWERS, NOT TWO, AND THE THIRD IS WHY THIS IS A COPRODUCT. The first version returned Bool +// with `Absent => true` when the kind declaration did not resolve -- an absorbing fallback that +// ADMITTED EVERYTHING exactly where it could establish nothing, which is the failure arm DESIGN +// section 5 forbids by name. A Bool cannot tell "this argument inhabits the kind" apart from "I +// could not look the kind up", so both answered true and the fail-open had somewhere to hide. +type KindInhabitance + = KindInhabited + | KindNotInhabited + | KindDeclarationUnresolved + +// WHAT THIS CHECKS, AND WHAT IT DELIBERATELY DOES NOT. The NAMED half is identity-keyed against the +// kind's declared alias target, so a kind naming PointerWidth cannot be satisfied by an unrelated +// declaration of the same shape -- that is the difference from the variant-shape proxy this +// replaced, which admitted any argument so long as the kind happened to own a one-field variant and +// never consulted the kind's identity at all. +// +// A NAT LITERAL IS ADMITTED IN ANY KINDED POSITION, and that is a LANGUAGE-LEVEL rule rather than a +// per-kind fact. Stated rather than hidden: this wall does not ground the literal half. It is NOT a +// regression -- before this change the check did not fire at all, so a literal was admitted in every +// kinded position already and still is. The per-kind literal roster is a declared frontier; every +// carrier available today fails to express it, enumerated at std.machine_constraints. +// RUNG HONESTY: a class's rung is the minimum across its paths, so this position's rung is set by +// the LITERAL axis, not by the named one. The position is NOT fully grounded and must not be cited +// as such. +fn type_arg_kind_inhabitance(arg: Node, kind_node: Node, env: TypeEnv, module_name: String) -> KindInhabitance { + let arg_is_type_var = match arg.inferred { + Present { value: inf } => is_type_variable(inferred: inf) + Absent => false + } + if arg_is_type_var { KindInhabited } + else if expr_literal_int_optional(expr: arg) != none { KindInhabited } + else { + match lookup_type_by_name(env: env, name: authored_name(env: env, node: kind_node)) { + Absent => KindDeclarationUnresolved + Present { value: kind_decl } => + let arg_name = authored_name(env: env, node: arg) + if kind_decl.children |> any(c => node_authored_or_own_name(n: c, env: env) == arg_name) { KindInhabited } + else if kind_names_admissible_inhabitant(kind_decl: kind_decl, arg_name: arg_name, env: env) { KindInhabited } + else if kind_inhabitant_matches_resolved(kind_node: kind_node, arg: arg, env: env, module_name: module_name) { KindInhabited } + else if type_arg_name_is_bound_generic_parameter(name: arg_name, env: env) { KindInhabited } + else { KindNotInhabited } + } + } +} + +// The kind's alias target IS the roster of admissible named inhabitants. CEILING, STATED: an alias +// carries exactly one target, so exactly one named inhabitant is expressible, and the comparison is +// NOMINAL against that target rather than a resolved-node identity. Both are narrower than the truth +// and narrow in the REFUSING direction, which is the correct direction for a wall to be wrong in. +fn kind_names_admissible_inhabitant(kind_decl: Node, arg_name: String, env: TypeEnv) -> Bool { + kind_admissible_inhabitant_name(kind_decl: kind_decl, env: env) == arg_name +} + +// RESOLVE BOTH SIDES AND COMPARE, because the kind declaration does NOT carry its target where this +// check can read it. MEASURED, by a trace this refusal printed on the real corpus: +// 'PointerWidth [decl=WidthResolution inferred=absent resolved_target= children=0]' +// A single-arm `type WidthResolution = PointerWidth` is parsed as a TYPE ALIAS, so the declaration +// reaching this site is a bare nominal node -- no inferred target, no children. Reading +// kind_decl.inferred therefore answers nothing, which is why the wall refused its own declared +// inhabitant and the positive control failed. Three separate readings of local_binding_for_item and +// lookup_type_by_name predicted otherwise; the trace is what settled it. +// So both sides go through the module's own resolution, symmetrically, and are compared on the +// resolved identity rather than on a field one side happens not to carry. +fn kind_inhabitant_matches_resolved(kind_node: Node, arg: Node, env: TypeEnv, module_name: String) -> Bool { + let resolved_kind = resolve_node_bounded(n: kind_node, env: env, module_name: module_name, depth: 0, masked: false).resolved + let resolved_arg = resolve_node_bounded(n: arg, env: env, module_name: module_name, depth: 0, masked: false).resolved + let kind_name = node_authored_or_own_name(n: resolved_kind, env: env) + let arg_name = node_authored_or_own_name(n: resolved_arg, env: env) + kind_name != "" && kind_name == arg_name +} + +// AUTHORED NAME, ELSE THE NODE'S OWN NAME. authored_name reads the SOURCE TEXT at a node's +// ident_span and answers "" for a synthetic or span-less node, which is what the alias target of a +// resolved kind declaration is -- so comparing authored names alone refused the kind's OWN declared +// inhabitant and made the wall refuse everything, positive control included. This is the corpus's +// existing idiom for the same problem (v1.compiler.emit_rust type_leaf_name_for_collapse), not a new +// one. Applied to BOTH sides so the comparison cannot be lopsided. +fn kind_admissible_inhabitant_name(kind_decl: Node, env: TypeEnv) -> String { + match kind_decl.inferred { + Present { value: Resolved { node: target } } => node_authored_or_own_name(n: target, env: env) + _ => "" + } +} + +// A DIAGNOSTIC THAT NAMES WHAT IT LOOKED AT. The refusal reported an empty declared inhabitant and +// could not say WHY -- whether the kind declaration carried no resolved target, or carried one whose +// name read empty. Those have different repairs, so the message carries both facts rather than +// costing another build cycle to distinguish them. +fn kind_lookup_trace(kind_decl: Node, env: TypeEnv) -> String { + concat( + "decl='", node_authored_or_own_name(n: kind_decl, env: env), + "' inferred=", match kind_decl.inferred { Present { value: _ } => "present" Absent => "absent" }, + " resolved_target='", kind_admissible_inhabitant_name(kind_decl: kind_decl, env: env), + "' children=", to_string(value: kind_decl.children |> count) + ) +} + +// The refusal names what it COMPARED AGAINST, not just what was supplied. A wall that says only +// "X is not an inhabitant" makes its own misbehaviour unreadable -- this one refused its own declared +// inhabitant and the message could not distinguish that from a correct refusal. +fn kind_decl_for_message(kind_node: Node, env: TypeEnv) -> Node { + match lookup_type_by_name(env: env, name: authored_name(env: env, node: kind_node)) { + Present { value: d } => d + Absent => kind_node + } +} + +fn node_authored_or_own_name(n: Node, env: TypeEnv) -> String { + let a = authored_name(env: env, node: n) + if a == "" { n.name } else { a } +} + +// An argument spelled as a bare name that is not an admitted inhabitant may still be a generic +// parameter of the ENCLOSING declaration (`fn f(x: Compose>)`), which +// reaches this position as a plain leaf rather than as an inferred type variable. Admitting it here +// is the same deferral the type-variable arm makes, for the same reason. +fn type_arg_name_is_bound_generic_parameter(name: String, env: TypeEnv) -> Bool { + match lookup_type_by_name(env: env, name: name) { + Absent => false + Present { value: bound } => + match bound.inferred { + Present { value: inf } => is_type_variable(inferred: inf) + Absent => false + } + } +} + fn is_width_nat_type_literal(n: Node) -> Bool { match n.expr_data { ExprLiteral { value: LitInt { value: _ } } => true @@ -520,6 +716,7 @@ fn resolve_node_bounded(n: Node, env: TypeEnv, module_name: String, depth: Int, let arity_diags = if expected_arity != actual_arity { [make_error_node(diagnostic: ArityMismatch { name: type_name, expected: expected_arity, got: actual_arity, span: n.span }, module_name: module_name)] } else { [] } + let kind_diags = type_param_kind_diagnostics(carrier: n, decl: decl, type_name: type_name, env: env, module_name: module_name) let arg_results = n.children |> map(child => resolve_node_bounded(n: child, env: env, module_name: module_name, depth: depth + 1, masked: masked)) let resolved_args = map(arg_results, ar => ar.resolved) let arg_diags = flat_map(arg_results, ar => ar.diagnostics) @@ -541,7 +738,7 @@ fn resolve_node_bounded(n: Node, env: TypeEnv, module_name: String, depth: Int, let is_recursive = is_recursive_type_by_name(env: env, name: type_name) let expanded_node = Node { occurrence_identity: OccurrenceSynthetic, name: type_name, span: n.span, ident_span: n.ident_span, children: target_result.resolved.children, connective: target_result.resolved.connective, params: [], inferred: target_result.resolved.inferred, return_cardinality: n.return_cardinality, uses: n.uses, body: n.body, transport: n.transport, properties: target_result.resolved.properties, type_annotation: n.type_annotation, is_self_recursive: is_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, module_item_kind: n.module_item_kind, declaration_marker: n.declaration_marker, expr_data: n.expr_data } let resolved_node = Node { occurrence_identity: n.occurrence_identity, name: type_name, span: n.span, ident_span: n.ident_span, children: resolved_args, connective: NoConnective, params: [], inferred: Present { value: Resolved { node: expanded_node } }, return_cardinality: n.return_cardinality, uses: n.uses, body: n.body, transport: n.transport, properties: target_result.resolved.properties, type_annotation: n.type_annotation, is_self_recursive: is_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, module_item_kind: n.module_item_kind, declaration_marker: n.declaration_marker, expr_data: n.expr_data } - NodeResolveResult { resolved: resolved_node, diagnostics: concat(arity_diags, arg_diags, target_result.diagnostics) } + NodeResolveResult { resolved: resolved_node, diagnostics: concat(arity_diags, kind_diags, arg_diags, target_result.diagnostics) } } else { let substituted_children = decl.children |> map(child => substitute_type_slots(n: child, slot_bindings: slot_bindings, decl_name: type_name, source_indices: env.source_indices) @@ -550,7 +747,7 @@ fn resolve_node_bounded(n: Node, env: TypeEnv, module_name: String, depth: Int, let expanded_node = Node { occurrence_identity: OccurrenceSynthetic, name: type_name, span: n.span, ident_span: n.ident_span, children: substituted_children, connective: decl.connective, params: [], inferred: n.inferred, return_cardinality: n.return_cardinality, uses: n.uses, body: n.body, transport: n.transport, properties: decl.properties, type_annotation: n.type_annotation, is_self_recursive: is_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, module_item_kind: n.module_item_kind, declaration_marker: n.declaration_marker, expr_data: n.expr_data } let result = NodeResolveResult { resolved: Node { occurrence_identity: n.occurrence_identity, name: type_name, span: n.span, ident_span: n.ident_span, children: resolved_args, connective: NoConnective, params: [], inferred: Present { value: Resolved { node: expanded_node } }, return_cardinality: n.return_cardinality, uses: n.uses, body: n.body, transport: n.transport, properties: decl.properties, type_annotation: n.type_annotation, is_self_recursive: is_recursive, has_non_tail_self_call: n.has_non_tail_self_call, match_pattern: n.match_pattern, module_item_kind: n.module_item_kind, declaration_marker: n.declaration_marker, expr_data: n.expr_data }, - diagnostics: concat(arity_diags, arg_diags) + diagnostics: concat(arity_diags, kind_diags, arg_diags) } result } diff --git a/src/v1/05_emit.dag b/src/v1/05_emit.dag index 1a1c7a9affe..ee49896375f 100644 --- a/src/v1/05_emit.dag +++ b/src/v1/05_emit.dag @@ -275,7 +275,8 @@ fn empty_emit_scope() -> InferScope { item_registry: empty_map(), lambda_param_provenance: empty_map(), caller_decl_name: "", - in_flight_lambda_param_names: [] + in_flight_lambda_param_names: [], + enclosing_declared_type_param_names: [] } } @@ -291,7 +292,8 @@ fn module_emit_scope(typed_module: TypedModule) -> InferScope { item_registry: typed_module.item_registry, lambda_param_provenance: empty_map(), caller_decl_name: "", - in_flight_lambda_param_names: [] + in_flight_lambda_param_names: [], + enclosing_declared_type_param_names: [] } } diff --git a/src/v1/stage0/src/cli_run/compile_clean.rs b/src/v1/stage0/src/cli_run/compile_clean.rs index dbccb6cf57b..826f09c02dc 100644 --- a/src/v1/stage0/src/cli_run/compile_clean.rs +++ b/src/v1/stage0/src/cli_run/compile_clean.rs @@ -1595,6 +1595,8 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::CallArgumentDuplicate { .. } => "CallArgumentDuplicate", CompilerDiagnostic::CallNamedArgOnFunctionValue { .. } => "CallNamedArgOnFunctionValue", CompilerDiagnostic::TypeArgumentArityMismatch { .. } => "TypeArgumentArityMismatch", + CompilerDiagnostic::TypeArgumentKindMismatch { .. } => "TypeArgumentKindMismatch", + CompilerDiagnostic::TypeParameterInValuePosition { .. } => "TypeParameterInValuePosition", CompilerDiagnostic::EqualityOnFunctionMember { .. } => "EqualityOnFunctionMember", CompilerDiagnostic::EqualityMemberUnjudgeable { .. } => "EqualityMemberUnjudgeable", CompilerDiagnostic::OccurrenceTransportViolation { .. } => "OccurrenceTransportViolation", @@ -1694,6 +1696,8 @@ pub fn compile_clean_diagnostic_histogram_key(d: &Rc) -> (String, Str CompilerDiagnostic::CallArgumentDuplicate { argument, .. } => argument.clone(), CompilerDiagnostic::CallNamedArgOnFunctionValue { argument, .. } => argument.clone(), CompilerDiagnostic::TypeArgumentArityMismatch { type_name, .. } => type_name.clone(), + CompilerDiagnostic::TypeArgumentKindMismatch { type_name, .. } => type_name.clone(), + CompilerDiagnostic::TypeParameterInValuePosition { name, .. } => name.clone(), CompilerDiagnostic::EqualityOnFunctionMember { type_name, .. } => type_name.clone(), CompilerDiagnostic::EqualityMemberUnjudgeable { type_name, .. } => type_name.clone(), CompilerDiagnostic::OccurrenceTransportViolation { .. } => { diff --git a/src/v1/stage0/src/std_machine_constraints.rs b/src/v1/stage0/src/std_machine_constraints.rs index 8c8765545e5..1d45e61f12d 100644 --- a/src/v1/stage0/src/std_machine_constraints.rs +++ b/src/v1/stage0/src/std_machine_constraints.rs @@ -10,10 +10,12 @@ use im::{vector as vec, HashMap, OrdSet as BTreeSet, Vector as Vec}; use std::rc::Rc; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct MachineWidth(pub std::marker::PhantomData); +pub struct PointerWidth(pub std::marker::PhantomData<()>); + +pub type WidthResolution = PointerWidth; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -pub struct PointerWidth(pub std::marker::PhantomData<()>); +pub struct MachineWidth(pub std::marker::PhantomData); pub type Compose = std::marker::PhantomData<(Algebra, MachineConstraint)>; diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 6d93a8eb94d..5f3f401c826 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -247,8 +247,8 @@ use crate::v1_std_core::CompilerDiagnostic::{ FrontierOccurrenceBudgetExceeded, InternalError, MethodExistenceFrontierAdmitted, MethodExistenceUndecided, MethodNotFound, MissingField, OptionalCastNotEliminated, ReceiverTypeUnestablished, ServiceConfigReferenceJudgmentDeferred, SoleConstructorViolation, - TypeArgumentArityMismatch, TypeMismatch, UnlistedVariantValueUse, UnresolvedType, - VariantCollision, + TypeArgumentArityMismatch, TypeMismatch, TypeParameterInValuePosition, UnlistedVariantValueUse, + UnresolvedType, VariantCollision, }; use crate::v1_std_core::Connective::{Arrow, Conj, Disj, NoConnective}; use crate::v1_std_core::DeclarationMarker::Unmarked; @@ -10123,14 +10123,28 @@ match scope_parent.clone() { })), span.clone(), span.clone()), diagnostics: variant_value_reference_diagnostics(scope.clone(), name.clone(), span.clone(), variant_owner_node(scope.clone(), name.clone())), }), - std::option::Option::None => { - let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); + std::option::Option::None => if binding_resolves_to_type_parameter(binding.clone()) { + Rc::new(InferResult { + typed: crate::v1_std_core::make_named_expr_node(texpr.occurrence_identity.clone(), name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: std::option::Option::None, +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: error_type(), +})), span.clone(), span.clone()), + diagnostics: Rc::new(vec![crate::v1_std_core::make_error_node(Rc::new(CompilerDiagnostic::TypeParameterInValuePosition { + name: name.clone(), + span: span.clone(), +}), scope.module_name.clone())]), +}) + } else { + { + let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); ok_infer(crate::v1_std_core::make_named_expr_node(texpr.occurrence_identity.clone(), name.clone(), Rc::new(ExprData::ExprVar { binding_kind: Some(binding_kind.clone()), }), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { node: binding.resolved.clone(), })), span.clone(), span.clone())) -}, +} + }, } }, std::option::Option::None => match (*crate::v1_compiler_infer_lookup::lookup_func_sig(scope.func_env.clone(), scope.type_env.clone(), name.clone())).clone() { @@ -10175,8 +10189,21 @@ match scope_parent.clone() { })), span.clone(), span.clone()), diagnostics: variant_value_reference_diagnostics(scope.clone(), name.clone(), span.clone(), Some(exp_enum.clone())), }), - std::option::Option::None => { - let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); + std::option::Option::None => if binding_resolves_to_type_parameter(gbinding.clone()) { + Rc::new(InferResult { + typed: crate::v1_std_core::make_named_expr_node(texpr.occurrence_identity.clone(), name.clone(), Rc::new(ExprData::ExprVar { + binding_kind: std::option::Option::None, +}), Rc::new(vec![]), Some(Rc::new(InferredNode::Resolved { + node: error_type(), +})), span.clone(), span.clone()), + diagnostics: Rc::new(vec![crate::v1_std_core::make_error_node(Rc::new(CompilerDiagnostic::TypeParameterInValuePosition { + name: name.clone(), + span: span.clone(), +}), scope.module_name.clone())]), +}) + } else { + { + let binding_kind = infer_var_binding_kind(scope.clone(), name.clone()); Rc::new(InferResult { typed: crate::v1_std_core::make_named_expr_node(texpr.occurrence_identity.clone(), name.clone(), Rc::new(ExprData::ExprVar { binding_kind: Some(binding_kind.clone()), @@ -10185,7 +10212,8 @@ Rc::new(InferResult { })), span.clone(), span.clone()), diagnostics: bare_product_reference_missing_field_diagnostics(scope.clone(), name.clone(), span.clone()), }) -}, +} + }, }, } }, @@ -20517,6 +20545,19 @@ pub fn split_sig_params( }) } +// TRANSIENT BOOTSTRAP PATCH -- replaced by generated bytes on the next regen, and NOT the authority. +// This seed's own copy of the floor wall keyed on `is_type_variable(binding.resolved.inferred)`, +// which conflates a DECLARED generic parameter with an UNRESOLVED INFERENCE VARIABLE, so it refused +// 24 ordinary value binders across 11 corpus modules. That refusal stops the seed compiling the +// corpus, which stops regen, which is the only thing that can replace this function -- a bootstrap +// deadlock. Returning false restores main's behaviour for the SEED only, so regen can run and emit +// the corrected wall from v1.compiler.infer, where the predicate now asks the enclosing +// declaration's DECLARED type-parameter roster instead. The .dag authority no longer declares this +// function at all; after the next regen this whole item disappears. +pub fn binding_resolves_to_type_parameter(_binding: Rc) -> bool { + false +} + pub fn param_is_generic_decl( p: Rc, source_indices: Rc>>, diff --git a/src/v1/stage0/src/v1_compiler_infer_resolve.rs b/src/v1/stage0/src/v1_compiler_infer_resolve.rs index d6fb892bd72..d01a541fd13 100644 --- a/src/v1/stage0/src/v1_compiler_infer_resolve.rs +++ b/src/v1/stage0/src/v1_compiler_infer_resolve.rs @@ -2,6 +2,7 @@ // Source module: v1.compiler.infer_resolve use self::AliasKind::*; +use self::KindInhabitance::*; use self::UnitVariantPhantomLookup::*; pub use crate::std_induction::SubValueRelation; use crate::std_induction::SubValueRelation::SubValueUnknown; @@ -27,7 +28,7 @@ use crate::v1_rt::{VecCompat, VecJoin}; pub use crate::v1_std_core::divergent_type; use crate::v1_std_core::Cardinality::{CardOptional, Required}; use crate::v1_std_core::CompilerDiagnostic::{ - ArityMismatch, InternalError, UnlistedImportUse, UnresolvedType, + ArityMismatch, InternalError, TypeArgumentKindMismatch, UnlistedImportUse, UnresolvedType, }; use crate::v1_std_core::Connective::{Arrow, Conj, Disj, NoConnective}; use crate::v1_std_core::DeclarationMarker::Unmarked; @@ -45,9 +46,10 @@ use crate::v1_std_core::ParsedModuleItemKind::*; use crate::v1_std_core::StringPart::{Interpolation, Text}; pub use crate::v1_std_core::{ arg_name_at, arg_value, arm_body, arm_guard, arm_pattern, authored_name_at, default_ident_span, - expr_call_func_at, expr_method_name_at, field_from_key_property_name, field_init_node_name_at, - field_init_node_value, field_node_cardinality, field_node_default_value, field_node_from_key, - field_node_name_at, field_node_type_expr, foreach_variable_at, generic_param_name_at, intern, + expr_call_func_at, expr_literal_int_optional, expr_method_name_at, + field_from_key_property_name, field_init_node_name_at, field_init_node_value, + field_node_cardinality, field_node_default_value, field_node_from_key, field_node_name_at, + field_node_type_expr, find_property, foreach_variable_at, generic_param_name_at, intern, is_compiler_error, is_container_type, is_kernel_type, is_local_transport, join_optional_cardinality, kernel_span, let_binding_name_at, local_transport_node, make_arg_node, make_arm_node, make_error_node, make_expr_error_node, make_expr_node, @@ -56,8 +58,8 @@ pub use crate::v1_std_core::{ make_transport_node, map_children, no_span, node_name_span, param_node_default_value, param_node_name_at, param_node_type_expr, preserve_outer_optional_cardinality, qualified_last_segment, resolved_node_is_kernel_identity_for_name, resource_use_name_at, - resource_use_resource, string_type, transport_request_body, unit_type, - with_optional_cardinality, with_required_cardinality, + resource_use_resource, string_type, transport_request_body, type_param_kind_property_name, + unit_type, with_optional_cardinality, with_required_cardinality, }; pub use crate::v1_std_core::{ Cardinality, CompilerDiagnostic, Connective, DeclarationMarker, ErrorNode, ExprData, @@ -139,6 +141,212 @@ pub fn lookup_unit_variant_phantom_type( } } +pub fn type_param_kind_diagnostics( + carrier: Rc, + decl: Rc, + type_name: String, + env: Rc, + module_name: String, +) -> Rc>> { + Rc::new({ + let mut __result = Vec::new(); + for pair in Rc::new( + decl.params + .clone() + .iter() + .cloned() + .enumerate() + .map(|(i, v)| (i as i64, v)) + .collect::>(), + ) + .iter() + .cloned() + { + __result.extend((*match crate::v1_std_core::find_property(pair.1.clone().properties.clone(), crate::v1_std_core::type_param_kind_property_name(), env.source_indices.clone()) { + std::option::Option::None => Rc::new(vec![]), + Some(kind_node) => match carrier.children.clone().iter().cloned().skip(pair.0.clone() as usize).next() { + std::option::Option::None => Rc::new(vec![]), + Some(arg) => { + let kind_name = crate::v1_compiler_infer_env::authored_name(env.clone(), kind_node.clone()); +let param_name = crate::v1_std_core::authored_name_at(env.source_indices.clone(), pair.1.clone()); +match type_arg_kind_inhabitance(arg.clone(), kind_node.clone(), env.clone()) { + KindInhabitance::KindInhabited => Rc::new(vec![]), + KindInhabitance::KindNotInhabited => Rc::new(vec![crate::v1_std_core::make_error_node(Rc::new(CompilerDiagnostic::TypeArgumentKindMismatch { + type_name: type_name.clone(), + param_name: param_name.clone(), + kind_name: kind_name.clone(), + supplied: v1_rt::concat(v1_rt::concat(v1_rt::concat(type_arg_display_spelling(arg.clone(), env.clone()), " [".to_string()), kind_lookup_trace(kind_decl_for_message(kind_node.clone(), env.clone()), env.clone())), "]".to_string()), + span: arg.span.clone(), +}), module_name.clone())]), + KindInhabitance::KindDeclarationUnresolved => Rc::new(vec![crate::v1_std_core::make_error_node(Rc::new(CompilerDiagnostic::TypeArgumentKindMismatch { + type_name: type_name.clone(), + param_name: param_name.clone(), + kind_name: kind_name.clone(), + supplied: v1_rt::concat(v1_rt::concat("(kind declaration '".to_string(), kind_name.clone()), "' did not resolve, so inhabitance could not be established)".to_string()), + span: arg.span.clone(), +}), module_name.clone())]), +} +}, +}, +}).iter().cloned()); + } + __result + }) +} + +pub fn type_arg_display_spelling(n: Rc, env: Rc) -> String { + match crate::v1_std_core::expr_literal_int_optional(n.clone()) { + Some(v) => (v.clone()).to_string(), + std::option::Option::None => { + crate::v1_compiler_infer_env::authored_name(env.clone(), n.clone()) + } + } +} + +#[derive( + Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, +)] +#[serde(tag = "_variant")] +pub enum KindInhabitance { + KindInhabited, + KindNotInhabited, + KindDeclarationUnresolved, +} + +pub fn type_arg_kind_inhabitance( + arg: Rc, + kind_node: Rc, + env: Rc, +) -> KindInhabitance { + { + let arg_is_type_var = match arg.inferred.clone() { + Some(inf) => is_type_variable(inf.clone()), + std::option::Option::None => false, + }; + if arg_is_type_var.clone() { + KindInhabitance::KindInhabited + } else { + if (crate::v1_std_core::expr_literal_int_optional(arg.clone()) + != std::option::Option::None) + { + KindInhabitance::KindInhabited + } else { + match crate::v1_compiler_infer_env::lookup_type_by_name( + env.clone(), + crate::v1_compiler_infer_env::authored_name(env.clone(), kind_node.clone()), + ) { + std::option::Option::None => KindInhabitance::KindDeclarationUnresolved, + Some(kind_decl) => { + let arg_name = + crate::v1_compiler_infer_env::authored_name(env.clone(), arg.clone()); + // TRANSIENT BOOTSTRAP PATCH -- the same arm v1.compiler.infer_resolve now + // declares, carried into the seed by hand because this seed is what compiles + // the corpus that regen needs, and without it the kind check cannot read a + // coproduct kind's arms and refuses its own declared inhabitant. Replaced by + // generated bytes on the next regen; not the authority. + if kind_decl.children.iter().any(|c| { + node_authored_or_own_name(c.clone(), env.clone()) == arg_name.clone() + }) { + KindInhabitance::KindInhabited + } else if kind_names_admissible_inhabitant( + kind_decl.clone(), + arg_name.clone(), + env.clone(), + ) { + KindInhabitance::KindInhabited + } else { + if type_arg_name_is_bound_generic_parameter( + arg_name.clone(), + env.clone(), + ) { + KindInhabitance::KindInhabited + } else { + KindInhabitance::KindNotInhabited + } + } + } + } + } + } + } +} + +pub fn kind_names_admissible_inhabitant( + kind_decl: Rc, + arg_name: String, + env: Rc, +) -> bool { + (kind_admissible_inhabitant_name(kind_decl.clone(), env.clone()) == arg_name.clone()) +} + +pub fn kind_admissible_inhabitant_name(kind_decl: Rc, env: Rc) -> String { + match kind_decl.inferred.clone().as_deref().cloned() { + Some(InferredNode::Resolved { node: target, .. }) => { + node_authored_or_own_name(target.clone(), env.clone()) + } + _ => "".to_string(), + } +} + +pub fn kind_lookup_trace(kind_decl: Rc, env: Rc) -> String { + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + v1_rt::concat( + "decl='".to_string(), + node_authored_or_own_name(kind_decl.clone(), env.clone()), + ), + "' inferred=".to_string(), + ), + match kind_decl.inferred.clone() { + Some(_) => "present".to_string(), + std::option::Option::None => "absent".to_string(), + }, + ), + " resolved_target='".to_string(), + ), + kind_admissible_inhabitant_name(kind_decl.clone(), env.clone()), + ), + "' children=".to_string(), + ), + (kind_decl.children.clone().len() as i64).to_string(), + ) +} + +pub fn kind_decl_for_message(kind_node: Rc, env: Rc) -> Rc { + match crate::v1_compiler_infer_env::lookup_type_by_name( + env.clone(), + crate::v1_compiler_infer_env::authored_name(env.clone(), kind_node.clone()), + ) { + Some(d) => d.clone(), + std::option::Option::None => kind_node.clone(), + } +} + +pub fn node_authored_or_own_name(n: Rc, env: Rc) -> String { + { + let a = crate::v1_compiler_infer_env::authored_name(env.clone(), n.clone()); + if (a.clone() == "".to_string()) { + n.name.clone() + } else { + a.clone() + } + } +} + +pub fn type_arg_name_is_bound_generic_parameter(name: String, env: Rc) -> bool { + match crate::v1_compiler_infer_env::lookup_type_by_name(env.clone(), name.clone()) { + std::option::Option::None => false, + Some(bound) => match bound.inferred.clone() { + Some(inf) => is_type_variable(inf.clone()), + std::option::Option::None => false, + }, + } +} + pub fn is_width_nat_type_literal(n: Rc) -> bool { match (*n.expr_data.clone()).clone() { ExprData::ExprLiteral { ref value, .. } @@ -1254,6 +1462,13 @@ Rc::new(NodeResolveResult { } else { Rc::new(vec![]) }; + let kind_diags = type_param_kind_diagnostics( + n.clone(), + decl.clone(), + type_name.clone(), + env.clone(), + module_name.clone(), + ); let arg_results = Rc::new({ let mut __result = Vec::new(); for child in n.children.clone().iter().cloned() { @@ -1423,7 +1638,10 @@ Rc::new(NodeResolveResult { Rc::new(NodeResolveResult { resolved: resolved_node.clone(), diagnostics: v1_rt::concat( - v1_rt::concat(arity_diags.clone(), arg_diags.clone()), + v1_rt::concat( + v1_rt::concat(arity_diags.clone(), kind_diags.clone()), + arg_diags.clone(), + ), target_result.diagnostics.clone(), ), }) @@ -1499,7 +1717,7 @@ Rc::new(NodeResolveResult { ident: None, }), diagnostics: v1_rt::concat( - arity_diags.clone(), + v1_rt::concat(arity_diags.clone(), kind_diags.clone()), arg_diags.clone(), ), }); @@ -3964,6 +4182,12 @@ pub fn resolve_item_types( }) } +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct KindInhabited; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct KindNotInhabited; +#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct KindDeclarationUnresolved; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct AliasParameterized; #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] diff --git a/src/v1/stage0/src/v1_compiler_parse.rs b/src/v1/stage0/src/v1_compiler_parse.rs index fe9c03b25b9..01b366a5ac9 100644 --- a/src/v1/stage0/src/v1_compiler_parse.rs +++ b/src/v1/stage0/src/v1_compiler_parse.rs @@ -96,7 +96,8 @@ pub use crate::v1_std_core::{ shell_transport_node, transport_auth_basic_key, transport_body_key, transport_headers_key, transport_method_key, transport_path_key, transport_path_template_key, transport_query_key, transport_response_format_key, transport_stdin_key, transport_tls_key, transport_url_key, - variant_node_fields, variant_node_name_at, with_required_cardinality, + type_param_kind_property_name, variant_node_fields, variant_node_name_at, + with_required_cardinality, }; pub use crate::v1_std_core::{ Cardinality, CompilerDiagnostic, Connective, DeclarationMarker, ErrorNode, ExprData, @@ -6929,8 +6930,10 @@ pub fn collect_type_param_names( let type_mint = mint_parsed_node_identity(ctx.clone()); let type_expr = leaf_type_node(type_mint.identity.clone(), r.name.clone(), span.clone()); - let param_mint = mint_parsed_node_identity(type_mint.ctx.clone()); - let param = crate::v1_std_core::make_param_node( + let kind_result = + parse_optional_type_param_kind(r.tokens.clone(), type_mint.ctx.clone()); + let param_mint = mint_parsed_node_identity(kind_result.ctx.clone()); + let bare_param = crate::v1_std_core::make_param_node( param_mint.identity.clone(), r.name.clone(), type_expr.clone(), @@ -6938,8 +6941,14 @@ pub fn collect_type_param_names( span.clone(), span.clone(), ); + let param = with_type_param_kind_property(bare_param.clone(), kind_result.kind.clone()); let next_params = v1_rt::rc_list_push(params.clone(), param.clone()); - match (*eat(r.tokens.clone(), Rc::new(ExpectedToken::ExpectComma))).clone() { + match (*eat( + kind_result.tokens.clone(), + Rc::new(ExpectedToken::ExpectComma), + )) + .clone() + { EatResult::EatConsumed { tokens: __ec, .. } => { let __tco_0 = __ec.clone(); let __tco_1 = param_mint.ctx.clone(); @@ -6952,7 +6961,7 @@ pub fn collect_type_param_names( EatResult::EatUnchanged { tokens: __eu, .. } => { break Rc::new(TypeParamsResult { params: next_params.clone(), - tokens: r.tokens.clone(), + tokens: kind_result.tokens.clone(), ctx: param_mint.ctx.clone(), }); } @@ -6967,6 +6976,77 @@ pub fn collect_type_param_names( } } +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct TypeParamKindResult { + pub kind: Option>, + pub tokens: Rc, + pub ctx: Rc, +} + +pub fn parse_optional_type_param_kind( + tokens: Rc, + ctx: Rc, +) -> Rc { + match (*eat(tokens.clone(), Rc::new(ExpectedToken::ExpectColon))).clone() { + EatResult::EatConsumed { tokens: __ec, .. } => { + let r = expect_ident(__ec.clone()); + let kind_mint = mint_parsed_node_identity(ctx.clone()); + Rc::new(TypeParamKindResult { + kind: Some(leaf_type_node( + kind_mint.identity.clone(), + r.name.clone(), + r.span.clone(), + )), + tokens: r.tokens.clone(), + ctx: kind_mint.ctx.clone(), + }) + } + EatResult::EatUnchanged { tokens: __eu, .. } => Rc::new(TypeParamKindResult { + kind: std::option::Option::None, + tokens: tokens.clone(), + ctx: ctx.clone(), + }), + } +} + +pub fn with_type_param_kind_property(param: Rc, kind: Option>) -> Rc { + match kind.clone() { + std::option::Option::None => param.clone(), + Some(k) => { + let prop = crate::v1_std_core::make_field_init_node( + Rc::new(NodeOccurrenceIdentity::OccurrenceSynthetic), + crate::v1_std_core::type_param_kind_property_name(), + k.clone(), + k.span.clone(), + crate::v1_std_core::no_span(), + ); + Rc::new(Node { + occurrence_identity: param.occurrence_identity.clone(), + name: param.name.clone(), + span: param.span.clone(), + ident_span: param.ident_span.clone(), + children: param.children.clone(), + connective: param.connective.clone(), + params: param.params.clone(), + inferred: param.inferred.clone(), + return_cardinality: param.return_cardinality.clone(), + uses: param.uses.clone(), + body: param.body.clone(), + transport: param.transport.clone(), + properties: v1_rt::rc_list_push(param.properties.clone(), prop.clone()), + type_annotation: param.type_annotation.clone(), + is_self_recursive: param.is_self_recursive.clone(), + has_non_tail_self_call: param.has_non_tail_self_call.clone(), + match_pattern: param.match_pattern.clone(), + module_item_kind: param.module_item_kind.clone(), + declaration_marker: param.declaration_marker.clone(), + expr_data: param.expr_data.clone(), + ident: None, + }) + } + } +} + #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] pub struct TypeArgsResult { pub args: Rc>>, diff --git a/src/v1/stage0/src/v1_std_core.rs b/src/v1/stage0/src/v1_std_core.rs index e4d93e10fab..264712cb1c4 100644 --- a/src/v1/stage0/src/v1_std_core.rs +++ b/src/v1/stage0/src/v1_std_core.rs @@ -853,6 +853,17 @@ pub enum CompilerDiagnostic { declared: i64, span: Rc, }, + TypeArgumentKindMismatch { + type_name: String, + param_name: String, + kind_name: String, + supplied: String, + span: Rc, + }, + TypeParameterInValuePosition { + name: String, + span: Rc, + }, OccurrenceTransportViolation { refusal: Rc, }, @@ -1032,6 +1043,8 @@ pub fn diagnostic_to_span(d: Rc) -> Rc { CompilerDiagnostic::EqualityOnFunctionMember { span: s, .. } => s.clone(), CompilerDiagnostic::EqualityMemberUnjudgeable { span: s, .. } => s.clone(), CompilerDiagnostic::TypeArgumentArityMismatch { span: s, .. } => s.clone(), + CompilerDiagnostic::TypeArgumentKindMismatch { span: s, .. } => s.clone(), + CompilerDiagnostic::TypeParameterInValuePosition { span: s, .. } => s.clone(), CompilerDiagnostic::OccurrenceTransportViolation { refusal: refusal, .. } => match occurrence_transport_refusal_diagnostic_span(refusal.clone()) { @@ -1105,6 +1118,8 @@ pub fn diagnostic_to_message(d: Rc) -> String { CompilerDiagnostic::EqualityOnFunctionMember { type_name: t, member: m, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("equality is not defined for '".to_string(), t.clone()), "': member '".to_string()), m.clone()), "' is function-valued, and function equality has no denotation — compare a declared identity for this type instead of '=='".to_string()), CompilerDiagnostic::EqualityMemberUnjudgeable { type_name: t, member: m, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("equality admission for '".to_string(), t.clone()), "' cannot be judged: ".to_string()), m.clone()), " — '==' is refused rather than admitted on an unjudged member".to_string()), CompilerDiagnostic::TypeArgumentArityMismatch { type_name: t, supplied: s, declared: d, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("type argument arity mismatch applying '".to_string(), t.clone()), "': ".to_string()), (s.clone()).to_string()), " type argument(s) supplied, ".to_string()), (d.clone()).to_string()), " type parameter(s) declared".to_string()), + CompilerDiagnostic::TypeArgumentKindMismatch { type_name: t, param_name: p, kind_name: k, supplied: sup, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("type argument does not inhabit the declared kind applying '".to_string(), t.clone()), "': parameter '".to_string()), p.clone()), "' is declared '".to_string()), k.clone()), "', and '".to_string()), sup.clone()), "' is not one of its inhabitants".to_string()), + CompilerDiagnostic::TypeParameterInValuePosition { name: n, .. } => v1_rt::concat(v1_rt::concat("'".to_string(), n.clone()), "' is a type parameter, not a value: a name bound as a type may not stand in an expression position".to_string()), CompilerDiagnostic::OccurrenceTransportViolation { refusal: refusal, .. } => occurrence_transport_refusal_diagnostic_message(refusal.clone()), CompilerDiagnostic::ContainerSpellingUnrecognized { name: n, container_leaf: leaf, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("unrecognized container spelling '".to_string(), n.clone()), "': its last segment '".to_string()), leaf.clone()), "' names a container, but no arity is declared for '".to_string()), n.clone()), "' in std.types container_type_arity — declare the row or spell the container by a declared name".to_string()), CompilerDiagnostic::ServiceConfigReferenceJudgmentDeferred { field: f, referenced_name: n, trigger: t, .. } => v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat(v1_rt::concat("service config field '".to_string(), f.clone()), "' carries the reference '".to_string()), n.clone()), "', and the reference judgment does not yet run on this field -- ".to_string()), t.clone()), ". This is a counted deferral, not a pass: nothing has established that '".to_string()), n.clone()), "' names anything".to_string()), @@ -1392,6 +1407,14 @@ pub fn diagnostic_disposition(d: Rc) -> Rc Rc::new(DiagnosticDisposition { severity: DiagnosticSeverity::SeverityError, gate: Rc::new(DiagnosticGateDisposition::GateBlocking), +}), + CompilerDiagnostic::TypeArgumentKindMismatch { .. } => Rc::new(DiagnosticDisposition { + severity: DiagnosticSeverity::SeverityError, + gate: Rc::new(DiagnosticGateDisposition::GateBlocking), +}), + CompilerDiagnostic::TypeParameterInValuePosition { .. } => Rc::new(DiagnosticDisposition { + severity: DiagnosticSeverity::SeverityError, + gate: Rc::new(DiagnosticGateDisposition::GateBlocking), }), CompilerDiagnostic::OccurrenceTransportViolation { refusal: _, .. } => Rc::new(DiagnosticDisposition { severity: DiagnosticSeverity::SeverityError, @@ -3374,6 +3397,10 @@ pub fn file_transport_node( } } +pub fn type_param_kind_property_name() -> String { + "__param_kind".to_string() +} + pub fn find_property( props: Rc>>, prop_name: String,