dart-lang / dart-lang/language
**UP** ignores certain function supertypes
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
Consider the following program:
```dart
void f1(int i, [bool b = false]) => print('f1: $i');
void f2(int i, {String s = ''}) => print('f2: $i');
var b = true;
void main() {
(b? f1 : f2)(4.66920161);
}
```
The invocation of the conditional expression in main is allowed according to [the specification](https://github.com/dart-lang/language/blob/master/resources/type-system/upper-lower-bounds.md#upper-bounds), because the **UP** of the two function types is `Function`, in spite of the fact that `void Function(int)` is a common supertype of the two operands.
The CFE (via `dart` from commit 497a331365164dd70decfd8cc166d99b284e8d23) follows the specification, accepts the program without reporting any compile-time errors, and throws at run time.
However, the analyzer rejects the program and reports an error mentioning that it has computed `void Function(int)` as the **UP** of the two types.
We could add an extra case to the rules defining **UP** in order to obtain the improved upper bound in this case:
- **UP**(`T0 Function(P00, ... P0k)`,
`T1 Function(P10, ... P1l, Named)`) =
`R0 Function(P20, ..., P2l)` if:
- None of the parameters in `Named` are required
- Each `B0i` and `B1i` are equal types (syntactically)
- `l` <= `k`
- `R0` is **UP**(`T0`, `T1`)
- `B2i` is `B0i`
- The type of `P2i` is **DOWN**(`typeof(P0i)`, `typeof(P1i)`), and `P2i` is a required positional parameter.
There would also be a symmetric rule. If we are willing to break the style for brevity, we could use this:
- **UP**(`T1 Function(P10, ... P1l, Named)`,
`T0 Function(P00, ... P0k)`) =
**UP**(`T0 Function(P00, ... P0k)`,
`T1 Function(P10, ... P1l, Named)`).
The new rules could be placed anywhere in the group of rules about **UP** on function types with explicitly specified parameter lists, because they are all mutually exclusive.
We could also introduce a small generalization to handle the following kind of situation:
```dart
void f1(int i, int j, [int k = 0, bool b = false]) => print('f1: $i');
void f2(int i, [int j = 0, int k = 0, String s = '']) => print('f2: $i');
var b = true;
void main() {
(b? f1 : f2)(1, 2, 3, throw 0);
(b? f1 : f2)(4, 5, 6);
}
```
In this case, the two function types have a common supertype which is `void Function(int, int, [int, Never])`, but both the CFE and the analyzer behave in a way which shows that they compute `Function` as the upper bound of `f1` and `f2`.
We could change the rule about function types with only positional parameters as follows:
- **UP**(`T0 Function(P00, ... P0k)`,
`T1 Function(P10, ... P1l)`) =
`R0 Function(P20, ..., P2q)` if:
- each `B0i` and `B1i` are equal types (syntactically)
- `q` is min(`k`, `l`)
- `R0` is **UP**(`T0`, `T1`)
- `B2i` is `B0i`
- The type of `P2i` is **DOWN**(`typeof(P0i)`, `typeof(P1i)`), and `P2i` is optional iff both `P0i` and `P1i` is optional.
We may wish to cut off remaining parameters starting with the first optional positional parameter whose type is `Never`. We don't have to pass these parameters, and each of the underlying functions will run just fine if we just don't pass anything. However, the current approach is to keep them, so we may also choose to keep them, just because it is in a narrow sense a breaking change to omit them.
I'd recommend that we adopt the first generalization, because the analyzer already supports it (and removing it from the analyzer would remove a compile-time error from certain call sites which are actually statically known to be type incorrect). For the second generalization, we don't have current behaviors to justify adopting it, but it would still have the same property: It would turn expressions of type `Function` into expressions that have a more informative function type, hence catching some type errors at compile time rather than at run time.
@natebosch, @leafpetersen, @munificent, @lrhn, @jakemac53, @stereotype441, WDYT?
Contributor guide
Research direction
Read the linked upper/lower bounds specification section and compare the CFE and analyzer behavior described in the issue. The issue names no repository files or tests; it is done when the proposed UP treatment is resolved and the specification and affected implementations are updated consistently.
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
- Needs clarification
- Newbie friendliness
- 25/100