Skip to content
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

bind[F[T]], where T is a value type (long, int, etc), does not work with @Inject constructors #56

Open
woparry opened this issue Jun 15, 2016 · 7 comments

Comments

@woparry
Copy link

woparry commented Jun 15, 2016

example:

scala> val module = new AbstractModule with ScalaModule {
     |   def configure() {
     |     bind[Option[Int]].toInstance(Some(1))
     |   }
     | }
module: com.google.inject.AbstractModule with net.codingwell.scalaguice.ScalaModule = $anon$1@4bd5a2cc

scala> class Test @Inject()(val optionOfInt: Option[Int])
defined class Test

scala> Guice.createInjector(module).getInstance(classOf[Test])
com.google.inject.ConfigurationException: Guice configuration errors:

1) No implementation for scala.Option<java.lang.Object> was bound.
  while locating scala.Option<java.lang.Object>
    for parameter 0 at Test.<init>(<console>:10)
  while locating Test

1 error
  at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1042)
  at com.google.inject.internal.InjectorImpl.getProvider(InjectorImpl.java:1001)
  at com.google.inject.internal.InjectorImpl.getInstance(InjectorImpl.java:1051)
  ... 43 elided

Note that it works fine if you use @provides instead

scala> val module = new AbstractModule with ScalaModule {
     |   @Provides
     |   def providesOptionOfInt: Option[Int] = Some(1)
     |   def configure = {}
     | }
module: com.google.inject.AbstractModule with net.codingwell.scalaguice.ScalaModule{def providesOptionOfInt: Option[Int]} = $anon$1@7ba2e475

scala> class Test @Inject()(val optionOfInt: Option[Int])
defined class Test

scala> Guice.createInjector(module).getInstance(classOf[Test])
res0: Test = Test@10042b25

or if T is a reference type

scala> val module = new AbstractModule with ScalaModule {
     |   def configure {
     |     bind[Option[String]].toInstance(Some("fish"))
     |   }
     | }
module: com.google.inject.AbstractModule with net.codingwell.scalaguice.ScalaModule = $anon$1@3ea66c1d

scala> class Test @Inject()(val optionOfString: Option[String])
defined class Test

scala> Guice.createInjector(module).getInstance(classOf[Test])
res1: Test = Test@22e84b6e
@chrisbenincasa
Copy link

I just started hitting this as well. @woparry did you ever find a workaround?

@jendakol
Copy link

@chrisbenincasa I'm working on solution. I have it done, respectively, but I'm still uncertain if I don't break something, because my fix is backward incompatible. I solved it using a macro.

@tsuckow
Copy link
Member

tsuckow commented Oct 4, 2017

What about making the type Option[java.lang.Integer] to avoid the primitive issue? If you really wanted an Int then you could use .map {_.toInt}

I'm not sure if there is a way to intercept the Injection and do the above automatically.

@jendakol
Copy link

jendakol commented Oct 4, 2017

Unfortunately I discovered there is a problem with binding Scala types. I'm fighting it.
The problem I spoke about above is more complicated than I thought that time 😞

@jendakol
Copy link

jendakol commented Oct 5, 2017

I believe there is no way how to intercept getting an instance from the Injector unless we wrap something. I'm working on it, unfortunately it will become almost completely new library :-)

@jendakol
Copy link

jendakol commented Oct 9, 2017

@tsuckow @chrisbenincasa @woparry Guys, I have bad news. I spent many hours with research and made a big effort to solve this issue, however it cannot be done. Here is why.

The problem lies in Java and Scala type system. In Java, you're unable to create Set<int>, only objects can be used as generic type parameter (means Set<Integer>), and even if you would hack the Guice somehow and bind int (the primitive one), it would throw an error. On the other hand, Scala allows you to create Set[Int] BUT the Int is not an object (in resulting bytecode) and therefore it turns into Set[Object] which is what you see in the error message. The problem is Guice will allow you to bind Set[Int] because it doesn't count with Scala types.

"Hey, lets change the type on-the-fly to some object and back when injecting" you may say - at least I did. So, when binding, we are able to change the type which is being bound (e.g. by macro defining a custom Manifest or TypeLiteral). Unfortunately there is no way (I didn't find any and I considered both nice and ugly solutions) how to intercept the search for existing binding. The problem is we let the injection up to the Guice and it uses Java reflection inside, which cannot be changed. The reflection finds real types for the types being injected and we cannot say "hey, look instead for this" like when binding.

So, for me the outcome is we would have to change the Guice itself. Before that being done, we have to use Java types when using generic type parameters (basically what @tsuckow suggested above, even though it's very sad).
BTW the maximum I'm able to do is to create a macro which will cause binding of such type to fail (maybe even in compile-time).

@vkostyukov
Copy link

Is there any news on this? From what I understand, the workaround is to use ScalaOptionBinder?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants