Skip to content

Say that you cannot override index or conflict with values. #2080

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Feb 4, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 35 additions & 30 deletions accepted/future-releases/enhanced-enums/feature-specification.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Dart Enhanced Enum Classes

Author: [email protected]<br>Version: 1.5<br>Tracking issue [#158](https://github.com/dart-lang/language/issues/158)
Author: [email protected]<br>Version: 1.6<br>Tracking issue [#158](https://github.com/dart-lang/language/issues/158)

This is a formal proposal for a language feature which allows `enum` declarations to declare classes with fields, methods and const constructors initializing those fields. Further, `enum` declarations can implement interfaces and, as an optional feature, apply mixins.

Expand Down Expand Up @@ -56,6 +56,7 @@ It is a **compile-time error** if the initializer list of a non-redirecting gene
_We will introduce the necessary super-invocation ourselves as an implementation detail. From the user’s perspective, they extend `Enum` which has no public constructors. We could allow `super()`, which would then be a constructor of `Enum`, but it's simpler to just disallow super invocations entirely._

It is a **compile-time error** to refer to a declared or default generative constructor of an `enum` declaration in any way, other than:

* As the target of a redirecting generative constructor of the same `enum`, or
* Implicitly in the enum value declarations of the same `enum`.

Expand All @@ -64,6 +65,8 @@ That also means that a redirecting *factory* constructor cannot redirect to a ge
and therefore no factory constructor of an `enum` declaration can be `const`, because a `const` factory constructor
must redirect to a generative constructor._

It's a **compile-time error** if the enum declaration contains a static or instance member declaration with the name `values`, or if the superclass or any superinterface of the enum declaration has an interface member named `values`. _A `values` static constant member will be provided for the class, this restriction ensures that there is no conflict with that declaration._

## Semantics

The `Enum` class behaves as if it was declared as:
Expand Down Expand Up @@ -91,7 +94,7 @@ The semantics of such an enum declaration, *E*, is defined as introducing a (sem
It’s a **compile-time error** if such a mixin application introduces any instance variables. _We need to be able to call an implementation specific superclass `const` constructor of `Enum`, and a mixin application of a mixin with a field does not make its forwarding constructor `const`. Currently that’s the only restriction, but if we add further restrictions on mixin applications having `const` forwarding constructors, those should also apply here._

* **Superinterfaces**: The immediate superinterfaces of *C* are the interface of the superclass and the interfaces declared by *E*.

* If `E` is declared as `enum Name with Mixin1, Mixin2 implements Type1, Type2 { … }` then the immediate superinterfaces of *C* are the interfaces of `Name with Mixin1, Mixin2`, `Type1` and `Type2`.

- **Declared members**: For each member declaration of the `enum` declaration *E*, the same member is added to the class *C*. This includes constructors (which must be `const` generative or non-`const` factory constructors.)
Expand All @@ -102,34 +105,32 @@ The semantics of such an enum declaration, *E*, is defined as introducing a (sem
```dart
const Name();
```
_(This differs from the default constructor of a normal `class` declaration by being constant.)_

_(This differs from the default constructor of a normal `class` declaration by being constant, and by being added even if a factory constructor is present. If no generative constructor is declared, and the unnamed constructor is taken by a factory constructor, there is no way for the enum declaration to compile successfully, since the declaration must contain at least one enum value, and that enum value must refer to a generative constructor.)_

- **Enum values**: For each `<enumEntry>` with name `id` and index *i* in the comma-separated list of enum entries, a constant value is created, and a static constant variable named `id` is created in *C* with that value. All the constant values are associated, in some implementation dependent way, with
- their name `id` as a string `"id"`,

- their name `id` as a string `"id"`,
- their index *i* as an `int`, and
- their `enum` class’s name as a string, `"Name"`,
all of which is accessible to the `toString` and `index` member of `Enum`, and to the `EnumName.name` extension getter. The values are computed as follows.
- `id` &mapsto; `Name()` (no arguments, equivalent to empty argument list)
- `id(args)` &mapsto; `Name(args)`
- `id<types>(args)` &mapsto; `Name<types>(args)`
- `id.named(args)` &mapsto; `Name._$named(args)`
- `id<types>.named(args)` &mapsto; `Name<types>._$named(args)`

all of which are accessible to the `toString` and `index` member of `Enum`, and to the `EnumName.name` extension getter. The values are computed as by the following constant constructor invocations.

- `id` &mapsto; `const Name()` (no arguments, equivalent to empty argument list)
- `id(args)` &mapsto; `const Name(args)`
- `id<types>(args)` &mapsto; `const Name<types>(args)`
- `id.named(args)` &mapsto; `const Name.named(args)`
- `id<types>.named(args)` &mapsto; `const Name<types>.named(args)`

where `args` are considered as occurring in a `const` context, and it’s a **compile-time error** if they are then not compile-time constants.

Type inference is applied to the resulting constructor invocations, with no context type, where necessary, so omitted type arguments to a generic `enum` class are filled in by type inference, using the type of arguments, if any, and then the type of the constant variable is the static type of the constant object creation expression.

The objects created here are *not canonicalized* like other constant object creations. _(In practice, the index value is considered part of the object, so no two objects will have the same state.)_

- **Static `values` list**: If the class does not declare or inherit a member with base-name `values`, a static constant variable named `values` is added as by the declaration `static const List<Name> values = [id1, …, idn];`
where `id1`…`idn` are the names of the enum entries of the `enum` declaration in source/index order.
If `Name` is generic, the `List<Name>` instantiates `Name` to its bounds.

The resulting constructor invocations are subject to type inference, using the empty context type. *This implies that inferred type arguments to the constructor invocation itself may depend on the types of the argument expressions of `args`.* The type of the constant variable is the static type of the resulting constant object creation expression.

The objects created here are *not canonicalized* like other constant object creations. _(In practice, the index value is considered part of the object, so no two objects will have the same state.)_

- **Static `values` list**: A static constant variable named `values` is added as by the declaration `static const List<Name> values = [id1, …, idn];`
where `id1`…`idn` are the names of the enum entries of the `enum` declaration in source/index order.
_If `Name` is generic, the `List<Name>` instantiates `Name` to its bounds._

If the resulting class would have any naming conflicts, or other compile-time errors, the `enum` declaration is invalid and a compile-time error occurs. Such errors include, but are not limited to:

Expand All @@ -151,13 +152,14 @@ It’s currently a compile-time error for a class to implement, extend or mix-in

Because we want to allow interfaces and mixins that are intended to be applied to `enum` declarations, and therefore to assume `Enum` to be a superclass, we loosen that restriction to:

- It’s a compile-time error if a *non-abstract* class has `Enum` as a superinterface (directly or transitively) unless it is the corresponding class of an `enum` declaration.

- It's a compile-time error if a *non-abstract* class has `Enum` as a superinterface (directly or transitively) unless it is the corresponding class of an `enum` declaration.
- It is a compile-time error if a class implements, extends or mixes-in the class or interface introduced by an `enum` declaration. _(An enum class can’t be used as a mixin since it is not a `mixin` declaration and the class has a superclass other than `Object`, but we include “mixes-in” for completeness.)_
- It's a **compile-time error** if a `class` or `mixin` declaration has `Enum` as a superinterface and the interface of the declarations contains an instance member with the name `values`, whether declared or inherited. _If any concrete class implements this interface, it will be an `enum` declaration class, and then the `values` member would conflict with the static `values` constant getter that is automatically added to `enum` declaration classes. Such an instance `values` declaration is either useless or wrong, so we disallow it entirely._
- It's a compile-time error if a `class`, `mixin` or `enum` declaration has `Enum` as a superinterface, and it declares a non-abstract instance member named `index`. _That member would override the `index` getter inherited from `Enum`, and we currently do not allow that._

Those restrictions allows abstract classes (interfaces) which implements `Enum` in order to have the `int index;` getter member available, and it allows `mixin` declarations to use `Enum` as an `on` type because `mixin` declarations cannot be instantiated directly.

This restriction still ensure `enum` values are the only object instances which implements `Enum`, while making it valid to declare `abstract class MyInterface implements Enum` and `mixin MyMixin on Enum` for interfaces and mixins intended to be used in declaring `enum` classes.
The restrictions still ensure `enum` values are the only object instances which implements `Enum`, while making it valid to declare `abstract class MyInterface implements Enum` and `mixin MyMixin on Enum` for interfaces and mixins intended to be used in declaring `enum` classes. It's also impossible to override or prevent the instance `index` and static `values` members without causing a compile-time error. _Say implementing an interface with `Never get index;` as a member, then because it's not possible to override `int get index;` from `Enum`, the resulting class does not implement its interface and is a compile-time error._

## Formatting

Expand Down Expand Up @@ -231,6 +233,8 @@ class LogPriority extends Enum with LogriorityMixin implements Comparable<LogPri
}
```

where the `_$` represents a fresh name.

In practice, we may choose to have a subclass of `Enum` as the actual superclass of the `enum` class, rather than use `Enum` directly. We’ll have to make sure that it makes no difference wrt. which declarations are valid (at least outside of `dart:core`, and for `enum`s declared inside `dart:core` it’s our own responsibility to not conflict with names used by the enum implementation.)

## Summary
Expand All @@ -241,13 +245,13 @@ The enum values can call the declared constructors, or the default unnamed zero-

Enum instances are objects like any other object, and with this change they can implement interfaces and inherit members from mixins. The main difference between an `enum` declaration and a hand-written “equivalent class” using the enum pattern is that:

- The `enum` types implement `Enum`. The `Enum` type is otherwise sealed against instantiation, so no other objects than enum entries can implement it.
- The `enum` types implement `Enum`. The `Enum` type is otherwise sealed against instantiation, so no other *objects* than enum entries can implement it. Abstract classes and mixins can implement or extend `Enum`, but unless they are implemented or mixed into an `enum` declaration, no objects can implement that type.
- The `enum` types themselves are completely sealed. No other class can implement an `enum` type.

- Because of that, `enum` types support exhaustiveness checking in `switch` cases in the language _(meaning that flow-control can see that an exhaustive switch over enum values cannot pass through without executing at least one `case`, which can then affect variable promotion)_.
- The `EnumName.name` extension member works on `enum` values.

If the *restrictions* (the type is sealed, there is only a finite, enumerable number of instances, and the class implements `Enum`, so it must have an `int index` getter), are acceptable, there should no longer be any reason to *not* make your enum class a language-based `enum`.
If the *restrictions* (the type is sealed, there is only a finite, enumerable number of instances, and the class implements `Enum`, so it must have an `int index` getter and no `values` member), are acceptable, there should no longer be any reason to *not* make your enum-like class a language-based `enum`.

## Examples:

Expand Down Expand Up @@ -446,4 +450,5 @@ There is a chance that people will start using `enum` declarations to declare si
1.2, 2021-10-25: Tweak some wordings and ambiguities.
1.3, 2021-10-27: Add examples of potential errors in the corresponding class declaration.
1.4, 2021-10-28: Say that it's an error to refer to generative constructors, and make the `Enum` constructor public.
1.5, 2021-12-07: Say that `index` and `toString` are inherited from the superclass, `values` is omitted if it would conflict. Rephrase specification in terms of defining a semantic class, not a syntactic one.
1.5, 2021-12-07: Say that `index` and `toString` are inherited from the superclass, `values` is omitted if it would conflict. Rephrase specification in terms of defining a semantic class, not a syntactic one.
1.6, 2022-01-27: Disallow overriding `index` or conflicting with `values`.