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:
Paul Berry 2023-07-17 18:06:16 +00:00 committed by Commit Queue
parent 53b2f9247b
commit 5db0c2b546
5 changed files with 386 additions and 0 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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 {}

View file

@ -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);
}
}

View file

@ -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);
}
}