-
Notifications
You must be signed in to change notification settings - Fork 224
Description
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:
If
p
has a type argument, thenE
is the type argument.Else if
p
has no elements thenE
is_
.Else, infer the type schema from the elements:
Let
es
be an empty list of type schemas.For each element
e
inp
:
- If
e
is a matching rest element with subpatterns
and the context type schema ofs
is anIterable<T>
for some type schemaT
, then addT
toes
....
From the Type checking and pattern required type section:
To type check a pattern
p
being matched against a value of typeM
:...
List:
Calculate the value's element type
E
:
If
p
has a type argumentT
, thenE
is the typeT
.Else if
M
implementsList<T>
for someT
thenE
isT
.Else if
M
isdynamic
thenE
isdynamic
.Else
E
isObject?
....
Map:
Calculate the value's entry key type
K
and value typeV
, and key contextC
:
If
p
has type arguments<K, V>
for someK
andV
then use those, andC
isK
.Else if
M
implementsMap<K, V>
for someK
andV
then use those, andC
isK
.Else if
M
isdynamic
thenK
andV
aredynamic
andC
is_
.Else
K
andV
areObject?
andC
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:
- 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 matchList<T>
, whereT = int
.) - If the type being matched is a
FutureOr<T>
type, andT
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 matchList<T>
for anyT
.) - 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 anextends
clause (at the site of the type parameter's declaration) and bounds introduced by type promotion. (E.g. the analyzer considersU&List<int>
to matchList<T>
, whereT = 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:
- 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 ofList<T>
for anyT
). - 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 ofList<T>
for anyT
). - 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 ofList<T>
, whereT = 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).