Allow `<void>[e]` and `<void, void>{e: e}` where `e` has type `void`? Records, too?
See https://github.com/dart-lang/sdk/issues/58456 for background discussions.
The current set of rules about errors reported for expressions of type void does not cover the elements of collection literals. For example <void>[e] is an error when e has static type void, and so is <void, void>{e: e}.
However, the implementations currently do not report this error. Presumably, said elements are allowed because they are somewhat similar to actual arguments, and it is allowed to pass an actual argument of type void to a formal parameter whose declared type is also void.
It would be a breaking change to start reporting these errors.
@dart-lang/language-team, WDYT? Should we add void-typed collection elements to the allowlist, or should we start reporting these errors?
Who will we be helping by allowing this?
I'd rather properly disallow it, so people who are putting bad or meaningless values into collections will stop doing that. And maybe notice that their code makes no sense.
For example: I was writing StreamBuilder<void>, because I had to make part of the widget tree reactive, but I did not want to use the value, not even ==, hashCode or toString. I guess I'm not the first person to think about that, because GitHub found 466 files. ~~If any generic widget creates a generic list <T>[] under the hood, then the code will work. If this issue is not accepted, then the code wouldn't work, I guess.~~ (Answer below about generics)
If the class is generic, this issue has no impact.
A type variable is not statically the void type.
Decision is to allow the cases listed here, and disallow [?e], {?e}, {?e:_} and {_:?e} where e has type void.
The latter are also currently allowed, but do not match the allowed use of passing a value along without looking at it, beause it looks at the value to check if it's null.
Seems this still requires a change (bug fix) in implementation since the front-end makes {v: 0} and {0: v} errors, even if it then infers a key/value type of void. Analyzer does not.
// ignore_for_file: unused_local_variable
void main() {
const void v = null;
// Implicit types.
var implicitList = [v];
var implicitSet = {v};
var implicitMapKey = {v: 0}..st<E<Map<void, int>>>(); // CFE error.
var implicitMapValue = {0: v}..st<E<Map<int, void>>>(); // CFE error.
// Explicit types
var explicitList = <void>[v];
var explicitSet = <void>{v};
var explicitMapKey = <void, int>{v: 0}..st<E<Map<void, int>>>();
var explicitMapValue = <int, void>{0: v}..st<E<Map<int, void>>>();
}
extension <T> on T {
void st<X extends E<T>>(){}
}
typedef E<T> = T Function(T);
That's inconsistent, and looks like a bug in the CFE. It probably rejects a void key/value based on the context type, not the map key/value type that is inferred later during upwards inference.
(It probably doesn't need to do two passes. If any key (or similarly for value) has type void, the LUB of the key types will be void, so it should always allow the void key if the literal has no type argument.)
May records be included in this issue? Example: if <void>[a, b] is valid or invalid, then the same behavior applies to (a, b).
Records are a little different from collections. They're more like "multiple independent values in a single chunk" than they are a collection of values. They're just for passing values around.
So they should probably allow any expression for a field that the context type allows, (int, void) v = (42, print("wat?")); could be valid because all it does is pass void to void.
(It would be nice if you couldn't assign an (int, void) to (int, Object?), as if every record assignment was a destructuring followed by restructuring so you briefly had a void-type value in a non-void context. Alas, not so.)