5

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.

Gilgamesh
  • 197
  • 2
  • 13
  • 1
    Try the approach from https://stackoverflow.com/q/66240962/. For example, `1 is T` and `null is T` can tell you if `T` is `int`, `int?` or something else. – jamesdlin May 08 '21 at 12:05
  • @jamesdlin That won't work. `1 is T` and `1.0 is T` or `double.maxFinite is T`, all of them are `true` for `Foo` or `Foo`. – Gilgamesh May 08 '21 at 13:15
  • 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. – jamesdlin May 08 '21 at 15:03

2 Answers2

10

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
}
jamesdlin
  • 81,374
  • 13
  • 159
  • 204
  • 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. – lrn May 09 '21 at 09:54
  • 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`. – jamesdlin May 10 '21 at 04:35
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.

lrn
  • 64,680
  • 7
  • 105
  • 121
  • 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;` safe? Or even better, as types are constant, something like this: `bool idType() => 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. – J F Nov 02 '21 at 16:27
  • 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. – lrn Nov 02 '21 at 20:46
  • this is nice. i feel it was so hacky to compare generic nullable type using toString before. thank you. – axunic Nov 16 '21 at 07:46