Skip to content

Commit cb7546f

Browse files
johnniwintherCommit Queue
authored and
Commit Queue
committed
[cfe] Handle unevaluated constants in enums for exhaustiveness checking
If enum values have used defined fields that use fromEnvironment constants the enum values themselves are seen as unevaluated constants when compiling with dart2js. This means that the enum value are not recognized correctly in exhaustiveness checking. This CL changes the enum value representation of the CFE to use the enum class and the name of the enum element, derived either from the instance constant or the unevaluated constant expression. Change-Id: I7d5791a41349dacd20b588f5dbfca37d8755ef79 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/299100 Commit-Queue: Johnni Winther <[email protected]> Reviewed-by: Jens Johansen <[email protected]>
1 parent 88721ba commit cb7546f

12 files changed

+533
-14
lines changed

pkg/front_end/lib/src/fasta/kernel/exhaustiveness.dart

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -246,8 +246,47 @@ class CfeTypeOperations implements TypeOperations<DartType> {
246246
}
247247
}
248248

249+
class EnumValue {
250+
final Class enumClass;
251+
final String name;
252+
253+
EnumValue(this.enumClass, this.name);
254+
255+
@override
256+
int get hashCode => Object.hash(enumClass, name);
257+
258+
@override
259+
bool operator ==(other) {
260+
if (identical(this, other)) return true;
261+
return other is EnumValue &&
262+
enumClass == other.enumClass &&
263+
name == other.name;
264+
}
265+
}
266+
267+
EnumValue? constantToEnumValue(CoreTypes coreTypes, Constant constant) {
268+
if (constant is InstanceConstant && constant.classNode.isEnum) {
269+
StringConstant name = constant
270+
.fieldValues[coreTypes.enumNameField.fieldReference] as StringConstant;
271+
return new EnumValue(constant.classNode, name.value);
272+
} else if (constant is UnevaluatedConstant) {
273+
Expression expression = constant.expression;
274+
if (expression is FileUriExpression) {
275+
expression = expression.expression;
276+
}
277+
if (expression is InstanceCreation && expression.classNode.isEnum) {
278+
ConstantExpression name =
279+
expression.fieldValues[coreTypes.enumNameField.fieldReference]
280+
as ConstantExpression;
281+
return new EnumValue(
282+
expression.classNode, (name.constant as StringConstant).value);
283+
}
284+
}
285+
return null;
286+
}
287+
249288
class CfeEnumOperations
250-
implements EnumOperations<DartType, Class, Field, Constant> {
289+
implements EnumOperations<DartType, Class, Field, EnumValue> {
251290
final ConstantEvaluator _constantEvaluator;
252291

253292
CfeEnumOperations(this._constantEvaluator);
@@ -271,7 +310,7 @@ class CfeEnumOperations
271310
}
272311

273312
@override
274-
Constant getEnumElementValue(Field enumField) {
313+
EnumValue getEnumElementValue(Field enumField) {
275314
// Enum field initializers might not have been replaced by
276315
// [ConstantExpression]s. Either because we haven't visited them yet during
277316
// normal constant evaluation or because they are from outlines that are
@@ -280,7 +319,8 @@ class CfeEnumOperations
280319
// enum element.
281320
StaticTypeContext context =
282321
new StaticTypeContext(enumField, _constantEvaluator.typeEnvironment);
283-
return _constantEvaluator.evaluate(context, enumField.initializer!);
322+
return constantToEnumValue(_constantEvaluator.coreTypes,
323+
_constantEvaluator.evaluate(context, enumField.initializer!))!;
284324
}
285325

286326
@override
@@ -394,7 +434,7 @@ class CfeSealedClassOperations
394434
}
395435

396436
class CfeExhaustivenessCache
397-
extends ExhaustivenessCache<DartType, Class, Class, Field, Constant> {
437+
extends ExhaustivenessCache<DartType, Class, Class, Field, EnumValue> {
398438
final TypeEnvironment typeEnvironment;
399439

400440
CfeExhaustivenessCache(
@@ -527,13 +567,15 @@ class PatternConverter with SpaceCreator<Pattern, DartType> {
527567

528568
Space convertConstantToSpace(Constant? constant, {required Path path}) {
529569
if (constant != null) {
530-
if (constant is NullConstant) {
570+
EnumValue? enumValue =
571+
constantToEnumValue(cache.typeEnvironment.coreTypes, constant);
572+
if (enumValue != null) {
573+
return new Space(path,
574+
cache.getEnumElementStaticType(enumValue.enumClass, enumValue));
575+
} else if (constant is NullConstant) {
531576
return new Space(path, StaticType.nullType);
532577
} else if (constant is BoolConstant) {
533578
return new Space(path, cache.getBoolValueStaticType(constant.value));
534-
} else if (constant is InstanceConstant && constant.classNode.isEnum) {
535-
return new Space(
536-
path, cache.getEnumElementStaticType(constant.classNode, constant));
537579
} else if (constant is RecordConstant) {
538580
Map<Key, Space> properties = {};
539581
for (int index = 0; index < constant.positional.length; index++) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2023, 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+
enum A {
6+
a(B.a),
7+
b(B.b),
8+
;
9+
10+
final B value;
11+
12+
const A(this.value);
13+
}
14+
15+
class B {
16+
final int value;
17+
18+
const B(this.value);
19+
20+
static const B a = const B(const int.fromEnvironment('a'));
21+
static const B b = const B(const int.fromEnvironment('b'));
22+
}
23+
24+
String method(A a) {
25+
switch (a) {
26+
case A.a:
27+
return 'a';
28+
case A.b:
29+
return 'b';
30+
}
31+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class A extends core::_Enum /*isEnum*/ {
6+
static const field core::List<self::A> values = #C13;
7+
final field self::B value;
8+
enum-element static const field self::A a = #C5;
9+
enum-element static const field self::A b = #C11;
10+
const constructor •(core::int #index, core::String #name, self::B value) → self::A
11+
: self::A::value = value, super core::_Enum::•(#index, #name)
12+
;
13+
method _enumToString() → core::String
14+
return "A.${this.{core::_Enum::_name}{core::String}}";
15+
static method _#new#tearOff(core::int #index, core::String #name, self::B value) → self::A
16+
return new self::A::•(#index, #name, value);
17+
}
18+
class B extends core::Object /*hasConstConstructor*/ {
19+
final field core::int value;
20+
static const field self::B a = #C3;
21+
static const field self::B b = #C9;
22+
const constructor •(core::int value) → self::B
23+
: self::B::value = value, super core::Object::•()
24+
;
25+
static method _#new#tearOff(core::int value) → self::B
26+
return new self::B::•(value);
27+
}
28+
static method method(self::A a) → core::String {
29+
#L1:
30+
switch(a) /*isExplicitlyExhaustive*/ {
31+
#L2:
32+
case #C5:
33+
{
34+
return "a";
35+
}
36+
#L3:
37+
case #C11:
38+
{
39+
return "b";
40+
}
41+
}
42+
}
43+
44+
constants {
45+
#C1 = "a"
46+
#C2 = eval const core::int::fromEnvironment(#C1)
47+
#C3 = eval self::B{value:#C2}
48+
#C4 = 0.0
49+
#C5 = eval self::A{value:#C3, index:#C4, _name:#C1}
50+
#C6 = eval const <dynamic>[#C5]
51+
#C7 = "b"
52+
#C8 = eval const core::int::fromEnvironment(#C7)
53+
#C9 = eval self::B{value:#C8}
54+
#C10 = 1.0
55+
#C11 = eval self::A{value:#C9, index:#C10, _name:#C7}
56+
#C12 = eval const <dynamic>[#C11]
57+
#C13 = eval #C6 + #C12
58+
}
59+
60+
61+
Constructor coverage from constants:
62+
org-dartlang-testcase:///enum_from_environment.dart:
63+
- B. (from org-dartlang-testcase:///enum_from_environment.dart:18:9)
64+
- Object. (from org-dartlang-sdk:///lib/core/object.dart)
65+
- A. (from org-dartlang-testcase:///enum_from_environment.dart:12:9)
66+
- _Enum. (from org-dartlang-sdk:///lib/core/enum.dart)
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
5+
class A extends core::_Enum /*isEnum*/ {
6+
static const field core::List<self::A> values = #C13;
7+
final field self::B value;
8+
enum-element static const field self::A a = #C5;
9+
enum-element static const field self::A b = #C11;
10+
const constructor •(core::int #index, core::String #name, self::B value) → self::A
11+
: self::A::value = value, super core::_Enum::•(#index, #name)
12+
;
13+
method _enumToString() → core::String
14+
return "A.${this.{core::_Enum::_name}{core::String}}";
15+
static method _#new#tearOff(core::int #index, core::String #name, self::B value) → self::A
16+
return new self::A::•(#index, #name, value);
17+
}
18+
class B extends core::Object /*hasConstConstructor*/ {
19+
final field core::int value;
20+
static const field self::B a = #C3;
21+
static const field self::B b = #C9;
22+
const constructor •(core::int value) → self::B
23+
: self::B::value = value, super core::Object::•()
24+
;
25+
static method _#new#tearOff(core::int value) → self::B
26+
return new self::B::•(value);
27+
}
28+
static method method(self::A a) → core::String {
29+
#L1:
30+
switch(a) /*isExplicitlyExhaustive*/ {
31+
#L2:
32+
case #C5:
33+
{
34+
return "a";
35+
}
36+
#L3:
37+
case #C11:
38+
{
39+
return "b";
40+
}
41+
}
42+
}
43+
44+
constants {
45+
#C1 = "a"
46+
#C2 = eval const core::int::fromEnvironment(#C1)
47+
#C3 = eval self::B{value:#C2}
48+
#C4 = 0.0
49+
#C5 = eval self::A{value:#C3, index:#C4, _name:#C1}
50+
#C6 = eval const <dynamic>[#C5]
51+
#C7 = "b"
52+
#C8 = eval const core::int::fromEnvironment(#C7)
53+
#C9 = eval self::B{value:#C8}
54+
#C10 = 1.0
55+
#C11 = eval self::A{value:#C9, index:#C10, _name:#C7}
56+
#C12 = eval const <dynamic>[#C11]
57+
#C13 = eval #C6 + #C12
58+
}
59+
60+
Extra constant evaluation status:
61+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:5:6 -> ListConstant(const <A>[const A{A.value: const B{B.value: 0.0}, _Enum.index: 0.0, _Enum._name: "a"}, const A{A.value: const B{B.value: 0.0}, _Enum.index: 1.0, _Enum._name: "b"}])
62+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:6:3 -> InstanceConstant(const A{A.value: const B{B.value: 0.0}, _Enum.index: 0.0, _Enum._name: "a"})
63+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:7:3 -> InstanceConstant(const A{A.value: const B{B.value: 0.0}, _Enum.index: 1.0, _Enum._name: "b"})
64+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:20:28 -> InstanceConstant(const B{B.value: 0.0})
65+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:21:28 -> InstanceConstant(const B{B.value: 0.0})
66+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:26:12 -> InstanceConstant(const A{A.value: const B{B.value: 0.0}, _Enum.index: 0.0, _Enum._name: "a"})
67+
Evaluated with empty environment: ConstantExpression @ org-dartlang-testcase:///enum_from_environment.dart:28:12 -> InstanceConstant(const A{A.value: const B{B.value: 0.0}, _Enum.index: 1.0, _Enum._name: "b"})
68+
Extra constant evaluation: evaluated: 21, effectively constant: 7
69+
70+
71+
Constructor coverage from constants:
72+
org-dartlang-testcase:///enum_from_environment.dart:
73+
- B. (from org-dartlang-testcase:///enum_from_environment.dart:18:9)
74+
- Object. (from org-dartlang-sdk:///lib/core/object.dart)
75+
- A. (from org-dartlang-testcase:///enum_from_environment.dart:12:9)
76+
- _Enum. (from org-dartlang-sdk:///lib/core/enum.dart)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
enum A {
2+
a(B.a),
3+
b(B.b),
4+
;
5+
6+
final B value;
7+
const A(this.value);
8+
}
9+
10+
class B {
11+
final int value;
12+
const B(this.value);
13+
static const B a = const B(const int.fromEnvironment('a'));
14+
static const B b = const B(const int.fromEnvironment('b'));
15+
}
16+
17+
String method(A a) {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
String method(A a) {}
2+
3+
class B {
4+
const B(this.value);
5+
final int value;
6+
static const B a = const B(const int.fromEnvironment('a'));
7+
static const B b = const B(const int.fromEnvironment('b'));
8+
}
9+
10+
enum A {
11+
a(B.a),
12+
b(B.b),
13+
;
14+
15+
final B value;
16+
const A(this.value);
17+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
library /*isNonNullableByDefault*/;
2+
import self as self;
3+
import "dart:core" as core;
4+
import "dart:_internal" as _in;
5+
6+
class A extends core::_Enum /*isEnum*/ {
7+
static const field core::List<self::A> values = #C13;
8+
final field self::B value;
9+
enum-element static const field self::A a = #C5;
10+
enum-element static const field self::A b = #C11;
11+
const constructor •(core::int #index, core::String #name, self::B value) → self::A
12+
: self::A::value = value, super core::_Enum::•(#index, #name)
13+
;
14+
method _enumToString() → core::String
15+
return "A.${this.{core::_Enum::_name}{core::String}}";
16+
static method _#new#tearOff(core::int #index, core::String #name, self::B value) → self::A
17+
return new self::A::•(#index, #name, value);
18+
}
19+
class B extends core::Object /*hasConstConstructor*/ {
20+
final field core::int value;
21+
static const field self::B a = #C3;
22+
static const field self::B b = #C9;
23+
const constructor •(core::int value) → self::B
24+
: self::B::value = value, super core::Object::•()
25+
;
26+
static method _#new#tearOff(core::int value) → self::B
27+
return new self::B::•(value);
28+
}
29+
static method method(self::A a) → core::String {
30+
#L1:
31+
switch(a) /*isExplicitlyExhaustive*/ {
32+
#L2:
33+
case #C5:
34+
{
35+
return "a";
36+
}
37+
#L3:
38+
case #C11:
39+
{
40+
return "b";
41+
}
42+
#L4:
43+
default:
44+
throw new _in::ReachabilityError::•("`null` encountered as case in a switch statement with a non-nullable type.");
45+
}
46+
}
47+
48+
constants {
49+
#C1 = "a"
50+
#C2 = eval const core::int::fromEnvironment(#C1)
51+
#C3 = eval self::B{value:#C2}
52+
#C4 = 0.0
53+
#C5 = eval self::A{value:#C3, index:#C4, _name:#C1}
54+
#C6 = eval const <dynamic>[#C5]
55+
#C7 = "b"
56+
#C8 = eval const core::int::fromEnvironment(#C7)
57+
#C9 = eval self::B{value:#C8}
58+
#C10 = 1.0
59+
#C11 = eval self::A{value:#C9, index:#C10, _name:#C7}
60+
#C12 = eval const <dynamic>[#C11]
61+
#C13 = eval #C6 + #C12
62+
}
63+
64+
65+
Constructor coverage from constants:
66+
org-dartlang-testcase:///enum_from_environment.dart:
67+
- B. (from org-dartlang-testcase:///enum_from_environment.dart:18:9)
68+
- Object. (from org-dartlang-sdk:///lib/core/object.dart)
69+
- A. (from org-dartlang-testcase:///enum_from_environment.dart:12:9)
70+
- _Enum. (from org-dartlang-sdk:///lib/core/enum.dart)

0 commit comments

Comments
 (0)