Skip to content

[patterns] Can we make type declarations and type tests look different? #2677

Closed
@eernstg

Description

@eernstg

I'm a little bit worried about patterns taking a step backwards, so to speak, in that they use a syntax which is otherwise declaring the type of a variable in a way that turns it into a dynamic type test. We're going to get a match failure rather than a dynamic error, but otherwise it's very similar to the implicit downcasts that we had a couple of years ago.

My main worries are (1) it is difficult to discern the actual semantics based on reading the source code, and (2) it's easy to get those type annotations wrong, and then they are just silently turned into type tests (this makes the construct similar to implicit downcasts, except that we're getting different/buggy control flow rather than getting an exception).

void main() {
  List<num> xs = [1, 2, 3];
  switch (xs) {
    case [Object o, num n, int i]: ...;
  }
}

The static type of xs is used to determine some elements of the list pattern (in particular, inferring the type argument num). The three variables are treated differently.

We might want to introduce a variable like o with a more general type because we might want to assign other values to o than the value that it gets via pattern matching. So it's not necessarily a mistake to give o the declared type Object, and we have to do it because the inferred type would otherwise be num, and then we couldn't assign those other values later on.

The variable n gets the type num that we would otherwise have gotten by inference. This may be useful for documentation purposes.

The variable i gets the specified type int, but pattern matching will now include a type test: The element at [2] which is known to be a num may or may not be an int as well, but we will test this at run time.

I think it would be helpful if we used a different syntax for the cases where the declared type is guaranteed to be satisfied by the matched value, and the cases where it may or may not hold, and there will be a type test at run time.

One possible approach could be to simply say that the version with a dynamic type test is not supported: If we declare a type which is not statically known to hold for the matched value then it's a compile-time error. The developer must then perform the dynamic type test in some other way (for instance, when i is int).

Another possibility would be to say that the type test can't be requested using the syntax int i (that's still an error), but you could use a different syntax, like var i is int. This would be similar to patterns using != null, and it would provide the promotion based on syntax which is already recognizable as such.

If we do this then num n would actually be more useful: It would serve as a contract between n and the surrounding source code: I'm expecting n to be able to have the type num, and I'd like to get a heads-up if this is not the case.

(Note that this wouldn't change anything for object patterns: We can still match an object using a dynamic type test for int by matching it with the object pattern int()—it is unsurprising that this kind of matching includes a type test, because the ability to handle different cases corresponding to a set of distinct subtypes of the scrutinee type is absolutely standard for all pattern matching mechanisms.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    patternsIssues related to pattern matching.questionFurther information is requested

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions