Jdbi: Ambiguous methods error (missing bridge method) for Kotlin covariant overload

Created on 9 Aug 2019  ·  8Comments  ·  Source: jdbi/jdbi

EDIT: Updated with a simpler example.

According to the documentation generics and JDBI work together, and I've seen this work before (albeit with an older version of JDBI), but for some reason I now get an error:

org.jdbi.v3.sqlobject.UnableToCreateSqlObjectException: interface Bar has ambiguous methods [public abstract java.lang.Object Foo.baz(java.util.UUID), public abstract java.util.UUID Bar.baz(java.util.UUID)], please resolve with an explicit override

http://jdbi.org/#_annotations_and_inheritance

I have tried @Override for good measure but that didn't change anything.

interface Foo<T> {
    @SqlQuery
    fun baz(a: UUID): T
}

interface Bar : Foo<UUID> {
    @SqlQuery("SELECT ClientID " +
              "FROM ClientInstance " +
              "WHERE ClientInstanceID = ?")
    override fun baz(a: UUID): UUID
}

JDBI.inTransactionUnchecked { handle ->
    val tester = handle.attach(Bar::class.java)
    val hest = tester.baz(UUID.fromString("7a949c0e-28db-424a-a449-b118e5175a3b"))
    LOGGER.info { "hest $hest" }
}
bug

Most helpful comment

Hi Tor,
We tested that a very similar construction works in vanilla Java: https://github.com/jdbi/jdbi/blob/master/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestSqlObject.java#L293-L307
Perhaps we missed a case for Kotlin. We should add a test corresponding to your use here and fix whatever bug is uncovered.

All 8 comments

Hi Tor,
We tested that a very similar construction works in vanilla Java: https://github.com/jdbi/jdbi/blob/master/sqlobject/src/test/java/org/jdbi/v3/sqlobject/TestSqlObject.java#L293-L307
Perhaps we missed a case for Kotlin. We should add a test corresponding to your use here and fix whatever bug is uncovered.

Ah, looking more closely: note that the return types are different, Object vs UUID. I expect this means the Kotlin compiler is emitting a synthetic bridge method that we are then not handling correctly.

For people searching Google, this bug yields a different error message when the parent interface is not annotated (it took me a while to find this!)

Exception in thread "main" java.lang.IllegalStateException: Method StringRepo.get must be default or be annotated with a SQL method annotation.
    at org.jdbi.v3.sqlobject.SqlObjectFactory.lambda$buildMethodHandler$15(SqlObjectFactory.java:148)
...
interface Repo<T> {
    fun get(): T
}
interface StringRepo : Repo<String> {
    @SqlQuery("SELECT 'Hello world!'")
    override fun get(): String
}
//...snip
jdbi.onDemand(StringRepo::class.java).get()

@LittleMikeDev, that's a slightly different problem. I've improved the error messaging to not be misleading about the class that is missing the annotation: https://github.com/jdbi/jdbi/pull/1590/files#diff-75b1c04d402bd8924262c7fececfcdb1L166

@TorRanfelt, I dug a bit more into this and it is not obvious what to do to fix it. It looks like the Kotlin compiler is not emitting a bridge method for the covariant method specialization. I filed an issue on the Kotlin bug tracker: https://youtrack.jetbrains.com/issue/KT-33634

As a workaround, Jdbi could emulate the needed bridge method. The code needed probably ends up being small but very tricky to write. I hope Kotlin will at least evaluate the issue soon.

@stevenschlansker Thanks for looking into it.
Kotlin must have had this bridge before because I have seen it work. - This explains why I couldn't get it to work again by reverting to older versions of JDBI.

It looks like this was a compromise to maintain Java 6 compatibility: 6 did not support bridge methods in interfaces, so Kotlin does not emit them. That sounds mighty silly nowadays but probably made sense 10 years ago ;) Hopefully they drop 6 support soon, or at least conditionally enable compatibility with the post-6 world.

Was this page helpful?
0 / 5 - 0 ratings