Status Update
Comments
yb...@google.com <yb...@google.com> #2
hmm room should've filtered out the overridden method, maybe the extends check is failing w/o kotlin.
st...@gmail.com <st...@gmail.com> #3
Kotlin has an interesting approach in this scenario and I am afraid it's designed into the language so I can only see Room adapting to it.
@Dao
interface FooDao: BaseDao<Long, Foo> {
@Query("select * from foo where id=:id")
override fun getItem(id: Long): Foo?
}
When a concrete class extends BaseDao
Kotlin actually creates 2 methods, one for the Java primitive and one for the boxed version of it.
Opening the generated version FooDao_Impl.java
you see just the primitive version implemented and an error because there is no boxed version. (from what I see, but I may be mistaking, when an abstract or interface extends BaseDao
the boxed version is not there and Room generates the class only for the primitive one).
Room should be able to detect this particular case and generate the boxed version of the method and delegate to the primitive version.
el...@google.com <el...@google.com>
ap...@google.com <ap...@google.com> #4
Branch: androidx-master-dev
commit 326386368885651cef121221c3d0a9156c72d51c
Author: Elif Bilgin <elifbilgin@google.com>
Date: Mon Sep 28 12:40:27 2020
Update to the DaoProcessor to fix compiler error on Dao with a generic super type with Kotlin "primitives".
When a Kotlin interface we generate has a method with primitives, it generates two methods for compatibility. This fix implements logic for delegating the duplicate methods to the concrete method. This is done by going through the list of unannotated methods encountered and finding the corresponding annotated method taking into account the method name, return type and method parameters.
Test: Baseline tests pass without throwing compiler errors in DaoPrimitiveTest.kt
Bug: 160258066
Relnote: Update to the DaoProcessor to fix compiler error on Dao with a generic super type with Kotlin "primitives".
Change-Id: Ice6bbd327c1a2693bafd371da0bfdfea8769b4c8
M room/compiler/src/main/kotlin/androidx/room/ext/javapoet_ext.kt
M room/compiler/src/main/kotlin/androidx/room/processor/DaoProcessor.kt
M room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
M room/compiler/src/main/kotlin/androidx/room/vo/Dao.kt
A room/compiler/src/main/kotlin/androidx/room/vo/KotlinBoxedPrimitiveMethodDelegate.kt
M room/compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
M room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
A room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/DaoPrimitiveTest.kt
el...@google.com <el...@google.com>
ap...@google.com <ap...@google.com> #5
Branch: androidx-main
commit 39908b855705e9e0d5f93e29323d7d96ccfdb103
Author: Yigit Boyar <yboyar@google.com>
Date: Fri Dec 18 21:54:23 2020
Handle primitives overriding generics in KSP
When a kotlin class specifies a non-nullable primitive type for a
super's generic argument, kotlin will duplicate methods that receive the
generic as an argument.
This CL changes the KSP override check to ignore those overrides so that
it also reports two methods. Note that this only happens for paremeters
and not return values. For return values, only the boxed one is generated.
To handle the return type boxing, we now use the overridden desclaration
when wrapping types, which lets us choose between primitive and
non-primitive by looking at the declaration.
Bug: 160258066
Bug: 160322705
Test: XExecutableElementTest
Change-Id: Id2fb76748ff014bcabe4543daed211d82d04bd4c
M room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodElement.kt
M room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspMethodType.kt
M room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/ResolverExt.kt
M room/compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
ph...@gmail.com <ph...@gmail.com> #6
The fix doesn't work for a case where the generic type parameter is only used as a parameter to another generic type. I'm using the roomerror.zip from the opening post, with only these two modifications:
interface BaseDao<Key, Value> {
fun getItem(id: Key): Value?
fun delete(id: Collection<Key>) // Previously: fun delete(id: Key)
}
@Dao
interface FooDao: BaseDao<Long, Foo> {
@Query("select * from foo where id=:id")
override fun getItem(id: Long): Foo?
@Query("delete from foo where id IN (:id)") // Previously: @Query("delete from foo where id=:id")
override fun delete(id: Collection<Long>) // Previously: override fun delete(id: Long)
}
Note that if it weren't for the super interface, the delete function accepting Collection<Long>
would be treated perfectly fine by Room. But now it complains:
roomerror/app/build/tmp/kapt3/stubs/debug/com/roomerror/BaseDao.java:11: error: An abstract DAO method must be annotated with one and only one of the following annotations: Insert,Delete,Query,Update,RawQuery
public abstract void delete(@org.jetbrains.annotations.NotNull()
^
I imagine this happens because of some type erasure. For the same reason, it's incredibly difficult for me to work around this issue. If I use a signature with vararg id: Key
instead, Room is happy, but I can't call the function anymore. That's because my call site looks sth like this:
class Manager<Key, Value> {
private val dao: BaseDao<Key, Value>
private val stuff: List<Key>
fun clear() { dao.delete(*stuff.toTypedArray()) } // It seems impossible to convert stuff to vararg when Key is not reified.
}
ph...@gmail.com <ph...@gmail.com> #7
Just realized it might be cleaner to file this as a new bug, as it's not specific to primitives and rather it's specific to type erasure.
ph...@gmail.com <ph...@gmail.com> #8
I believe this test case for room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeElementTest.kt
might reproduce the underlying problem:
@Test
fun overrideMethodWithGenericTypeArgument() {
val src = Source.kotlin(
"ParentWithGenericTypeArgument.kt",
"""
interface ParentWithGenericTypeArgument: GenericInterface<String> {
override fun child(arg: Collection<String>)
}
interface GenericInterface<T> {
fun child(arg: Collection<T>)
}
""".trimIndent()
)
runProcessorTest(sources = listOf(src)) { invocation ->
val objectMethodNames = invocation.processingEnv.requireTypeElement(Any::class)
.getAllMethods().jvmNames()
fun XMethodElement.signature(
owner: XType
): String {
val methodType = this.asMemberOf(owner)
val params = methodType.parameterTypes.joinToString(",") {
it.typeName.toString()
}
return "$jvmName($params):${returnType.typeName}"
}
fun XTypeElement.allMethodSignatures(): List<String> = getAllMethods().filterNot {
it.jvmName in objectMethodNames
}.map { it.signature(this.type) }.toList()
invocation.processingEnv.requireTypeElement(
"ParentWithGenericTypeArgument"
).let { parent ->
assertWithMessage(parent.qualifiedName).that(
parent.allMethodSignatures()
).containsExactly(
"child(java.util.Collection<java.lang.String>):void"
)
}
}
}
In particular, room-compiler
fails to recognize that child(arg: Collection<String>)
overrides child(arg: Collection<T>)
. Kotlin clearly thinks that it does override, as it doesn't complain about the override
keyword.
ph...@gmail.com <ph...@gmail.com> #9
This problem happens because fun child(arg: Collection<T>)
is converted to void child(@NonNull Collection<? extends T>)
in Java, because Kotlin declares a covariant type parameter Collection<out T>
. This isn't the case for MutableCollection
, so using that instead make things work, though I consider that an ugly workaround.
Description
Component used:
Compiler
Version used:
2.2.4
Devices/Android versions reproduced on:
Any
Consider we have an interface in Kotlin:
And the implementation:
Compiling this yields:
An abstract DAO method must be annotated with one and only one of the following annotations: Insert,Delete,Query,Update,RawQuery public abstract Value getItem(Key id);
This is due to the Kotlin way of handling its primitives and even though the
Key
type will be set correctly tojava.lang.Long
but the method signature will begetItem(long id)
.