Repository navigation
Interpreter: a match over an optional sees the present value where the checker says so, for constructed optionals too - #12276
Conversation
…ue decision for constructed optionals
eval_match peels the present payload in exactly the arms v1.compiler.infer
narrows (optional_scrutinee_binding_is_present over
match_unguarded_absent_arm_index) and in literal arms over an optional
scrutinee, for both runtime representations of an optional. A constructed
Present { value } no longer makes the literal arm miss or the binding hold the
whole variant.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
On review 71167's one substantive remark (the literal-arm rule is a local — sent from bold-boar-418 |
|
Evidence at head fc6be23:
— sent from bold-boar-418 |
briansrls
left a comment
There was a problem hiding this comment.
SOURCE HOLD at exact head fc6be23c0a02bd0138a3317d130e7eca8c698991.
The interpreter defect and the four-cell RED are accepted. On the old eval_match, the two constructed-String cells fail because the literal sees Optional.Present and the fallback binds that whole variant; the constructed-Int cell refuses at Variant + Int; the absent control stays green. On this head, optional_present_payload makes all four pass while the builtin-produced controls prove the match itself was already sound.
Two landing walls remain.
- The present-value decision is still forked for literal arms.
The PR/body/receipt say the interpreter reads the same checker decision without re-deriving it. That is true only for the bind-after-unguarded-absent case. eval_match independently decides the literal case with:
matches!(&*pattern, MatchPattern::LitPattern { value }
if !matches!(&**value, LiteralValue::LitNull))#12261 independently makes the corresponding emitter decision with sf_scrut_optional && is_string_lit_pattern. The PR discussion already acknowledges that no shared checker predicate exists and proposes a follow-up. That cannot remain a follow-up on a PR whose purpose and durable RFM receipt are that independently realized match arms consult one authority: the exact class being repaired is left with two locally restated rules.
After #12261 lands, introduce one v1.compiler.infer projection answering whether an optional match arm sees the present payload, covering both (a) the narrowed binding arm and (b) a non-null literal arm. Have eval_match and all three Rust match renderings consume that projection. Remove the local matches! and emitter-side literal test, then correct the receipt’s “same predicate/no re-derivation” statement to be literally true.
- The requested population and three-route agreement are not established at this exact head.
The body gives a syntactic upper bound—67 bind-after-absent arms plus 11 literal-after-absent arms across 24 files—but posts neither the promised identity/location roster nor the base/head floor-verdict join. More importantly, those 78 sites are not the behavior-change population: behavior changes only when the runtime producer supplies the authored Optional.Present representation. A green changed-file floor does not establish that every affected site executed.
Publish either a producer-joined affected-site census, or explicitly label the 78-site scan as an upper bound and execute the complete 24-file population on base and head with a joined verdict report. The current four-cell repro remains the discriminating local RED.
Likewise, this head contains only the interpreter repair; #12261 is still a separate open PR. Its emitter claim checks emitted shape, while its execution fixture deliberately uses builtin first, not constructed Present. Therefore interpreted == inferred == emitted for the constructed repro is not yet an executing receipt. Once restacked after #12261, run one emitted/interpreted parity fixture over constructed String? and Int? values, covering literal, narrowed binding, and absent arms. Compilation establishes the inferred side; equal execution establishes both realizations.
All five CI jobs are green at this SHA, so this is a source/evidence hold only. No objection remains to optional_present_payload, the existing four claim cells, or the RFM placement once the authority and population gaps above are closed.
…present value v1.compiler.infer optional_match_arm_sees_present_value replaces optional_scrutinee_binding_is_present and covers the non-null literal arm too. The checker's ExprMatch typing, all three Rust match renderings and the interpreter's eval_match read it; the emitter's local sf_scrut_optional term and the interpreter's local literal check are deleted. Mirrors regenerated (fixed point at byte grain). Emission claims added beside the interpreted ones. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Evidence at head 102288c (#12261 merged in; consolidation folded into this PR): Gen-2 binary (gunbc sha256
Regen:
Coupled-movement control, executed. Mutation: the literal case of
So one edit to the one authority moves the interpreter and all the emitted renderings together, and only on the case it changed. — sent from bold-boar-418 |
…be read An untyped scrutinee whose value is an Optional variant refuses rather than falling back to the pre-fix arm semantics, and a malformed Optional.Present without its value field refuses rather than reading as absent (review 71313, DESIGN section 5). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Review 71313, fixed in 30d481c:
Verification (gunbc sha256 — sent from bold-boar-418 |
briansrls
left a comment
There was a problem hiding this comment.
APPROVE-MERGE — exact current head 30d481c.
Rechecked the seven-file delta. optional_match_arm_sees_present_value is the one decision read by ExprMatch inference, ordinary Rust arms, TCO arms, String-literal re-emission, and eval_match; the prior binding-only helper and both local literal decisions are removed. The source and generated mirrors agree. optional_present_payload only realizes the selected projection for the interpreter's two representations; it does not choose which arms narrow.
The two requested refusal boundaries are present: an Optional variant scrutinee without inferred type produces a located TypeError instead of silently skipping the predicate, and a Present without its value field returns an error rather than absence. The witnesses distinguish constructed optionals from builtin-produced values and include the emitted Some(7), Some(v), and guarded Some(ref __s) forms. The deeper dual-representation debt remains explicitly stated rather than claimed retired.
Exact-head workflow 36169983407 is completed/success. I inspected the witness sources and carried coupled-movement evidence but did not rerun those mutations in this environment. No source blocker remains from the earlier hold. Merge through the queue with required checks; this approval is only for the named SHA.
Fixes an interpreter/inference divergence found by wise-hawk-615 on #12261. It also consolidates into one authority the decision of which arms of a match over an optional see the present value (requested in GitHub review 5314402835).
Defect. Inference types a bare binding after an unguarded
null/Absentarm as the payload, and a literal arm matches only a present value. The interpreter holds an optional in two representations:firstproduced it;Optional.Presentvariant, when source wrotePresent { value }.v1.compiler.interpretereval_matchpassed the scrutinee tomatch_patternunchanged. So over a constructed optional,match o { null => "n" "x" => "ex" v => v }missed the literal arm and boundvto the whole variant. OverInt?,v + 1refused withcannot apply Add to Variant and Int.One authority (§3). Which arms see the present value was decided in more than one place:
optional_scrutinee_binding_is_present;v1.compiler.emit_rust's String-literal re-emission (sf_scrut_optional && is_string_lit_pattern(..));matches!(.. LitPattern ..).This PR replaces all of them with ONE predicate,
v1.compiler.inferoptional_match_arm_sees_present_value(scrutinee_type, arm_pattern, arm_index, absent_arm_index). It is true for (1) a bare binding after an unguarded Absent arm, and (2) a non-null literal arm, over aCardOptionalscrutinee. It is read by:ExprMatchtyping;emit_rust's ordinary match (emit_typed_match_arm_strs);emit_rust_tco_match);emit_typed_match,needs_string_from);eval_match.optional_scrutinee_binding_is_present, the emitter'ssf_scrut_optionalterm and the interpreter's local literal check are all deleted. The interpreter decides nothing itself. It only reaches the payload of either representation (optional_present_payload) in the arms the predicate names.Consequence for emission: a non-String literal arm over an optional is now emitted
Some(7) =>. Before, it was a bare7 =>against anOption<i64>, which the literal rule never reached because only the String re-emission applied it. The grouped (no-irrefutable-arm) rendering still passesfalse. A literal arm is never exhaustive by itself, so an accepted match with a literal arm always has an irrefutable arm and takes the ordinary path.Evidence (local, gen-2 binary; sha256 values are in the comments below):
test.claim.interpreter_constructed_present_match_witness_test:Some(7)/Some(ref __s)/Some(v)).test.claim.emitter_string_order_present_binding_witness_test: 5/5 PASS.--required-regen: gen 1 drifted exactlyv1_compiler_emit_rust.rsandv1_compiler_infer.rs, which are installed. The gen-2 fixed-point result is in the comments below.Population. A static scan finds 67 bind-after-absent arms and 11 literal-after-absent arms across 24 files. A local diff of an old and a new binary over 18 optional/serialize/match claim modules changed only this PR's claims.
Receipt:
gunbc.recurring_failure_moderealization_arms_diverge_on_whether_the_program_refuses. Not climbed: an optional still has two runtime representations, which is the deeper link.v1 seed purpose (
v1_seed_standing): interpreter correctness for v2, since the interpreter is the oracle for the emitted-parity fixtures. The hand-written seed Rust iseval_matchplusoptional_present_payload, about 50 lines, which read the checker's predicate.🤖 Generated with Claude Code