Skip to content

[flow analysis] Switch to associative promotion chain join. - #4771

Merged
stereotype441 merged 3 commits into
dart-lang:mainfrom
stereotype441:flow-analysis
Sep 14, 2026
Merged

stereotype441 merged 3 commits into
dart-lang:mainfrom
stereotype441:flow-analysis

Conversation

@stereotype441

@stereotype441 stereotype441 commented Sep 11, 2026

Copy link
Copy Markdown
Member

Changes the flow analysis spec so that the join of promotion chains is now specified to be the greatest common subsequence of the two inputs, as discussed in #4757.

Adjusts the Lean proofs to establish:

  • That a greatest common subsequence between any two promotion chains always exists.

  • That the greatest common subsequence is unique. This justifies calling it "the join" of the two promotion chains, but leaves open the question of how to compute it.

  • An equivalent procedural definition, based on filtering one chain and retaining those elements that are in the other. This establishes that the join operation is computable, though this definition is O(m*n) (where m and n are the lengths of the two chains); we will do better than that in the actual implementation.

  • That the new join operation is idempotent, commutative, and associative.

I've also renamed several of the Lean theorems to better follow Mathlib conventions, and I've added some additional theorems that I believe will be useful in later proofs.

In a follow-up PR, I plan to introduce a correctness proof for the optimized implementation in
https://dart-review.googlesource.com/c/sdk/+/543721 (which is O(m+n) rather than O(m*n), and avoids unnecessary list allocations).


  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
Contribution guidelines:

Many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.

Note: The Dart team is trialing Gemini Code Assist. Don't take its comments as final Dart team feedback. Use the suggestions if they're helpful; otherwise, wait for a human reviewer.

Changes the flow analysis spec so that the join of promotion chains is
now specified to be the maximal common subsequence of the two inputs,
as discussed in dart-lang#4757.

Adjusts the Lean proofs to establish:

- That a maximal common subsequence between any two promotion chains
  always exists.

- That the maximal common subsequence is unique. This justifies
  calling it "the join" of the two promotion chains, but leaves open
  the question of how to compute it.

- An equivalent procedural definition, based on filtering one chain
  and retaining those elements that are in the other. This establishes
  that the join operation is computable, though this definition is
  `O(m*n)` (where `m` and `n` are the lengths of the two chains); we
  will do better than that in the actual implementation.

- That the new join operation is idempotent, commutative, and
  associative.

I've also renamed several of the Lean theorems to better follow
Mathlib conventions, and I've added some additional theorems that I
believe will be useful in later proofs.

In a follow-up PR, I plan to introduce a correctness proof for the
optimized implementation in
https://dart-review.googlesource.com/c/sdk/+/543721 (which is `O(m+n)`
rather than `O(m*n)`, and avoids unnecessary list allocations).
@stereotype441
stereotype441 requested a review from lrhn September 11, 2026 18:33
@stereotype441
stereotype441 merged commit cd0a726 into dart-lang:main Sep 14, 2026
10 checks passed
@stereotype441
stereotype441 deleted the flow-analysis branch September 14, 2026 12:39
stereotype441 added a commit to stereotype441/language that referenced this pull request Sep 17, 2026
Adds a Lean model of the Dart method `PromotionModel.joinPromotedTypes`
(as of SDK commit 5651b872ea36c3, with
`promotionChainIntersectionJoinEnabled = true`), along with a proof
that it computes the join of promotion chains specified in
`flow-analysis.md`.

This is the follow-up promised in
dart-lang#4771: the spec defines the
join of two promotion chains as their unique greatest common
subsequence, and the Lean model backs that up with a filter-based
definition establishing that the join is computable. But that
definition is `O(m*n)` (where `m` and `n` are the lengths of the two
chains), whereas the SDK uses an `O(m+n)` algorithm that also avoids
unnecessary list allocations. This PR proves that the two agree.

Details:

- `FlowAnalysis/PromotionChain/JoinImpl.lean` contains
  `joinPromotedTypesImpl` and `joinPromotedTypesImpl'`, which model the
  Dart algorithm using Lean's imperative `do` notation, so that they
  track the structure of the Dart code as closely as possible
  (including the mutable loop indices, the two "advance" flags, and the
  lazily allocated `result` accumulator).

- `joinPromotedTypesImpl_correct` uses Lean's weakest precondition
  framework (`Std.Do`) to prove that for all promotion chains `c₁` and
  `c₂`, the model terminates, returns `(c₁.join c₂).val`, and leaves
  the monadic state unchanged. The proof is by loop invariant; the
  invariant and the decreasing measure used to establish termination
  are spelled out in comments.

- `FlowAnalysis/PromotionChain/Basic.lean` (renamed from
  `FlowAnalysis/PromotionChain.lean` to make room for the new file)
  gains the three recurrence relations for `join` that drive the
  correctness proof, covering the cases `T₁ = T₂`, `T₁ ≤ T₂`, and
  `¬T₁ ≤ T₂`. These correspond to the three branches of the loop body
  in the Dart implementation.

- `FlowAnalysis/WP.lean` contains general purpose helpers for reasoning
  about monadic models of Dart code: `WP_ite` (which allows an
  `if/then/else` to be split without `simp` rewriting the surrounding
  monadic code into an unreadable form), `stateIs`, and a simp lemma
  for `SVal.curry`.

- `DartTypeRepr` now extends `Inhabited` (and `SimpleType`'s default is
  `Never`), so that the models can use Lean's `xs[i]!` notation for
  list indexing, avoiding the need to clutter them with proofs that the
  indices are in range.

TAG=agy
CONV=a3f3022c-cb28-41fe-b867-ab2cdc117925
stereotype441 added a commit that referenced this pull request Sep 18, 2026
…in. (#4776)

Adds a Lean model of the Dart method
`PromotionModel.joinPromotedTypes` (as of SDK CL
https://dart-review.googlesource.com/c/sdk/+/551800), with
`promotionChainIntersectionJoinEnabled = true`), along with a proof
that it computes the join of promotion chains specified in
`flow-analysis.md`.

This is the follow-up promised in
#4771: the spec defines the
join of two promotion chains as their unique greatest common
subsequence, and the Lean model backs that up with a filter-based
definition establishing that the join is computable. But that
definition is `O(m*n)` (where `m` and `n` are the lengths of the two
chains), whereas the SDK uses an `O(m+n)` algorithm that also avoids
unnecessary list allocations. This PR proves that the `O(m+n)`
algorithm agrees with the other two definitions.

Details:

- `FlowAnalysis/PromotionChain/JoinImpl.lean` contains
  `joinPromotedTypesImpl` and `joinPromotedTypesImpl'`, which model
  the Dart algorithm using Lean's imperative `do` notation, so that
  they track the structure of the Dart code as closely as possible
  (including the mutable loop indices, the two "advance" flags, and
  the lazily allocated `result` accumulator).

- `joinPromotedTypesImpl_correct` uses Lean's weakest precondition
  framework (`Std.Do`) to prove that for all promotion chains `c₁` and
  `c₂`, the model terminates, returns `(c₁.join c₂).val`, and leaves
  the monadic state unchanged. The proof is by loop invariant; the
  invariant and the decreasing measure used to establish termination
  are spelled out in comments.

- `FlowAnalysis/PromotionChain/Basic.lean` (renamed from
  `FlowAnalysis/PromotionChain.lean` to make room for the new file)
  gains the three recurrence relations for `join` that drive the
  correctness proof, covering the cases `T₁ = T₂`, `T₁ ≤ T₂`, and
  `¬T₁ ≤ T₂`. These correspond to the three branches of the loop body
  in the Dart implementation.

- `FlowAnalysis/WP.lean` contains general purpose helpers for
  reasoning about monadic models of Dart code: `WP_ite` (which allows
  an `if/then/else` to be split without `simp` rewriting the
  surrounding monadic code into an unreadable form), `stateIs`, and a
  simp lemma for `SVal.curry`.

- `DartTypeRepr` now extends `Inhabited` (and `SimpleType`'s default
  is `Never`), so that the models can use Lean's `xs[i]!` notation for
  list indexing, avoiding the need to clutter them with proofs that
  the indices are in range.
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.

2 participants