dart-lang / dart-lang/language
Allow type promotion based on `runtimeType`
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
## Background
A common pattern in the Flutter framework is to define `operator ==` in terms of the `runtimeType` getter. Here is an example from the `OutlinedButtonThemeData` class:
```dart
class OutlinedButtonThemeData with Diagnosticable {
...
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
return other is OutlinedButtonThemeData && other.style == style;
}
}
```
(For more background on why `operator ==` checks `runtimeType`, see https://github.com/dart-lang/language/issues/2900).
It would be nice if the test `if (other.runtimeType != runtimeType)` were sufficient to promote the type of `other` to `OutlinedButtonThemeData`. Then the check `other is OutlinedButtonThemeData` would be unnecessary.
## Proposal
An equality expression of the form `E1.runtimeType == E2.runtimeType` (where `E1` and `E2` represent arbitrary expressions) should have the following effect on type promotion:
- If `E2` refers to a promotable entity `P` (a variable or promotable field), and the static type of `E1` is a subtype of the static type of `E2`, then in the code path where the equality expression evaluates to `true`, `P` is promoted to the static type of `E1`.
- If `E1` refers to a promotable entity `P`, and the static type of `E2` is a subtype of the static type of `E1`, _and_ the value of `P` _cannot be changed_ by the evaluation of `E2`, then in the code path where the equality expression evaluates to `true`, `P` is promoted to the static type of `E2`.
An equality expression of the form `E.runtimeType == T` or `T == E.runtimeType` (where `E` represents an arbitrary expression and `T` is a type literal expression) should have the following effect on type promotion:
- If `E` refers to a promotable entity `P`, and `T` is a subtype of the static type of `E`, `P` is promoted to `T`.
## Soundness
As of Dart 3.2, it is unsound to promote based on `runtimeType`, because any class is allowed to override the `runtimeType` getter without restrictions. For example:
```dart
class C {
@override
Type get runtimeType => int;
}
main() {
C c = C();
if (c.runtimeType == int) {
print(c.isEven); // ERROR: `c` is not an `int`, even though it claims to be
}
}
```
However, we could patch this soundness hole by doing the following:
1. Make it illegal for code outside the Dart SDK to override the `runtimeType` getter. This would be a breaking change to the langauge and would have to go through the [breaking change process](https://github.com/dart-lang/sdk/blob/main/docs/process/breaking-changes.md).
2. Ensure that all implementations of `runtimeType` in the Dart SDK have the property that `E.runtimeType` evaluates to a supertype of the actual runtime type of `E`. I believe this property is already satisfied, but it would be good to double check.
(Note that it's not necessary for `E.runtimeType` to evaluate to the exact runtime type of `E`, just to a supertype of it. This is important, because the implementations of `runtimeType` in the VM and web runtimes do not always return the exact runtime type. For example the VM treats all strings as having the runtime type `String`, even though there are multiple internal representations of strings that are implemented using different classes.)
Contributor guide
Research direction
Start by reading docs/process/breaking-changes.md and the linked language discussion. Audit the Dart SDK implementations of runtimeType and consider the VM and web runtime behavior mentioned in the issue. Done means the soundness conditions and breaking-change implications are resolved for the proposed promotion rules.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100