Skip to content

[flow analysis] Clean up documentation of promotion chains, add Lean model. - #4754

Open
stereotype441 wants to merge 3 commits into
dart-lang:mainfrom
stereotype441:flow_analysis
Open

[flow analysis] Clean up documentation of promotion chains, add Lean model.#4754
stereotype441 wants to merge 3 commits into
dart-lang:mainfrom
stereotype441:flow_analysis

Conversation

@stereotype441

Copy link
Copy Markdown
Member

This PR adds a top level directory lean, containing the beginnings of a Lean model of the behavior of flow analysis. Currently the model includes:

  • An abstraction of the Dart type system, defining some of the operations needed by flow analysis (I will expand on this abstaction as needed).

  • A concrete simplified model of the Dart type system, omitting complications that are irrelevant to flow analysis (such as support for f-bounded types). This model is defined as an instantiation of the above abstraction.

  • A model of promotion chains and their properties.

I intend to build out this model as part of documenting (and improving) the flow analysis specification.

The Lean code builds locally without errors (which validates all the theorems contained in it). In a follow-up PR I intend to add a github check that will verify that the code continues to build without errors.

The PR also makes the following improvements to flow-analysis.md:

  • The notation for lists has been changed to use ++ for list concatenation. This is easier to precisely explain than the old notation using ..., and is more consistent with the Lean model.

  • The notion of a subsequence is defined, using the symbol <+ (which Lean also uses, though it uses the terminology "sublist"). This is used to prove that the join of two promotion chains is a promotion chain (see below).

  • The notion of strict subtyping is defined, using the symbol <<:. Note that the Lean model uses for subtyping and < for strict subtyping, since this allows a number of convenient theorems to come into play.

  • The definition of promotion chains has been moved to its own section.

  • The notion of a promotion chain being "valid for declared type T" has been renamed to "strictly bounded by T". This lays the groundwork for future PRs that will specify the behavior of private field promotion. (I will want to avoid talking about the "declared type" of a field, because of ambiguities that arise when a field is declared with one type in one class and then overridden with another type in a subclass.)

  • The join algorithm for promotion chains is now fully specified. A sketch is given of the proof that this algorithm is idempotent and commutative, and a counterexample illustrates that it is not associative. I will file an issue to discuss whether to change to an algorithm that is associative.

…model.

This PR adds a top level directory `lean`, containing the beginnings
of a Lean model of the behavior of flow analysis. Currently the model
includes:

- An abstraction of the Dart type system, defining some of the
  operations needed by flow analysis (I will expand on this abstaction
  as needed).

- A concrete simplified model of the Dart type system, omitting
  complications that are irrelevant to flow analysis (such as support
  for f-bounded types). This model is defined as an instantiation of
  the above abstraction.

- A model of promotion chains and their properties.

I intend to build out this model as part of documenting (and
improving) the flow analysis specification.

The Lean code builds locally without errors (which validates all the
theorems contained in it). In a follow-up PR I intend to add a github
check that will verify that the code continues to build without
errors.

The PR also makes the following improvements to `flow-analysis.md`:

- The notation for lists has been changed to use `++` for list
  concatenation. This is easier to precisely explain than the old
  notation using `...`, and is more consistent with the Lean model.

- The notion of a subsequence is defined, using the symbol `<+` (which
  Lean also uses, though it uses the terminology "sublist"). This is
  used to prove that the join of two promotion chains is a promotion
  chain (see below).

- The notion of strict subtyping is defined, using the symbol
  `<<:`. Note that the Lean model uses `≤` for subtyping and `<` for
  strict subtyping, since this allows a number of convenient theorems
  to come into play.

- The definition of promotion chains has been moved to its own
  section.

- The notion of a promotion chain being "valid for declared type `T`"
  has been renamed to "strictly bounded by `T`". This lays the
  groundwork for future PRs that will specify the behavior of private
  field promotion. (I will want to avoid talking about the "declared
  type" of a field, because of ambiguities that arise when a field is
  declared with one type in one class and then overridden with another
  type in a subclass.)

- The join algorithm for promotion chains is now fully specified. A
  sketch is given of the proof that this algorithm is idempotent and
  commutative, and a counterexample illustrates that it is not
  associative. I will file an issue to discuss whether to change to an
  algorithm that _is_ associative.
@stereotype441
stereotype441 requested a review from lrhn August 27, 2026 00:31
type in `p` is a subtype of `T`. _Note that since the subtyping relation is
transitive, in order to establish that `p` is valid for declared type `T`,
it is sufficient to check that the first type in `p` is a subtype of `T`._
- We use the notation `l₁ ++ l₂` to denote the concatenation of lists.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Very nice use of Unicode!
I don't think there is anything looking like <+, but how about ⪿? Or any of , or just plain old ? (I'll stop now 😁 )

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I could possibly get behind ⪿, though I worry a little that it people will interpret it as an exclusive subsequence (by analogy to ). The relation I'm trying to define is inclusive.

, , and worry me because of potential confusion with , which I would like to reserve for denoting list membership (i.e. x ∈ l iff ∃k, l[k] = x).

<+ has the advantage of being consistent with the Lean code.

I asked Gemini for suggestions and it offered some more alternatives:

  • (apparently common in computer science papers, although some people use this to mean "contiguous prefix")
  • ⊆ₒ ("subset ordered")

🤷 I could probably be happy with any of these.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like using operators for operations that are extremely common and have a well-known familiar operator already. For other operations, I would be inclined to use a readable name.

I think ++ for concatenation is familiar enough to work well (and I like that it disambiguates from addition).

I'm less enthused about using a Unicode operator for "subsequence" which is already a fairly unfamiliar operation. Maybe just subseq(l₁, l₂) or l₁ subseq l₂ if you think the latter helps clarify which argument is a subsequence of the other?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok, I'm convinced that using a custom operator for "subsequence" is more clever than necessary. I've changed to subseq(l₁, l₂).

Comment thread resources/type-system/flow-analysis.md Outdated
Comment thread resources/type-system/flow-analysis.md Outdated
Comment thread resources/type-system/flow-analysis.md Outdated
Comment thread resources/type-system/flow-analysis.md Outdated
### Promotion chains

A list of types `c` is called a _promotion chain_ iff, for all `i < c.length -
1`, `c[i + 1] <<: c[i]`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Complete side-track, because I was looking at it for another reason. If we have

void foo<X extends Never, Y extends X>(X value) { 
   if (value is Y) {
     // Not promoting to X&Y because X and Y are mutual subtypes (both bottom).
   }
}

Should we promote value to X&Y here? Or will flow analysis recognize that we never take the branch anyway, and not promote because of that. (Not that we can ever call the function.)

It probably doesn't matter in any way, because the result is assignable to Y whether we promote or not (it's a bottom type), and it has all members (it's a bottom type).
And because the code can't possibly be run either way.
That means we can't ever have an X&R where X is bottom-bounded, and UP of any two bottom types can just be Never, you can't tell the difference.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Currently we don't promote to X&Y because, as you say, X and Y are mutual subtypes. Despite the messy state of the spec, I believe the implementation is solidly consistent in maintaining the invariants that

  • Every type in a promotion chain is a strict subtype of the previous.
  • The promotion chain for a local variable is always strictly bounded by the local variable's (declared or inferred) unpromoted type. (Private field promotion is a whole other story that I'll get to documenting in a later PR.)

I definitely want to keep the first invariant, because without it, join gets messy. I'd prefer to keep second invariant too, because it feels nicely consistent with the first. So my vote would be to keep the current behavior of not promoting to X&Y in this case.

Comment thread resources/type-system/flow-analysis.md Outdated
valid for declared type `declared`, and all types `T` in `promotionChain`
satisfy `written <: T`, is the promotion chain `newPromotionChain`, defined as
strictly bounded by `declared`, and all types `T` in `promotionChain` satisfy
`written <: T`, is the promotion chain `newPromotionChain`, defined as

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

<:, not <<: ....

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, this section is a bit of a mess, and definitely doesn't line up with the current implementation behavior. I'd prefer to leave it as is for now, and come back to it in a later PR so I can think about it as a whole and get it right.

difference)_.
- If the `written` type is in `p2` then `newPromotionChain` is
`[...promotionChain, written]`. _Writing a value whose static type is a
`promotionChain ++ [written]`. _Writing a value whose static type is a

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

.... and here we push written even though the last element of promotionChain could be a mutual subtype of it.
(We only check whether they're the same type in the case above, not whether it's a mutual subtype, and the constraints on promotionChain only requires written <: T, not <<:.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged. I will address this in a follow-up PR.

in `promotionChain`. Therefore, `newPromotionChain` satisfies the
definition of a promotion chain, and is valid for declared type
`declared`._
definition of a promotion chain, and is strictly bounded by `declared`._

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Except it doesn't because written <: T is not sufficient, it needs written <<: T.
(Is this what's implemented, and can we make something break?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged. The current implementation doesn't have this mistake. I will address this in a follow-up PR.

to._
- _Since `T <: provisionalType <: declared`, and all types `U` in
`promotionChain` satisfy `provisionalType <: U`, it follows that all
types `U` in `promotionChain` satisfy `T <: U`. Therefore

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Probably can't break this. If the provisional type is a mutual subtype of written, then any type T in p2 where written <: T <: provisionalType is also a mutual subtype, and since provisionalType is probably also a type of interest, there won't be a least.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Acknowledged. I will clean this up in a follow-up PR.

- `VM = VariableModel(declared, promotionChain, tested, true, false, captured)`.
- Otherwise:
- Let `written = T`.
- Let `promotionChain' = demote(promotionChain, written)`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(Only place demote is used, right?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Correct.

@lrhn

lrhn commented Aug 27, 2026

Copy link
Copy Markdown
Member

I think the promotion chain join can be improved.
(And there are some places where I don't think we're handling mutual subtypes correctly in assingment promotion, but not sure if it's something that's a real problem.)

type in `p` is a subtype of `T`. _Note that since the subtyping relation is
transitive, in order to establish that `p` is valid for declared type `T`,
it is sufficient to check that the first type in `p` is a subtype of `T`._
- We use the notation `l₁ ++ l₂` to denote the concatenation of lists.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I like using operators for operations that are extremely common and have a well-known familiar operator already. For other operations, I would be inclined to use a readable name.

I think ++ for concatenation is familiar enough to work well (and I like that it disambiguates from addition).

I'm less enthused about using a Unicode operator for "subsequence" which is already a fairly unfamiliar operation. Maybe just subseq(l₁, l₂) or l₁ subseq l₂ if you think the latter helps clarify which argument is a subsequence of the other?

l₂.length`._
- We use the notation `l₁ <+ l₂` to denote that `l₁` is a subsequence of
`l₂`. That is, `l₁ = [l₂[k₀], l₂[k₁], ... l₂[kₙ₋₁]]` for some `k₀ < k₁ <
... < kₙ₋₁`. _Note that subsequences need not be contiguous._

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The non-contiguous callout is helpful here. I definitely didn't assume that at first.

@stereotype441

Copy link
Copy Markdown
Member Author

I think the promotion chain join can be improved. (And there are some places where I don't think we're handling mutual subtypes correctly in assingment promotion, but not sure if it's something that's a real problem.)

To summarize my comments above (since Github makes it hard to thread conversations):

  • Regarding your proposed improvement to promotion chain join, I love it! I've filed Simplify promotion chain join #4757 to track it. I'll leave this PR as is since it correctly documents the current behavior (though I did add a link). Let me know if Simplify promotion chain join #4757 is missing any crucial information.
  • Regarding the mishandling of mutual subtypes correctly in assignment promotion, the text of the spec definitely doesn't match the implementation. Possibly in benign ways, possibly not. I'm not sure. I'd prefer to leave it as is and return to it in a later PR so I can think carefully about it and get it right.

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.

3 participants