Repository navigation
rigor-infer: descend toward the site under non-descending containers — fixes #380 - #389
Merged
Merged
Conversation
…fixes #380 `entry_descend` flat-applied `Loop`/`Case`/`When` subtrees and `BeginRescue`'s else/ensure arms without ever reaching the deferred-body barrier, so a literal block/lambda nested under one never replayed its own `closure_mutations` — `while c; [1].each { h.default ||= 0; h[:a].frobnicate }; break; end` kept the indexed narrowing and fired a `call.undefined-method` the oracle doesn't emit. The flat envelope is unchanged; after it, `entry_descend_site_child` descends into the one child holding the site so the barrier handling (env capture + positional mutation replay) still runs. The non-Sequence `Statements` carriers stay flat: the oracle does not evaluate `Recovered` (`x = (each{..}) rescue nil`), `Jump` (`break each{..}`), or `Inert` (`END {}`) children as call sites — all three fire on both engines. Probe rows vs the oracle: while/until/for/modifier-while, case-when, case-in, else/ensure arms, nested while, if-in-while, lambda-under-until — all byte-identical (silent in-body, post-container read fires). Controls — read-before-write under while, the three carriers above — both reads fire on both engines. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Probing the oracle showed a block inside a `when`/`in` CONDITION keeps
its writes out of the in-body read's scope — `when [1].each {
h.default ||= 0; h[:a].m }` fires there on the reference. Descending
through `flow_children(When)` (conditions + body) suppressed it; restrict
the descent to `body`.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
The barrier capture `*env = flow.env.clone()` is the end-of-FILE env:
reached from a non-descending container it resurrects post-statement
rebinds (`x = "s"; while x; [1].each { x.upcase }; break; end; x = 1`
fired `upcase for 1` where the oracle and master are silent). Thread
`preserve_env` through `entry_descend`/`entry_children`; only the
container-originated descent sets it, so every barrier/def/lambda
capture keeps the flat scope the container pass already built. The
top-level/rescue-clause/main-body capture paths are unchanged — the
same FP family there predates this branch and stays a separate issue.
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.
Closes #380
Summary
entry_descendflat-appliedLoop/Case/Whensubtrees andBeginRescue's else/ensure arms viaapply_subtree_effectsand never crossed into the site-holding child, so a literal block/lambda nested under one never replayed its ownclosure_mutations:Fix (two parts):
apply_subtree_effectsstill applies every contained effect — and afterwardsentry_descend_site_childdescends into the oneflow_childrenchild holding the site, so theFlowEdge::Barrierpositional mutation replay runs for the deferred body inside.when/indescend into their body only — the oracle does not evaluate a condition-position block into the read's scope (when [1].each { w; r }still fires in-body).preserve_envthreads throughentry_descend/entry_children: on the container-originated descent the barrier/lambda/def captures offlow.envare skipped.flow.envis the end-of-FILE env — installing it resurrected post-statement rebinds (x = "s"; while x; [1].each { x.upcase }; break; end; x = 1firedupcase for 1; ref + master silent). The top-level,beginmain-body andrescue-clause capture paths are unchanged — the same FP family there predates this branch → filed as FP: deferred-body barrier captures flow.env (end-of-file env) — post-statement rebinds leak into earlier blocks #390.Non-Sequence
Statementscarriers stay flat by design — probed: the oracle does not evaluateRecovered(x = (each{w;r}) rescue nil),Jump(break each{w;r}), orInert(END { each{w;r} }) children as call sites; all three fire on both engines.Probes vs the reference (
harness/probe.py, full tuples + exit code)Suppression rows — all
=(byte-identical), in-body silent / post-container read fires:while/until/ modifier-whilewhile c; each{w;r}; break; endfor(lowers toNode::Loop)for i in [1]; each{w;r}; endcase/when,case/inbeginelse/ensurearmsbegin;nil;rescue;nil;else;each{w;r};endwhile,if-in-while, lambda-under-untilrescueclauseReview-regression rows (post-container local rebind must not leak into the earlier block) — all silent on both engines after the
preserve_envfix:while,until,forx="s"; while x; each{x.upcase}; break; end; x=1case/whenensurearm,elsearmwhilel = -> { x.upcase }if+whileType-drift control:
x = 1; while x; [1].each { x.upcase }; break; end; x = "s"now reportsupcase for 1on both engines — the pre-fix head drifted tofor "s".Must-still-fire controls — all
=, in-body + post-container both fire:whilex = (each{w;r}) rescue nil(Recovered),break each{w;r}(Jump),END { each{w;r} }(Inert)defbody underwhile(fresh scope, no replay)Gates
harness/gate.sh: pass on the final head (docs_check,cargo test -p rigor-cli+-p rigor-infer,run_snapshot.rb)container_nested_block_attr_write_drops_indexed_narrowing(12 suppression + 4 control rows),container_nested_block_keeps_flat_env(7 silent + composed + drift-control rows)fp_audit --gaps --sweepon the final head: see belowDisclosures (pre-existing, unchanged by this diff — verified on master)
beginmain-body/rescue-clause/loop dobarrier captures still installflow.env(end-of-file env) —x = "s"; [1].each { x.upcase }; x = 1firesfor 1on master and this head alike. Deliberately out of scope (wider blast radius: every barrier site's env).whilebody drops the in-body record — same on master.flow.always-truthy-conditiononif <pinned>inside awhenarm — port doesn't warn; different rule, unrelated.class_narrowing.rsgates onevaluatedonly) is deliberately out of scope — coverage-loss direction, not an FP.Generated with Devin