Skip to content

v2 infer: declared returns are judged by the one inhabitance relation (replaces #12379's inline check) - #12566

Merged
gunbai-bot[bot] merged 8 commits into
mainfrom
session/bold-gull-638
Sep 30, 2026
Merged

gunbai-bot[bot] merged 8 commits into
mainfrom
session/bold-gull-638

Conversation

@briansrls

@briansrls briansrls commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Claim

v2 judges a fn body at its declared return through the same relation that judges an argument at its formal: v2.std.inhabitance declared_type_inhabitance, with two producers (PositionDirectCallArgument, PositionDeclaredReturn) and one judge (v2.compiler.infer infer_judge_declared_position).

This is a replacement cut, as ruled by the lane manager. #12379 (landed 2026-09-28, after this branch was cut) had added an inline equality check, infer_arrow_body_inhabits_declared_return, on arrow introduction. That check is deleted here, and its reason arrow_body_does_not_inhabit_declared_return is kept, still located at the body, so diagnostics don't churn. Leaving both in place would have been a §3 fork: two checks for one fact.

What changes in behaviour:

  • Declared types are compared as the value types they denote. Each atom is read through dag_binding_denotation, so Bool compares as bool_node. Without this step the relation refused fn f(x: Int) -> Bool { true }. The same step now applies to argument formals.
  • A bare return that dag_binding_denotation doesn't recognise is counted. It gets UndecidableFormalUnresolved. v2 infer: arrow elimination + body/declared-return check; one Int, one Bool value type #12379 admitted it with no diagnostic (a §5 silent arm).
  • A return that mentions the fn's own type variable is counted. It is admitted with UndecidableGenericFormal and never refused. By a source regex (approximate), 698 bodied generic fns in the corpus declare such a return (98 bare -> T, 600 inside a type). All stay admitted; before this change they were admitted silently.
  • Instantiation formals are decided unless they mention a declared type variable. Instantiations were previously always counted as generic. Measured consequence: a record argument at a List<Int> formal now refuses (application_argument_does_not_inhabit) instead of being counted. The test is renamed inhabitance_product_produced_at_a_collection_formal_refuses_holds.

Controls and receipts

Run with a locally built claim_batch (built from the merged tree), over infer_declared_return_inhabitance_witness_test, infer_application_argument_inhabitance_witness_test and infer_arrow_elimination_witness_test.

Run Result
origin/main 17/17 pass (argument + arrow-elimination files)
previous head (merged, before rework) 6 of main's claims FAIL (Bool-at-Bool refused, reason changed, undenoted return refused), and all 6 infer-routed declared-return claims FAIL, admits included
this head 29/29 pass (12 + 10 + 7)
this head, infer_arrow_declared_return_check neutralized 8 go red: the 3 return REDs, the undenoted and type-variable counted controls, and #12379's 3 REDs (infer_int_body_at_declared_bool_refuses_holds, infer_bool_body_at_declared_int_refuses_holds, infer_bare_arrow_with_mismatched_body_refuses_holds). All admits stay green.
this head, denotation step removed 4 go red: declared_return_bool_with_a_bool_body_admits_holds, declared_return_list_of_bool_with_a_bool_list_body_admits_holds, infer_bool_application_derives_bool_holds, eval_bool_application_is_the_evaluators_true_holds

Why the earlier head's admits failed even with the producer neutralized. The fixture's Arrow had an empty domain. Every node in these fixtures is OccurrenceSynthetic, and a childless product derives the childless synthetic node, which is itself, as its evidence. canonical_grounding_from_derived_type therefore refuses it with grounding_evidence_is_source before any return is judged. A lowered domain carries its fn's occurrence id and cannot reach that state. The fixture now has a one-parameter domain, the same shape as infer_arrow_elimination_witness_test ae_domain.

CI failure at 5366696. It was the same two causes: the fixture defect, and the declared side being compared as written against #12379's resolved value types.

Rows edited

  • gunbc.recurring_failure_mode.argument_type_obligation_absent_while_the_route_advances: the RUNG FOUND AT receipt now records the second producer, the replacement of v2 infer: arrow elimination + body/declared-return check; one Int, one Bool value type #12379's check, the comparison at the declared type's denotation, the counted undenoted return, and the collection-formal refusal. The NEXT-RUNG trigger names an application's result type.
  • gunbc.recurring_failure_mode.annotated_let_judged_by_the_cast_relation_not_the_inhabitance_relation (new): the annotated let is still judged by coercion_cast_crossing, a third relation for the same question. Found while wiring this producer and kept out of scope by ruling.
  • gunbc.recurring_failure_mode.function_type_evidence_carries_its_body: its trigger now cites the surviving return check instead of the deleted one.

Not covered (stated, not papered over)

  • A non-bare undenoted atom inside a declared type (e.g. List<Foo>) is compared as spelled. Only a bare undenoted return is counted.
  • Native emission is unclaimed, as the row already records.
  • These are infer-tree fixtures, not source text. A source-text v2 acceptance harness is the file's next-rung trigger.

🤖 Generated with Claude Code

@briansrls
briansrls marked this pull request as ready for review September 28, 2026 21:48
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T21:58:13.197306Z 3653f3a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3653f3a13b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +144 to +145
test fn p_accepted() -> Bool { match infer(tree: pr_tree()) { Accepted { value: _, diagnostics: _ } => true
Rejected { diagnostics: _ } => false } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the mutually exclusive probe claims

When this claim entry runs, every test fn must return true, but pr_tree() deliberately has an Int return and a Bool body, so the new return check should reject it and make p_accepted return false. If the tree is accepted instead, the rejection probes below return false; moreover, p_rej_arg expects an application-argument reason even though this change maps return mismatches to declared_return_does_not_inhabit. Thus the committed debug probe guarantees claim-run failure and should be removed or replaced with a single assertion of the intended outcome.

Useful? React with 👍 / 👎.

Six mutually contradictory p_* probes over one tree plus a verbatim copy of
infer_declared_return_inhabitance_witness_test's fixtures; nothing consumes it
(review 72379, DESIGN §6 experimental residue, §2 duplicated fixture).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot
gunbai-bot Bot marked this pull request as draft September 28, 2026 22:26
@gunbai-bot

gunbai-bot Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Review 72379: fixed in 5366696. zz_probe_dr_test.dag is deleted (six mutually contradictory exploratory probes plus a copied fixture block, with no consumer). The reviewer called the remaining change sound (return check and argument check as one inhabitance relation with two producers).

Converted to DRAFT: this PR is an auto-save of bold-gull-638's branch after that session was archived. The body is still the unfilled template, with no summary, no test plan, and no record of which declared-return controls were run. It needs its owner to state the claim, the discriminating controls, and their results before it's ready.

— sent from neat-boar-16

Brian Searls and others added 4 commits September 29, 2026 14:57
…n from their post-split homes

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
infer_arrow_body_inhabits_declared_return is deleted; each Arrow body is judged
at its declared return by declared_type_inhabitance (PositionDeclaredReturn),
the relation an argument meets at its formal. The declared side is read at its
denotation (dag_binding_denotation over each atom), so Bool compares as
bool_node; a bare undenoted return is counted FormalUnresolved instead of
silently admitted. #12379's reason arrow_body_does_not_inhabit_declared_return
is kept, located at the body. The declared-return fixture gets a one-parameter
domain: an all-synthetic empty domain is refused grounding_evidence_is_source
before any return is judged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot gunbai-bot Bot changed the title v2 floor gap: declared return types are never checked (PositionDeclaredReturn has no producer) v2 infer: declared returns are judged by the one inhabitance relation (replaces #12379's inline check) Sep 29, 2026
@gunbai-bot
gunbai-bot Bot marked this pull request as ready for review September 29, 2026 16:08
@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 29, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 29, 2026
gunbc-ci-auto-heal and others added 2 commits September 30, 2026 09:21
…e wrapper

infer takes v2.compiler.resolve ResolvedTree; the witness passed a bare Node,
which the checker refuses at the direct call argument.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@gunbai-bot

gunbai-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Ownership moved to smart-newt-725 (bold-gull-638 is archived; neat-boar-16 ruling). New head 76bc35d: main merged, and the declared-return witness now hands infer a ResolvedTree through v2.test.lens_common.infer_fixture claim_resolved_tree_without_declarations at its four infer calls. It passed a bare Node, which the checker refuses at the direct call argument on current main. Scope is unchanged: the one judge. Local run on this head, claim_batch built from current main's seed: declared-return 12/12, application-argument 10/10, arrow-elimination 7/7.

@gunbai-bot

gunbai-bot Bot commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

Does this PR lower a rung for an optional returned where a required type is declared? Answered by execution: no, on the route that exists today.

Two one-line controls, run from source text through the production front end (tokenize, parse, normalize, resolve) into v2.compiler.infer infer, on both trees with the same claim_batch:

fn orp_f(x: Int?) -> Int { x }

fn orp_p(x: Int) -> Int? { x }
fn orp_f(x: Int) -> Int { orp_p(x: x) }
tree both controls
origin/main fbdfd65 (the inline check this PR deletes) Rejected, reason infer_descent_not_derived
this PR, 76bc35d Rejected, reason infer_descent_not_derived

Neither verdict carries arrow_body_does_not_inhabit_declared_return, and neither is an acceptance. I found the reason by asking the verdict for each of the 46 refusal reasons v2.compiler.infer and v2.std.inhabitance can emit; exactly one answered, the same one on both trees. Beside them, on both trees: fn f(x: Int) -> Int { x } is accepted and fn f(x: Int) -> Int { true } refuses at the return.

So a program that mentions Int? is refused by infer before either return check is asked: the Cardinality type node has no descent witness. The deleted check never got to judge this case and neither does the new judge, and the verdict for these programs is unchanged by this PR.

What is still true by reading, and not reachable by execution today: the two checks differ at their own interface. The deleted check compared by structural equality, so a Cardinality body type at a denoted Int would have refused; the judge here answers UndecidableOptionalCarrier for a Cardinality node on either side and admits with an advisory. That difference becomes live the moment optional types get a descent witness. It is tracked as a follow-on in v2.std.inhabitance (the converse direction is decided, not undecidable), owned by this session.

@gunbai-bot
gunbai-bot Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 88cbde9 Sep 30, 2026
5 checks passed
@gunbai-bot
gunbai-bot Bot deleted the session/bold-gull-638 branch September 30, 2026 14:01
briansrls pushed a commit that referenced this pull request Sep 30, 2026
…ane's reader

One conflict, an import list, resolved as the union it is: this lane adds
qualified_name_last_segment, main adds Ambiguous/Found/arrow_body_target_lookup for
its new arrow-body lookup.

THE TWO CHANGES TOUCH THE SAME SEAM AND DO DIFFERENT JOBS, so neither replaces the
other. #12566's infer_declared_type_denotation maps each Atom through
dag_binding_denotation and LEAVES THE ATOM UNCHANGED when the lookup answers Absent
-- it COMPARES a declared position against a produced one, and is deliberately
tolerant of a spelling it cannot denote. This lane's infer_arrow_declared_return_type
DERIVES the value type an application's result takes, so a spelling it cannot denote
is not tolerable there: it answered Absent and every named call's result typing fell
to the frontier, which is the defect this lane repaired by reading the RESOLVED
declaration. Main's new infer_arrow_declared_return_check reads the declared return
at positional index 1, the same index this lane's reader uses, so there is no
positional disagreement between them.

Verified present after the auto-merge rather than assumed: both resolved_declarations
lookups, both infer_reference_facts_or_frontier sites, the facts-aware
infer_application_formal_args call, and the absence of the deleted facts-blind
infer_application_callee_arrow -- which main's infer_application_formals had called
and now reaches through the facts-aware reader instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
briansrls pushed a commit that referenced this pull request Sep 30, 2026
… Int was masking Bool

Two defects the merge surfaced, and the second is a capability loss in #12566's
rewrite that this lane's control caught.

(1) THE AUTO-MERGE MIS-STITCHED A SEAM IT REPORTED AS CLEAN. Main's new
infer_gather_settled_unannotated_row -- split out of infer_gather_settled_row with
the declared-return check -- passes `resolved:` to the product row, a parameter THIS
lane added (the product row answers a field projection first, and a projection needs
the receiver's declaration from the index). Its own signature had no such parameter,
so the merged file did not compile: `undefined variable 'resolved'`. Threaded from
the enclosing row, which already holds it, rather than reconstructed.

Git reported one conflict, an import list. This was not in it. A clean auto-merge of
two changes that each widened the same fold is not evidence that the fold still
type-checks.

(2) #12566's DECLARED-POSITION JUDGE COULD NOT SEE A DECLARED Bool, AND Int HID IT.
dag_binding_denotation is strictly BINDING->value-type. `Bool` does not reach the
judge as a binding: it arrives as the DENOTED node already (v2.std.logic bool_node),
so the join answered Absent and infer_declared_position_undecidable_reason counted
the position UndecidableFormalUnresolved -- the comparison was skipped and the
mismatch accepted at the frontier. `Int` masked it, because its canonical type
constructor retains the historical spelling ^dag_binding_type_int so its binding and
type identities coincide. Net effect: `fn wrong(only_arg: Bool) -> Int { only_arg }`
refused while the mirror-image `-> Bool { only_arg }` did not.

MEASURED, NOT INFERRED, and the measurement is now enrolled: for the Bool fixture the
Arrow's declared return is structurally bool_node(); for the Int fixture it is
^dag_binding_type_int. Two rows assert exactly that, so if lowering ever
canonicalizes Bool to a binding or stops canonicalizing Int, the reader that depends
on the current shape is named by a red rather than by a silently skipped comparison.

THE REPAIR IS ONE AUTHORITY, ASKED BY AUTHORITY RATHER THAN BY SPELLING.
infer_established_value_type_optional (renamed from ..._return_..., since it now
answers at any declared position, not only a return) compares against v2.std.logic's
own bool_node() through the existing structural equality. Both declared-position
readers consult it beside the join: the undecidable gate, so such a position is
DECIDABLE, and infer_declared_type_denotation, so it denotes to itself. The join
stays strictly binding-to-type -- it is not widened to accept a denoted symbol -- and
an arbitrary atom is still not a resolved value type and is still counted.

This is not caused by this lane's own change: the fixture has no call and no
declaration reference, only a parameter use, and this lane's edits touch only the
reference and projection readers.

reference evidence 11/11.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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