30e8ed8410
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>
97 lines
2.3 KiB
Dart
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));
|
|
}
|