-
Notifications
You must be signed in to change notification settings - Fork 219
Description
In my work on flow analysis for patterns, I've made the decision that any pattern match with a required type should promote the type of the scrutinee. That means that, for instance, the following pattern match promotes x
:
num x = ...;
switch (x) {
case int():
x.isEven; // OK: `x` has been promoted to `int`, and `int` supports `.isEven`
}
And so does this:
num x = ...;
switch (x) {
case int y:
x.isEven; // also OK
}
To be consistent, I've made pattern variable declarations and pattern assignments behave the same way. It's only observable when the scrutinee is dynamic
, because otherwise the assignment would be invalid. So, for example:
dynamic x = ...;
var (int y) = x; // OK; `x` is downcast to `int`
x.foo(); // ERROR: `x` has been promoted to `int`, and there is no such method `int.foo`
and:
dynamic x = ...;
int y;
(y) = x; // OK; `x` is downcast to `int`
x.foo(); // ERROR: `x` has been promoted to `int`, and there is no such method `int.foo`
However, this feels a little weird, because we don't promote the RHS of ordinary assignments and variable declarations, e.g.:
dynamic x = ...;
int y = x; // OK; `x` is downcast to `int`
x.foo(); // OK; the call to `foo` is dynamically dispatched
and:
dynamic x = ...;
int y;
y = x; // OK; `x` is downcast to `int`
x.foo(); // OK; the call to `foo` is dynamically dispatched
A related problem is that with ordinary assignment expressions, a coercion such as an implicit tearoff of .call
affects the value of the assignment expression, e.g.:
class C {
void call() {}
void m() {}
}
f(C c) {
void Function() g;
var x = g = c; // `g = c` is interpreted as `g = c.call`, so `x` has inferred type `void Function()`
x.m(); // ERROR: type `void Function()` has no such method `m`
}
But for pattern matches it doesn't really make sense to apply coercions to the right hand side of the assignment, e.g.:
class C {
void call() {}
void m() {}
}
f((C, int) x) {
void Function() g;
var y = (g, _) = x;
// tearoff should probably happen as part of the subpattern match of `g`, so `y` should get inferred type
// `(C, int)`, and thus this should be valid:
y.$1.m();
}
In which case how do we want this code to behave?
class C {
void call() {}
void m() {}
}
f(C c) {
void Function() g;
var x = (g) = c;
x.m(); // ???
}
For consistency with non-pattern assignments, it would make sense to have an error (because the value of (g) = c
is the coerced value); for consistency with pattern assignments, it would make sense to have no error (because the coercion happened as part of the subpattern match of g
).
Personally, my preference is to say that all pattern assignments and pattern variable declarations should follow the new rules for patterns, and we shouldn't worry about these subtle inconsistencies with old non-pattern behaviour. (In fact, in the long term, I would love to change the behaviour of old-style assignments and variable declarations to match the new pattern behaviour, but that's another story). In which case, in (g) = c
, the coercion would happen as part of the subpattern match of g
, and therefore x
would have type C
, so x.m()
would be valid.
But I would love to hear other people's thoughts.
CC @dart-lang/language-team