Dart: how to determine nullable generic type at runtime?
Asked Answered
E

2

7

I need to determine if a generic type is a String, bool, int, double or another class at runtime. I didn't found a way to do it for nullable types:

class Foo<T> {  
  void foo() {
    if (T == int) {
      print("'T' is an int");
    } else {
      print("'T' is not an int");
    }
  }
}

void main() {
  final foo = Foo<int>();
  final bar = Foo<int?>();
  foo.foo();
  bar.foo();
}

console output:

// 'T' is an int
// 'T' is not an int

Is there any syntax I'm unaware of to check for the nullable type?, I've already tried with int? but it doesn't compile.

Eyre answered 8/5, 2021 at 9:35 Comment(3)
Try the approach from https://mcmap.net/q/612579/-how-do-i-check-whether-a-generic-type-is-nullable-in-dart-nnbd. For example, 1 is T and null is T can tell you if T is int, int? or something else.Conditional
@Conditional That won't work. 1 is T and 1.0 is T or double.maxFinite is T, all of them are true for Foo<int?> or Foo<double?>.Eyre
It does work, but you're not trying hard enough.=P Something like 1.5 is T isn't true for both int? and double? (and your cases are all true only for Dart for the web, not for the Dart VM). I've added an answer with a more concrete example.Conditional
C
13

Here is a more concrete example, based on the approach from How do I check whether a generic type is nullable in Dart NNBD?.

Note that when transpiling to JavaScript, all numbers are IEEE-754 double-precision floating-point values, so to distinguish between Dart double/double? and int/int?, we must first check with a floating-point literal that cannot be an int.

void foo<T>() {
  if (1.5 is T) {
    if (null is T) {
      print('double?');
    } else {
      print('double');
    }
  } else if (1 is T) {
    if (null is T) {
      print('int?');
    } else {
      print('int');
    }
  } else {
    print('something else');
  }
}

void main() {
  foo<int?>();    // Prints: int?
  foo<int>();     // Prints: int
  foo<double?>(); // Prints: double?
  foo<double>();  // Prints: double
  foo<bool>();    // Prints: something else
}

Note that the above approach won't work for void or Null. Null could be handled by checking T == Null, but T == void isn't valid syntax (similar to T == int?). You can work around that by making them type parameters to a generic function that does the comparison, so another approach is:

/// Returns true if T1 and T2 are identical types.
///
/// This will be false if one type is a derived type of the other.
bool typesEqual<T1, T2>() => T1 == T2;

void foo<T>() {
  if (typesEqual<T, void>()) {
    print('void');
  } else if (typesEqual<T, Null>()) {
    print('Null');
  } else if (typesEqual<T, int>()) {
    print('int');
  } else if (typesEqual<T, int?>()) {
    print('int?');
  } else if (typesEqual<T, double>()) {
    print('double');
  } else if (typesEqual<T, double?>()) {
    print('double?');
  } else {
    print('something else');
  }
}

void main() {
  foo<int?>();    // Prints: int?
  foo<int>();     // Prints: int
  foo<double?>(); // Prints: double?
  foo<double>();  // Prints: double
  foo<void>();    // Prints: void
  foo<Null>();    // Prints: Null
  foo<bool>();    // Prints: something else
}
Conditional answered 8/5, 2021 at 15:1 Comment(2)
The first code would print double for num. The typesEqual relies on Type.==, which is valid, if somewhat restricted, but I'd personally prefer to avoid Type objects entirely.Personalty
True. The first approach could handle num by checking 1 is T && 1.5 is T (and it could handle Object by Object() is T), but I suppose it's ultimately hopeless since there'd be no way to identify dynamic.Conditional
P
1

I recommend avoiding using Type objects for anything serious (other than printing or dart:mirrors).

You can create functions to check whether two types, provided as type arguments, are equivalent. Here are some examples:

/// Whether two types are equivalent.
///
/// The types are equivalent if they are mutual subtypes.
bool equivalentTypes<S, T>() {
  return _Helper<S Function(S)>() is _Helper<T Function(T)>; 
}
class _Helper<T> {}

// Or alternatively:
bool equivalentTypes2<S, T>() {
  S func(S value) => value;
  return func is T Function(T);
}

/// Whether two types are the same type.
///
/// Uses the same definition as the language specification for when
/// two types are the same.
/// Currently the same as mutual subtyping.
bool sameTypes<S, T>() {
  void func<X extends S>() {}
  // Spec says this is only true if S and T are "the same type".
  return func is void Function<X extends T>();
}

void main() {
  print(equivalentTypes<int, int>());
  print(equivalentTypes<int?, int?>());
  print(equivalentTypes<int?, int>());

  print(equivalentTypes2<int, int>());
  print(equivalentTypes2<int?, int?>());
  print(equivalentTypes2<int?, int>());

  print(sameTypes<int, int>());
  print(sameTypes<int?, int?>());
  print(sameTypes<int?, int>());
}

The language only has one operator for comparing a type to anything, the is operator, which compares an object to a type. That's why all the functions here create an object of a know type depending on S and check it against a type depending on T.

Personalty answered 9/5, 2021 at 10:17 Comment(3)
As now types can be used even in the switch statement, I would like to know if you still discourage the usage of type in a simpler way. I mean, isn't a simple function like bool eqType<T, V>() => T == V; safe? Or even better, as types are constant, something like this: bool idType<T, V>() => identical(T, V); is safe? In my test both these functions work, but I don't know if there is some subtlety that I'm missing.Quita
Comparing types for equality works, but it's not particularly clear to me what it means. When do you need to know that two types are the same type? Consider the problem you are trying to solve, and my guess is that 99 times out of 100, comparing types for equality is not the best solution to that problem. It's a usually a sign of someone trying to make a shortcut to a solution, rather than doing the proper general solution.Personalty
this is nice. i feel it was so hacky to compare generic nullable type using toString before. thank you.Saharan

© 2022 - 2024 — McMap. All rights reserved.