dart-lang / dart-lang/language

Consider pushing implicit conversions down

Open
#2,129 6 comments 5 reactions 0 assignees View on GitHub
Dominant language
TeX
Stars
2.9k
Forks
239
Avg merge
2d 18h
Merged PRs (30d)
14

Description

The Dart language currently supports the following kinds of implicit conversions:
- Conversion from an integer literal to an equivalent double literal
- Conversion from `dynamic` to some other type via an implicit cast
- Conversion from an interface type containing a `.call` method to a function type, by implicitly tearing off `.call`

The first conversion only happens on integer literals, and is based on the context type. The other two conversions are currently only performed at sites where the spec demands assignability, and is based on the type being assigned to. The language team is considering changing the behavior so that all implicit conversions are based on context type, and happen at the innermost expression where they're applicable, regardless of assignability constraints. (We would retain the restriction that int-to-double conversion only happens to literals).

Here are some examples to illustrate the user-observable consequences of this change (changes are in **bold**). (Assume `F` is a function type, `f` is an expression of type `F` , `fq` is an expression of type `F?`, `C` is a callable class with a `foo` method of type `void Function()` and a call method of type `F`, `c` is an expression of type `C`, `cq` is an expression of type `C?`, and `d` is an expression of type `dynamic`. Also assume there's a declaration of `Object o = f;` in scope, with declared type `Object` and promoted type `F`):
- (1) `F x = b ? c : f;` will be interpreted as `F x = b ? c.call : f;`, which **will work**. Today it's a compile time error because `b ? c : f` has static type `Object`, which is not assignable to `F`.
- (2) `F x = b ? c : d;` will be interpreted as `F x = b ? c.call : (d as F)`, which **will work**, unless `b` is `false` and `d`'s runtime type is not `F`. Today this code is accepted, because `b ? c : d` has static type `dynamic`, but it fails at runtime if `b` is `true`, because there is no implicit tearoff of `call`.
- (3) `F x = b ? "x" : d;` *won't change*. Today it's interpreted as `F x = (b ? "x" : d) as F;`. This will change to `F x = b ? ("x" as F) : (d as F);`, which is indistinguishable.
- (4) `o = c;` will be interpreted as `o = c.call;`, **retaining the promotion on `o`**. Today this code is accepted, but there is no implicit tearoff of `call`, so it demotes `o` to `Object`, which is probably not what the user wants.
- (5) `o = d;` will be interpreted as `o = (d as F);`, which **retains the promotion on `o`, but fails at runtime if `d`'s runtime type is not `F`**. Today this code is interpreted as `o = (d as Object);`, which demotes `o`.
- (6) `F x = cq ?? f;` *won't change*. This code is rejected because we don't imply implicit `.call` tearoffs to nullable expressions, so `cq ?? f` has type `Object`, which is not assignable to `F`.
- (7) `var x = cq ?? f;` *wont' change*. This code is accepted and interpreted as `Object x = cq ?? f;` (no downcasts or implicit tearoffs).
- (8) `var x = cq ?? d;` will be interpreted as `C? x = cq ?? (d as C?)`, which **has a more specific type, but will fail at runtime if `cq` is `null` and `d`'s runtime type is not `C?`**. Today this code is interpreted as `dynamic x = cq ?? d;`, which never fails but has a less specific type.
- (9) `var x = fq ?? d;` will be interpreted as `F? x = fq ?? (d as F?)`, which **has a more specific type, but will fail at runtime if `fq` is `null` and `d`'s runtime type is not `F?`**. Today this code is interpreted as `dynamic x = fq ?? d;`, which never fails but has a less specific type.
- (10) `F x = fq ?? c;` will be interpreted as `F x = fq ?? c.call;`, which **will work**. Today it's a compile time error because `fq ?? c` has static type `Object`, which is not assignable to `F`.
- (11) `var x = fq ?? c` will be interpreted as `F x = fq ?? c.call;`, which **is more useful**. Today it's accepted and interpreted as `Object x = fq ?? c`, which doesn't fail, but also leaves `x` in a state where it's hard for the user to do anything useful with it, because at runtime it might either be an instance of `C` or a function.
- (12) `F x = c..foo();` **will be a compile-time error** because it's interpreted as `F x = c.call..foo();`, and `c.call` has type `F`, which has no method `foo`. Today it's interpreted as `F x = (c..foo()).call;`, which works.
- (13) `F x = (c);` *won't change*. Today it's interpreted as `F x = (c).call;`. This will change to `F x = (c.call);`, which is indistinguishable.

(Thanks @lrhn for working through most of these examples).

A rough summary of this might be:
- For the conditional operator (cases (1) through (3)) it's a clear win: code that leads to compile-time and runtime errors today will "just work".
- For assignments to local variables (cases (4) and (5)) it's probably a win; I suspect that retaining the promotion is more likely to be what the user wants in most circumstances. But it's not as clear cut, since the downside is a tighter downcast for (5), which might lead to a runtime failure if it's not what the user intended.
- For the null-aware operator (cases (6) through (11)) it's also not 100% clear whether it's an improvement. Cases (10) and (11) seem like clear wins, but cases (8) and (9) could lead to runtime failures. Personally I think it's still a worthwhile tradeoff, because even though (8) and (9) could lead to runtime failures, they also lead to the variable `x` having a more specific type, which should help reduce dynamism (and catch errors at compile time) in the code that follows.
- For cascades (case (12)) it's a clear loss of functionality, although IMHO it's probably an acceptable loss, since I think doing this kind of thing is rare, and there's an easy workaround (the user can just explicitly write `F x = (c..foo()).call;`).

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.