Skip to content

Analyzer and front end have different (and dubious) interpretations of type tests in pattern spec #3586

@stereotype441

Description

@stereotype441

The patterns spec contains the following text (emphasis mine):

From the Pattern context type schema section:

The context type schema for a pattern p is:

...

  • List: A context type schema List<E> where:

    1. If p has a type argument, then E is the type argument.

    2. Else if p has no elements then E is _.

    3. Else, infer the type schema from the elements:

      1. Let es be an empty list of type schemas.

      2. For each element e in p:

        1. If e is a matching rest element with subpattern s and the context type schema of s is an Iterable<T> for some type schema T, then add T to es.

...

From the Type checking and pattern required type section:

To type check a pattern p being matched against a value of type M:

...

  • List:

    1. Calculate the value's element type E:

      1. If p has a type argument T, then E is the type T.

      2. Else if M implements List<T> for some T then E is T.

      3. Else if M is dynamic then E is dynamic.

      4. Else E is Object?.

...

  • Map:

    1. Calculate the value's entry key type K and value type V, and key context C:

      1. If p has type arguments <K, V> for some K and V then use those, and C is K.

      2. Else if M implements Map<K, V> for some K and V then use those, and C is K.

      3. Else if M is dynamic then K and V are dynamic and C is _.

      4. Else K and V are Object? and C is _.

...

All of the boldfaced tests are type tests, checking whether a given type (or type schema) matches an expected interface type, and if it does, extracting the type argument(s). As currently implemented, these tests all have the following behavioral subtleties in common:

  1. If the type being matched is a nullable type or a star type, then the analyzer and front end consider it to match. (E.g., List<int>? is considered to match List<T>, where T = int.)
  2. If the type being matched is a FutureOr<T> type, and T is considered to match, then the analyzer and front end do not consider it to match. (E.g., FutureOr<List<int>> is not considered to match List<T> for any T.)
  3. If the type being matched is a type parameter type (or a type parameter type followed by * or ?), and the type parameter's bound would have matched, then the analyzer considers it to match; the front end does not. This is the case both for bounds introduced by an extends clause (at the site of the type parameter's declaration) and bounds introduced by type promotion. (E.g. the analyzer considers U&List<int> to match List<T>, where T = int; the front end does not).

This is not what I would have expected, from a naive reading of the spec. I would have expected the matching to behave like a subtype check, so:

  1. If the type being matched is a nullable type or a star type, then it doesn't match (because e.g., List<int>? is not a subtype of List<T> for any T).
  2. If the type being matched is a FutureOr<T> type, then it doesn't match (because e.g., FutureOr<List<int>> is not a subtype of List<T> for any T).
  3. If the type being matched is a type parameter type (not followed by * or ?), and the type parameter's bound would have matched, then it matches (because e.g., U&List<int> is a subtype of List<T>, where T = int).

I suspect none of these behavioral differences were intentional, and I suspect that none of them matter in practice. As far as I've been able to tell so far, none of them are covered by any test cases.

I don't think we have a clear definition anywhere of what we mean in the spec by phrases like "is a Foo<T> for some T" and "implements Foo<T> for some T", so I would love to hear thoughts from @dart-lang/language-team: do you agree with the behavior I would have expected? If so, I would like to move ahead with a bug fix (and breaking change announcement).

Metadata

Metadata

Assignees

No one assigned

    Labels

    patternsIssues related to pattern matching.technical-debtDealing with a part of the language which needs clarification or adjustments

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions