mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 00:13:50 +00:00
Test some more corner cases of field promotion.
While rereading the analyzer logic that decides whether to suppress field promotion due to the presence of a `noSuchMethod` forwarder, I found a few corner cases that aren't handled correctly: - The logic for deciding which fields are included in a class's implementation currently doesn't understand that an abstract field is abstract; it treats it as a concrete field, therefore if a concrete subclass implements of `noSuchMethod`, but fails to implement the field, the analyzer fails to detect that there will be a `noSuchMethod` forwarder (and thus fails to suppress promotion). - The logic for collecting the set of fields (and getters) that are included in a class's interface (or implementation) currently stops at a library boundary, so it doesn't properly handle the situation where there is a library cycle, and two classes in one library are related through an intermediate class in some other library. - The logic for collecting the set of getters that are included in a class's interface currently ignores the `on` clauses of mixins. This CL includes tests for all these corner cases; I will fix them in a follow-up CL. Change-Id: I92d73c0643f1ab89144feefab68779418a2c7a35 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/313840 Reviewed-by: Erik Ernst <eernst@google.com> Reviewed-by: Lasse Nielsen <lrn@google.com> Commit-Queue: Paul Berry <paulberry@google.com>
This commit is contained in:
parent
53b2f9247b
commit
5db0c2b546
|
@ -0,0 +1,93 @@
|
|||
// 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 non-inherited abstract fields are properly accounted for when
|
||||
// deciding whether a `noSuchMethod` suppresses field promotion.
|
||||
|
||||
// SharedOptions=--enable-experiment=inference-update-2
|
||||
|
||||
import "../static_type_helper.dart";
|
||||
|
||||
class A {
|
||||
final int? _f1;
|
||||
final int? _f2;
|
||||
final int? _f3;
|
||||
final int? _f4;
|
||||
final int? _f5;
|
||||
|
||||
A(int? i)
|
||||
: _f1 = i,
|
||||
_f2 = i,
|
||||
_f3 = i,
|
||||
_f4 = i,
|
||||
_f5 = i;
|
||||
}
|
||||
|
||||
abstract class B1 {
|
||||
abstract final int? _f1;
|
||||
}
|
||||
|
||||
abstract mixin class B2 {
|
||||
abstract final int? _f2;
|
||||
}
|
||||
|
||||
abstract mixin class B3 {
|
||||
abstract final int? _f3;
|
||||
}
|
||||
|
||||
abstract class B4 {
|
||||
abstract final int? _f4;
|
||||
}
|
||||
|
||||
class C extends B1 with B2 {
|
||||
@override
|
||||
noSuchMethod(_) => null;
|
||||
}
|
||||
|
||||
class D = C with B3 implements B4;
|
||||
|
||||
testAbstractFieldFromTargetClass(A a) {
|
||||
if (a._f1 != null) {
|
||||
a._f1.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testAbstractFieldFromMixin(A a) {
|
||||
if (a._f2 != null) {
|
||||
a._f2.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testAbstractFieldFromTargetClassOfClassAlias(A a) {
|
||||
if (a._f3 != null) {
|
||||
a._f3.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testAbstractFieldFromMixinOfClassAlias(A a) {
|
||||
if (a._f4 != null) {
|
||||
a._f4.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testBasicPromotion(A a) {
|
||||
// Since all of the above tests check that field promotion *fails*, if we've
|
||||
// made a mistake causing field promotion to be completely disabled in this
|
||||
// file, all the tests will continue to pass. So to verify that we haven't
|
||||
// made such a mistake, verify that field promotion works under ordinary
|
||||
// circumstances.
|
||||
if (a._f5 != null) {
|
||||
a._f5.expectStaticType<Exactly<int>>;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
for (var a in [A(null), A(0)]) {
|
||||
testAbstractFieldFromTargetClass(a);
|
||||
testAbstractFieldFromMixin(a);
|
||||
testAbstractFieldFromTargetClassOfClassAlias(a);
|
||||
testAbstractFieldFromMixinOfClassAlias(a);
|
||||
testBasicPromotion(a);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// 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 inherited abstract fields are properly accounted for when deciding
|
||||
// whether a `noSuchMethod` suppresses field promotion.
|
||||
|
||||
// SharedOptions=--enable-experiment=inference-update-2
|
||||
|
||||
import "../static_type_helper.dart";
|
||||
|
||||
class A {
|
||||
final int? _f1;
|
||||
final int? _f2;
|
||||
final int? _f3;
|
||||
final int? _f4;
|
||||
final int? _f5;
|
||||
|
||||
A(int? i)
|
||||
: _f1 = i,
|
||||
_f2 = i,
|
||||
_f3 = i,
|
||||
_f4 = i,
|
||||
_f5 = i;
|
||||
}
|
||||
|
||||
abstract class B {
|
||||
abstract final int? _f1;
|
||||
abstract int? _f2;
|
||||
}
|
||||
|
||||
abstract class C {
|
||||
abstract final int? _f3;
|
||||
abstract int? _f4;
|
||||
}
|
||||
|
||||
class D extends C implements B {
|
||||
@override
|
||||
noSuchMethod(_) => 0;
|
||||
}
|
||||
|
||||
testFinalAbstractFieldImpliesInterfaceGetter(A a) {
|
||||
// Class `D` has an `_f1` getter in its interface, inherited from `B`. `D` has
|
||||
// a non-default `noSuchMethod` method, but it neither inherits nor declares
|
||||
// any implementation of `_f1`. Because of that, `D` introduces an implicit
|
||||
// and concrete `noSuchMethod`-forwarding `_f1` getter implementation, and the
|
||||
// presence of an implicit forwarding concrete getter with the same name
|
||||
// inhibits promotion of `A._f1`.
|
||||
if (a._f1 != null) {
|
||||
a._f1.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testNonFinalAbstractFieldImpliesInterfaceGetter(A a) {
|
||||
// Class `D` has an `_f2` getter in its interface, inherited from `B`. `D` has
|
||||
// a non-default `noSuchMethod` method, but it neither inherits nor declares
|
||||
// any implementation of `_f2`. Because of that, `D` introduces an implicit
|
||||
// and concrete `noSuchMethod`-forwarding `_f2` getter implementation, and the
|
||||
// presence of an implicit forwarding concrete getter with the same name
|
||||
// inhibits promotion of `A._f2`.
|
||||
if (a._f2 != null) {
|
||||
a._f2.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testFinalAbstractFieldDoesNotImplyImplementationField(A a) {
|
||||
// Class `D` has an `_f3` getter in its interface, inherited from `C`. `D` has
|
||||
// a non-default `noSuchMethod` method, but it neither inherits nor declares
|
||||
// any implementation of `_f3`. Because of that, `D` introduces an implicit
|
||||
// and concrete `noSuchMethod`-forwarding `_f3` getter implementation, and the
|
||||
// presence of an implicit forwarding concrete getter with the same name
|
||||
// inhibits promotion of `A._f3`.
|
||||
if (a._f3 != null) {
|
||||
a._f3.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testNonFinalAbstractFieldDoesNotImplyImplementationField(A a) {
|
||||
// Class `D` has an `_f4` getter in its interface, inherited from `C`. `D` has
|
||||
// a non-default `noSuchMethod` method, but it neither inherits nor declares
|
||||
// any implementation of `_f4`. Because of that, `D` introduces an implicit
|
||||
// and concrete `noSuchMethod`-forwarding `_f4` getter implementation, and the
|
||||
// presence of an implicit forwarding concrete getter with the same name
|
||||
// inhibits promotion of `A._f4`.
|
||||
if (a._f4 != null) {
|
||||
a._f4.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testBasicPromotion(A a) {
|
||||
// Since all of the above tests check that field promotion *fails*, if we've
|
||||
// made a mistake causing field promotion to be completely disabled in this
|
||||
// file, all the tests will continue to pass. So to verify that we haven't
|
||||
// made such a mistake, verify that field promotion works under ordinary
|
||||
// circumstances.
|
||||
if (a._f5 != null) {
|
||||
a._f5.expectStaticType<Exactly<int>>;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
for (var a in [A(null), A(0)]) {
|
||||
testFinalAbstractFieldImpliesInterfaceGetter(a);
|
||||
testNonFinalAbstractFieldImpliesInterfaceGetter(a);
|
||||
testFinalAbstractFieldDoesNotImplyImplementationField(a);
|
||||
testNonFinalAbstractFieldDoesNotImplyImplementationField(a);
|
||||
testBasicPromotion(a);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
// 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.
|
||||
|
||||
// Library classes `B` and `E`, used by
|
||||
// `field_promotion_and_no_such_method_library_cycle_test.dart`.
|
||||
|
||||
import 'field_promotion_and_no_such_method_library_cycle_test.dart';
|
||||
|
||||
class B extends C {
|
||||
B(super.f1);
|
||||
|
||||
// This class has an `_f1` getter in its interface, inherited from `C`. It
|
||||
// also inherits an implementation of `_f1` from `C`. Therefore, even though
|
||||
// there is a non-default `noSuchMethod` method, this class _doesn't_
|
||||
// introduce a `noSuchMethod`-forwarding `_f1` getter implementation.
|
||||
//
|
||||
// Hence, the presence of this `noSuchMethod` method doesn't prevent `_f1`
|
||||
// from undergoing type promotion in the other library.
|
||||
noSuchMethod(_) => 0;
|
||||
}
|
||||
|
||||
abstract class E implements F {}
|
|
@ -0,0 +1,106 @@
|
|||
// 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 when determining whether field promotion should be inhibited due
|
||||
// to a `noSuchMethod` forwarder, the implementation doesn't terminate its
|
||||
// exploration of the class hierarchy at a library boundary; it continues in
|
||||
// order to find transitive superclass relationships via an intermediate class
|
||||
// in another library. Note that this situation can only arise in libraries that
|
||||
// are part of a library cycle.
|
||||
|
||||
// SharedOptions=--enable-experiment=inference-update-2
|
||||
|
||||
// The class hierarchy implemented in this test looks like this:
|
||||
//
|
||||
// C {_f1} F {_f2} H {_f1, _f2}
|
||||
// ^ ^
|
||||
// |extends |implements
|
||||
// B E
|
||||
// ^ ^
|
||||
// |extends |implements
|
||||
// A D {_f1}
|
||||
// ^ ^
|
||||
// |extends |implements
|
||||
// +---------+
|
||||
// |
|
||||
// G
|
||||
//
|
||||
// With classes B and E in a different library than this one.
|
||||
//
|
||||
// In brackets following each class name are the names of fields explicitly
|
||||
// declared in the corresponding class declaration.
|
||||
|
||||
import "../static_type_helper.dart";
|
||||
|
||||
import 'field_promotion_and_no_such_method_library_cycle_lib.dart';
|
||||
|
||||
class A extends B {
|
||||
A(super.f1);
|
||||
}
|
||||
|
||||
class C {
|
||||
final int? _f1;
|
||||
C(this._f1);
|
||||
}
|
||||
|
||||
// Note: this class is abstract because it has an `_f2` getter in its interface,
|
||||
// inherited from `F` (by way of `E`), but it neither inherits nor declares any
|
||||
// implementation of `_f2`. Since it does not have a non-default `noSuchMethod`
|
||||
// implementation, it needs to be abstract to avoid a compile-time error.
|
||||
abstract class D implements E {
|
||||
// Note: this is `_f1` on purpose; not `_f2`. The reason for this field is to
|
||||
// make the overall test more robust; if there is a bug that prevents the
|
||||
// implementation from finding `C._f1` when it walks the class hierarchy of
|
||||
// `G` (e.g. because it stops walking the class hierarchy at a library
|
||||
// boundary--a bug that exists as of
|
||||
// 98b63e1dcf600402c910740cf2338cb18e05f68d), it will still find `D._f1`,
|
||||
// causing promotion of `H._f1` to suppressed and causing the test to fail.
|
||||
final int? _f1;
|
||||
D(this._f1);
|
||||
}
|
||||
|
||||
abstract class F {
|
||||
final int? _f2;
|
||||
F(this._f2);
|
||||
}
|
||||
|
||||
class G extends A implements D {
|
||||
G(super.f1);
|
||||
@override
|
||||
noSuchMethod(_) => 0;
|
||||
}
|
||||
|
||||
class H {
|
||||
final int? _f1;
|
||||
final int? _f2;
|
||||
H(int? i)
|
||||
: _f1 = i,
|
||||
_f2 = i;
|
||||
}
|
||||
|
||||
testImplementedFieldSeenViaOtherLib(H h) {
|
||||
// Class `G` inherits an implmentation of `_f1` from `C` (via `B` and
|
||||
// `A`). Therefore it doesn't need a noSuchMethod forwarder for `_f1`, and
|
||||
// consequently, promotion of `H._f1` works.
|
||||
if (h._f1 != null) {
|
||||
h._f1.expectStaticType<Exactly<int>>;
|
||||
}
|
||||
}
|
||||
|
||||
testInterfceFieldSeenViaOtherLib(H h) {
|
||||
// Class `G` inherits `_f2` into its interface from `F` (via `E` and `D`). But
|
||||
// it doesn't inherit an implementation of `_f2` from anywhere. Therefore it
|
||||
// needs a noSuchMethod forwarder for `_f2`, and consequently, promotion of
|
||||
// `H._f2` is disabled.
|
||||
if (h._f2 != null) {
|
||||
h._f2.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
for (var h in [H(null), H(0)]) {
|
||||
testImplementedFieldSeenViaOtherLib(h);
|
||||
testInterfceFieldSeenViaOtherLib(h);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
// 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 a mixin's `on` clause is properly accounted for when determining
|
||||
// whether field promotion should be inhibited due to a `noSuchMethod`
|
||||
// forwarder.
|
||||
|
||||
// SharedOptions=--enable-experiment=inference-update-2
|
||||
|
||||
import "../static_type_helper.dart";
|
||||
|
||||
mixin M on C {}
|
||||
|
||||
class C {
|
||||
final int? _f1;
|
||||
C(this._f1);
|
||||
}
|
||||
|
||||
class D implements M {
|
||||
@override
|
||||
noSuchMethod(_) => 0;
|
||||
}
|
||||
|
||||
class E extends C {
|
||||
final int? _f2;
|
||||
E(this._f2) : super(_f2);
|
||||
}
|
||||
|
||||
testOnClauseAffectsInterface(E e) {
|
||||
// The presence of the clause `on C` in the declaration of `mixin M` means
|
||||
// that `M` contains a getter named `_f1` in its interface. Consequently,
|
||||
// class `D` will have a `noSuchMethod` forwarder for `_f1`, defeating
|
||||
// promotion for `E._f1`.
|
||||
if (e._f1 != null) {
|
||||
e._f1.expectStaticType<Exactly<int?>>;
|
||||
}
|
||||
}
|
||||
|
||||
testBasicPromotion(E e) {
|
||||
// Since the above test checks that field promotion *fails*, if we've made a
|
||||
// mistake causing field promotion to be completely disabled in this file, the
|
||||
// test will continue to pass. So to verify that we haven't made such a
|
||||
// mistake, verify that field promotion works under ordinary circumstances.
|
||||
if (e._f2 != null) {
|
||||
e._f2.expectStaticType<Exactly<int>>;
|
||||
}
|
||||
}
|
||||
|
||||
main() {
|
||||
for (var e in [E(null), E(0)]) {
|
||||
testOnClauseAffectsInterface(e);
|
||||
testBasicPromotion(e);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue