Skip to content

Where is void allowed in pattern matching? #2907

Description

@lrhn

We currently do not allow a void-typed expression to be used in a context where its value is used (only a context of type void or dynamic, and dynamic was only allowed to not have to rewrite legacy code).

We should consider whether void is allowed to flow into pattern matches.
We are already doing something on some platforms, but we should be consistent and deliberate about it.

I'd say "hard no", and disallow (aka. make compile-time error):

  • Any switch expression with static type void.
  • Any if-case matched value expression with static type void.
  • Any pattern being type-checked with matched value type void, unless the pattern is _ (or any other non-checking, non-binding pattern which also wouldn't need to look up the value of a list or map at all). That's needed for var (x, _, z) = o as (int, void, int);.

The first two are really just expressions-in-position-where-value-is-used, and could be covered by current behavior.
The last one is new, because there is no expression the original program which has static type void. It only exists in the desugared version. Still, the is a value with static type void which is pushed into a pattern match, and that pattern match actually looks at the value (it binds it to a new variable, one not typed as void).

We an allow a pattern to bind a void value to another variable of type void or dynamic, but you really shouldn't.
I'd prefer to make the void protection stronger here, since it's new syntax that doesn't have to accommodate existing code.

  • Also disallow using void as the type of a binding pattern, void v, to introduce a void-typed variable. Just use _, because you are not supposed to look at the value later anyway.

And

  • Disallow void as the static type of an object pattern. (Also if doing so through a type alias, but T(:var hashCode) is OK, even if T ends up bound to void at runtime. This is just static checking.)

Even though the object pattern void() is technically not looking at the object, it's also not useful, and you should just use _.
Doing void(:var runtimeType) is just plain wrong, you're invoking members on a void typed value. So

(Now that we have _, we no longer need to allow you to assign void values to anything. Or at least, we won't when we allow _ as a parameter name.)

Example code:

typedef OQ = Object?;

void main() {
  void v;
  
  switch (v) {
  // Error: This expression has type 'void' and can't be used.
  //   switch (v) {
  //           ^
    case OQ(:var hashCode): print(hashCode);
  }
  if (v case Object? o) print(o.hashCode);
  // Error: This expression has type 'void' and can't be used.
  //   if (v case Object? o) print(o.hashCode);
  //       ^
  
  if ((1, v) case (int _, Object? o)) { // No error
    print(o.hashCode);
  }

  if (null case void v) { // Safe, but unnecessary
    print(v as dynamic);
  }
  
  if (null case Void(:var hashCode)) { // Reads `.hashCode` of `void`
    print(hashCode);
  }
}

This was run in dartpad.dev on master branch.
Dart2js rejects two of the cases, but not the third.
The analyzer gives no warnings whatsoever. (Should probably be considered a bug.)

@dart-lang/language-team

Metadata

Metadata

Assignees

No one assigned

    Labels

    patternsIssues related to pattern matching.

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions