Skip to content

Commit 22c46e5

Browse files
stereotype441Commit Queue
authored and
Commit Queue
committed
Field type promotion: fix CFE handling of abstract fields.
Bug: dart-lang/language#2020 Change-Id: Ie35f7ff2d3de33c5f94bb7b703c13c9764a85c70 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/269981 Commit-Queue: Paul Berry <[email protected]> Reviewed-by: Johnni Winther <[email protected]>
1 parent a975bf0 commit 22c46e5

21 files changed

+593
-9
lines changed

pkg/front_end/lib/src/fasta/source/source_class_builder.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,14 +1540,17 @@ class SourceClassBuilder extends ClassBuilderImpl
15401540
}
15411541
for (Procedure procedure in cls.procedures) {
15421542
// An instance getter makes fields with the same name unpromotable if it's
1543-
// concrete.
1544-
if (procedure.isGetter &&
1545-
procedure.isInstanceMember &&
1546-
!procedure.isAbstract &&
1543+
// concrete. Also, an abstract instance setter that's desugared from an
1544+
// abstract non-final field makes fields with the same name unpromotable.
1545+
if (procedure.isInstanceMember &&
15471546
_isPrivateNameInThisLibrary(procedure.name)) {
1548-
ProcedureStubKind procedureStubKind = procedure.stubKind;
1549-
if (procedureStubKind == ProcedureStubKind.Regular ||
1550-
procedureStubKind == ProcedureStubKind.NoSuchMethodForwarder) {
1547+
if (procedure.isGetter && !procedure.isAbstract) {
1548+
ProcedureStubKind procedureStubKind = procedure.stubKind;
1549+
if (procedureStubKind == ProcedureStubKind.Regular ||
1550+
procedureStubKind == ProcedureStubKind.NoSuchMethodForwarder) {
1551+
unpromotablePrivateFieldNames.add(procedure.name.text);
1552+
}
1553+
} else if (procedure.isSetter && procedure.isAbstractFieldAccessor) {
15511554
unpromotablePrivateFieldNames.add(procedure.name.text);
15521555
}
15531556
}

pkg/front_end/lib/src/fasta/source/source_field_builder.dart

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,7 @@ class AbstractOrExternalFieldEncoding implements FieldEncoding {
16121612
new FunctionNode(null, positionalParameters: [
16131613
new VariableDeclaration(extensionThisName)..fileOffset
16141614
]),
1615+
isAbstractFieldAccessor: isAbstract,
16151616
fileUri: fileUri,
16161617
reference: getterReference)
16171618
..fileOffset = charOffset
@@ -1636,6 +1637,7 @@ class AbstractOrExternalFieldEncoding implements FieldEncoding {
16361637
returnType: const VoidType())
16371638
..fileOffset = charOffset
16381639
..fileEndOffset = charEndOffset,
1640+
isAbstractFieldAccessor: isAbstract,
16391641
fileUri: fileUri,
16401642
reference: setterReference)
16411643
..fileOffset = charOffset
@@ -1648,7 +1650,9 @@ class AbstractOrExternalFieldEncoding implements FieldEncoding {
16481650
} else {
16491651
_getter = new Procedure(
16501652
dummyName, ProcedureKind.Getter, new FunctionNode(null),
1651-
fileUri: fileUri, reference: getterReference)
1653+
isAbstractFieldAccessor: isAbstract,
1654+
fileUri: fileUri,
1655+
reference: getterReference)
16521656
..fileOffset = charOffset
16531657
..fileEndOffset = charEndOffset
16541658
..isNonNullableByDefault = isNonNullableByDefault;
@@ -1667,6 +1671,7 @@ class AbstractOrExternalFieldEncoding implements FieldEncoding {
16671671
positionalParameters: [parameter], returnType: const VoidType())
16681672
..fileOffset = charOffset
16691673
..fileEndOffset = charEndOffset,
1674+
isAbstractFieldAccessor: isAbstract,
16701675
fileUri: fileUri,
16711676
reference: setterReference)
16721677
..fileOffset = charOffset

pkg/front_end/lib/src/fasta/type_inference/type_inference_engine.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,10 @@ class OperationsCfe
347347
Set<String>? unpromotablePrivateFieldNames =
348348
this.unpromotablePrivateFieldNames;
349349
if (unpromotablePrivateFieldNames == null) return false;
350-
if (property is! Field) return false;
350+
if (property is Procedure && !property.isAbstractFieldAccessor) {
351+
// We don't promote methods or explicit abstract getters.
352+
return false;
353+
}
351354
String name = property.name.text;
352355
if (!name.startsWith('_')) return false;
353356
return !unpromotablePrivateFieldNames.contains(name);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests that field promotion logic properly handles abstract fields.
6+
7+
// SharedOptions=--enable-experiment=inference-update-2
8+
9+
abstract class C {
10+
abstract final int? _f1;
11+
abstract int? _f2;
12+
}
13+
14+
class D {
15+
final int? _f1;
16+
final int? _f2;
17+
18+
D(int i) : _f1 = i, _f2 = i;
19+
}
20+
21+
void acceptsInt(int x) {}
22+
23+
void testAbstractFinalFieldIsPromotable(C c) {
24+
if (c._f1 != null) {
25+
var x = c._f1;
26+
// `x` has type `int` so this is ok
27+
acceptsInt(x);
28+
}
29+
}
30+
31+
void testAbstractNonFinalFieldIsNotPromotable(C c) {
32+
// Technically, it would be sound to promote an abstract non-final field, but
33+
// there's no point because it's just going to be implemented by a concrete
34+
// non-final field or a getter/setter pair, which will prevent promotion. So
35+
// we might as well prevent promotion even in the absence of an
36+
// implementation.
37+
if (c._f2 != null) {
38+
var x = c._f2;
39+
// `x` has type `int?` so this is ok
40+
x = null;
41+
}
42+
}
43+
44+
void testAbstractFinalFieldDoesNotBlockPromotionElsewhere(D d) {
45+
if (d._f1 != null) {
46+
var x = d._f1;
47+
// `x` has type `int` so this is ok
48+
acceptsInt(x);
49+
}
50+
}
51+
52+
void testAbstractNonFinalFieldBlocksPromotionElsewhere(D d) {
53+
// Technically, it would be sound if an abstract non-final field didn't block
54+
// promotion, but there's no point because it's just going to be implemented
55+
// by a concrete non-final field or a getter/setter pair, which will block
56+
// promotion. So we might as well block promotion even in the absence of an
57+
// implementation.
58+
if (d._f2 != null) {
59+
var x = d._f2;
60+
// `x` has type `int?` so this is ok
61+
x = null;
62+
}
63+
}
64+
65+
main() {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
abstract class C {
2+
abstract final int? _f1;
3+
abstract int? _f2;
4+
}
5+
6+
class D {
7+
final int? _f1;
8+
final int? _f2;
9+
D(int i)
10+
: _f1 = i,
11+
_f2 = i;
12+
}
13+
14+
void acceptsInt(int x) {}
15+
void testAbstractFinalFieldIsPromotable(C c) {}
16+
void testAbstractNonFinalFieldIsNotPromotable(C c) {}
17+
void testAbstractFinalFieldDoesNotBlockPromotionElsewhere(D d) {}
18+
void testAbstractNonFinalFieldBlocksPromotionElsewhere(D d) {}
19+
main() {}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
abstract class C {
2+
abstract final int? _f1;
3+
abstract int? _f2;
4+
}
5+
6+
class D {
7+
D(int i)
8+
: _f1 = i,
9+
_f2 = i;
10+
final int? _f1;
11+
final int? _f2;
12+
}
13+
14+
main() {}
15+
void acceptsInt(int x) {}
16+
void testAbstractFinalFieldDoesNotBlockPromotionElsewhere(D d) {}
17+
void testAbstractFinalFieldIsPromotable(C c) {}
18+
void testAbstractNonFinalFieldBlocksPromotionElsewhere(D d) {}
19+
void testAbstractNonFinalFieldIsNotPromotable(C c) {}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
abstract class C extends core::Object {
6+
synthetic constructor •() → self::C
7+
: super core::Object::•()
8+
;
9+
abstract get _f1() → core::int?;
10+
abstract get _f2() → core::int?;
11+
abstract set _f2(core::int? #externalFieldValue) → void;
12+
}
13+
class D extends core::Object {
14+
final field core::int? _f1;
15+
final field core::int? _f2;
16+
constructor •(core::int i) → self::D
17+
: self::D::_f1 = i, self::D::_f2 = i, super core::Object::•()
18+
;
19+
}
20+
static method acceptsInt(core::int x) → void {}
21+
static method testAbstractFinalFieldIsPromotable(self::C c) → void {
22+
if(!(c.{self::C::_f1}{core::int?} == null)) {
23+
core::int x = c.{self::C::_f1}{core::int};
24+
self::acceptsInt(x);
25+
}
26+
}
27+
static method testAbstractNonFinalFieldIsNotPromotable(self::C c) → void {
28+
if(!(c.{self::C::_f2}{core::int?} == null)) {
29+
core::int? x = c.{self::C::_f2}{core::int?};
30+
x = null;
31+
}
32+
}
33+
static method testAbstractFinalFieldDoesNotBlockPromotionElsewhere(self::D d) → void {
34+
if(!(d.{self::D::_f1}{core::int?} == null)) {
35+
core::int x = d.{self::D::_f1}{core::int};
36+
self::acceptsInt(x);
37+
}
38+
}
39+
static method testAbstractNonFinalFieldBlocksPromotionElsewhere(self::D d) → void {
40+
if(!(d.{self::D::_f2}{core::int?} == null)) {
41+
core::int? x = d.{self::D::_f2}{core::int?};
42+
x = null;
43+
}
44+
}
45+
static method main() → dynamic {}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
abstract class C extends core::Object {
6+
synthetic constructor •() → self::C
7+
: super core::Object::•()
8+
;
9+
abstract get _f1() → core::int?;
10+
abstract get _f2() → core::int?;
11+
abstract set _f2(core::int? #externalFieldValue) → void;
12+
}
13+
class D extends core::Object {
14+
final field core::int? _f1;
15+
final field core::int? _f2;
16+
constructor •(core::int i) → self::D
17+
: self::D::_f1 = i, self::D::_f2 = i, super core::Object::•()
18+
;
19+
}
20+
static method acceptsInt(core::int x) → void {}
21+
static method testAbstractFinalFieldIsPromotable(self::C c) → void {
22+
if(!(c.{self::C::_f1}{core::int?} == null)) {
23+
core::int x = c.{self::C::_f1}{core::int};
24+
self::acceptsInt(x);
25+
}
26+
}
27+
static method testAbstractNonFinalFieldIsNotPromotable(self::C c) → void {
28+
if(!(c.{self::C::_f2}{core::int?} == null)) {
29+
core::int? x = c.{self::C::_f2}{core::int?};
30+
x = null;
31+
}
32+
}
33+
static method testAbstractFinalFieldDoesNotBlockPromotionElsewhere(self::D d) → void {
34+
if(!(d.{self::D::_f1}{core::int?} == null)) {
35+
core::int x = d.{self::D::_f1}{core::int};
36+
self::acceptsInt(x);
37+
}
38+
}
39+
static method testAbstractNonFinalFieldBlocksPromotionElsewhere(self::D d) → void {
40+
if(!(d.{self::D::_f2}{core::int?} == null)) {
41+
core::int? x = d.{self::D::_f2}{core::int?};
42+
x = null;
43+
}
44+
}
45+
static method main() → dynamic {}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
abstract class C extends core::Object {
6+
synthetic constructor •() → self::C
7+
;
8+
abstract get _f1() → core::int?;
9+
abstract get _f2() → core::int?;
10+
abstract set _f2(core::int? #externalFieldValue) → void;
11+
}
12+
class D extends core::Object {
13+
final field core::int? _f1;
14+
final field core::int? _f2;
15+
constructor •(core::int i) → self::D
16+
;
17+
}
18+
static method acceptsInt(core::int x) → void
19+
;
20+
static method testAbstractFinalFieldIsPromotable(self::C c) → void
21+
;
22+
static method testAbstractNonFinalFieldIsNotPromotable(self::C c) → void
23+
;
24+
static method testAbstractFinalFieldDoesNotBlockPromotionElsewhere(self::D d) → void
25+
;
26+
static method testAbstractNonFinalFieldBlocksPromotionElsewhere(self::D d) → void
27+
;
28+
static method main() → dynamic
29+
;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
abstract class C extends core::Object {
6+
synthetic constructor •() → self::C
7+
: super core::Object::•()
8+
;
9+
abstract get _f1() → core::int?;
10+
abstract get _f2() → core::int?;
11+
abstract set _f2(core::int? #externalFieldValue) → void;
12+
}
13+
class D extends core::Object {
14+
final field core::int? _f1;
15+
final field core::int? _f2;
16+
constructor •(core::int i) → self::D
17+
: self::D::_f1 = i, self::D::_f2 = i, super core::Object::•()
18+
;
19+
}
20+
static method acceptsInt(core::int x) → void {}
21+
static method testAbstractFinalFieldIsPromotable(self::C c) → void {
22+
if(!(c.{self::C::_f1}{core::int?} == null)) {
23+
core::int x = c.{self::C::_f1}{core::int};
24+
self::acceptsInt(x);
25+
}
26+
}
27+
static method testAbstractNonFinalFieldIsNotPromotable(self::C c) → void {
28+
if(!(c.{self::C::_f2}{core::int?} == null)) {
29+
core::int? x = c.{self::C::_f2}{core::int?};
30+
x = null;
31+
}
32+
}
33+
static method testAbstractFinalFieldDoesNotBlockPromotionElsewhere(self::D d) → void {
34+
if(!(d.{self::D::_f1}{core::int?} == null)) {
35+
core::int x = d.{self::D::_f1}{core::int};
36+
self::acceptsInt(x);
37+
}
38+
}
39+
static method testAbstractNonFinalFieldBlocksPromotionElsewhere(self::D d) → void {
40+
if(!(d.{self::D::_f2}{core::int?} == null)) {
41+
core::int? x = d.{self::D::_f2}{core::int?};
42+
x = null;
43+
}
44+
}
45+
static method main() → dynamic {}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
// Tests that field promotion logic properly handles external fields. External
6+
// fields are not promotable because they are effectively just getters; the
7+
// language can't guarantee that they always return the same value.
8+
9+
abstract class C {
10+
external final int? _f1;
11+
}
12+
13+
extension on C {
14+
external final int? _f2;
15+
}
16+
17+
void testExternalFieldInClass(C c) {
18+
if (c._f1 != null) {
19+
var x = c._f1;
20+
// `x` has type `int?` so this is ok
21+
x = null;
22+
}
23+
}
24+
25+
void testExternalFieldInExtension(C c) {
26+
if (c._f2 != null) {
27+
var x = c._f2;
28+
// `x` has type `int?` so this is ok
29+
x = null;
30+
}
31+
}
32+
33+
main() {}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
abstract class C {
2+
external final int? _f1;
3+
}
4+
5+
extension on C {
6+
external final int? _f2;
7+
}
8+
9+
void testExternalFieldInClass(C c) {}
10+
void testExternalFieldInExtension(C c) {}
11+
main() {}

0 commit comments

Comments
 (0)