Skip to content
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

Allow type-checking type variables #459

Open
lrhn opened this issue Jul 19, 2019 · 13 comments
Open

Allow type-checking type variables #459

lrhn opened this issue Jul 19, 2019 · 13 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@lrhn
Copy link
Member

lrhn commented Jul 19, 2019

I often see people trying to check the type of a type variable by doing if (T is int) ....
This doesn't work, the expression T evaluates to a Type object which is not of type int.
The idea has merit, though.

We could perhaps special case typeVariable is type to actually check whether the type of typeVariable is a subtype of type. The expression is useless otherwise, so it's not like we will break any reasonable program, and users obviously find the syntax intuitive, since they keep using it.

@lrhn lrhn added the feature Proposed language feature that solves one or more problems label Jul 19, 2019
@leafpetersen
Copy link
Member

If we made the Type type generic, then you could write if (T is Type<int>) with no special casing. Not very intuitive though.

@simolus3
Copy link

simolus3 commented Feb 2, 2020

Making Type generic might be the less intuitive approach for subtype checking via is, but it opens up other possibilities. For instance, code generators often use a List<Type> somewhere in an annotation, but really need each type to be a subtype of some interface. Being able to use a List<Type<Interface>> could express this more accurately. It would also help users spot errors easier and could improve auto-complete (since only appropriate subtypes would be suggested).

This happens fairly often with annotation processors in Java, and I think it would be nice to have a similar feature in Dart. Speaking of similarities to Java, I'd like to add something to the Type<T> suggestion: For an expression e with a static type T, I think it's appropriate for e.runtimeType to have static type of Type<T> (similar to how getClass() behaves in Java).

@lrhn
Copy link
Member Author

lrhn commented Feb 3, 2020

One issue with making Type objects generic is that if we get "open type"/type deconstruction functionality, which would let you get the T of a List<T> as a type variable, then you would also be able to reify a Type<T> object's type as a type variable.
That's a significant cost to ahead-of-time compilers. They currently know that the only types which can occur as a type variable are the types that are introduced as a type argument. Only those types need to be retained at run-time in a way that supports being a real type.

If you can do doWith(Object x) { if (x.runtimeType is Type<var T>) { ... use T ... }} then all types need to be retained. That's a significant extra overhead.

@a14n
Copy link

a14n commented Feb 3, 2020

I often see people trying to check the type of a type variable by doing if (T is int) ....

We could also add a lint to prevent this case.

@a14n
Copy link

a14n commented Feb 3, 2020

We could also add a lint to prevent this case.

However if (T is num) could be valid of num, ìnt, and double. if (T == num) is only valid for num.

@simolus3
Copy link

simolus3 commented Feb 3, 2020

then all types need to be retained. That's a significant extra overhead.

@lrhn This would not be a problem if runtimeType returned a Type<dynamic>, right? Type literals could still have a parameterized static type, they have to evaluate to a real type either way for subtype checking to work. Not changing runtimeType behavior would also be less breaking, since it can be overridden.

@eernstg
Copy link
Member

eernstg commented Feb 3, 2020

Of course, we might as well mention that the test which is the topic of this issue can be expressed today, if we're willing to pay for an allocation of a list (or we could use some other generic class, if that's cheaper):

void foo<X>() {
  if (<X>[] is List<int>) {
    // Here it is guaranteed that `X <: int`.
  }
}

void main() {
  foo<String>();
}

But we should definitely try to find the time to get support for a generic Type, such that X is Type<T> iff X <: T.

@lrhn
Copy link
Member Author

lrhn commented Feb 3, 2020

I would assume that an instance of Type<dynamic> represents the type dynamic, so it would be surprising if 2.runtimeType returned Type<dynamic> instead of Type<int>.

If we actually have a Type<dynamic> representing the type int, then we also lose most of the advantage of having the type parameter on Type. There would be no way to trust the Type type parameter, so you can't actually use it for anything important.

It's also a detour and a waste of effort to turn a type parameter into a Type object in order to check what type it refers to. The Type object is unnecessary if we introduced a type ordering operator, say <: so you could just ask if (T <: num) .... No Type object in sight, and none needed. And we avoid having to worry about Type objects, which are generally useless anyway.

(Or we could decide that is can do double duty as both instance check and subtype check when the LHS is a type expression, and we are back at the originally proposed T is num).

@eernstg
Copy link
Member

eernstg commented Feb 3, 2020

Surely, 2.runtimeType should return the reification of int, and that object should have a type that implements Type<int> (this essentially means that it would have the type argument int at the type Type, e.g., because it's an instance of BuiltInVmType<int> and class BuiltInVmType<X> implements Type<X> {...}).

In other words, if we make Type generic then each reified type should definitely have a type argument which is maximally informative.

It's also a detour and a waste of effort

Agreed, but nothing stops a compiler from compiling if (X is Type<T>) ... into if (X <: T) ... where the subtype operator <: would be supported directly in the kernel language. So we shouldn't have to worry too much about the fact that X is Type<T> looks expensive.

@Levi-Lesches
Copy link

Pulling from #1971, given these:

class Constraint { int get temp => 0; }
void constrained<T extends Constraint>(T arg) {}

these two variations seem like they should work:

void foo1<T>(T arg) {
  if(T is Constraint) {  // always false because T is a Type
    print(arg.temp);
    constrained<T>(arg); 
  }
}

void foo2<T>(T arg) {
  if(arg is Constraint) {
    print(arg.temp);  /// valid, since `arg` is promoted to `Constraint`
    barConstrained<T>(arg);  /// error, since `T` is NOT promoted to `T extends Constraint`
  }
}

Seems this issue is about foo1, but it would also be nice if foo2 worked as well -- after all, the compiler has already promoted arg to be a Constraint, so by extension it should promote T as well.

@eernstg
Copy link
Member

eernstg commented Nov 12, 2021

We wouldn't be able to (soundly) promote T based on a type test on the value of arg: T could perfectly well be Object even though arg is a Constraint, and then we couldn't assume that T is Constraint (or a subtype of that) because we could have, say, other parameters of type T, and we haven't tested them. But we would be able to conclude that arg must have type Constraint (or a subtype) if we could test and promote T to Constraint: We know that arg is a T, and now we just now a bit more about the value of T.

So we'd really need the ability to express directly that we want to test and promote a type variable, and that's what this issue is about.

@om-ha
Copy link

om-ha commented Jul 31, 2022

Is it possible to check if a generic type implements an interface during runtime?

abstract class MyInterface {
  void someCapability();
}

class MyConcreteType implements MyInterface {
  @override
  void someCapability() {
    throw UnimplementedError('Coming soon!');
  }
}

void MyFlexibleFunction<T>(T param) {
  // This condition does not currently exist or compile successfully
  // But conveys what I am trying to achieve
  if (T implements MyInterface) {
    final MyInterface myInterface = param as MyInterface;
    myInterface.someCapability();
  }
}

@Levi-Lesches
Copy link

This proposal aside, this is currently possible with

if (param is MyInterface)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

7 participants