google / google/guice

Add support for Functional types / non-fully-specified types to assisted Inject

Open
#1,010 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
Java
Stars
12.7k
Forks
1.7k
Avg merge
11m
Merged PRs (30d)
2

Description

> As a Kotlin developer, I would like to use assisted inject with a functional type as a factory so that I am able to test my code with less boiler plate

So right now in Kotlin there is no elegant solution for binding `kotlin.Function` as factories.

I thought I could circumvent this with guava's impossibly clever `TypeLiteral`, no dice.

Given the class

``` Kotlin
class Component @Inject constructor(@Assisted importantRuntimeValue : Int){
interface Factory { fun create(importantRuntimeValue : Int) : Component }
}

//...

class ComponentFactoryConsumer @Inject constructor(val factory : Component.Factory){
//...
}

class ComponentFactoryTestClass{
@Test fun when_doing_X_should_result_in_Y(){
val componentUnderTest = ComponentFactoryConsumer( { importantRV -> mock(Component::class) } )
//not legal kotlin, since functional types exist in kotlin, SAM conversion is not available here.
}
}
```

The corresponding (almost identical) java has become idiomatic on our project

Since the above kotlin is not legal, as we move forward with kotlin, we need new idioms. Unfortunately the most obvious one requires some modifications within guice.

what I would like to do is skip the named factory type alltogether, and simply use a `(Int) -> Component` (kotlin alias for `kotlin.Function` which itself is an alias for `kotlin.jvm.functions.Function`), thus:

``` Kotlin
class Component @Inject constructor(@Assisted importantRuntimeValue : Int){
//no need for named factory!
}

//...

class ComponentFactoryConsumer @Inject constructor(val factory : (Int) -> Component){
//...
}

class ComponentFactoryTestClass{
@Test fun when_doing_X_should_result_in_Y(){
val componentUnderTest = ComponentFactoryConsumer( { importantRV -> mock(Component::class) } )
//now legal since the lambda is a simple function
}
}
```

But this of course requires the binding code:

``` Kotlin
@Test fun when_trying_to_use_reified_type_and_clever_typeLiteral_should_also_fail(){
assertThatThrownBy { Guice.createInjector(object : AbstractModule() {
override fun configure() {
install(FactoryModuleBuilder().build<(Int) -> KotlinClassWithJavaIdiomaticFactory>())
}
}) }.isInstanceOf(CreationException::class.java)
}

inline fun FactoryModuleBuilder.build() : Module {
return build(object : TypeLiteral(){})
}
```

which results in

```
May 06, 2016 4:20:08 PM com.google.inject.internal.MessageProcessor visit
INFO: An exception was caught and reported. Message: java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but is of type com.google.inject.internal.MoreTypes$WildcardTypeImpl
java.lang.IllegalArgumentException: Expected a Class, ParameterizedType, or GenericArrayType, but is of type com.google.inject.internal.MoreTypes$WildcardTypeImpl
at com.google.inject.internal.MoreTypes.getRawType(MoreTypes.java:208)
at com.google.inject.TypeLiteral.(TypeLiteral.java:90)
at com.google.inject.TypeLiteral.get(TypeLiteral.java:157)
at com.google.inject.TypeLiteral.resolve(TypeLiteral.java:181)
at com.google.inject.TypeLiteral.getReturnType(TypeLiteral.java:339)
at com.google.inject.assistedinject.FactoryProvider2.(FactoryProvider2.java:239)
at com.google.inject.assistedinject.FactoryModuleBuilder$1.configure(FactoryModuleBuilder.java:334)
at com.google.inject.AbstractModule.configure(AbstractModule.java:62)
at com.google.inject.spi.Elements$RecordingBinder.install(Elements.java:340)
at com.google.inject.AbstractModule.install(AbstractModule.java:122)
at com.empowerops.language.KotlinLanguageFixture$when_trying_to_use_reified_type_and_clever_typeLiteral_should_also_fail$1$1.configure(KotlinLanguageFixture.kt:74)
at com.google.inject.AbstractModule.configure(AbstractModule.java:62)
at com.google.inject.spi.Elements$RecordingBinder.install(Elements.java:340)
at com.google.inject.spi.Elements.getElements(Elements.java:110)
at com.google.inject.internal.InjectorShell$Builder.build(InjectorShell.java:138)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:104)
at com.google.inject.Guice.createInjector(Guice.java:96)
at com.google.inject.Guice.createInjector(Guice.java:73)
at com.google.inject.Guice.createInjector(Guice.java:62)
at com.empowerops.language.KotlinLanguageFixture$when_trying_to_use_reified_type_and_clever_typeLiteral_should_also_fail$1.call(KotlinLanguageFixture.kt:72)
at org.assertj.core.api.ThrowableAssert.catchThrowable(ThrowableAssert.java:61)
at org.assertj.core.api.StrictAssertions.catchThrowable(StrictAssertions.java:594)
at org.assertj.core.api.StrictAssertions.assertThatThrownBy(StrictAssertions.java:564)
at com.empowerops.language.KotlinLanguageFixture.when_trying_to_use_reified_type_and_clever_typeLiteral_should_also_fail(KotlinLanguageFixture.kt:72)
```

I'm not sure if the problem is the fact that I'm using the type-parametric factory `(Int) -> Component` (aka `kotlin.Function`), or if the problem is within kotlin and its handling of contravariance, but in either event I figured guice should do something other than log a message at level info and then throw that same message as an exception.

The very similar code:

``` Java

public static class Component{
@Inject
public Component(@Assisted Integer value){}
}

@Test
public void when_guice(){
TypeLiteral> factoryInterfaceKey = new TypeLiteral>() {};

AbstractThrowableAssert assertion = assertThatThrownBy(() -> {
Guice.createInjector(binder -> {
binder.install(new FactoryModuleBuilder().build(factoryInterfaceKey));
});
});

assertion.isInstanceOf(CreationException.class);
assertion.hasMessageContaining("Unable to create injector, see the following errors:");
assertion.hasMessageContaining("1) java.util.function.Function cannot be used as a key; It is not fully specified.");
}
}
```

... passes, meaning guice throws a more conventional exception when called from java.

**tl;dr, add support for binding functional types as factories**

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.