Files
sdk/tests/language/inference_update_2/condition_variable_test.dart
T
Paul Berry 30e8ed8410 Flow analysis: fix field promotion based on condition variables.
When the result of an `is` test or null check is stored in a boolean
variable, and later recalled for use in flow control, the flow models
that were computed at the time the variable was stored need to be
updated to reflect any further change to flow state that happened
between the test and the usage. This is done by
`FlowModel.rebaseForward` method. `rebaseForward` takes two flow
models as input: `this`, which represents the flow state that was
computed at the time the condition variable was stored, and `base`,
which represents the flow state at the time the condition variable is
recalled.

Flow analysis adds promotion keys for variables to the flow state at
the time their declarations are encountered, and in certain
circumstances removes them after they go out of scope. But for
properties, it only adds promotion keys when the promotion occurs. So
prior to the addition of field promotion, if `this` contained a
promotion key that wasn't present in `base`, that could only mean that
the promotion key was associated with a variable that had gone out of
scope; accordingly, it was safe for `rebaseForward` to simply ignore
that key. (It did so implicitly, by only ever examining the promotion
keys in `this`). But with the addition of field promotion, it is now
possible that the promotion key represents a property that was
promoted in `this`, and hence the promotion needs to be kept. This CL
adds the necessary logic to keep the promotion.

In addition, there is a subtle difference in the relationship between
the `PromotionModel` and `SsaNode` data structures for local variables
versus properties. For local variables, the promotion key is
determined solely from the variable name; then, this promotion key is
looked up in the current `FlowModel` to obtain a `PromotionModel`, and
the `PromotionModel` contains a prointer to the `SsaNode`. For
properties, the property name is looked up in the
`promotableProperties` map of the parent `SsaNode`; this points to a
`_PropertySsaNode`, which contains the promotion key, and when this
promotion key is looked up in the current `FlowModel` to obtain a
`PromotionModel`, that `PromotionModel` contains a pointer to a bogus
`SsaNode`.

For local variables, the `SsaNode` pointed to by the `PromotionModel`
is important, because if it's different between `this` and `base`,
then the variable in question received a new value between the time
the condition variable was stored and the time the condition variable
was recalled; therefore the promotion should be disregarded. However,
for properties, the `SsaNode` pointed to by the `PromotionModel` is
bogus, so if it's different between `this` and `base`, that shouldn't
block promotion. This CL adds the necessary logic to avoid the
`SsaNode` check for properties.

This situation is very confusing so I've added more detail to the
comment above `PromotionModel.SsaNode` explaining it. In a future CL I
will try to clean up the confusing situation by eliminating the bogus
`SsaNode`s pointed to by `PromotionModel`s for properties.

Fixes https://github.com/dart-lang/sdk/issues/53273.

Bug: https://github.com/dart-lang/sdk/issues/53273
Change-Id: I1d528e25de1eb2ed63d0ee1a00faa5ad5b5061ca
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/321752
Reviewed-by: Johnni Winther <johnniwinther@google.com>
Commit-Queue: Paul Berry <paulberry@google.com>
2023-08-24 14:02:29 +00:00

97 lines
2.3 KiB
Dart

// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Tests that field promotion works when the promotion condition is stored in a
// local variable.
import '../static_type_helper.dart';
class C {
final Object? _o;
C(this._o);
}
void usingNullCheck_withoutInterveningPromotion(C c) {
bool b = c._o != null;
if (b) {
c._o.expectStaticType<Exactly<Object>>();
}
}
void usingIsTest_withoutInterveningPromotion(C c) {
bool b = c._o is int;
if (b) {
c._o.expectStaticType<Exactly<int>>();
}
}
void usingNullCheck_withInterveningRelatedPromotion(C c) {
bool b = c._o != null;
if (c._o != null) {
c._o.expectStaticType<Exactly<Object>>();
}
if (b) {
c._o.expectStaticType<Exactly<Object>>();
}
}
void usingIsTest_withInterveningRelatedPromotion(C c) {
bool b = c._o is int;
if (c._o is int) {
c._o.expectStaticType<Exactly<int>>();
}
if (b) {
c._o.expectStaticType<Exactly<int>>();
}
}
void usingNullCheck_withInterveningUnrelatedPromotion(C c, int? i) {
bool b = c._o != null;
i!;
if (b) {
c._o.expectStaticType<Exactly<Object>>();
}
}
void usingIsTest_withInterveningUnrelatedPromotion(C c, int? i) {
bool b = c._o is int;
i!;
if (b) {
c._o.expectStaticType<Exactly<int>>();
}
}
void usingNullCheck_disabledByAssignment(C c, C c2) {
bool b = c._o != null;
if (c._o != null) {
c._o.expectStaticType<Exactly<Object>>();
}
c = c2;
if (b) {
c._o.expectStaticType<Exactly<Object?>>();
}
}
void usingIsTest_disabledByAssignment(C c, C c2) {
bool b = c._o is int;
if (c._o is int) {
c._o.expectStaticType<Exactly<int>>();
}
c = c2;
if (b) {
c._o.expectStaticType<Exactly<Object?>>();
}
}
main() {
usingNullCheck_withoutInterveningPromotion(C(0));
usingIsTest_withoutInterveningPromotion(C(0));
usingNullCheck_withInterveningRelatedPromotion(C(0));
usingIsTest_withInterveningRelatedPromotion(C(0));
usingNullCheck_withInterveningUnrelatedPromotion(C(0), 1);
usingIsTest_withInterveningUnrelatedPromotion(C(0), 1);
usingNullCheck_disabledByAssignment(C(0), C(1));
usingIsTest_disabledByAssignment(C(0), C(1));
}