Skip to content
This repository has been archived by the owner on Feb 22, 2018. It is now read-only.

Reify type in "generic" methods #301

Closed
vsmenon opened this issue Aug 24, 2015 · 8 comments
Closed

Reify type in "generic" methods #301

vsmenon opened this issue Aug 24, 2015 · 8 comments

Comments

@vsmenon
Copy link
Contributor

vsmenon commented Aug 24, 2015

For methods we consider generic, we needs to instantiate the correct reified type.

E.g.,

var iter = <int>[1, 2, 3].map((int x) => x);
print(iter is Iterable<int>);
print(iter is Iterable<String>);

should print true then false.

An initial implementation will be based on whitelists/annotations of methods considered generic and inference at use sites.

See #300 for the static part and #28 for the general bug on generic methods.

@jmesserly
Copy link
Contributor

The example above should basically work once we update analyzer & our SDK
edit: we still need to implement codegen side of things. Currently we drop the generic type arg when we codegen the call.

Note however, there's another issue: reified tear offs:

var list = <int>[1, 2, 3];
var mapper = list.map/*<String>*/; // (int -> String) -> Iterable<String>
var genericMapper = list.map;      // <R>(int -> R) -> Iterable<R>

print(mapper((x) => '$x'));
// "int" would be inferred here, but assume we don't know that...
print(genericMapper/*<int>*/((x) => x + 42));

@jmesserly
Copy link
Contributor

An issue: generic types can subtype non-generic ones, e.g. <T>() -> T <: () -> Object. That makes the calling convention a bit tricky. Since the base method can be called without type parameters, we need to support two methods, like o.m(x, y) and o.m$(int)(x, y).

Also, a cleaner syntax like o.m.of(int)(x,y) would be problematic because of how this works in JS.

Two method names is the best I've been able to come up with, e.g.

class B {
  m(x, y) {
    // ...
  }
}

class D extends B {
  m$([T = dynamic]) {
    return (x, y) => {
      // ...
    };
  }
}
D.prototype.m = D.prototype.m$();

function main() {
  let d = new D();
  d.m$(core.int)(1, 2);
  d.m(1, 2);
}

It's not very satisfying, though.

We could symbolize:

// ...
class D extends B {
  [generic.m]([T = dynamic]) {
    return (x, y) => {
      // ...
    };
  }
}
D.prototype.m = D.prototype[generic.m]();

function main() {
  let d = new D();
  d[generic.m](core.int)(1, 2);
  d.m(1, 2);
}

Or use a string that is an illegal identifier in Dart (pretty much the same idea, but call should be faster by avoiding the symbol lookup):

// ...
class D extends B {
  ['m<>']([T = dynamic]) {
    return (x, y) => {
      // ...
    };
  }
}
D.prototype.m = D.prototype['m<>']();

function main() {
  let d = new D();
  d['m<>'](core.int)(1, 2);
  d.m(1, 2);
}

Oh, there's also unicode characters.

Can't say that any of these look great. Oh, and that's just for public members. Private ones need yet another scheme.

@jmesserly
Copy link
Contributor

jmesserly commented Feb 20, 2016

There's another family of solutions, where we try and pass the type parameters as optional parameters.

// ...
class D extends B {
  // note, this won't actually work, because what if "T" is
  // used later as a named parameter in a subtype.
  // But something along these lines could work.
  m(x, y, {T} = {T:dynamic}) => {
      // ...
  }
}

function main() {
  let d = new D();
  d.m(1, 2, {T: core.int});
  d.m(1, 2);
}

But that gets us into trouble with the already complex named & optional parameters.

@jmesserly
Copy link
Contributor

One thing to note, this problem won't come up for generic functions, or generic methods that don't override non-generic ones, or for other uses of universal function types:

/*=T*/ g/*<T>*/(/*=T*/ t) = t;
Func1 f = g; // implicitly g/*<dynamic>*/

In that case, there's an implicit instantiation. We can generate code to instantiate it.

I wonder if we'd be rejecting too much if we disallowed the generic-override-non-generic in strong mode. But those overrides do make sense in some cases, e.g.:

class B {
  Iterable map(f(x)); // (* -> *) -> Iterable<*>
}
class D extends B {
  Iterable<T> map<T>(T f(x)); // <T>(* -> T) -> Iterable<T>
}

@jmesserly
Copy link
Contributor

For now I'm going to punt on the override-non-generic-with-generic case, and try to handle all of the "normal" cases. Split that out into #459

@jmesserly jmesserly added P1 high and removed js labels Mar 2, 2016
@jmesserly
Copy link
Contributor

fyi, digging into this again.

@jmesserly
Copy link
Contributor

I split out a few issues (#525, #526, #527) that may not be in the first CL.

@jmesserly
Copy link
Contributor

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Development

No branches or pull requests

3 participants