Skip to content

Commit b901765

Browse files
Rework flow analysis of == and !=. (#1134)
Previously, we specified flow states `null()` and `nonNull()` (similar to `true()` and `false()`), and the special behaviors of `== null` and `!= null` checks fell out of a general analysis of `E1 == E2`. But it didn't work in practice, because it required doing joins like `join(null(E1), null(E2))`, which isn't sound in general (because it carries the flow control implication that either E1 or E2 is evaluated but not both). Because of these problems, the implementation never matched this specification, and instead used ad-hoc pattern matching to recognize `== null` and `!= null`. In the new formulation, we remove the flow states `null()` and `nonNull()` and retain the ad-hoc pattern matching for `== null` and `!= null`, consistent with the current implementation. However, we also add cases to recognize when an equality test is guaranteed to evaluate to `true` or `false`; this addresses dart-lang/sdk#41985 and ensures that we recognize dead code associated with unnecessary null checks.
1 parent 028a4ff commit b901765

File tree

1 file changed

+89
-86
lines changed

1 file changed

+89
-86
lines changed

resources/type-system/flow-analysis.md

Lines changed: 89 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,6 @@ The following functions associate flow models to nodes:
193193
- `false(E)`, where `E` is an expression, represents the *flow model* just after
194194
execution of `E`, assuming that `E` completes normally and evaluates to `false`.
195195

196-
- `null(E)`, where `E` is an expression, represents the *flow model* just after
197-
execution of `E`, assuming that `E` completes normally and evaluates to `null`.
198-
199-
- `notNull(E)`, where `E` is an expression, represents the *flow model* just
200-
after execution of `E`, assuming that `E` completes normally and does not
201-
evaluate to `null`.
202-
203196
- `break(S)`, where `S` is a `do`, `for`, `switch`, or `while` statement,
204197
represents the join of the flow models reaching each `break` statement
205198
targetting `S`.
@@ -225,8 +218,8 @@ The following functions associate flow models to nodes:
225218
expression in the recurrent part of `S`, where the "recurrent" part of `s` is
226219
defined as in `assignedIn`, above.
227220

228-
Note that `true`, `false`, `null`, and `notNull` are defined for all expressions
229-
regardless of their static types.
221+
Note that `true` and `false` are defined for all expressions regardless of their
222+
static types.
230223

231224
We also make use of the following auxiliary functions:
232225

@@ -429,14 +422,25 @@ Definitions:
429422
`demoted` is `Q::previous`
430423
- otherwise `demoted` is `previous`
431424

425+
- `stripParens(E1)`, where `E1` is an expression, is the result of stripping
426+
outer parentheses from the expression `E1`. It is defined to be the
427+
expression `E3`, where:
428+
- If `E1` is a parenthesized expression of the form `(E2)`, then `E3` =
429+
`stripParens(E2)`.
430+
- Otherwise, `E3` = `E1`.
431+
432+
- `equivalentToNull(T)`, where `T` is a type, indicates whether `T` is
433+
equivalent to the `Null` type. It is defined to be true if `T <: Null` and
434+
`Null <: T`; otherwise false.
435+
432436
- `promote(E, T, M)` where `E` is an expression, `T` is a type which it may be
433-
promoted to, and `M1 = FlowModel(r, VI)` is the flow model in which to
434-
promote, is defined to be `M3`, where:
435-
- If `E` is not a promotion target, then `M3` = `M1`
436-
- If `E` is a promotion target `x`, then
437+
promoted to, and `M = FlowModel(r, VI)` is the flow model in which to promote,
438+
is defined to be `M3`, where:
439+
- If `stripParens(E)` is not a promotion target, then `M3` = `M`
440+
- If `stripParens(E)` is a promotion target `x`, then
437441
- Let `VM = VariableModel(declared, promoted, tested, assigned, unassigned,
438442
captured)` be the variable model for `x` in `VI`
439-
- If `x` is not promotable via type test to `T` given `VM`, then `M3` = `M1`
443+
- If `x` is not promotable via type test to `T` given `VM`, then `M3` = `M`
440444
- Else
441445
- Let `S` be the current type of `x` in `VM`
442446
- If `T <: S` then let `T1` = `T`
@@ -482,43 +486,24 @@ constructor, or field declaration to be analyzed.
482486
### Expressions
483487

484488
Analysis of an expression `N` assumes that `before(N)` has been computed, and
485-
uses it to derive `after(N)`, `null(N)`, `notNull(N)`, `true(N)`, and
486-
`false(N)`.
487-
488-
If `N` is an expression, and the following rules specify the values to be
489-
assigned to `true(N)` and `false(N)`, but do not specify values for `null(N)`,
490-
`notNull(N)`, or `after(N)`, then they are by default assigned as follows:
491-
- `null(N) = unreachable(after(N))`.
492-
- `notNull(N) = join(true(N), false(N))`.
493-
- `after(N) = notNull(N)`.
489+
uses it to derive `after(N)`, `true(N)`, and `false(N)`.
494490

495491
If `N` is an expression, and the following rules specify the values to be
496-
assigned to `null(N)` and `notNull(N)`, but do not specify values for `true(N)`,
497-
`false(N)`, or `after(N)`, then they are by default assigned as follows:
498-
- `true(N) = notNull(N)`.
499-
- `false(N) = notNull(N)`.
500-
- `after(N) = join(null(N), notNull(N))`.
492+
assigned to `true(N)` and `false(N)`, but do not specify the value for
493+
`after(N)`, then it is by default assigned as follows:
494+
- `after(N) = join(true(N), false(N))`.
501495

502496
If `N` is an expression, and the following rules specify the value to be
503-
assigned to `after(N)`, but do not specify values for `true(N)`, `false(N)`,
504-
`null(N)`, or `notNull(N)`, then they are all assigned the same value as
505-
`after(N)`.
497+
assigned to `after(N)`, but do not specify values for `true(N)` and `false(N)`,
498+
then they are all assigned the same value as `after(N)`.
506499

507500

508501
- **Variable or getter**: If `N` is an expression of the form `x`
509502
where the type of `x` is `T` then:
510503
- If `T <: Never` then:
511-
- Let `null(N) = unreachable(before(N))`.
512-
- Let `notNull(N) = unreachable(before(N))`.
513-
- Otherwise if `T <: Null` then:
514-
- Let `null(N) = before(N)`.
515-
- Let `notNull(N) = unreachable(before(N))`.
516-
- Otherwise if `T` is non-nullable then:
517-
- Let `null(N) = unreachable(before(N))`.
518-
- Let `notNull(N) = before(N)`.
504+
- Let `after(N) = unreachable(before(N))`.
519505
- Otherwise:
520-
- Let `null(N) = promote(x, Null, before(N))`
521-
- Let `notNull(N) = promoteToNonNull(x, before(N))`
506+
- Let `after(N) = before(N)`.
522507

523508
- **True literal**: If `N` is the literal `true`, then:
524509
- Let `true(N) = before(N)`.
@@ -528,13 +513,10 @@ assigned to `after(N)`, but do not specify values for `true(N)`, `false(N)`,
528513
- Let `true(N) = unreachable(before(N))`.
529514
- Let `false(N) = before(N)`.
530515

531-
- **null literal**: If `N` is the literal `null`, then:
532-
- Let `null(N) = before(N)`.
533-
- Let `notNull(N) = unreachable(before(N))`.
516+
- TODO(paulberry): list, map, and set literals.
534517

535518
- **other literal**: If `N` is some other literal than the above, then:
536-
- Let `null(N) = unreachable(before(N))`.
537-
- Let `notNull(N) = before(N)`.
519+
- Let `after(N) = before(N)`.
538520

539521
- **throw**: If `N` is a throw expression of the form `throw E1`, then:
540522
- Let `before(E1) = before(N)`.
@@ -546,21 +528,35 @@ assigned to `after(N)`, but do not specify values for `true(N)`, `false(N)`,
546528
- Let `after(N) = assign(x, E1, after(E1))`.
547529
- Let `true(N) = assign(x, E1, true(E1))`.
548530
- Let `false(N) = assign(x, E1, false(E1))`.
549-
- Let `null(N) = assign(x, E1, null(E1))`.
550-
- Let `notNull(N) = assign(x, E1, notNull(E1))`.
551-
552-
TODO(leafp): Per
553-
discussion
554-
[here](https://github.com/dart-lang/language/pull/763/files#r364003138), this is
555-
wrong. This needs reconsideration.
556-
- **operator==** If `N` is an expression of the form `E1 == E2` then:
557-
- Let `before(E1) = before(N)`
558-
- Let `before(E2) = after(E1)`
559-
- Let `true(N) = join(join(null(E1), null(E2)),
560-
join(notNull(E1), notNull(E2)))`
561-
- Let `false(N) = join(join(null(E1), notNull(E2)),
562-
join(notNull(E1), null(E2)),
563-
join(notNull(E1), notNull(E2)))`
531+
532+
- **operator==** If `N` is an expression of the form `E1 == E2`, where the
533+
static type of `E1` is `T1` and the static type of `E2` is `T2`, then:
534+
- Let `before(E1) = before(N)`.
535+
- Let `before(E2) = after(E1)`.
536+
- If `equivalentToNull(T1)` and `equivalentToNull(T2)`, then:
537+
- Let `true(N) = after(E2)`.
538+
- Let `false(N) = unreachable(after(E2))`.
539+
- Otherwise, if `equivalentToNull(T1)` and `T2` is non-nullable, or
540+
`equivalentToNull(T2)` and `T1` is non-nullable, then:
541+
- Let `true(N) = unreachable(after(E2))`.
542+
- Let `false(N) = after(E2)`.
543+
- Otherwise, if `stripParens(E1)` is a `null` literal, then:
544+
- Let `true(N) = after(E2)`.
545+
- Let `false(N) = promoteToNonNull(E2, after(E2))`.
546+
- Otherwise, if `stripParens(E2)` is a `null` literal, then:
547+
- Let `true(N) = after(E1)`.
548+
- Let `false(N) = promoteToNonNull(E1, after(E2))`.
549+
- Otherwise:
550+
- Let `after(N) = after(E2)`.
551+
552+
Note that it is tempting to generalize the two `null` literal cases to apply
553+
to any expression whose type is `Null`, but this would be unsound in cases
554+
where `E2` assigns to `x`. (Consider, for example, `(int? x) => x == (x =
555+
null) ? true : x.isEven`, which tries to call `null.isEven` in the event of a
556+
non-null input).
557+
558+
- **operator!=** If `N` is an expression of the form `E1 != E2`, it is treated
559+
as equivalent to the expression `!(E1 == E2)`.
564560

565561
- **instance check** If `N` is an expression of the form `E1 is S` where the
566562
static type of `E1` is `T` then:
@@ -582,11 +578,18 @@ wrong. This needs reconsideration.
582578

583579
TODO: This isn't really right, `E1` isn't really an expression here.
584580

585-
- **Non local-variable conditional assignment**: If `N` is an expression of the form
586-
`E1 ??= E2` where `E1` is not a local variable, then:
587-
- Let `before(E1) = before(N)`
588-
- Let `before(E2) = split(null(E1))`.
589-
- Let `after(N) = merge(after(E2), split(notNull(E1)))`
581+
- **Conditional assignment to a non local-variable**: If `N` is an expression of
582+
the form `E1 ??= E2` where `E1` is not a local variable, and the type of `E1`
583+
is `T1`, then:
584+
- Let `before(E1) = before(N)`.
585+
- If `T1` is strictly non-nullable, then:
586+
- Let `before(E2) = unreachable(after(E1))`.
587+
- Let `after(N) = after(E1)`.
588+
- Otherwise:
589+
- Let `before(E2) = split(after(E1))`.
590+
- Let `after(N) = merge(after(E2), split(after(E1)))`.
591+
592+
TODO(paulberry): this doesn't seem to match what's currently implemented.
590593

591594
- **Conditional expression**: If `N` is a conditional expression of the form `E1
592595
? E2 : E3`, then:
@@ -596,14 +599,18 @@ TODO: This isn't really right, `E1` isn't really an expression here.
596599
- Let `after(N) = merge(after(E2), after(E3))`.
597600
- Let `true(N) = merge(true(E2), true(E3))`.
598601
- Let `false(N) = merge(false(E2), false(E3))`.
599-
- Let `null(N) = merge(null(E2), null(E3))`.
600-
- Let `notNull(N) = merge(notNull(E2), notNull(E3))`.
601602

602-
- **If-null**: If `N` is an if-null expression of the form `E1 ?? E2`, then:
603+
- **If-null**: If `N` is an if-null expression of the form `E1 ?? E2`, where the
604+
type of `E1` is `T1`, then:
603605
- Let `before(E1) = before(N)`.
604-
- Let `before(E2) = split(null(E1))`.
605-
- Let `null(N) = unsplit(null(E2))`.
606-
- Let `notNull(N) = merge(split(notNull(E1)), notNull(E2))`.
606+
- If `T1` is strictly non-nullable, then:
607+
- Let `before(E2) = unreachable(after(E1))`.
608+
- Let `after(N) = after(E1)`.
609+
- Otherwise:
610+
- Let `before(E2) = split(after(E1))`.
611+
- Let `after(N) = merge(after(E2), split(after(E1)))`.
612+
613+
TODO(paulberry): this doesn't seem to match what's currently implemented.
607614

608615
- **Shortcut and**: If `N` is a shortcut "and" expression of the form `E1 && E2`,
609616
then:
@@ -623,27 +630,23 @@ TODO: This isn't really right, `E1` isn't really an expression here.
623630
`??`are handled as calls to the appropriate `operator` method.
624631

625632
- **Null check operator**: If `N` is an expression of the form `E!`, then:
626-
- Let `before(E) = before(N)`
627-
- Let `null(N) = unreachable(null(E))`
628-
- Let `nonNull(N) = nonNull(E)`
633+
- Let `before(E) = before(N)`.
634+
- Let `after(E) = promoteToNonNull(E, after(E))`.
629635

630636
- **Method invocation**: If `N` is an expression of the form `E1.m1(E2)`, then:
631637
- Let `before(E1) = before(N)`
632-
- Let `before(E2) = after(E2)`
638+
- Let `before(E2) = after(E1)`
633639
- Let `T` be the static return type of the invocation
634640
- If `T <: Never` then:
635-
- Let `null(N) = unreachable(before(N))`.
636-
- Let `notNull(N) = unreachable(before(N))`.
637-
- Otherwise if `T <: Null` then:
638-
- Let `null(N) = before(N)`.
639-
- Let `notNull(N) = unreachable(before(N))`.
640-
- Otherwise if `T` is non-nullable then:
641-
- Let `null(N) = before(N)`.
642-
- Let `notNull(N) = unreachable(before(N))`.
641+
- Let `after(N) = unreachable(after(E2))`.
642+
- Otherwise, if `m1` is a method declared on `Object` (e.g. `toString`), then:
643+
- Let `after(N) = after(E2)`.
643644
- Otherwise:
644-
- Let `null(N) = promote(x, Null, before(N))`
645-
- Let `notNull(N) = promoteToNonNull(x, before(N))`
645+
- Let `after(N) = promoteToNonNull(E1, after(E2))`
646646

647+
TODO(paulberry): is the `promoteToNonNull` part of method invocations
648+
implemented?
649+
TODO(paulberry): handle `E1.m1(E2, E3, ...)`.
647650

648651
TODO: Add missing expressions, handle cascades and left-hand sides accurately
649652

0 commit comments

Comments
 (0)