Description
(This came up organically in a project I'm working on with @sobrienpdx. I've reduced it down to a minimal repro.)
The following code is accepted by the analyzer but rejected by the front end:
abstract class A {
String get s;
}
abstract class B implements A {}
enum E1 implements B {
e1;
@override
String get s => 'E1';
}
enum E2 implements B {
e2;
@override
String get s => 'E2';
}
final allValues = [...E1.values, ...E2.values];
main() {
for (var value in allValues) {
print(value.s);
}
}
The front end issues this error message:
Error: The getter 's' isn't defined for the class 'Object'.
- 'Object' is from 'dart:core'.
Try correcting the name to the name of an existing getter, or defining a getter or field named 's'.
print(value.s);
^
After some digging, I figured out that the difference comes down to a subtle interaction between least upper bound and an ambiguity in the enhanced enums spec.
The spec doesn't fully pin down what the class hierarchy of an enum should look like. It says:
The specification here does not specify how the index and name of an enum is associated with the enum instances. In practice it’s possible to desugar an
enum
declaration to aclass
declaration, as long as the desugaring can access private names from other libraries (dart:core
in particular).The existing enums are implemented as desugaring into a class extending a private
_Enum
class which holds thefinal int index;
declaration and afinal String _name;
declaration (used by the theEnumName.name
getter), and both fields are initialized by a constructor.In practice, the implementation of the enhanced enums will likely be something similar.
Either first declare
Enum
as:abstract class Enum { Enum._(this.index, this._name); final int index; final String _name; String _$enumToString(); String toString() => _$enumToString(); }or retain the current
_Enum
class and make that the actual superclass ofenum
classes. Either works, I’ll useEnum
as the superclass directly in the following.
What was implemented was:
- In the analyzer, all enums extend
Enum
, which extendsObject
. - In the front end, all enums extend
_Enum
, which in turn implementsEnum
, which extendsObject
.
This means that, when analyzing the code example above, the analyzer considers the class hierarchy to be:
graph BT
B --> A --> Object
Enum --> Object
E1 --> B
E1 --> Enum
E2 --> B
E2 --> Enum
Whereas the front end considers the class hierarchy to be:
graph BT
B --> A --> Object
_Enum --> Enum --> Object
E1 --> B
E1 --> _Enum
E2 --> B
E2 --> _Enum
The inferred type of allValues
is List<LUB(E1, E2)>
. LUB takes the class that's at the greatest unique depth in the hierarchy, so that means in the analyzer case the LUB is B
, whereas in the front end case the LUB is Object
.
I think we should probably change the analyzer to match the front end behavior, and adjust the spec to say that all enums extend a private _Enum
class in dart:core
. (It's not necessary to specify that _Enum
implements Enum
, because both the analyzer and the front end can see this by analyzing the core library).