Repository navigation
Distinct nominal product types silently inhabit a direct-call argument - #9608
Merged
Merged
Conversation
DESIGN section 4b floor: "values inhabit declared types". Measured on a
compiler built from this tree, each fixture its own entry compile, every
one of these reported ZERO blocking diagnostics:
Alpha<String> at a Beta<String> parameter distinct constructors
Wrapper<KindB> at a Wrapper<KindA> parameter one constructor, distinct
type arguments, K phantom
Alpha at a Beta parameter neither applied, actual a call
THE BRIEF FRAMED THIS AS GENERICS DEFEATING THE CHECK. IT IS NOT. The
third case has no type parameters anywhere and is admitted for the same
reason as the first two, and the case that DID refuse -- Alpha{...} at a
Beta parameter -- refused because its actual is a record LITERAL, not
because it is non-generic. Read off the producers rather than inferred
from the silence: structured_application_site_type_mismatch is the only
judgment that ever compared product heads here and it fires solely for an
ExprRecordLit actual; kernel_value_declared_type_mismatch returns false
at its first gate for a non-kernel actual; coproduct_payload_where_parent
_required leaves its body unless the formal's declaration is a coproduct.
A call actual at a product formal was judged by nothing. Root-cause count:
ONE, and one arm closes all three.
THE ARM lives in declared_type_inhabitance, the single decision procedure,
and consumes v1.compiler.type_head_exposure rather than deriving a second
head view. Two new refusal reasons. Every guard fails open: containers,
callables, optionals, anonymous records, unresolvable names, arity
mismatch and type-variable arguments are all admitted.
node_type_shape now renders an application's arguments. Without it the
type-argument refusal printed declared 'Node(Wrapper)', produced
'Node(Wrapper)' -- typed and located and carrying nothing actionable.
FALSE-POSITIVE CENSUS, because this relation runs on EVERY module: the
direct-call TYPE judgment's v2.* exemption (module_skips_direct_call_arg
_check) does not cover this path. src/v2/compiler/00_compile.dag, the
widest required entry, is 0 blocking / 2167 advisory with the arm on --
byte-identical to the same binary with the arm disabled by env var, a
one-variable A/B rather than two builds. dag/std/abi.dag, src/v1/04_infer
.dag and src/v1/compile.dag are 0 blocking.
TWO CUTS OF THIS ARM PRODUCED 15 FALSE POSITIVES ON THAT ENTRY and both
refutations are recorded in the annotation rather than quietly fixed:
asking whether an argument's NAME resolves in the type environment is not
a type-variable test, because type PARAMETERS are bound there and S and R
answer exactly as KindA does; and the exposure's constructor_identity is
keyed on the REFERENCING file, so one Measure read from two modules
yields two identities and can never establish agreement across modules.
The test that works is that a type variable resolves to nothing.
Five witness arms: three discriminating REDs against the prior compiler
and two anti-refuse-everything controls, all verified by execution. The
enrolled expected-red roster needs no edit -- the peano arm is untouched
by construction (declared_realizes_as_kernel_numeric runs ahead of this
predicate) and measured unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r arm twice Requested by the lane that found the original defect, whose objection to omitting it was correct and which I had answered wrongly. THE TWO FIXTURES FAIL ON DIFFERENT BUGS, which is the test for whether a second control earns its place. The Wrapper arm's two candidate arguments are KindA and KindB -- distinct records with distinct fields -- so any comparison that reaches representation at all sees a difference. Here the two candidates are representationally IDENTICAL (DemandKey and WorkKey are both a single String field) and the parameter they occupy is PHANTOM: K appears in the parameter list and in no field. The instantiations differ only nominally, at every level below the type. The bug this catches and the other does not: "a type argument that does not affect layout cannot affect inhabitance" is a reasonable-sounding optimization that would leave the Wrapper arm green while reopening this class whole. It is also the production shape. product.fabric's demand/work/attempt/ grant/offer/receipt identities are non-interchangeable ONLY because of a phantom K in FabricIdentity<P,K>, and there is no runtime fallback: fabric_identity_eq compares principal and key, so a wrong-kind identity that reaches a parameter is not merely uncaught downstream -- the equality CONFIRMS it. Measured: 1 blocking, at the direct-call argument, caret on the actual -- declared 'Node(FabricIdentity<Primitive(String),Product(DemandKey)>)', produced 'Node(FabricIdentity<Primitive(String),Product(WorkKey)>)'. Witness file only; no compiler change, so no mirror regeneration. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed argument The floor lane refused strict preparation with 60 inhabitance diagnostics on the live corpus, of which 58 were false positives from this branch's new arm. Root cause, and it refutes the test this branch shipped with: the previous guard treated "the resolved argument name is empty" as the type-variable test, generalised from a four-entry census. Over the whole 4181-module subject, `Measure<Time,S,Nat>`, `MembershipPlan<M,At>`, `ContainmentPath<N>`, `PackageLockNode<..>` and `Appropriation<Q,S>` resolve their parameters to nodes that KEEP the authored name, so each parameter rendered as a nominal head (`Primitive(M)`) and the concrete argument instantiating it was refused as a distinctness violation -- the substitution that was supposed to happen. An entry-based census is not a substitute for the floor's subject; it reaches modules no entry compiles. The corrected test is structural rather than nominal: `applied_type_argument_identity_known` now requires the resolved declaration to be a `Conj` or `Disj` with children. A type parameter's bare-leaf binding carries no structure, so it is left unjudged; kernel types are admitted by name as before. This also covers the empty-head class -- an unresolved declared head answers false and produces no refusal, rather than rendering ignorance as a confident red. Measured on the floor's own subject: strict-preparation state=completed, modules_resolved=4181, 0 inhabitance diagnostics. Two of the sixty were the wall firing correctly, on dag/examples/nominal_distinctness_witness -- a RED/GREEN twin pair authored for exactly this class, consumed by nothing, whose RED half had been compiling clean. Since strict preparation compiles every module, a module that must not compile stops the line, so the pair is deleted; a disposition annotation records that its discrimination now lives in the enrolled fixture arms, which are re-measured every floor run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gunbai-bot Bot
pushed a commit
that referenced
this pull request
Aug 29, 2026
…nd its shape rendering: repair two at the source, re-key the spine's frontier row to the shape the renderer now spells - pid_control_program: curve_points_agree compared ZoneDemandValue through the Measure comparator; ZoneDemandValue is deliberately not a Measure, so it gets its own agreement fn over the same exact_decimal_equal rule. - fabric_terminal_contract witness: the positive control's receipt was the one inline literal in the file, so P inferred String against the grant's NonEmptyStr; hoisted to an annotated data like every sibling. - unresolved_method_frontier: the spine row's key was measured before #9608 added the Node(name<args>) arm to node_type_shape; identical receiver, new spelling. Re-keyed to the observed shape, which narrows the admission. Attribution by run bisection and #9608's own diff, recorded on the row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KH3G4UzMgvQobyNgHYy4GU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
DESIGN section 4b floor: "values inhabit declared types". Measured on a
compiler built from this tree, each fixture its own entry compile, every
one of these reported ZERO blocking diagnostics:
Alpha at a Beta parameter distinct constructors
Wrapper at a Wrapper parameter one constructor, distinct
type arguments, K phantom
Alpha at a Beta parameter neither applied, actual a call
THE BRIEF FRAMED THIS AS GENERICS DEFEATING THE CHECK. IT IS NOT. The
third case has no type parameters anywhere and is admitted for the same
reason as the first two, and the case that DID refuse -- Alpha{...} at a
Beta parameter -- refused because its actual is a record LITERAL, not
because it is non-generic. Read off the producers rather than inferred
from the silence: structured_application_site_type_mismatch is the only
judgment that ever compared product heads here and it fires solely for an
ExprRecordLit actual; kernel_value_declared_type_mismatch returns false
at its first gate for a non-kernel actual; coproduct_payload_where_parent
_required leaves its body unless the formal's declaration is a coproduct.
A call actual at a product formal was judged by nothing. Root-cause count:
ONE, and one arm closes all three.
THE ARM lives in declared_type_inhabitance, the single decision procedure,
and consumes v1.compiler.type_head_exposure rather than deriving a second
head view. Two new refusal reasons. Every guard fails open: containers,
callables, optionals, anonymous records, unresolvable names, arity
mismatch and type-variable arguments are all admitted.
node_type_shape now renders an application's arguments. Without it the
type-argument refusal printed declared 'Node(Wrapper)', produced
'Node(Wrapper)' -- typed and located and carrying nothing actionable.
FALSE-POSITIVE CENSUS, because this relation runs on EVERY module: the
direct-call TYPE judgment's v2.* exemption (module_skips_direct_call_arg
_check) does not cover this path. src/v2/compiler/00_compile.dag, the
widest required entry, is 0 blocking / 2167 advisory with the arm on --
byte-identical to the same binary with the arm disabled by env var, a
one-variable A/B rather than two builds. dag/std/abi.dag, src/v1/04_infer
.dag and src/v1/compile.dag are 0 blocking.
TWO CUTS OF THIS ARM PRODUCED 15 FALSE POSITIVES ON THAT ENTRY and both
refutations are recorded in the annotation rather than quietly fixed:
asking whether an argument's NAME resolves in the type environment is not
a type-variable test, because type PARAMETERS are bound there and S and R
answer exactly as KindA does; and the exposure's constructor_identity is
keyed on the REFERENCING file, so one Measure read from two modules
yields two identities and can never establish agreement across modules.
The test that works is that a type variable resolves to nothing.
Five witness arms: three discriminating REDs against the prior compiler
and two anti-refuse-everything controls, all verified by execution. The
enrolled expected-red roster needs no edit -- the peano arm is untouched
by construction (declared_realizes_as_kernel_numeric runs ahead of this
predicate) and measured unchanged.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
Files
src/v1/04_infer.dag— the arm indeclared_type_inhabitance, two newInhabitanceRefusalReasonmembers, and the guardssrc/v1/04_types.dag—node_type_shaperenders an application's argumentsdag/test/claim/declared_type_inhabitance_direct_call_witness_test.dag— three discriminating REDs, two controlssrc/v1/stage0/src/v1_compiler_infer{,_types}.rs— regenerated mirrors (claim_executor --required-regen, not hand-written)Measurement table
Every refusal is
DeclaredTypeNotInhabitedat the exact direct-call argument, caret on the actual.Alpha<String>atBeta<String>Node(Beta<Primitive(String)>), producedNode(Alpha<Primitive(String)>)Wrapper<KindB>atWrapper<KindA>(no brands)Node(Wrapper<Product(KindA)>), producedNode(Wrapper<Product(KindB)>)AlphaatBeta, non-generic, actual a callAlpha{...}record literal atBetatype mismatch+ this arm)"plain"atBeta<String>What this does NOT close
Alpha { a: "z" }atBeta<String>) is still admitted. That isstructured_application_site_type_mismatch's territory — it peels the formal but the literal carries no type arguments — and it is a separate seam with its own evidence, not silently folded in here.DeclaredTypePositionmembers still have no obligation producer; this changes the relation, not the wiring.