Home > Blockchain >  Why aren't constructor and static function tear-offs, with extending type parameters, identical
Why aren't constructor and static function tear-offs, with extending type parameters, identical

Time:07-03

Consider the following class:

class MyClass<T extends num> {
  const MyClass();

  void instanceFunction() {}

  static void staticFunction<T extends num>() {}
}

The following expressions are all true:

identical(const MyClass(), const MyClass<num>());
identical(const MyClass.new(), const MyClass<num>.new());
identical(const MyClass().instanceFunction, const MyClass<num>().instanceFunction);

identical(MyClass.new, MyClass.new);
identical(MyClass<num>.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction);
identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>);

But when the base type, num, is included in one tear-off, such as in the expressions below, they are false:

identical(MyClass.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction<num>);

Why is this the case?

CodePudding user response:

The true cases:

identical(const MyClass(), const MyClass<num>());

You instantiate an MyClass object. Since MyClass is declared with MyClass<T extends num>, MyClass() with no explicit type specified is shorthand for MyClass<num>(). This expression therefore is the same as identical(const MyClass<num>, const MyClass<num>), which should obviously be true.

identical(const MyClass.new(), const MyClass<num>.new());

This is just a more roundabout version of the previous case.

identical(const MyClass().instanceFunction, const MyClass<num>().instanceFunction);

This is true for the same reason as the first case. const MyClass() is shorthand for const MyClass<num>(), const instances are canonicalized, so you're comparing .instanceFunction members of the exact same object.

identical(MyClass.new, MyClass.new);

Both arguments are the same and both are statically-known at compile-time, so there's no reason for this to be false.

identical(MyClass<num>.new, MyClass<num>.new);

This would be true for the same reason as the previous case.

identical(MyClass.staticFunction, MyClass.staticFunction);`
identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>);

Both arguments to identical are the same, so that these evaluate to true shouldn't be surprising.

The false cases:

identical(MyClass.new, MyClass<num>.new);
identical(MyClass.staticFunction, MyClass.staticFunction<num>);

MyClass.new and MyClass.staticFunction are not shorthand for MyClass<num>.new and MyClass.staticFunction<num> respectively; they evaluate to generic functions:

var c1 = MyClass.new;
var c2 = MyClass<num>.new;

var f1 = MyClass.staticFunction;
var f2 = MyClass.staticFunction<num>;

then the type of c1 is MyClass<T> Function<T extends num>() and the type of c2 is MyClass<num> Function(). Similarly, the type of f1 is void Function<T extends num>() but the type of f2 is void Function(). c1 and f1 are still generic; you can do c1<num>() or f2<double>() later.

c2 and f2 have the type parameter statically bound already, so they have a different type and clearly should not be the same as c1 and f1 respectively.

A more interesting case is:

identical(f1<num>, MyClass.staticFunction<num>))

which evaluates to false. Why isn't this the same as the identical(MyClass.staticFunction<num>, MyClass.staticFunction<num>) case? I'm not sure exactly why, but I would presume that it's an implementation detail from the Dart compiler being able to trivially canonicalize MyClass.staticFunction<num> at compilation-time but not f1<num> due to f1 being potentially variable.

  • Related