Description
Problem
The TypeConversions#scalaTypeToJavaType
ends up creating a TypeLiteral of the wrapped type of a value class instead of the type of the value class. Guice however will bind to the value class type as the TypeLiteral of the Key. Thus, any code path that goes through net.codingwell.scalaguice.typeLiteral[T]
will end up with an incongruous TypeLiteral. This is true both when binding and then trying to @Inject
a type, or when looking up an already bound type from the injector via the InjectorExtensions
.
We're using scalaguice 4.2.9, guice 4.2.3, Scala 2.12.10 on JDK 8.
A simple example:
Welcome to Scala 2.12.10 (JDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> case class UserId(id: Long) extends AnyVal
defined class UserId
scala> import net.codingwell.scalaguice._
import net.codingwell.scalaguice._
scala> typeLiteral[UserId]
res0: com.google.inject.TypeLiteral[UserId] = java.lang.Long
This is different than the result before the switch from Manifests to TypeTags (using scalaguice 4.2.0 and guice 4.2.1 with Scala 2.12.10 and JDK8):
Welcome to Scala 2.12.10 (JDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> case class UserId(id: Long) extends AnyVal
defined class UserId
scala> import net.codingwell.scalaguice._
import net.codingwell.scalaguice._
scala> typeLiteral[UserId]
res0: com.google.inject.TypeLiteral[UserId] = UserId
Why is this potentially bad? A somewhat more involved example:
Welcome to Scala 2.12.10 (JDK 64-Bit Server VM, Java 1.8.0_222).
Type in expressions for evaluation. Or try :help.
scala> case class UserId(id: Long) extends AnyVal
defined class UserId
scala> import com.twitter.util.Future
import com.twitter.util.Future
scala> type UserIdFunction = (UserId) => Future[Boolean]
defined type alias UserIdFunction
scala> import javax.inject.Singleton
import javax.inject.Singleton
scala> import com.google.inject.{AbstractModule, Guice, Provides}
import com.google.inject.{AbstractModule, Guice, Provides}
scala> import net.codingwell.scalaguice._
import net.codingwell.scalaguice._
scala> val injector = Guice.createInjector(
| new AbstractModule with ScalaModule {
| @Provides
| @Singleton
| def provideUserIdFunction: UserIdFunction = (uid: UserId) => {
| if (uid.id > 0) Future.value(true) else Future.value(false)
| }
| }
| )
injector: com.google.inject.Injector = Injector{bindings=[InstanceBinding{key=Key[type=com.google.inject.Stage, annotation=[none]], source=[unknown source], instance=DEVELOPMENT}, ProviderInstanceBinding{key=Key[type=com.google.inject.Injector, annotation=[none]], source=[unknown source], scope=Scopes.NO_SCOPE, provider=Provider<Injector>}, ProviderInstanceBinding{key=Key[type=java.util.logging.Logger, annotation=[none]], source=[unknown source], scope=Scopes.NO_SCOPE, provider=Provider<Logger>}, ProviderInstanceBinding{key=Key[type=scala.Function1<UserId, com.twitter.util.Future<java.lang.Object>>, annotation=[none]], source=public scala.Function1 $anon$1.provideUserIdFunction(), scope=Scopes.SINGLETON, provider=@Provides $anon$1.provideUserIdFunction(<console...
scala> import net.codingwell.scalaguice.InjectorExtensions._
import net.codingwell.scalaguice.InjectorExtensions._
scala> injector.instance[UserIdFunction]
com.google.inject.ConfigurationException: Guice configuration errors:
1) No implementation for scala.Function1<java.lang.Long, com.twitter.util.Future<java.lang.Boolean>> was bound.
while locating scala.Function1<java.lang.Long, com.twitter.util.Future<java.lang.Boolean>>
1 error
at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1120)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1126)
at net.codingwell.scalaguice.InjectorExtensions$ScalaInjector$.instance$extension0(InjectorExtensions.scala:27)
... 34 elided
scala>
Guice binds with a Key with type scala.Function1<UserId, com.twitter.util.Future<java.lang.Object>>
but we end up looking for a Key based on scala.Function1<java.lang.Long, com.twitter.util.Future<java.lang.Boolean>>
. Looking through the codebase, I don't see tests for this case though it seems like a regression. Maybe this is related to the fix for Mixins, we're going to test against the version before that fix.
We're still attempting to upgrade from scalaguice 4.2.0 to a recent version for the Finatra framework but are hitting various type conversion issues from our extensive usage internally. Any help would be appreciated.