-
Notifications
You must be signed in to change notification settings - Fork 205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[patterns] How do we understand the required type of a pattern? #2783
Comments
I'm going to merge #2784 into this issue, so copy/pasting that description here: Given: typedef Inv<T> = T Function(T); Consider: Object obj = ...
if (obj case Inv()) { ... } What does this mean? In particular, what type argument do we infer for If we think of it as a class test, it's almost like we treat it as: Object obj = ...
if (obj case Inv<var T>()) { ... } Almost as if the case itself is generic. But we don't actually have type patterns. If we allow this, then what happens if there is further code that expects us to now know a type, like: Object obj = ...
if (obj case Inv() && var x) { ... } Is there a type we can promote Or: Object obj = ...
if (obj case Inv(call: var y)) { ... } What is the type of @leafpetersen and I talked about this some and we're currently leaning towards making this an error: If a type in an object pattern has an invariant type parameter and the matched value type doesn't give us enough context to infer a type, then it's simply an error. You either need to use a more specific matched value type (i.e. not In other words, I think an object pattern should always match a type, and not a family of unrelated types. Because, otherwise, you can get yourself into a place further in where you do actually need it to be a type. |
I think we should insist that every list pattern and every map pattern has actual type arguments. In the case where they are written by the developer there is no issue. When they are not given in the source code they will be obtained from the matched value type (with the usual fallbacks: if the matched value type is This means that we will refrain from trying to handle a pattern like @munificent and @leafpetersen, I think this is fully compatible with the approach that you mentioned. |
SGTM. Do you have any thoughts on how this should be described in the proposal? This is a little outside of my wheelhouse. |
I'll create a PR. It might be possible to make it quite small. |
Done in #2884. |
An
<objectPattern>
is used to perform a kind of type test (along, optionally, with a set of<patternField>
s used to match the result of getter invocations on the matched object). The "head" of this construct must denote a type:We already have some discussions (such as #2770) about the case where that type is a generic class, and no actual type arguments have been specified. The feature spec continues as follows:
(In this setting,
M
is the matched value type andX
is the type which is denoted by the head of the pattern.)However, I do not think there is a straightforward interpretation of such patterns where this generic class actually has a specific type argument. In other words, we might not be justified in thinking that "we always have a type argument, but it might be invisible because it's provided by a type-inference-ish step".
In particular, we can have cases where no actual type argument in the pattern will yield something that we could consider to be the type of the pattern.
The feature spec uses the phrase required type to establish the final assignability check: When all the steps have been completed (e.g., in a declaration context: computing a pattern context type schema, inferring the type of the initializing expression, and type-checking/finalizing the pattern), it is required that the initializing expression is assignable to the required type of the pattern. For example:
The tricky case arises when the required type of a pattern must be a proper supertype of any type that actually describes the pattern, because the types that do describe the pattern do not have a common supertype with the same "shape":
We can't consider the object pattern
C()
to be the same thing asC<T>()
for any specificT
, because we would (presumably) want to accept aC<S>
for every possibleS
that satisfies the bounds ofC
(that is, all types), andC<T>
is not a common supertype of all such types, no matter which value we've chosen forT
.So we might have to consider
C()
to have an existential type like∃X. C<X>
, which would be a type which coversC<S>
for allS
, even though that set of types doesn't have a (sufficiently useful) common supertype in the current Dart subtype hierarchy.For the dynamic semantics, we'd want to perform a similar check (like a check against the existential type in the sense that it succeeds if there exists an
S
such that the matched object has typeC<S>
or a subtype thereof). As far as I can see, the semantics of Dart today does not include any such type test.We can encounter a similar situation already today with function types:
In this case we might want to say that
F()
can match a value of typeF<S>
for allS
, but the nearest common supertype of those types isFunction
.The program is accepted by the analyzer, but the CFE encounters an unhandled exception.
Stack trace
The following example uses a generic function, and the outcome is basically the same:
The text was updated successfully, but these errors were encountered: