-
Notifications
You must be signed in to change notification settings - Fork 207
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
Testing for type parameter bounds #3837
Comments
Type variable promotion came up in a discussion about promoting When you do Let's just assume we introduce some way to test a type variable, strawman First, the declared type of a type variable declaration with name If the current static type of We also need to do something about promoted variables with a type of So let's include the bound we promoted from in the intersection type, so it's
And if we promote List<T> list = [];
if (T <: int) list.add(2); As usual, further promotion from (If we get contravariant type parameters, I said above that the scope was "on the true branch". There's a little more to flow analysis than that. So, for each type variable, we retain the chain of types it has had so far, starting at the original bound Example: List<T> foo<T extends Object>(T value) {
List<T> result;
if (T <: num) {
// Promotion chain `T extends Object`, `T extends num`
if (T <: int) {
// Promotion chain `T extends Object`, `T extends num`, `T extends int`
result = Uint32List(1)..[0] = value;
} else if (T <: double) {
// Promotion chain `T extends Object`, `T extends num`, `T extends double`
result = Float32List(1)..[0] = value;
} else {
throw ArgumentError.value(T, "T", "Must be concrete type if number");
}
// Promotion chain `T extends Object`, `T extends num` (intersection)
// and `T extends UP(int, double)` = `T extends num` was already there.
// T is still `num`-bounded.
}
// Back to `T extends Object`.
result ??= <T>[value];
return result;
} One thing that worries me a little is that there is no way to demote, other than leaving the scope of the promotion. A type variable with bound If I could do, say, If we start treating type variables more like variables than like anonymous types, then we might end up wanting to treat them more like variables! (But still, never ever convert a |
That's a lot to think about, I'll take some time to answer more things as I fully comprehend them. Just one thought that occurred to me, about what you said
This could be thought about creating something along the lines of: void fn<T>() {
if <T2 extends T>(T <: S) {
// In here we would have `T2` as a new type variable that means the above.
}
} The syntax is similar to the rest and we could think about allowing this only inside if blocks (maybe switch as well?) and not in for/while blocks since at least for me it doesn't make much sense. |
Cool stuff! Here's a fun consequence of promoting a type variable: // --- Library 'looks_innocent.dart'.
extension E on A<num> {
void bar() => print('In E.bar!');
}
// --- Library 'my_program.dart'.
import 'looks_innocent.dart';
class A<X> {
void foo() {
if (X <: num) {
bar();
}
}
} The ability to call It's probably not much harder to keep track of this than a number of other situations in Dart, but it does give rise to some new causal connections. It is also worth noting that we might want to get even more control: List<T> foo<T extends Object>(T value) {
List<T> result;
if (T <: int) {
result = (Uint32List(1)..[0] = value) as List<T>;
}
result ??= <T>[value];
return result;
} The cast to |
Couldn't we do something like (consider List<T> foo<T extends Object>(T value) {
List<T> result;
if (T <: int && T > Never) { // T is subtype of int but can't be Never
result = Uint32List(1)..[0] = value;
}
result ??= <T>[value];
return result;
} |
What you want is probably to ask whether Checking But |
One more thing. This would also help in "breaking up" type variables into "new" (inner) ones. E.g.: void foo<T extends Object?>(T obj) {
if <E extends Object?>(T <: Iterable<E>) {
// In here we know that `T` is an `Iterable` and we can work with `E` as the type for all elements in it.
}
} Today we can almost do this. There are some limitations but: extension Ext<E extends Object?> on Iterable<E> {
// Here we have a way to work with the type `E`
} If we use the above extension, testing that |
It would be much more convenient to define the predicate |
@lrhn wrote:
That wouldn't suffice in examples like the one I mentioned: We're returning a ( So we'd need to handle "OK, no big deal?" Well, I considered that to be a separate feature. In a context where we are talking about "promotion" of type variables: We don't have anything similar for promotion of value variables, which will establish that the value of a promotable variable has a type which is a supertype of anything, we can only establish that it is a subtype. So if we only support promotion of type variables then we'd only support "No big deal again?" Suppose we allow type variable "promotions" to be established using a more general form This is all sound and fine, but it is not obvious to me that we can easily characterize the exact set of conclusions that we can draw from a general test of the form I think it's at least worth considering a simple model where we only support "promotions" of type variables in very specific ways. For example, only the left operand is "promoted" (so it must be a type variable), and we can test lower and upper bounds, as in |
@tatumizer wrote:
How would that be useful in the more general case As far as I can see, the Dart type system just doesn't have a way to express this kind of knowledge. We may know that a type variable has a specific upper bound, but we never know that it doesn't satisfy that upper bound by having the value If it is not about the typing but only about what to do next then we can test if (X <: int) {
if (X == Never) throw "Impossible type encountered!";
// `Never != X <: int` is now known to us,
// but the type system only knows `X <: int`.
} |
@FMorschel wrote:
Right, that's the "existential open" operation, which can also be seen as an application of type patterns (#170). But the ability to introduce fresh type variables and bind them during a type test is a feature in its own right. |
What was the reason for NOT introducing a type parameter for Type? (I remember some discussions years ago, but I don't remember the rationale). |
One argument against a generic Also , |
We should be acutely aware of issues like less effective tree shaking, but I do not believe it is known to be an unavoidable (or even likely) consequence of adding a type argument to |
Is this worth worrying about? If the type is Never, the program will most likely throw anyway. And how the proposed static extensions are supposed to work if
The introduction of the generic Type feature doesn't automatically imply that |
Also related: #4219 |
Context
As I've commented on dart-lang/sdk#56028.
Today this code has only "bad" solutions (the basic ways I could think of were not able to solve this and I could not think of another solution than the following), one which was proposed later on that same issue involves calling a helper function with
(fn as Function)<T>(...)
. This is doable by the programmer but it's easier to get it wrong.From @eernstg in dart-lang/sdk#56028:
Request
I'd like to request/propose that type arguments could be tested for specific bounds.
That would not only return a
true
orfalse
for the test expression, but it would also consider that in the inner context.That would make it so that in this context:
This would be more of a "help" for the language to type-solve harder contexts.
The text was updated successfully, but these errors were encountered: