eclipse-archived / ceylon

The Ceylon compiler, language module, and command line tools
http://ceylon-lang.org
Apache License 2.0
399 stars 62 forks source link

Java 8: support auto-SMI of our Callable types #1617

Closed CeylonMigrationBot closed 8 years ago

CeylonMigrationBot commented 10 years ago

[@FroMage] In theory we should be able to pretend that any method in Java which accepts an SMI (Single-Method Interface) accepts a union of the SMI and its corresponding Ceylon Callable, and do the magic auto-conversion in our backend, when provided with a Callable.

Similarly we should be able to pretend that any method in Java which returns an SMI returns an intersection of the SMI and its corresponding Ceylon Callable, and do the magic auto-conversion in our backend.

I'm not 100% convinced we can do this due to the usual issues with types and bounds and type-arguments, but we can try and investigate.

I highly doubt we'll have time to do this for 1.1, so marking 1.2

[Migrated from ceylon/ceylon-compiler#1617]

FroMage commented 8 years ago

And the fix is to finally bite the bullet and get rid of the special cases in transformeExpression yay!

fwgreen commented 8 years ago
CompletableFuture.supplyAsync(() => repo.listAll(), mes.execute)
error: Ceylon backend error: incompatible types: <anonymous AbstractCallable<Object>> cannot be converted to Executor

I should point out that the messy way...

CompletableFuture.supplyAsync(object satisfies Supplier<List<Person>> { get() => repo.listAll(); }, mes)

continues to work, so we're not dead in the water.

As an aside, Kotlin currently has the same limitation, but uses a shorthand along the lines of https://github.com/ceylon/ceylon/issues/5739 to ease the pain.

CompletableFuture.supplyAsync(Supplier { repo.listAll() }, mes )
FroMage commented 8 years ago

Yeah I didn't have time to push my fix yet

FroMage commented 8 years ago

Damn, I forgot coercions to invocations :(

FroMage commented 8 years ago

@fwgreen it should work now.

FroMage commented 8 years ago

OK invocations work. And the metamodel is not affected because the runtime metamodel will not report functional interfaces: the overloads don't exist at runtime.

FroMage commented 8 years ago

Now the IDEs have to implement the model loader methods for this to work in the IDEs.

bjansen commented 8 years ago

What should happen when a class defines both

Future<?> executeOnPooledThread(@NotNull Runnable action);

and

<T> Future<T> executeOnPooledThread(@NotNull Callable<T> action);

? If I use the following argument:

void submitChangesTask() { }

I get this error:

[ceylon-compile] /Users/bastien/Dev/ceylon/ceylon-ide-intellij/plugin-ceylon-code/source/org/intellij/plugins/ceylon/ide/ceylonCode/model/CeylonModelManager.ceylon:272: error: argument must be assignable to parameter 'arg0' of 'executeOnPooledThread' in 'Application': 'Anything()' is not assignable to 'Object()'
[ceylon-compile]             => submitChangesFuture = application.executeOnPooledThread(submitChangesTask);
[ceylon-compile]                                                                        ^

I can't disambiguate using of:

[ceylon-compile] /Users/bastien/Dev/ceylon/ceylon-ide-intellij/plugin-ceylon-code/source/org/intellij/plugins/ceylon/ide/ceylonCode/model/CeylonModelManager.ceylon:272: error: specified type does not cover the cases of the operand expression: 'Runnable' does not cover 'Anything()'
[ceylon-compile]             => submitChangesFuture = application.executeOnPooledThread(submitChangesTask of Runnable);
[ceylon-compile]                                                                        ^
bjansen commented 8 years ago

Oh, it looks like Object submitChangesTask() => null; is accepted. My model loader accepts both versions, so I'm not sure if I implemented it correctly or not?

FroMage commented 8 years ago

To be honest that's a use-case we didn't think about: overloading where there are two exactly similar SAM types. I don't think the overloading resolution can pick either one. WDYT @gavinking ?

gavinking commented 8 years ago

I think it's very easy to use object Runnable { run=submitChangesTask; } in such cases. I don't think we should be worried about this one.

What API is it anyway, @bjansen?

bjansen commented 8 years ago

IntelliJ's Application

bjansen commented 8 years ago

I agree it's easy to use an object instead, but perhaps the TC should throw an error saying that this isn't possible on overloads?

fwgreen commented 8 years ago

It's great that this now works

shared default void list(suspended AsyncResponse response) {
        CompletableFuture.supplyAsync(repo.listAll)
                         .thenApply(generic)
                         .thenAccept((list) => response.resume(list));
    }

    GenericEntity<List<Person>> generic(List<Person> list) => object extends GenericEntity<List<Person>>(list) {};

Feeling lucky (greedy), I tried using a Java method reference

...thenAccept(response.resume);

which triggers

error: ambiguous callable reference to overloaded method or class: 'resume' is overloaded
                         .thenAccept(response.resume);
gavinking commented 8 years ago

Feeling lucky (greedy), I tried using a Java method reference

That's correct. We've never supported method refs to overloaded Java methods. For pretty good reasons.

jvasileff commented 8 years ago

Is it intentional that optional return types aren't allowed?

I image needing to return null is much less common than wanting to have non-optional parameter types (which is allowed). But still, it may be useful at times.

gavinking commented 8 years ago

Is it intentional that optional return types aren't allowed?

@jvasileff Example?

@FroMage is this perhaps just a matter of setting uncheckedNulls correctly in all the right places?

FroMage commented 8 years ago

That's still a TODO, I haven't tested this at all.

jvasileff commented 8 years ago

@gavinking anything, really. But for testing, something like:

import java.util.concurrent { CompletableFuture }

void foo(CompletableFuture<String> future) {
    CompletableFuture<String> newFuture
        = future.thenApply<String>((String? s) => s);
}
FroMage commented 8 years ago

is this perhaps just a matter of setting uncheckedNulls correctly in all the right places?

Nope, this is on TypedDeclaration and not on Type (and we don't want it on Callable).

@gavinking not sure how best to proceed. I can fudge with the signature of the Callable but will it be enough?

gavinking commented 8 years ago

@FroMage I found a bug.

The parameter of Stream.map() gets the function type Nothing(String)?. That not right. It should be R(String)?.

gavinking commented 8 years ago

Also, I don't think we actually need to mark these callable parameter types optional. If for some dumb reason you want to pass null, which seems to me pretty unlikely, you can use the other overload, DYT?

gavinking commented 8 years ago

@gavinking not sure how best to proceed. I can fudge with the signature of the Callable but will it be enough?

I think probably the best thing we can do is arrange for the typechecker to set uncheckedNulls on an anonymous function when it gets passed as a parameter to a Java method.

Is isJava() set to true on the generated Value parameter? If not could you do me a favor and set it to true, please?

FroMage commented 8 years ago

The parameter of Stream.map() gets the function type Nothing(String)?. That not right. It should be R(String)?.

This is due to the fudging I just did or is it older?

gavinking commented 8 years ago

This is due to the fudging I just did or is it older?

Not sure. Probably older.

FroMage commented 8 years ago

If for some dumb reason you want to pass null, which seems to me pretty unlikely, you can use the other overload, DYT?

Well, not sure, depends on how common that is. I haven't used the stream API enough to judge.

gavinking commented 8 years ago

Well, not sure, depends on how common that is.

Let's review all the method of Stream, and see if any of them accept null.

FroMage commented 8 years ago

Well, it's not the methods of Stream, it's the functional types that accept or return null.

FroMage commented 8 years ago

Is isJava() set to true on the generated Value parameter? If not could you do me a favor and set it to true, please?

Done. Though you could have trusted the method.

gavinking commented 8 years ago

I think probably the best thing we can do is arrange for the typechecker to set uncheckedNulls on an anonymous function when it gets passed as a parameter to a Java method.

Actually this won't work for multiple reasons:

  1. A Java SAM could have a return type like int or float.
  2. Anonymous functions don't have a declared return type, so the inferred return type will always be optional.

So that's a dead end, it seems to me.

gavinking commented 8 years ago

Well, it's not the methods of Stream, it's the functional types that accept or return null.

Nono, I'm talking about how Stream.map() accepts R(T)? instead of R(T). That's nothing to do with the functional interface type, it depends on what map() itself accepts.

FroMage commented 8 years ago

I can't reproduce your issue with Stream.map, it seems fine here:

|  |  |  |  |  |  |  +  [QualifiedMemberExpression] (113:15-114:15) : Stream<Integer>(Integer(Integer)?) : Stream<Integer>.map<Integer> : function Stream<T>.map<R>(R(T)? arg0) => Stream<R>
FroMage commented 8 years ago

Nono, I'm talking about how Stream.map() accepts R(T)? instead of R(T)

But they do already. That's not related to what @jvasileff was asking for. He was asking about being able to pass functions that take and return optional parameters.

FroMage commented 8 years ago

Well, the model loader certainly has the information as to whether the SAM function type can accept null params or return null values. It's just a matter of passing that info the typechecker.

gavinking commented 8 years ago

But they do already.

Right, but I don't want them to.

That's not related to what @jvasileff was asking for.

I know I know, this is something different I'm asking for.

FroMage commented 8 years ago

Would it help if we generated types such as String?(Integer primitive, Integer? notPrimitive)?

FroMage commented 8 years ago

I know I know, this is something different I'm asking for.

If you're asking me you'll have to be clearer then ;) I've no idea.

FroMage commented 8 years ago

Why don't you want Stream functions to accept null parameters? What's bad about that?

gavinking commented 8 years ago

Would it help if we generated types such as String?(Integer primitive, Integer? notPrimitive)?

Yes, that's the right thing to do, I think. Return types of callable parameters are contravariant like the type of a parameter value. We usually generate ? in that case. It's in covariant locations that we use uncheckedNulls.

gavinking commented 8 years ago

Why don't you want Stream functions to accept null parameters? What's bad about that?

I'm scared it might mess with:

gavinking commented 8 years ago

It's in covariant locations that we use uncheckedNulls.

For example, to be completely consistent, we should produce the type String?(Integer primitive, Integer notPrimitive) in your example. And notPrimitive should have uncheckedNulls marked to true. But I don't think the typechecker would know what to do with that right now, so String?(Integer primitive, Integer? notPrimitive) is good enough for now.

FroMage commented 8 years ago

Yes, that's the right thing to do, I think. Return types of callable parameters are contravariant like the type of a parameter value. We usually generate ? in that case. It's covariant locations that we use uncheckedNulls..

Well, we're left with the problem of parameters. If I do that you can't pass a function(Integer prim, Foo notPrim) to a String?(Integer prim, Foo? notPrim), can you?

gavinking commented 8 years ago

Well, we're left with the problem of parameters. If I do that you can't pass a function(Integer prim, Foo notPrim) to a String?(Integer prim, Foo? notPrim), can you?

Correct. See my previous comment.

FroMage commented 8 years ago

And notPrimitive should have uncheckedNulls marked to true. But I don't think the typechecker would know what to do with that right now

Well, there's no place to mark it. It's not a Function with parameters: it's a Callable type. That can't be marked in any way for its parameters.

gavinking commented 8 years ago

But I don't think the typechecker would know what to do with that right now, so String?(Integer primitive, Integer? notPrimitive) is good enough for now.

Oh wait; screw that, that would mean the parameter of map() would get the type R?(T?) which would drive everyone nuts! No no, it has to be R(T) for sure.

gavinking commented 8 years ago

Well, there's no place to mark it. It's not a Function with parameters: it's a Callable type.

Just make it String?(Integer primitive, Integer notPrimitive) for now. We'll deal with null in the next release by generating a functional parameter instead of a value parameter.

FroMage commented 8 years ago

Well, I can create a functional parameter too, if that's easier?

FroMage commented 8 years ago

At least, if it doesn't impact the rest of the backend work I did…

gavinking commented 8 years ago

Well, I can create a functional parameter too, if that's easier?

I don't think it will help much right now, without some additional work in the typechecker.

gavinking commented 8 years ago

I can't reproduce your issue with Stream.map, it seems fine here:

My bad, it's an intellij-specific bug. Works fine in the CLI.