mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:29:47 +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