Type mutable-array length/reads and emit bounds obligations (closes #1117) - #1150
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f39468747
ℹ️ 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".
| return ArrayGet(self.location, self.typeof, subject_red, position_red) | ||
|
|
||
| @dataclass | ||
| class ArrayLength(Term): |
There was a problem hiding this comment.
Add ArrayLength to rewrite walkers
Adding this new Term subclass without teaching the explicit rewrite helpers about it makes proof tactics fail on otherwise type-checked formulas that mention length(a): apply_rewrites calls count_marks before replace/simplify, and count_marks/find_mark/replace_mark/rewrite_aux all enumerate known node types and fall through to internal_error for ArrayLength. In practice, a theorem or future imperative obligation containing length(a) cannot be rewritten or simplified unless the rewrite happens to avoid these walkers entirely.
Useful? React with 👍 / 👎.
| # A mutable-array index must be an unsigned/integer type, consistent with the | ||
| # pure-array reduction (which handles UInt and Nat positions). Checked only on | ||
| # the mutable-array path (#1117); pure-array indexing is left untouched. | ||
| _ARRAY_INDEX_TYPES = ('UInt', 'Nat', 'Int') |
There was a problem hiding this comment.
Keep mutable indexes compatible with length bounds
Including Nat and Int here lets a[i] type-check for index types that cannot use the emitted bounds shape: length(a) is typed as the stdlib list length result (UInt), while the existing < overloads compare homogeneous pairs, so a user cannot type-check the natural precondition i < length(a) for a Nat or Int index. Those reads will later produce unprovable or ill-typed bounds obligations unless the checker either restricts mutable-array indexes to UInt or builds/coerces the bounds goal in the index's type.
Useful? React with 👍 / 👎.
…1117) Phase 2h of the imperative verification layer (#854). - checker_types: type-check `a[i]` on a mutable `[T]!` handle to the element type with an unsigned/integer index requirement (pure `[T]` unchanged), and type `length(a)` on an array handle as the array-length type via a new symbolic ArrayLength node. - imperative_verifier: build the source-located `i < length(a)` ARRAY_BOUNDS obligation for a mutable-array read, with a per-pass dedup collector keyed on source access. - Unit tests distinguishing the ArrayType/MutableArrayType paths and covering obligation construction, dedup, and discharge; .pf fixtures for a valid read/length contract (should-warn) and a rejected non-integer index (should-error). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t index Codex review on PR #1150: - Teach the explicit rewrite walkers (count_marks/find_mark/replace_mark/ rewrite_aux) about ArrayLength so `replace`/`simplify`/rewrite tactics no longer internal_error on a formula mentioning `length(a)`. - Restrict the `length` intercept to mutable `[T]!` handles only, keeping ArrayLength confined to procedure specs and bounds obligations (never reaching the recursive-call/compiler walkers that pure runtime terms use). - Require a mutable-array index to be UInt: `length(a)` is UInt and `<` compares homogeneous pairs, so only a UInt index can form a provable `i < length(a)` bound. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4f39468 to
2add2f8
Compare
|
Thanks — both P2 comments addressed in
I also scoped the |
Phase 2h: type mutable-array
length/reads and emit bounds obligationsCloses #1117 (parent umbrella #854). Depends only on the already-merged
#1111 (proc signatures/contracts) and #1112 (obligation API), so this runs in
parallel with the #1113–#1116 chain exactly as the issue's parallelization note
describes.
What changed
Type checking (
checker_types.py)a[i]on a mutable[T]!handle now type-checks to the element typeT,mirroring the pure
[T]path, and requires aUIntindex (length(a)isUIntand<compares homogeneous pairs, so only aUIntindex can form aprovable
i < length(a)bound). The pureArrayTypepath is leftbyte-for-byte unchanged, so existing pure-array behavior (which never checked
the index type) is preserved.
length(a)on a mutable[T]!handle type-checks to the array-length type(
UInt) via a new symbolicArrayLengthterm node. The result type is readoff the in-scope
lengthfunction's return type rather than fabricated, solength(a)on an array agrees withlengthon a list. Any otherlength(...)call — including on a pure
[T]array — falls back to ordinary list-lengthresolution. The intercept is applied in both
type_synth_termandtype_check_termso it works in synthesis, call-argument, and equalitypositions.
Obligation API (
imperative_verifier.py)array_bounds_goal(read)builds the source-locatedi < length(a)bounds goal for a mutable-array read, using the same post-typecheck
ResolvedVarconstructor idiommkEqualuses for=.ArrayBoundsObligationscollectsARRAY_BOUNDSobligations for the readsseen in one verification pass and deduplicates repeated reads of the same
source access, so a given
a[i]yields at most one bounds goal.New AST node
ArrayLengthinabstract_syntax/terms.py. It is produced only by the typechecker (never parsed), so both parsers still see the surface
length(a)asan ordinary
Calland parser-equivalence/round-trip coverage is unaffected.It has no runtime reduction (an array's length is immutable for the handle's
lifetime in Phase 2). The proof-rewrite walkers (
count_marks/find_mark/replace_mark/rewrite_aux) are taught about it soreplace/simplify/rewrite tactics work on formulas mentioning
length(a). Scoping thelengthintercept to
[T]!keepsArrayLengthconfined to procedure specs and boundsobligations, which never reach the recursive-call or compiler walkers that
pure runtime terms use.
Acceptance criteria
discharges the obligation against a matching
requires i < length(a)given.should-errorfixture (bool index) plus unit tests; element mismatch isrejected by the existing
=check over the read's element type.ArrayTypeandMutableArrayTypepaths —test/unit/test_array_bounds.py.Scope boundary
This slice delivers the typing and the obligation-construction/dedup
machinery. The pass that walks a verified procedure body and feeds each read
into
ArrayBoundsObligations.record(...)is straight-line VC generation(#1115+), which does not exist on
mainyet; the API here is the tool thatslice will call. Everything in this PR is exercised end-to-end through the
type checker (specs are type-checked today via #1111) and at the unit level.
Review response (Codex on
4f39468)ArrayLengthcases tocount_marks,find_mark,replace_mark, andrewrite_auxso rewriting/simplifying a formula withlength(a)no longer hitsinternal_error.UInt(wasUInt/Nat/Int), so a read always admits a provablei < length(a)bound.length(a)typing is restricted to mutable[T]!only,keeping
ArrayLengthout of the recursive-call/compiler walkers.Testing
python3 test-deduce.py(full default incl. parser equivalence) — green.python3 test-deduce.py --imperative— green (both parsers; flag enrollment).ruff check .,mypy .,keywords.py,reference_grammar.py— all green.test/unit/test_array_bounds.py— 9/9 (run manually; pytest not presentin this container).
Resume this session on ginger:
~/deduce-runner/resume.sh ce1bb127-0e51-4624-8b17-22ff0279807e routine/20260728-040201🤖 Generated with Claude Code