Skip to content

Support generic function instantiation for callable objects? #1827

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

Closed
eernstg opened this issue Aug 30, 2021 · 2 comments
Closed

Support generic function instantiation for callable objects? #1827

eernstg opened this issue Aug 30, 2021 · 2 comments
Labels
question Further information is requested

Comments

@eernstg
Copy link
Member

eernstg commented Aug 30, 2021

The language specification allows for something that we could informally describe as invocation of callable objects:

class A {
  void call<X>(X x) {}
}

void main() {
  var a = A();
  a('is callable!'); // Desugared to `a.call('is callable!')`, then subject to normal type inference.
  a<int>(80); // Desugared to `a.call<int>(80)`.
}

We have specified that expressions of the form a(...) or a<T1..Tk>(...) are desugared to a.call(...) respectively a.call<T1..Tk>(...) when the static type of a is a class that has a method named call (not a getter). The desugared forms may have compile-time errors, but then we just report them.

In general, we can take the cue directly from the expression syntax: We are using e(...) or e<...>(...), hence the expression e must denote a function.

We do not have a similar rule for function closurization, and we do not have a similar rule for generic function instantiation:

class A {
  void call<X>(X x) {}
}

void main() {
  void Function<X>(X) f = A(); // Compile-time error.
  Function g = A(); // Compile-time error.
  void Function(int) h = a; // Compile-time error.
}

However, the current implementations (dartanalyzer, dart, dart2js at least) do allow the function closurization (apparently based on the context type), but they reject the generic function instantiation.

I think the consistent approach at this point would be to allow both the closurization (because it would be a breaking change to disallow it) and the generic function instantiation (because of the generalization in #1812).

We could consider this approach to be reasonably consistent, because it allows "all usages" of callable objects as functions, as long as the static type allows the compiler to use a small desugaring step to achieve the semantics that corresponds to the semantics that we would have with an actual function object.

If we do this we'd need to update the language specification as well as the constructor-tearoffs specification.

@munificent, @jakemac53, @lrhn, @stereotype441, @leafpetersen, @natebosch, WDYT?

@lrhn
Copy link
Member

lrhn commented Aug 31, 2021

However, the current implementations (dartanalyzer, dart, dart2js at least) do allow the function closurization (apparently based on the context type), but they reject the generic function instantiation.

I think we've asked for that behavior explicitly.

As I remember it, we effectively specified implicit .call insertion as:
If e has a static type which is a callable interface type, and it's either followed by an <argumentsPart> or it occurs in a context type which is a subtype of Function, then we implicitly convert e to e.call.

(The latter is referred to as "implicit property extraction" in the spec, but only mentioned in relation to extension methods where it doesn't apply.)

Also, we allow instantiated instance method tear-off if an instance method tear-off for a generic instance method occurs with a context type which is a non-generic function type, and we can find a matching instantiation. (That's just the definition of instantiated instance method tear-off, just like other instantiated tear-offs we allowed.)

Together those two should just make the instantiated callable object call method tear-off work.

We then explicitly added rules to disallow that combination in order to avoid callable objects from being more useful than normal function closures. (I may be misremembering this, and we never did any exception, and I'm just remembering the "don't do implicit .call tear-off of extension call methods" rule.)

If we allow general generic function value instantiation (#1812), not just tear-off instantiation, then there is no reason for such a restriction any more (whether we had it or not).
We should remove the extra rule, and just let .call be inserted and then be and instantiated instance method tear-off for a callable object with a generic call method in a non-generic function-typed context.

class A {
  X call<X>(X x) => x;
}

void main() {
  X Function<X>(X) f = A(); // This should *always* have worked. It's just an implicit `.call` tear-off
  Function g = A(); // This too should just work, and do a tear-off of the generic `call` method.
  int Function(int) h = A(); // This used to be disallowed, but should be made to work.

  var j = A()<int>; // This too will be valid, we now insert `.call` if followed by type arguments or arguments.
}

(The constructor tear-off specification already includes this, I never thought it wouldn't work.)

@eernstg
Copy link
Member Author

eernstg commented Sep 3, 2021

Closing: This is already working, and the spec is updated accordingly in #1842.

@eernstg eernstg closed this as completed Sep 3, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants