Skip to content

The lambda-typing gate is INERT: three arms show zero emitted-byte delta, so #8758's carrier gets a corrected cause (two roots, per-site) and the four-name roster goes as cleanup - #8772

Merged
briansrls merged 10 commits into
mainfrom
session/sharp-carp-197
Aug 21, 2026

Conversation

@briansrls

@briansrls briansrls commented Aug 21, 2026 •

Copy link
Copy Markdown
Contributor

What this PR is, stated before anything else

Two things, and neither of them fixes the three E0282 sites the branch title names:

  1. A correction to A1: lambda-argument typing gate — the fourth bare-name-roster instance, recorded as a carrier #8758's merged carrier, whose content asserts a mechanism this branch refutes by execution.
  2. Deletion of that mechanism — a four-name literal roster in emit_typed_call — as a cleanup whose receipt is a measured zero emitted-byte delta.

The branch title is the original brief. Its causal claim did not survive measurement, and this PR says so rather than quietly delivering something else under it.

The measurement

Two-arm emission. Subject: emitted closures of v2.compiler.tokenize, v2.compiler.parse, v2.lens.reference_deps. Producer: gunbc compile --target rust, two separately built release binaries in one dispatch. Controls run before either arm: arm B mirror lambda_argument_scope=2 / roster hits=0, arm A lambda_argument_scope=0 / roster hits=1 — the binaries are provably different.

arm A (roster) arm B (per-arg scope)
files emitted 258 258
file sets identical identical
files differing 0
lines differing 0
diagnostics 31 / 187 / 326 31 / 187 / 326

Arm C — the binding deleted entirely, not generalized — is also identical. That is what separates "the mechanism is inert" from "my replacement happens to agree with the roster", and it is the control that makes the null conclusive.

Why the mechanism cannot reach emitted bytes

Every scope. read in src/v1/05_emit_rust.dag: type_env 204, module_name 7, item_registry 1, locals 1. That single scope.locals reader is is_already_optional, on the arm taken only when a node carries no resolved type. Emission runs off the typed tree.

And the declared parameter types the gate was supposed to supply are already consulted one stage earlier — v1.compiler.infer infer_expr's ExprCall arm passes each callee formal as the argument's expected type, and its ExprLambda arm binds the lambda's parameters from that formal and writes the resolved types back onto the parameter nodes, which is exactly what lambda_scope_from_children reads. So "consult the resolved callee from lookup_item" would have been a second consultation of a fact inference had already resolved, landing in the same inert scope.

What changes in the emitter

  • The four-name roster (map/filter/flat_map/fold) is deleted.
  • Each argument is emitted in the call scope extended by that argument's own lambda parameters (lambda_argument_scope).
  • Scoping moves from per-call to per-argument. The previous form left the last lambda's parameter names visible while emitting sibling arguments and the callee's filled default expressions, positions where those names are not bound.

That narrowing is construction cleanup with NO MEASURED CURRENT CONSEQUENCE, and an earlier revision of this body called it "a real leak" — which the same null refutes. scope.locals is read once in the whole Rust emitter, on an arm taken only when a node carries no resolved type, so nothing measured here shows the leak producing a wrong byte either. It is narrowed because the narrow form is the correct construction, not because a defect was observed, and it stays unearned as a repair until a fixture makes it observable. A PR whose entire point is that an unmeasured mechanism claim was wrong must not carry a second unmeasured mechanism claim in its own body.

The surface was a genuine bare-name-keying shape — the null refutes its consequence, not its shape — so this is a syntactic instance with no observable consequence, and deleting it is a cleanup rather than a defect repair. A literal roster that reads as a live decision and decides nothing is a false coverage surface.

The carrier correction, and the dangerous half

#8758's dissolution trigger read "dies when emit_typed_call binds a lambda argument's parameters from the callee's resolved declaration rather than from the four-name roster." With the mechanism refuted, that trigger is exactly satisfiable by a change that fixes nothing — delete the roster, retire the row, three sites still red. A false cause misleads a reader; a false dissolution trigger disposes of the evidence. The replacement keys on the only fact that can honestly retire the row — the three sites emitting and compiling without an inference failure — and states that no repair to the gate satisfies it on its own.

The mechanism verdict is now a typed row carrying its own measurement (MechanismRefutedByEmittedByteNull), not prose, because prose is precisely how the previous cause got planned against. Cause is CauseUnlocated.

Deliberately not over-retracted: the three sites, their locations, the E0282 × E0061 intersection that derived them, the seven companions in the held class, and UpperBoundPendingResidueCheck with its standing prediction all survive untouched. None of them depended on the cause. The reach figure is deleted outright rather than softened — it sized the blast radius of repairing a mechanism that changes no byte.

Superseded: the map_keys repair this PR used to carry

This section described a two-line map_keys repair to
trait_derive_supplemental_generic_bound_contract_test.dag that rode along on this branch.
It is no longer in this diff and the section is kept only so the record is not silently
rewritten.
That repair landed independently through #8780, and when this branch absorbed
main the hunks dropped out on their own rather than needing resolution — verified after the
merge: the file is now byte-identical to main. Hand-resolving them would have re-authored a fix
already on main and produced a hybrid no generator emitted.

Anyone tracing who fixed the .keys() sites should land on #8780, not here.

Caveats, stated rather than buried

  • Arm B is a hand-edited stage0 mirror. That the .dag authority emits that same Rust is not established here; it needs regen. The null is a statement about the mechanism's reach, and arm C carries it without depending on the mirror.
  • The null was measured against the pre-merge main; origin/main has since been merged into this branch (it added rust_call_arg_function_value_adapt at the same site, kept).

Where the real root looks to be — not in this PR

The emitted specimen:

call:  is_prefix_of(prefix.clone(), source.clone(), |a, b| (a.clone() == b.clone()))
decl:  pub fn is_prefix_of<T: Clone>(prefix: Rc<FreeMonoid<T>>, xs: Rc<FreeMonoid<T>>,
                                     eq: impl Fn(T, T) -> bool + Clone) -> bool
site:  pub fn lex_match_prefix(prefix: String, source: String)
emitted: pub type String = std::string::String;   // v2_std_text.rs
         pub type FreeMonoid<T> = Vec<T>;          // std_algebra.rs

eq's parameters are inferable from impl Fn(T, T) the moment T is known, and T is knowable only from prefix/xs. v2.std.text declares String = FreeMonoid<Char> but emits as the host std::string::String, so the argument cannot unify with Rc<Vec<T>>, T never binds, and rustc reports at the closure. The closure is the symptom location, not the cause — which is why a roster of callee names was never going to move it. DESIGN already records this alias special-case in emit_typed_item. Hypothesis with strong supporting structure, not yet a measurement; confirming it needs cargo on the emitted crate, and that is the next lane's work.

🤖 Generated with Claude Code

Brian Searls and others added 3 commits August 21, 2026 15:49
… and the derivation stand

The row asserted a cause -- emit_typed_call's four-name collection_scope gate
produces the three E0282 sites. Two-arm emission over three closures, from two
binaries whose difference was confirmed before either arm ran, is byte-identical:
258 files per arm, 0 differing files, 0 differing lines. A third arm deleting the
binding entirely is also identical, which is what separates "refuted" from "my
replacement happens to agree with the roster".

The mechanism cannot reach emitted bytes: scope.locals has exactly one reader in
the Rust emitter (is_already_optional) on the arm taken only when a node carries
no resolved type, and the declared parameter types the gate was meant to supply
are already bound onto the lambda's param nodes one stage earlier by infer_expr's
ExprCall/ExprLambda path.

THE DISSOLUTION TRIGGER WAS THE DANGEROUS HALF. It keyed on repairing that gate,
so it was exactly satisfiable by a change that fixes nothing -- delete the roster,
retire the row, three sites still red. A false cause misleads a reader; a false
trigger disposes of the evidence. The replacement keys on the three sites emitting
and compiling without an inference failure, and says outright that no repair to
the gate satisfies it.

Not over-retracted: the three sites, the E0282 x E0061 intersection that derived
them, and UpperBoundPendingResidueCheck with its standing prediction all survive
untouched -- none depended on the cause. The surface is still a bare-name-keying
shape, now at the honest strength: syntactic, with no observable consequence.

The reach figure is deleted rather than softened; it sized the blast radius of
repairing a mechanism that changes no byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review August 21, 2026 16:03
Brian Searls and others added 2 commits August 21, 2026 16:47
…keys conversion

CARRIER: cause becomes a PER-SITE field, because the sites and the causes were
established by different instruments with different groupings. The three sites
were derived together by one intersection over one subject; the causes were
established one site at a time by three cargo probes over three emitted
closures. A single row-level CauseAttributed would force one instrument's
grouping onto the other's -- the same collapse this row was already corrected
for once.

  parse/parse_lexeme_digest        StringDeclarationSiteAlias   (help annotates ch)
  target_model/...apply_callee     UntypedFoldInitAccumulator   (help annotates acc)
  tokenize/lex_match_prefix        StringDeclarationSiteAlias   (help annotates a)

Three real reds, TWO causes. Row-level CauseUnlocated is retired rather than
answered, replaced by CauseGrouping = CausePerSite. The dissolution trigger now
states that no single repair can fire it.

The discriminator is free and sits in output everyone reads past: rustc's help
names WHICH parameter it wants annotated. Element means the collection could not
supply T; accumulator means the fold's init could not supply its own type.

Also recorded: a pre-registered hunch that was REFUTED (field access on the
lambda parameter was named in advance as the first place to look, and rustc's
caret is on acc, never on row), and a warning that four E0282s in the parse
crate belong to the held contains class so a count there is not a population.

MAP_KEYS, NOT MINE AND SAID SO: the witness floor refuses on
trait_derive_supplemental_generic_bound_contract_test.dag with "method 'keys'
not found on receiver type 'Map(...)'". Confirmed pre-existing on main at
c603c06 -- same file, same two lines, same message, on a commit unrelated to
this branch. #8597 converted six sites in that file to map_keys and left two
behind. This applies the same conversion to the stragglers; it repairs a
main-wide red, not a defect this branch introduced.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title Bare-name roster root, declaration-consulting half (NOT held by the contains ruling): emit_typed_call binds a lambda argument's params only if the callee's bare name is one of four literals - consult the resolved callee from lookup_item instead The lambda-typing gate is INERT: three arms show zero emitted-byte delta, so #8758's carrier gets a corrected cause (two roots, per-site) and the four-name roster goes as cleanup Aug 21, 2026
Brian Searls and others added 3 commits August 21, 2026 16:52
…futes it

The per-argument narrowing was described as closing a real leak -- one lambda's
parameter names visible while emitting sibling arguments and filled defaults.
That is a mechanism claim, and the measurement in this PR refutes it exactly as
it refutes the roster: scope.locals is read once in the whole Rust emitter, on
an arm taken only when a node carries no resolved type, so nothing measured
shows the leak producing a wrong byte.

It is now stated as construction cleanup with no measured current consequence --
narrowed because the narrow form is the correct construction, not because a
defect was observed, and unearned as a repair until a fixture makes it
observable.

A change whose whole point is that an unmeasured mechanism claim was wrong must
not carry a second unmeasured mechanism claim in its own text. Same defect, one
level down. Caught by external review, not by me.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…not #8597's

Commit bbf4350 in this branch blamed #8597 for the two surviving `.keys()`
call sites. That is wrong, and a commit repairing a main-wide red while
misattributing it puts a false fact in the durable record beside a true fix.

Verified per-commit on the file rather than taken on trust:

  #8726  dd1ac18   +6 map_keys lines,  +0 .keys() lines
  #8749  8077603   +0 map_keys lines,  +2 .keys() lines   <- introduced both
  #8597  aa8a65e   +0 in either direction, touched neither

THE MECHANISM IS ALSO NOT WHAT bbf4350 SAID. It described an incomplete
conversion -- six done, two missed. It was not. #8726's sweep was COMPLETE when
it ran: it converted every site that existed at that point. #8749 then
introduced NEW uses of the retired spelling, from a branch that predated the
deletion. No census over #8726's tree could have found them, because they did
not exist yet.

So the class is REINTRODUCTION AFTER A SWEEP, not a missed census, and it is a
merge-time question rather than a branch-time one: neither branch is wrong on
its own, and the defect exists only in their union. Nothing guards that today.

This two-line repair is therefore the right SCOPE and explicitly NOT the root.
It clears a main-wide red; what stops the next reintroduction is unbuilt.

Caught by quick-lynx-620 pushing back on the attribution I was given.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The target_model site in lambda_typing_gate_sites has a distinct cause with a
distinct repair, so it gets a row. empty_map() in a fold's `empty:` position
emits as v1_rt::rc_empty_map::<_, _>(); the only thing that could close those
type parameters is the fold's own closure, which RECEIVES the accumulator -- so
the target compiler is asked to infer a parameter's type from a body that uses
it, and refuses.

What rules out the neighbours, since this site was first filed under one: the
collection is a genuine List<TargetEffectCalleeRow>, rustc never asks about
`row`, and `acc` traces to the init rather than to the collection or the
callee's spelling. The discriminator is rustc's own help text naming which
parameter it wants annotated -- |acc: Type, row| here, |acc, ch: Type| for the
collection-realization class.

POPULATION IS ObservedSitesOnly. One measured site is not a census: this one was
found only because it sat in a partition being probed member by member, and no
sweep for untyped empty-collection literals in fold-init position has been run.
The sweep is named rather than performed so the absence is a stated gap, not an
implied zero.

The site STAYS LISTED in lambda_typing_gate_sites with a cross-reference, because
that row records that the three were derived TOGETHER by one intersection over
one subject. Removing a member because its cause turned out to differ would
destroy the only artifact of that derivation.

Trigger is keyed to the site compiling, not to any repair -- the sibling row
learned that the expensive way. It also refuses the cheap escape: annotating the
closure at the call site moves the obligation to the author instead of deriving
it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Brian Searls and others added 2 commits August 21, 2026 19:17
…ction, delete the refuted one

The only genuine conflict was gunbc.lambda_argument_typing_gate, and it was
substantive rather than textual. Main had added a citation-enrollment projection
to the carrier and still held the ORIGINAL dissolution trigger -- the one keyed
to emit_typed_call binding lambda params from the callee's resolved declaration
rather than from the four-name roster. That mechanism was measured inert on this
branch: three arms, 258 files each, zero differing files, zero diff lines,
including a deletion control.

Keeping both sides would leave the row with two answers to when it dies, one of
them satisfiable by a change that fixes nothing -- delete the roster, retire the
row, three sites still red. So main's trigger and the reach paragraph arguing for
it are DELETED, not kept beside the replacement, and a merge note records that
they were deleted and why, so the removal is not a silent drop of authored rows.
Main's enrollment projection is untouched; the imports are the union of both
sides.

The trait_derive_supplemental_generic_bound_contract_test.dag hunks this branch
carried DROPPED OUT on absorb rather than needing resolution, because that repair
landed independently through #8780. Verified after the merge: the file is now
byte-identical to main. Hand-resolving them would have re-authored a fix already
on main and produced a hybrid no generator emitted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…use, not a copied one

Adding per-site `cause` and `cause_receipt` to LambdaTypingGateSite made every
existing literal incomplete, and the floor refused exactly one:
dag/test/claim/long/cited_symbol_resolution_witness_test.dag's planted red
control, landed by #8775 after this branch's type change was authored. That
refusal is the fail-closed census working -- the widened type surfaced its one
real dependent loudly rather than silently defaulting it.

The planted site is SYNTHETIC: it names a declaration that does not exist, so no
probe can have measured why it fails to type and there is nothing for a receipt
to point at. It therefore carries SiteCauseUnmeasured / NotYetProbed, and an
annotation says why it must stay that way -- copying a real site's cause here to
make the literal look complete would put a measured attribution on a specimen no
run produced, which is the fabricated-receipt failure the receipt field exists to
prevent.

Census over the corpus: this is the only LambdaTypingGateSite literal outside the
carrier itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit b5f5d22 into main Aug 21, 2026
1 check passed
@briansrls
briansrls deleted the session/sharp-carp-197 branch August 21, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant