Remove XType.asTypeElement & XType.isType
This CL removes XType.asTypeElement, which was unsafe. Instead, XType
has a new `typeElement:XTypeElement?` property. This property can be
used to get the type element, if it exists. It is nullable, hence the
calling code has to handle the null case. Appearantly, we were not
handling this which would result in compiler exceptions. I've added a
bunch of new error messages to detect them and fail more gracefully.
I've also removed XType.isType. Looks like we were using it just to
prevent Auto-Common from throwing when `isTypeOf` is called. Instead,
we're now safe-guarding it in the java abstraction and simply returning
false.
Bug: 175417726
Test: CustomConverterProcessorTest, DatabaseProcessorTest,
PojoProcessorTest, ShortcutMethodProcessorTest
Change-Id: I4ec706d6fad3e345c7b7fdf01fc90b2436d345e2
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt
index f43939a..be23e7b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/MethodCollector.kt
@@ -34,7 +34,7 @@
val selection = target.getDeclaredMethods().forEach(::addToSelection)
target.superType
- ?.asTypeElement()
+ ?.typeElement
?.getAllMethods()
?.forEach(::addIfNotOverridden)
target.getSuperInterfaceElements().forEach {
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
index d03e5f8..9249173 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/XType.kt
@@ -46,11 +46,14 @@
val nullability: XNullability
/**
- * Casts the current type to [XTypeElement].
+ * The [XTypeElement] that represents this type.
+ *
+ * Note that it might be null if the type is not backed by a type element (e.g. if it is a
+ * primitive, wildcard etc)
*
* @see isTypeElement
*/
- fun asTypeElement(): XTypeElement
+ val typeElement: XTypeElement?
/**
* Returns `true` if this type can be assigned from [other]
@@ -96,7 +99,7 @@
/**
* Returns `true` if this is a [List]
*/
- fun isList(): Boolean = isType() && isTypeOf(List::class)
+ fun isList(): Boolean = isTypeOf(List::class)
/**
* Returns `true` if this is `void`
@@ -106,12 +109,12 @@
/**
* Returns `true` if this is a [Void]
*/
- fun isVoidObject(): Boolean = isType() && isTypeOf(Void::class)
+ fun isVoidObject(): Boolean = isTypeOf(Void::class)
/**
* Returns `true` if this is the kotlin [Unit] type.
*/
- fun isKotlinUnit(): Boolean = isType() && isTypeOf(Unit::class)
+ fun isKotlinUnit(): Boolean = isTypeOf(Unit::class)
/**
* Returns `true` if this represents a `byte`.
@@ -124,11 +127,6 @@
fun isNone(): Boolean
/**
- * Returns true if this represented by a [XTypeElement].
- */
- fun isType(): Boolean
-
- /**
* Returns true if this represented by an [Enum].
*/
fun isEnum(): Boolean
@@ -202,7 +200,7 @@
contract {
returns(true) implies (this@isCollection is XDeclaredType)
}
- return isType() && (isTypeOf(List::class) || isTypeOf(Set::class))
+ return isTypeOf(List::class) || isTypeOf(Set::class)
}
/**
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
index 173ac32..96e7f7c 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacType.kt
@@ -20,7 +20,6 @@
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XRawType
import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.javac.kotlin.KmType
import androidx.room.compiler.processing.safeTypeName
import com.google.auto.common.MoreTypes
@@ -41,6 +40,17 @@
JavacRawType(env, this)
}
+ override val typeElement by lazy {
+ val element = try {
+ MoreTypes.asTypeElement(typeMirror)
+ } catch (notAnElement: IllegalArgumentException) {
+ null
+ }
+ element?.let {
+ env.wrapTypeElement(it)
+ }
+ }
+
override fun isError() = typeMirror.kind == TypeKind.ERROR
override fun isInt(): Boolean {
@@ -100,12 +110,6 @@
}
}
- override fun asTypeElement(): XTypeElement {
- return env.wrapTypeElement(
- MoreTypes.asTypeElement(typeMirror)
- )
- }
-
override fun isNone() = typeMirror.kind == TypeKind.NONE
override fun toString(): String {
@@ -130,20 +134,22 @@
}
override fun isTypeOf(other: KClass<*>): Boolean {
- return MoreTypes.isTypeOf(
- other.java,
- typeMirror
- )
+ return try {
+ MoreTypes.isTypeOf(
+ other.java,
+ typeMirror
+ )
+ } catch (notAType: IllegalArgumentException) {
+ // `MoreTypes.isTypeOf` might throw if the current TypeMirror is not a type.
+ // for Room, a `false` response is good enough.
+ false
+ }
}
override fun isSameType(other: XType): Boolean {
return other is JavacType && env.typeUtils.isSameType(typeMirror, other.typeMirror)
}
- override fun isType(): Boolean {
- return MoreTypes.isType(typeMirror)
- }
-
/**
* Create a copy of this type with the given nullability.
* This method is not called if the nullability of the type is already equal to the given
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index c60d55a..fd9b3b0b 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -19,7 +19,6 @@
import androidx.room.compiler.processing.XEquality
import androidx.room.compiler.processing.XNullability
import androidx.room.compiler.processing.XType
-import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.tryBox
import androidx.room.compiler.processing.tryUnbox
import com.google.devtools.ksp.symbol.ClassKind
@@ -53,18 +52,11 @@
}
}
- private val _typeElement by lazy {
- check(ksType.declaration is KSClassDeclaration) {
- """
- Unexpected case where ksType's declaration is not a KSClassDeclaration.
- Please file a bug.
- """.trimIndent()
+ override val typeElement by lazy {
+ val declaration = ksType.declaration as? KSClassDeclaration
+ declaration?.let {
+ env.wrapClassDeclaration(it)
}
- env.wrapClassDeclaration(ksType.declaration as KSClassDeclaration)
- }
-
- override fun asTypeElement(): XTypeElement {
- return _typeElement
}
override fun isAssignableFrom(other: XType): Boolean {
@@ -111,10 +103,6 @@
return false
}
- override fun isType(): Boolean {
- return ksType.declaration is KSClassDeclaration
- }
-
override fun isTypeOf(other: KClass<*>): Boolean {
// closest to what MoreTypes#isTypeOf does.
// accept both boxed and unboxed because KClass.java for primitives wrappers will always
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
index 814425b..630d63c 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/XTypeTest.kt
@@ -84,7 +84,7 @@
)
}
- type.asTypeElement().getMethod("wildcardParam").let { method ->
+ type.typeElement!!.getMethod("wildcardParam").let { method ->
val wildcardParam = method.parameters.first()
val extendsBoundOrSelf = wildcardParam.type.extendsBoundOrSelf()
assertThat(extendsBoundOrSelf.rawType)
diff --git a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
index 8609c80..a2df256 100644
--- a/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
+++ b/room/compiler-processing/src/test/java/androidx/room/compiler/processing/ksp/KspTypeTest.kt
@@ -99,7 +99,7 @@
assertThat(type.isError()).isTrue()
assertThat(type.typeArguments).isEmpty()
assertThat(type.typeName).isEqualTo(ERROR_TYPE_NAME)
- assertThat(type.asTypeElement().className).isEqualTo(ERROR_TYPE_NAME)
+ assertThat(type.typeElement!!.className).isEqualTo(ERROR_TYPE_NAME)
}
subject.getField("listOfErrorType").type.asDeclaredType().let { type ->
@@ -134,7 +134,7 @@
subject.getField("listOfNullableStrings").type.asDeclaredType().let { type ->
assertThat(type.nullability).isEqualTo(NONNULL)
assertThat(type.typeArguments).hasSize(1)
- assertThat(type.asTypeElement().className).isEqualTo(
+ assertThat(type.typeElement!!.className).isEqualTo(
List::class.typeName()
)
type.typeArguments.single().let { typeArg ->
@@ -158,7 +158,7 @@
)
).isTrue()
}
- assertThat(type.asTypeElement().className).isEqualTo(
+ assertThat(type.typeElement!!.className).isEqualTo(
List::class.className()
)
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
index b7b237c..89f2d6c 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/CustomConverterProcessor.kt
@@ -46,10 +46,18 @@
val annotation = element.toAnnotationBox(TypeConverters::class)
return annotation?.let {
val classes = it.getAsTypeList("value")
- .filter { it.isType() }
.mapTo(LinkedHashSet()) { it }
val converters = classes.flatMap {
- CustomConverterProcessor(context, it.asTypeElement()).process()
+ val typeElement = it.typeElement
+ if (typeElement == null) {
+ context.logger.e(
+ element,
+ ProcessorErrors.typeConverterMustBeDeclared(it.typeName)
+ )
+ emptyList()
+ } else {
+ CustomConverterProcessor(context, typeElement).process()
+ }
}
reportDuplicates(context, converters)
ProcessResult(classes, converters.map(::CustomTypeConverterWrapper))
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
index c8f13ab..f97846d 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/DatabaseProcessor.kt
@@ -82,13 +82,22 @@
}.filterNot {
// remove methods that belong to room
it.enclosingTypeElement.className == RoomTypeNames.ROOM_DB
- }.map { executable ->
+ }.mapNotNull { executable ->
// TODO when we add support for non Dao return types (e.g. database), this code needs
// to change
- val daoType = executable.returnType.asTypeElement()
- val dao = DaoProcessor(context, daoType, declaredType, dbVerifier)
- .process()
- DaoMethod(executable, executable.name, dao)
+ val daoType = executable.returnType
+ val daoElement = daoType.typeElement
+ if (daoElement == null) {
+ context.logger.e(
+ executable,
+ ProcessorErrors.DATABASE_INVALID_DAO_METHOD_RETURN_TYPE
+ )
+ null
+ } else {
+ val dao = DaoProcessor(context, daoElement, declaredType, dbVerifier)
+ .process()
+ DaoMethod(executable, executable.name, dao)
+ }
}
validateUniqueDaoClasses(element, daoMethods, entities)
@@ -290,8 +299,19 @@
entityList.isNotEmpty(), element,
ProcessorErrors.DATABASE_ANNOTATION_MUST_HAVE_LIST_OF_ENTITIES
)
- return entityList.map {
- EntityProcessor(context, it.asTypeElement()).process()
+ return entityList.mapNotNull {
+ val typeElement = it.typeElement
+ if (typeElement == null) {
+ context.logger.e(
+ element,
+ ProcessorErrors.invalidEntityTypeInDatabaseAnnotation(
+ it.typeName
+ )
+ )
+ null
+ } else {
+ EntityProcessor(context, typeElement).process()
+ }
}
}
@@ -299,9 +319,19 @@
dbAnnotation: XAnnotationBox<androidx.room.Database>
): Map<XTypeElement, DatabaseView> {
val viewList = dbAnnotation.getAsTypeList("views")
- return viewList.map {
- val viewElement = it.asTypeElement()
- viewElement to DatabaseViewProcessor(context, viewElement).process()
+ return viewList.mapNotNull {
+ val viewElement = it.typeElement
+ if (viewElement == null) {
+ context.logger.e(
+ element,
+ ProcessorErrors.invalidViewTypeInDatabaseAnnotation(
+ it.typeName
+ )
+ )
+ null
+ } else {
+ viewElement to DatabaseViewProcessor(context, viewElement).process()
+ }
}.toMap()
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
index a79d82e..eec6de9 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/FtsTableEntityProcessor.kt
@@ -167,7 +167,11 @@
if (entityType.isSameType(defaultType)) {
return null
}
- val contentEntityElement = entityType.asTypeElement()
+ val contentEntityElement = entityType.typeElement
+ if (contentEntityElement == null) {
+ context.logger.e(element, ProcessorErrors.FTS_EXTERNAL_CONTENT_CANNOT_FIND_ENTITY)
+ return null
+ }
if (!contentEntityElement.hasAnnotation(androidx.room.Entity::class)) {
context.logger.e(
contentEntityElement,
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
index d56d3ce..adccafa 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/PojoProcessor.kt
@@ -28,8 +28,8 @@
import androidx.room.compiler.processing.XType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.XVariableElement
-import androidx.room.compiler.processing.asDeclaredType
import androidx.room.compiler.processing.isCollection
+import androidx.room.compiler.processing.isDeclared
import androidx.room.ext.isNotVoid
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_GETTER_FOR_FIELD
import androidx.room.processor.ProcessorErrors.CANNOT_FIND_SETTER_FOR_FIELD
@@ -391,7 +391,14 @@
variableElement: XFieldElement
): EmbeddedField? {
val asMemberType = variableElement.asMemberOf(declaredType)
- val asTypeElement = asMemberType.asTypeElement()
+ val asTypeElement = asMemberType.typeElement
+ if (asTypeElement == null) {
+ context.logger.e(
+ variableElement,
+ ProcessorErrors.EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE
+ )
+ return null
+ }
if (detectReferenceRecursion(asTypeElement)) {
return null
@@ -448,17 +455,24 @@
context.logger.e(ProcessorErrors.CANNOT_FIND_TYPE, element)
return null
}
- val declared = asMember.asDeclaredType()
- val asType = if (declared.isCollection()) {
- declared.typeArguments.first().extendsBoundOrSelf()
+ if (!asMember.isDeclared()) {
+ context.logger.e(
+ relationElement,
+ ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
+ )
+ return null
+ }
+ val asType = if (asMember.isCollection()) {
+ asMember.typeArguments.first().extendsBoundOrSelf()
} else {
asMember
}
- if (asType.isError()) {
- context.logger.e(asType.asTypeElement(), ProcessorErrors.CANNOT_FIND_TYPE)
+ val typeElement = asType.typeElement
+ if (asType.isError() || typeElement == null) {
+ context.logger.e(typeElement ?: relationElement, ProcessorErrors.CANNOT_FIND_TYPE)
return null
}
- val typeElement = asType.asTypeElement()
+
val entityClassInput = annotation.getAsType("entity")
// do we need to decide on the entity?
@@ -466,7 +480,16 @@
val entityElement = if (inferEntity) {
typeElement
} else {
- entityClassInput!!.asTypeElement()
+ entityClassInput!!.typeElement
+ }
+ if (entityElement == null) {
+ // this should not happen as we check for declared above but for compile time
+ // null safety, it is still good to have this additional check here.
+ context.logger.e(
+ typeElement,
+ ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE
+ )
+ return null
}
if (detectReferenceRecursion(entityElement)) {
@@ -495,7 +518,14 @@
val junctionElement: XTypeElement? = if (junctionClassInput != null &&
!junctionClassInput.isTypeOf(Any::class)
) {
- junctionClassInput.asTypeElement()
+ junctionClassInput.typeElement.also {
+ if (it == null) {
+ context.logger.e(
+ relationElement,
+ ProcessorErrors.NOT_ENTITY_OR_VIEW
+ )
+ }
+ }
} else {
null
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index c380e6a..a10bbb0 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -301,6 +301,10 @@
" ${converters.joinToString(", ") { it.toString() }}"
}
+ fun typeConverterMustBeDeclared(typeName: TypeName): String {
+ return "Invalid type converter type: $typeName. Type converters must be a class."
+ }
+
// TODO must print field paths.
val POJO_FIELD_HAS_DUPLICATE_COLUMN_NAME = "Field has non-unique column name."
@@ -770,4 +774,27 @@
This mismatch might cause unexpected $fieldName values when $ownerType is read from the
database.
""".trim()
-}
+
+ val DATABASE_INVALID_DAO_METHOD_RETURN_TYPE = "Abstract database methods must return a @Dao " +
+ "annotated class or interface."
+
+ fun invalidEntityTypeInDatabaseAnnotation(typeName: TypeName): String {
+ return "Invalid Entity type: $typeName. An entity in the database must be a class."
+ }
+
+ fun invalidViewTypeInDatabaseAnnotation(typeName: TypeName): String {
+ return "Invalid View type: $typeName. Views in a database must be a class or an " +
+ "interface."
+ }
+
+ val EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE = "The type of an Embedded field must be a " +
+ "class or an interface."
+ val RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE = "Entity type in a Relation must be a class " +
+ "or an interface."
+
+ fun shortcutMethodArgumentMustBeAClass(
+ typeName: TypeName
+ ): String {
+ return "Invalid query argument: $typeName. It must be a class or an interface."
+ }
+}
\ No newline at end of file
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
index 64161db..737da25 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/RawQueryMethodProcessor.kt
@@ -74,8 +74,15 @@
private fun processObservedTables(): Set<String> {
val annotation = executableElement.toAnnotationBox(RawQuery::class)
return annotation?.getAsTypeList("observedEntities")
- ?.map {
- it.asTypeElement()
+ ?.mapNotNull {
+ it.typeElement.also { typeElement ->
+ if (typeElement == null) {
+ context.logger.e(
+ executableElement,
+ ProcessorErrors.NOT_ENTITY_OR_VIEW
+ )
+ }
+ }
}
?.flatMap {
if (it.isEntityElement()) {
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
index f174a1e..e900a25 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutMethodProcessor.kt
@@ -64,16 +64,25 @@
val targetEntity = if (targetEntityType != null &&
!targetEntityType.isTypeOf(Any::class)
) {
- processEntity(
- element = targetEntityType.asTypeElement(),
- onInvalid = {
- context.logger.e(
- executableElement,
- ProcessorErrors.INVALID_TARGET_ENTITY_IN_SHORTCUT_METHOD
- )
- return emptyMap<String, ShortcutEntity>() to emptyList()
- }
- )
+ val targetTypeElement = targetEntityType.typeElement
+ if (targetTypeElement == null) {
+ context.logger.e(
+ executableElement,
+ ProcessorErrors.INVALID_TARGET_ENTITY_IN_SHORTCUT_METHOD
+ )
+ null
+ } else {
+ processEntity(
+ element = targetTypeElement,
+ onInvalid = {
+ context.logger.e(
+ executableElement,
+ ProcessorErrors.INVALID_TARGET_ENTITY_IN_SHORTCUT_METHOD
+ )
+ return emptyMap<String, ShortcutEntity>() to emptyList()
+ }
+ )
+ }
} else {
null
}
@@ -100,31 +109,43 @@
ShortcutEntity(entity = targetEntity, partialEntity = null)
} else {
// Target entity and pojo param are not the same, process and validate partial entity.
- val pojo = PojoProcessor.createFor(
- context = context,
- element = param.pojoType.asTypeElement(),
- bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
- parent = null
- ).process().also { pojo ->
- pojo.fields.filter { targetEntity.findFieldByColumnName(it.columnName) == null }
- .forEach {
- context.logger.e(
- it.element,
- ProcessorErrors.cannotFindAsEntityField(
- targetEntity.typeName.toString()
- )
+ val pojoTypeElement = param.pojoType.typeElement
+ val pojo = if (pojoTypeElement == null) {
+ context.logger.e(
+ targetEntity.element,
+ ProcessorErrors.shortcutMethodArgumentMustBeAClass(
+ typeName = param.pojoType.typeName
+ )
+ )
+ null
+ } else {
+ PojoProcessor.createFor(
+ context = context,
+ element = pojoTypeElement,
+ bindingScope = FieldProcessor.BindingScope.BIND_TO_STMT,
+ parent = null
+ ).process().also { pojo ->
+ pojo.fields
+ .filter { targetEntity.findFieldByColumnName(it.columnName) == null }
+ .forEach {
+ context.logger.e(
+ it.element,
+ ProcessorErrors.cannotFindAsEntityField(
+ targetEntity.typeName.toString()
+ )
+ )
+ }
+
+ if (pojo.relations.isNotEmpty()) {
+ // TODO: Support Pojos with relations.
+ context.logger.e(
+ pojo.element,
+ ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY
)
}
-
- if (pojo.relations.isNotEmpty()) {
- // TODO: Support Pojos with relations.
- context.logger.e(
- pojo.element,
- ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY
- )
+ onValidatePartialEntity(targetEntity, pojo)
}
- onValidatePartialEntity(targetEntity, pojo)
}
ShortcutEntity(entity = targetEntity, partialEntity = pojo)
}
@@ -133,17 +154,26 @@
private fun extractEntities(params: List<ShortcutQueryParameter>) =
params.mapNotNull {
- val entity = processEntity(
- element = it.pojoType!!.asTypeElement(),
- onInvalid = {
- context.logger.e(
- it.element,
- ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
- )
- return@mapNotNull null
- }
- )
- it.name to ShortcutEntity(entity = entity!!, partialEntity = null)
+ val entitiyTypeElement = it.pojoType?.typeElement
+ if (entitiyTypeElement == null) {
+ context.logger.e(
+ it.element,
+ ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+ )
+ null
+ } else {
+ val entity = processEntity(
+ element = entitiyTypeElement,
+ onInvalid = {
+ context.logger.e(
+ it.element,
+ ProcessorErrors.CANNOT_FIND_ENTITY_FOR_SHORTCUT_QUERY_PARAMETER
+ )
+ return@mapNotNull null
+ }
+ )
+ it.name to ShortcutEntity(entity = entity!!, partialEntity = null)
+ }
}.toMap()
private inline fun processEntity(element: XTypeElement, onInvalid: () -> Unit) =
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
index 3491a72..7500950 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
@@ -56,18 +56,17 @@
val processingEnv = context.processingEnv
fun verifyAndPair(pojoType: XType, isMultiple: Boolean): Pair<XType?, Boolean> {
- if (!pojoType.isType()) {
- // kotlin may generate ? extends T so we should reduce it.
- val boundedVar = pojoType.extendsBound()
- return boundedVar?.let {
- verifyAndPair(boundedVar, isMultiple)
- } ?: Pair(null, isMultiple)
+ // kotlin may generate ? extends T so we should reduce it.
+ val boundedVar = pojoType.extendsBound()
+ return if (boundedVar != null) {
+ verifyAndPair(boundedVar, isMultiple)
+ } else {
+ Pair(pojoType, isMultiple)
}
- return Pair(pojoType, isMultiple)
}
fun extractPojoTypeFromIterator(iterableType: XDeclaredType): XType {
- iterableType.asTypeElement().getAllNonPrivateInstanceMethods().forEach {
+ iterableType.typeElement!!.getAllNonPrivateInstanceMethods().forEach {
if (it.name == "iterator") {
return it.asMemberOf(iterableType)
.returnType
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
index 2091b46..fe29b71 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/TableEntityProcessor.kt
@@ -227,9 +227,8 @@
)
return@map null
}
- val parentElement = try {
- it.parent.asTypeElement()
- } catch (noClass: IllegalArgumentException) {
+ val parentElement = it.parent.typeElement
+ if (parentElement == null) {
context.logger.e(element, ProcessorErrors.FOREIGN_KEY_CANNOT_FIND_PARENT)
return@map null
}
@@ -392,7 +391,7 @@
val remainingFields = availableFields.filterNot {
it.element.enclosingTypeElement == typeElement
}
- collectPrimaryKeysFromEntityAnnotations(mySuper.asTypeElement(), remainingFields)
+ collectPrimaryKeysFromEntityAnnotations(mySuper.typeElement!!, remainingFields)
} else {
emptyList()
}
@@ -445,7 +444,7 @@
// i have not declared anything, delegate to super
val mySuper = typeElement.superType
if (mySuper != null && mySuper.isNotNone()) {
- return choosePrimaryKey(candidates, mySuper.asTypeElement())
+ return choosePrimaryKey(candidates, mySuper.typeElement!!)
}
PrimaryKey.MISSING
} else {
@@ -522,8 +521,13 @@
if (typeMirror == null || typeMirror.isNone()) {
return emptyList()
}
- val parentElement = typeMirror.asTypeElement()
- val myIndices = parentElement
+ val parentTypeElement = typeMirror.typeElement
+ @Suppress("FoldInitializerAndIfToElvis")
+ if (parentTypeElement == null) {
+ // this is coming from a parent, shouldn't happen so no reason to report an error
+ return emptyList()
+ }
+ val myIndices = parentTypeElement
.toAnnotationBox(androidx.room.Entity::class)?.let { annotation ->
val indices = extractIndices(annotation, tableName = "super")
if (indices.isEmpty()) {
@@ -540,15 +544,15 @@
} else {
context.logger.w(
Warning.INDEX_FROM_PARENT_IS_DROPPED,
- parentElement,
+ parentTypeElement,
ProcessorErrors.droppedSuperClassIndex(
childEntity = element.qualifiedName,
- superEntity = parentElement.qualifiedName
+ superEntity = parentTypeElement.qualifiedName
)
)
emptyList()
}
} ?: emptyList()
- return myIndices + loadSuperIndices(parentElement.superType, tableName, inherit)
+ return myIndices + loadSuperIndices(parentTypeElement.superType, tableName, inherit)
}
}
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
index 22caffd..2bc265a 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/TypeAdapterStore.kt
@@ -479,7 +479,8 @@
if (typeMirror.isError()) {
return null
}
- if (typeMirror.isDeclared()) {
+ val typeElement = typeMirror.typeElement
+ if (typeElement != null && typeMirror.isDeclared()) {
if (typeMirror.typeArguments.isNotEmpty()) {
// TODO one day support this
return null
@@ -493,7 +494,7 @@
context.collectLogs { subContext ->
val pojo = PojoProcessor.createFor(
context = subContext,
- element = typeMirror.asTypeElement(),
+ element = typeElement,
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = null
).process()
@@ -510,12 +511,11 @@
if (rowAdapter == null && query.resultInfo == null) {
// we don't know what query returns. Check for entity.
- val asElement = typeMirror.asTypeElement()
- if (asElement.isEntityElement()) {
+ if (typeElement.isEntityElement()) {
return EntityRowAdapter(
EntityProcessor(
context = context,
- element = asElement
+ element = typeElement
).process()
)
}
@@ -545,7 +545,7 @@
// try to guess user's intention and hope that their query fits the result.
val pojo = PojoProcessor.createFor(
context = context,
- element = typeMirror.asTypeElement(),
+ element = typeElement,
bindingScope = FieldProcessor.BindingScope.READ_FROM_CURSOR,
parent = null
).process()
@@ -567,9 +567,7 @@
typeMirror: XType,
isMultipleParameter: Boolean
): QueryParameterAdapter? {
- if (typeMirror.isType() &&
- context.COMMON_TYPES.COLLECTION.rawType.isAssignableFrom(typeMirror)
- ) {
+ if (context.COMMON_TYPES.COLLECTION.rawType.isAssignableFrom(typeMirror)) {
val declared = typeMirror.asDeclaredType()
val typeArg = declared.typeArguments.first().extendsBoundOrSelf()
// An adapter for the collection type arg wrapped in the built-in collection adapter.
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
index 090dfc8..4527dd2 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/types/EnumColumnTypeAdapter.kt
@@ -72,7 +72,7 @@
private fun enumToStringMethod(scope: CodeGenScope): MethodSpec {
return scope.writer.getOrCreateMethod(object :
- ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_enumToString") {
+ ClassWriter.SharedMethodSpec(out.typeElement!!.name + "_enumToString") {
override fun getUniqueKey(): String {
return "enumToString_" + out.typeName.toString()
}
@@ -108,7 +108,7 @@
private fun stringToEnumMethod(scope: CodeGenScope): MethodSpec {
return scope.writer.getOrCreateMethod(object :
- ClassWriter.SharedMethodSpec(out.asTypeElement().name + "_stringToEnum") {
+ ClassWriter.SharedMethodSpec(out.typeElement!!.name + "_stringToEnum") {
override fun getUniqueKey(): String {
return out.typeName.toString()
}
@@ -149,7 +149,7 @@
private fun getEnumConstantElements(): List<XFieldElement> {
// TODO: Switch below logic to use`getDeclaredFields` when the
// functionality is available in the XTypeElement API
- val typeElementFields = out.asTypeElement().getAllFieldsIncludingPrivateSupers()
+ val typeElementFields = out.typeElement!!.getAllFieldsIncludingPrivateSupers()
return typeElementFields.filter {
// TODO: (b/173236324) Add kind to the X abstraction API to avoid using kindName()
ElementKind.ENUM_CONSTANT.toString().toLowerCase(Locale.US) == it.kindName()
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index 77bc8a3..d8b0dda3 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -24,6 +24,7 @@
import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
import androidx.room.testing.TestInvocation
import androidx.room.vo.CustomTypeConverter
+import com.google.common.truth.Truth
import com.google.testing.compile.CompileTester
import com.google.testing.compile.JavaFileObjects
import com.squareup.javapoet.ClassName
@@ -262,6 +263,28 @@
}.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
}
+ @Test
+ fun invalidConverterType() {
+ val source = JavaFileObjects.forSourceString(
+ "foo.bar.Container",
+ """
+ package foo.bar;
+ import androidx.room.*;
+ @TypeConverters(int.class)
+ public class Container {}
+ """
+ )
+ simpleRun(source) { invocation ->
+ val result = CustomConverterProcessor.findConverters(
+ invocation.context,
+ invocation.processingEnv.requireTypeElement("foo.bar.Container")
+ )
+ Truth.assertThat(result.converters).isEmpty()
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.typeConverterMustBeDeclared(TypeName.INT)
+ )
+ }
+
private fun createConverter(
from: TypeName,
to: TypeName,
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 63ef8a5..bce82ea 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -18,12 +18,12 @@
import COMMON
import androidx.room.RoomProcessor
-import androidx.room.parser.ParsedQuery
-import androidx.room.parser.QueryType
-import androidx.room.parser.Table
import androidx.room.compiler.processing.XDeclaredType
import androidx.room.compiler.processing.XTypeElement
import androidx.room.ext.getTypeElementsAnnotatedWith
+import androidx.room.parser.ParsedQuery
+import androidx.room.parser.QueryType
+import androidx.room.parser.Table
import androidx.room.solver.query.result.EntityRowAdapter
import androidx.room.solver.query.result.PojoRowAdapter
import androidx.room.testing.TestInvocation
@@ -32,11 +32,13 @@
import androidx.room.vo.DatabaseView
import androidx.room.vo.ReadQueryMethod
import androidx.room.vo.Warning
-import com.google.common.truth.Truth
+import com.google.common.truth.Truth.assertAbout
+import com.google.common.truth.Truth.assertThat
import com.google.testing.compile.CompileTester
import com.google.testing.compile.JavaFileObjects
import com.google.testing.compile.JavaSourcesSubjectFactory
import com.squareup.javapoet.ClassName
+import com.squareup.javapoet.TypeName
import compileLibrarySource
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.equalTo
@@ -50,6 +52,7 @@
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mockito.mock
+import simpleRun
import java.io.File
import javax.tools.JavaFileObject
import javax.tools.StandardLocation
@@ -403,7 +406,7 @@
}
"""
)
- Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(BOOK, BOOK_DAO, DB1, DB2, db1_2))
.processedWith(RoomProcessor())
.compilesWithoutError()
@@ -1070,11 +1073,66 @@
)
}
+ @Test
+ fun daoMethod_nonDeclaredReturnType() {
+ val badDaoType = JavaFileObjects.forSourceString(
+ "foo.bar.MyDb",
+ """
+ package foo.bar;
+ import androidx.room.*;
+ @Database(version = 1, entities = {})
+ public abstract class MyDb extends RoomDatabase {
+ abstract long getDao();
+ }
+ """
+ )
+ simpleRun(badDaoType) { invocation ->
+ val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
+ val result = DatabaseProcessor(
+ baseContext = invocation.context,
+ element = element
+ ).process()
+ assertThat(result.daoMethods).isEmpty()
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.DATABASE_INVALID_DAO_METHOD_RETURN_TYPE
+ )
+ }
+
+ @Test
+ fun nonDeclaredEntity() {
+ val badDaoType = JavaFileObjects.forSourceString(
+ "foo.bar.MyDb",
+ """
+ package foo.bar;
+ import androidx.room.*;
+ @Database(version = 1, entities = {long.class}, views = {int.class})
+ public abstract class MyDb extends RoomDatabase {
+ }
+ """
+ )
+ simpleRun(badDaoType) { invocation ->
+ val element = invocation.processingEnv.requireTypeElement("foo.bar.MyDb")
+ val result = DatabaseProcessor(
+ baseContext = invocation.context,
+ element = element
+ ).process()
+ assertThat(result.entities).isEmpty()
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.invalidEntityTypeInDatabaseAnnotation(
+ TypeName.LONG
+ )
+ ).and().withErrorContaining(
+ ProcessorErrors.invalidViewTypeInDatabaseAnnotation(
+ TypeName.INT
+ )
+ )
+ }
+
private fun resolveDatabaseViews(
views: Map<String, Set<String>>,
body: (List<DatabaseView>) -> Unit
): CompileTester {
- return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ return assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(DB3, BOOK))
.processedWith(
TestProcessor.builder()
@@ -1125,7 +1183,7 @@
}
"""
)
- return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ return assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(listOf(BOOK, bookDao) + dbs)
.processedWith(RoomProcessor())
}
@@ -1136,7 +1194,7 @@
classpathFiles: Set<File> = emptySet(),
handler: (Database, TestInvocation) -> Unit
): CompileTester {
- return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
+ return assertAbout(JavaSourcesSubjectFactory.javaSources())
.that(
otherFiles.toMutableList() +
JavaFileObjects.forSourceString(
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
index 893e0bd..21ec28d 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/PojoProcessorTest.kt
@@ -306,6 +306,19 @@
}
@Test
+ fun embedded_badType() {
+ singleRun(
+ """
+ int id;
+ @Embedded
+ int embeddedPrimitive;
+ """
+ ) { _ ->
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.EMBEDDED_TYPES_MUST_BE_A_CLASS_OR_INTERFACE)
+ }
+
+ @Test
fun duplicateColumnNames() {
singleRun(
"""
@@ -415,6 +428,19 @@
}
@Test
+ fun relation_notDeclared() {
+ singleRun(
+ """
+ int id;
+ @Relation(parentColumn = "id", entityColumn = "uid")
+ public long user;
+ """
+ ) { _ ->
+ }.failsToCompile()
+ .withErrorContaining(ProcessorErrors.RELATION_TYPE_MUST_BE_A_CLASS_OR_INTERFACE)
+ }
+
+ @Test
fun relation_missingParent() {
singleRun(
"""
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
index 2cd6906..7ad29b0 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/ShortcutMethodProcessorTest.kt
@@ -490,6 +490,21 @@
}.failsToCompile().withErrorContaining(ProcessorErrors.INVALID_RELATION_IN_PARTIAL_ENTITY)
}
+ @Test
+ fun targetEntity_notDeclared() {
+ singleShortcutMethod(
+ """
+ @${annotation.java.canonicalName}(entity = User.class)
+ abstract public int foo(long x);
+ """
+ ) { _, _ ->
+ }.failsToCompile().withErrorContaining(
+ ProcessorErrors.shortcutMethodArgumentMustBeAClass(
+ TypeName.LONG
+ )
+ )
+ }
+
abstract fun invalidReturnTypeError(): String
abstract fun process(