Add tests reproducing language issue 4127. These tests exercise the current (unintended) behavior described https://github.com/dart-lang/language/issues/4127, which was previously not well tested. Adding these tests acts as a safeguard to make sure that we don't change the current behavior by accident. If/when we decide to fix https://github.com/dart-lang/language/issues/4127, the test expectations will need to be updated. Bug: https://github.com/dart-lang/language/issues/4127 Change-Id: I02fd1d393038a304401d11cf2c19e97755ba90a0 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/389584 Reviewed-by: Kallen Tu <kallentu@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
diff --git a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart index 4dc13f8..5987070 100644 --- a/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart +++ b/pkg/_fe_analyzer_shared/test/flow_analysis/flow_analysis_test.dart
@@ -7249,6 +7249,54 @@ ]); }); }); + + group('and equality:', () { + test('promoted type ignored on LHS', () { + // Normally flow analysis understands when an `if` test is guaranteed to + // succeed (or fail) based on the static types of the LHS and RHS. But + // due to https://github.com/dart-lang/language/issues/4127, this + // doesn't fully work when the LHS or RHS is a property reference; in + // that case, the unpromoted type is used. + + // This test is here to make sure we don't change the existing behavior + // by accident; if/when we fix #4127, this test should be changed + // accordingly. + h.addMember('C', 'f', 'Object?', promotable: true); + h.thisType = 'C'; + h.run([ + if_(thisProperty('f').isNot('Null'), [return_()]), + checkPromoted(thisProperty('f'), 'Null'), + if_(thisProperty('f').eq(nullLiteral), [ + checkReachable(true), + ], [ + checkReachable(true), + ]), + ]); + }); + + test('promoted type ignored on RHS', () { + // Normally flow analysis understands when an `if` test is guaranteed to + // succeed (or fail) based on the static types of the LHS and RHS. But + // due to https://github.com/dart-lang/language/issues/4127, this + // doesn't fully work when the LHS or RHS is a property reference; in + // that case, the unpromoted type is used. + + // This test is here to make sure we don't change the existing behavior + // by accident; if/when we fix #4127, this test should be changed + // accordingly. + h.addMember('C', 'f', 'Object?', promotable: true); + h.thisType = 'C'; + h.run([ + if_(thisProperty('f').isNot('Null'), [return_()]), + checkPromoted(thisProperty('f'), 'Null'), + if_(nullLiteral.eq(thisProperty('f')), [ + checkReachable(true), + ], [ + checkReachable(true), + ]), + ]); + }); + }); }); group('Patterns:', () {
diff --git a/tests/language/inference_update_2/promoted_field_type_in_equality_test.dart b/tests/language/inference_update_2/promoted_field_type_in_equality_test.dart new file mode 100644 index 0000000..0998a0e --- /dev/null +++ b/tests/language/inference_update_2/promoted_field_type_in_equality_test.dart
@@ -0,0 +1,265 @@ +// Copyright (c) 2024, 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 the behavior described in +// https://github.com/dart-lang/language/issues/4127, namely the fact that when +// deciding whether an `==` or `!=` comparison is guaranteed to evaluate to +// `true` or `false`, flow analysis considers promoted fields to have their base +// type rather than their promoted type. + +// This test is here to make sure we don't change the existing behavior by +// accident; if/when we fix #4127, this test should be changed accordingly. + +import '../static_type_helper.dart'; + +class C { + final Object? _f; + C(this._f); + + void testImplicitThisReferenceOnLhsOfEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (_f is! Null) return; + _f.expectStaticType<Exactly<Null>>(); + if (_f == null) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `_f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testImplicitThisReferenceOnRhsOfEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (_f is! Null) return; + _f.expectStaticType<Exactly<Null>>(); + if (null == _f) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `_f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testImplicitThisReferenceOnLhsOfNotEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (_f is! Null) return; + _f.expectStaticType<Exactly<Null>>(); + if (_f != null) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `_f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testImplicitThisReferenceOnRhsOfNotEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (_f is! Null) return; + _f.expectStaticType<Exactly<Null>>(); + if (null != _f) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `_f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testExplicitThisReferenceOnLhsOfEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (this._f is! Null) return; + this._f.expectStaticType<Exactly<Null>>(); + if (this._f == null) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `this._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testExplicitThisReferenceOnRhsOfEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (this._f is! Null) return; + this._f.expectStaticType<Exactly<Null>>(); + if (null == this._f) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `this._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testExplicitThisReferenceOnLhsOfNotEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (this._f is! Null) return; + this._f.expectStaticType<Exactly<Null>>(); + if (this._f != null) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `this._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } + + void testExplicitThisReferenceOnRhsOfNotEquals() { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (this._f is! Null) return; + this._f.expectStaticType<Exactly<Null>>(); + if (null != this._f) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `this._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); + } +} + +void testExplicitPropertyReferenceOnLhsOfEquals(C c) { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (c._f is! Null) return; + c._f.expectStaticType<Exactly<Null>>(); + if (c._f == null) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `c._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); +} + +void testExplicitPropertyReferenceOnRhsOfEquals(C c) { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (c._f is! Null) return; + c._f.expectStaticType<Exactly<Null>>(); + if (null == c._f) { + x = null; + } else { + y = null; + } + // In analyzing the `==` check, flow analysis assumes that `c._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); +} + +void testExplicitPropertyReferenceOnLhsOfNotEquals(C c) { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (c._f is! Null) return; + c._f.expectStaticType<Exactly<Null>>(); + if (c._f != null) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `c._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); +} + +void testExplicitPropertyReferenceOnRhsOfNotEquals(C c) { + int? x = 0; + int? y = 0; + x.expectStaticType<Exactly<int>>(); + y.expectStaticType<Exactly<int>>(); + if (c._f is! Null) return; + c._f.expectStaticType<Exactly<Null>>(); + if (null != c._f) { + x = null; + } else { + y = null; + } + // In analyzing the `!=` check, flow analysis assumes that `c._f` has its + // unpromoted type (`Object?`), so both branches of the `if` are + // reachable. Therefore both `x` and `y` should both be demoted here. + x.expectStaticType<Exactly<int?>>(); + y.expectStaticType<Exactly<int?>>(); +} + +main() { + for (var value in [null, '']) { + var c = C(value); + c.testImplicitThisReferenceOnLhsOfEquals(); + c.testImplicitThisReferenceOnRhsOfEquals(); + c.testImplicitThisReferenceOnLhsOfNotEquals(); + c.testImplicitThisReferenceOnRhsOfNotEquals(); + c.testExplicitThisReferenceOnLhsOfEquals(); + c.testExplicitThisReferenceOnRhsOfEquals(); + c.testExplicitThisReferenceOnLhsOfNotEquals(); + c.testExplicitThisReferenceOnRhsOfNotEquals(); + testExplicitPropertyReferenceOnLhsOfEquals(c); + testExplicitPropertyReferenceOnRhsOfEquals(c); + testExplicitPropertyReferenceOnLhsOfNotEquals(c); + testExplicitPropertyReferenceOnRhsOfNotEquals(c); + } +}