-
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
Type patterns #4215
Comments
Could you explain the advantages and disadvantages of this proposal compared to #170? |
I think the 2 proposals are just 2 different syntactic forms for implementing a single feature: the ability to extract type arguments as Type objects. Today, we can say |
I agree that this is basically the same feature as #170, with a different syntax for binding, and without the union types. Both are barely more useful than These type patterns allow capturing the type argument in a type variable, which can be used as a type argument, or on an void copyAllowed(
List<<E>> target, List<Object?> source) {
for (var o in source) {
if (o is E) target.add(o);
}
} |
I think the original syntax with |
"Type patterns" were considered as part of the patterns feature, but didn't make it.
Proposal
A "type pattern" is a type where a type argument is not always a plain type. It can also be a binding declaration, or a constrained binding declaration, which captures the type of the actual type argument when used to match against a type or an object. (See: "Existential open".)
Strawman syntax is that
<X>
or<X extends T>
occurring where a type argument is expected will be a binding type pattern.Example:
if (value case Map<<K>, <V>>) { ... use<K, V>... }
.(Another possible syntax is
final X
/final X extends T
, making itvalue case Map<final K, final V>: ...
. It just needs to be distinguishable from a type and include a type variable and bound, and the<X extends T>
is how we usually introduce a type variable.)A type pattern of
<X>
matches any type, and is irrefutable.A type pattern of
<X extends B>
matches any subtype ofB
, just like the non-binding type pattern (aka. type)B
.(That is
case Foo<Bar>()
andcase Foo<<X extends Bar>>():
matches the same types and objects, one just binds the type argument to a variable, just likeint()
andint x;
matches the same objects, one just binds that value to a variable.)Type patterns can be used in patterns, both refutable and declaration patterns. In the latter, they must be irrefutable (so no bounds that aren't guaranteed to be satisfied by the matched value type, which basically means no useful bounds).
Type patterns do require a feature that Dart doesn't have today: "|Existential Open", the ability to go from an instantiated generic type (including the runtime type of an object) to a type variable bound to its type argument.
This is a useful feature, but it requires actually retaining the type information at runtime. So does any reference to the type variable inside the class itself, so it's not new, it's just possible to add uses on the side, so a class can't convince itself that it never uses its type arguments for anything at runtime, and expect them to be tree-shaken. Someone else might extract the type argument.
Type patterns can only be used to extract type arguments.
It's deliberately not allowed to use a type pattern for an entire value, like
switch (value) { case <T> v: ... }
which should be matching against the runtime type ofvalue
. This is disallowed precisely so that the newly introduced type variables can never be bound to runtime decided type that wasn't passed as a type argument in at least one place in the program.It also means it shouldn't leak implementation details. The runtime type of a native
<int>[]
is_GrowableList<int>
. Since a type pattern has to beList<<T>>
, you can only ask for type parameters of interfaces that you can name. It's restricted to the public interfaces of the API, private types are not leaked unless they are visible in the interface.Potentially, we could allow destructuring function types, like
case <R> Function(<P>): ...
. That may cause more types to be accessible at runtime and be bad for tree-shaking. It would be useful. (But structural types are not trivial, allowing(<T1>, <T2>)
to match a record is the same as matching a value's runtime type. Functions might be different, because it's not the value's type. Or at least it's not any value.)Other uses
If Dart has type patterns, they could be used in some places where you'd currently have to use a type variable, in some places where you can't use a type variable, and maybe just make some things a little shorter.
Having a
List<<T>>
pattern is basically the same as having aList<T>
pattern with a<T>
declaration on the side,like what we do for
void foo<T>(List<T> arg) { ... } /*...*/ foo(arg);
, except that type arguments are statically resolved, and type patterns are runtime destructuring/opening the runtime type.We could allow type patterns in
extension
declaration'son
type:It could also be used in
catch
clauses,on GenericError<<X>>
, but errors are rarely generic.The text was updated successfully, but these errors were encountered: