Closed cacoco closed 4 years ago
I suspect this replicates the behaviour of when we used manifests. As such does this mean in guice Foo with Bar looks the same as Foo alone? Meaning if you have a constructor looking for Foo with Bar and bind a plain old Foo, guice will try to inject it and you'll runtime error when you try to access something from Bar?
I suspect this replicates the behaviour of when we used manifests. As such does this mean in guice Foo with Bar looks the same as Foo alone? Meaning if you have a constructor looking for Foo with Bar and bind a plain old Foo, guice will try to inject it and you'll runtime error when you try to access something from Bar?
That is what I noticed stepping through the debugger of binding this type with a standard Guice AbstractModule, the TypeLiteral built is just for type Foo
. Let me test what happens when binding Foo with Bar
and Foo
to see what happens but I suspect this fails which makes a type of sense.
As I expected this fails.
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> class SomeClient {
| private[this] val underlying: String = "Alice"
|
| def doSomething: String = s"Hello, $underlying."
| }
defined class SomeClient
scala>
scala> trait Augmentation { self: SomeClient =>
| override def doSomething: String = s"${self.doSomething} Welcome to Wonderland."
| }
defined trait Augmentation
scala> import com.google.inject.{AbstractModule, Guice, Provides}
import com.google.inject.{AbstractModule, Guice, Provides}
scala> import net.codingwell.scalaguice.ScalaModule
import net.codingwell.scalaguice.ScalaModule
scala> import javax.inject.Singleton
import javax.inject.Singleton
scala> Guice.createInjector(
| new AbstractModule with ScalaModule {
| @Provides
| @Singleton
| def provideSomeClient: SomeClient with Augmentation = {
| new SomeClient with Augmentation
| }
| },
| new AbstractModule with ScalaModule {
| @Provides
| @Singleton
| def provideSomeClient: SomeClient = {
| new SomeClient
| }
| }
| )
com.google.inject.CreationException: Unable to create injector, see the following errors:
1) A binding to SomeClient was already configured at $anon$1.provideSomeClient().
at $anon$3.provideSomeClient(<console>:29)
1 error
at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:554)
at com.google.inject.internal.InternalInjectorCreator.initializeStatically(InternalInjectorCreator.java:161)
at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:108)
at com.google.inject.Guice.createInjector(Guice.java:87)
at com.google.inject.Guice.createInjector(Guice.java:69)
at com.google.inject.Guice.createInjector(Guice.java:59)
... 49 elided
And accessing the type Foo
from the injector even though Foo with Bar
was bound appears to work:
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> class SomeClient {
| protected val underlying: String = "Hello, Alice."
|
| def doSomething: String = underlying
| }
defined class SomeClient
scala>
scala> trait Augmentation { self: SomeClient =>
| override def doSomething: String = s"${self.underlying} Welcome to Wonderland."
| }
defined trait Augmentation
scala> import com.google.inject.{AbstractModule, Guice, Provides}
import com.google.inject.{AbstractModule, Guice, Provides}
scala> import net.codingwell.scalaguice.ScalaModule
import net.codingwell.scalaguice.ScalaModule
scala> import javax.inject.Singleton
import javax.inject.Singleton
scala> import com.google.inject.Stage
import com.google.inject.Stage
scala> val i = Guice.createInjector(
| Stage.PRODUCTION,
| new AbstractModule with ScalaModule {
| @Provides
| @Singleton
| def provideSomeClient: SomeClient with Augmentation = {
| new SomeClient with Augmentation
| }
| })
i: com.google.inject.Injector = Injector{bindings=[InstanceBinding{key=Key[type=com.google.inject.Stage, annotation=[none]], source=[unknown source], instance=PRODUCTION}, 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=SomeClient, annotation=[none]], source=public SomeClient $anon$1.provideSomeClient(), scope=Scopes.SINGLETON, provider=@Provides $anon$1.provideSomeClient(<console>:23)}]}
scala> import net.codingwell.scalaguice.InjectorExtensions._
import net.codingwell.scalaguice.InjectorExtensions._
scala> val r = i.instance[SomeClient]
r: SomeClient = $anon$1$$anon$2@1a3611b1
scala> r.doSomething
res0: String = Hello, Alice. Welcome to Wonderland.
And appears to return the correct binding (that is Foo with Bar
).
And a ClassCastException when you try to use the Foo with Bar
though only a Foo
was bound:
scala> import javax.inject.Inject
import javax.inject.Inject
scala> trait Augmentation { self: SomeClient =>
| override def doSomething: String = s"${self.underlying} Welcome to Wonderland."
|
| def onlyHere: String = "Only on augmented classes."
| }
defined trait Augmentation
scala> val i = Guice.createInjector(
| Stage.PRODUCTION,
| new AbstractModule with ScalaModule {
| @Provides
| @Singleton
| def provideSomeClient: SomeClient = {
| new SomeClient
| }
| })
i: com.google.inject.Injector = Injector{bindings=[InstanceBinding{key=Key[type=com.google.inject.Stage, annotation=[none]], source=[unknown source], instance=PRODUCTION}, 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=SomeClient, annotation=[none]], source=public SomeClient $anon$1.provideSomeClient(), scope=Scopes.SINGLETON, provider=@Provides $anon$1.provideSomeClient(<console>:26)}]}
scala> class Baz @Inject() (client: SomeClient with Augmentation) {
| client.onlyHere
| }
defined class Baz
scala> i.instance[Baz]
com.google.inject.ProvisionException: Unable to provision, see the following errors:
1) Error injecting constructor, java.lang.ClassCastException: SomeClient cannot be cast to Augmentation
at Baz.<init>(<console>:25)
while locating Baz
1 error
at com.google.inject.internal.InternalProvisionException.toProvisionException(InternalProvisionException.java:226)
at com.google.inject.internal.InjectorImpl$1.get(InjectorImpl.java:1097)
at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1126)
at net.codingwell.scalaguice.InjectorExtensions$ScalaInjector.instance(InjectorExtensions.scala:27)
... 34 elided
Caused by: java.lang.ClassCastException: SomeClient cannot be cast to Augmentation
... 36 more
Problem
It is not currently possible to support building a
TypeLiteral
from a complex Scala type, e.g.,Foo with Trait
.Solution
Add a case statement in the
net.codingwell.scalaguice.ClassType#unapply
to match on ascala.reflect.internal.RefinedType
which is defined as a class representing the intersection of types with refinements of the formparents0 with parentsN
. Then use this as signal to process the erasure form of the type passing it back through the unapply.Add test cases. This addresses #87.