[go: nahoru, domu]

blob: 9a28341b8f726edcc870843c16161bf2d9cbedb2 [file] [log] [blame]
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.room.compiler.processing
import androidx.room.compiler.processing.XNullability.NONNULL
import androidx.room.compiler.processing.XNullability.NULLABLE
import androidx.room.compiler.processing.XNullability.UNKNOWN
import androidx.room.compiler.processing.util.Source
import androidx.room.compiler.processing.util.getField
import androidx.room.compiler.processing.util.getMethodByJvmName
import androidx.room.compiler.processing.util.getParameter
import androidx.room.compiler.processing.util.runProcessorTestWithoutKsp
import androidx.room.compiler.processing.util.runProcessorTest
import com.google.common.truth.Truth.assertThat
import com.squareup.javapoet.TypeName
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class XNullabilityTest {
/**
* This is important for javac
*/
@Test
fun elementInferredNullability() {
val source = Source.java(
"foo.bar.Baz",
"""
package foo.bar;
import androidx.annotation.*;
import java.util.List;
class Baz {
public static int primitiveInt;
public static Integer boxedInt;
@NonNull
public static List<String> nonNullAnnotated;
@Nullable
public static List<String> nullableAnnotated;
@NonNull
public String returnsNonNull() {
return "";
}
public String parameters(
int primitiveParam,
@Nullable String nullableParam,
@NonNull String nonNullParam,
List<String> unknown
) {
return "";
}
}
""".trimIndent()
)
// TODO run with KSP once https://github.com/google/ksp/issues/167 is fixed
runProcessorTestWithoutKsp(
sources = listOf(source)
) {
val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
element.getField("primitiveInt").let { field ->
assertThat(field.type.nullability).isEqualTo(NONNULL)
}
element.getField("boxedInt").let { field ->
assertThat(field.type.nullability).isEqualTo(UNKNOWN)
}
element.getField("nonNullAnnotated").let { field ->
assertThat(field.type.nullability).isEqualTo(NONNULL)
}
element.getField("nullableAnnotated").let { field ->
assertThat(field.type.nullability).isEqualTo(NULLABLE)
}
element.getMethodByJvmName("returnsNonNull").let { method ->
assertThat(method.returnType.nullability).isEqualTo(NONNULL)
assertThat(method.executableType.returnType.nullability)
.isEqualTo(NONNULL)
}
element.getMethodByJvmName("parameters").let { method ->
assertThat(method.returnType.nullability).isEqualTo(UNKNOWN)
method.getParameter("primitiveParam").let { param ->
assertThat(param.type.nullability).isEqualTo(NONNULL)
}
method.getParameter("nullableParam").let { param ->
assertThat(param.type.nullability).isEqualTo(NULLABLE)
}
method.getParameter("nonNullParam").let { param ->
assertThat(param.type.nullability).isEqualTo(NONNULL)
}
method.getParameter("unknown").let { param ->
assertThat(param.type.nullability).isEqualTo(UNKNOWN)
}
}
// also assert parameter types from executable type
element.getMethodByJvmName("parameters").executableType.let { method ->
assertThat(method.returnType.nullability).isEqualTo(UNKNOWN)
// int primitiveParam,
method.parameterTypes[0].let { paramType ->
assertThat(paramType.nullability).isEqualTo(NONNULL)
}
// @Nullable String nullableParam,
method.parameterTypes[1].let { paramType ->
assertThat(paramType.nullability).isEqualTo(NULLABLE)
}
// @NonNull String nonNullParam,
method.parameterTypes[2].let { paramType ->
assertThat(paramType.nullability).isEqualTo(NONNULL)
}
// List<String> unknown
method.parameterTypes[3].let { paramType ->
assertThat(paramType.nullability).isEqualTo(UNKNOWN)
}
}
// types inferred from type elements shouldn't have nullability
assertThat(element.type.nullability).isEqualTo(UNKNOWN)
}
}
@Test
fun kotlinNullability() {
val source = Source.kotlin(
"Baz.kt",
"""
package foo.bar;
import androidx.annotation.*;
import java.util.List;
class Baz(
val intField: Int,
val nullableIntField: Int?,
val genericFieldWithNullableTypeParam: List<Int?>
) {
fun nullableReturn(): Int? = TODO()
suspend fun suspendNullableReturn(): Int? = TODO()
fun genericWithNullableTypeArgReturn(): Map<String, Long?> = TODO()
suspend fun suspendGenericWithNullableTypeArgReturn(): Map<String, Long?> = TODO()
fun nonNullReturn(): Int = TODO()
suspend fun suspendNonNullReturn(): Int = TODO()
fun methodParams(
nonNull: Int,
nullable: Int?,
nullableGenericWithNonNullType: List<Int>?,
nullableGenericWithNullableType: List<Int?>?,
nonNullGenericWithNonNullType: List<Int>,
nonNullGenericWithNullableType: List<Int?>
) {
}
}
""".trimIndent()
)
runProcessorTest(
sources = listOf(source)
) {
val element = it.processingEnv.requireTypeElement("foo.bar.Baz")
element.getField("intField").let { field ->
assertThat(field.type.nullability).isEqualTo(NONNULL)
}
element.getField("nullableIntField").let { field ->
assertThat(field.type.nullability).isEqualTo(NULLABLE)
}
element.getField("genericFieldWithNullableTypeParam").let { field ->
assertThat(field.type.nullability).isEqualTo(NONNULL)
val declared = field.type
assertThat(declared.typeArguments.first().nullability).isEqualTo(NULLABLE)
}
element.getMethodByJvmName("nullableReturn").let { method ->
assertThat(method.returnType.nullability).isEqualTo(NULLABLE)
assertThat(method.executableType.returnType.nullability).isEqualTo(NULLABLE)
}
element.getMethodByJvmName("suspendNullableReturn").let { method ->
// kotlin adds @Nullable annotation for suspend methods' javac signature
assertThat(method.returnType.nullability).isEqualTo(NULLABLE)
val executableType = method.executableType
check(executableType.isSuspendFunction())
assertThat(executableType.returnType.nullability).isEqualTo(NULLABLE)
assertThat(executableType.getSuspendFunctionReturnType().nullability)
.isEqualTo(NULLABLE)
}
element.getMethodByJvmName("genericWithNullableTypeArgReturn").let { method ->
listOf(method.returnType, method.executableType.returnType).forEach { type ->
assertThat(type.nullability).isEqualTo(NONNULL)
assertThat(type.typeArguments[0].nullability)
.isEqualTo(NONNULL)
assertThat(type.typeArguments[1].nullability)
.isEqualTo(NULLABLE)
}
}
element.getMethodByJvmName("suspendGenericWithNullableTypeArgReturn").let { method ->
val executableType = method.executableType
check(executableType.isSuspendFunction())
executableType.getSuspendFunctionReturnType().let { type ->
assertThat(type.nullability).isEqualTo(NONNULL)
assertThat(type.typeArguments[0].nullability)
.isEqualTo(NONNULL)
assertThat(type.typeArguments[1].nullability)
.isEqualTo(NULLABLE)
}
listOf(method.returnType, executableType.returnType).forEach { type ->
// kotlin suspend functions return nullable in jvm stub
assertThat(type.nullability).isEqualTo(NULLABLE)
assertThat(type.typeArguments).isEmpty()
}
}
element.getMethodByJvmName("nonNullReturn").let { method ->
assertThat(method.returnType.nullability).isEqualTo(NONNULL)
assertThat(method.executableType.returnType.nullability).isEqualTo(NONNULL)
}
element.getMethodByJvmName("suspendNonNullReturn").let { method ->
// suspend methods return nullable in java declarations
assertThat(method.returnType.nullability).isEqualTo(NULLABLE)
val executableType = method.executableType
check(executableType.isSuspendFunction())
assertThat(executableType.returnType.nullability).isEqualTo(NULLABLE)
assertThat(executableType.getSuspendFunctionReturnType().nullability)
.isEqualTo(NONNULL)
}
element.getMethodByJvmName("methodParams").let { method ->
assertThat(method.getParameter("nonNull").type.nullability)
.isEqualTo(NONNULL)
assertThat(method.getParameter("nullable").type.nullability)
.isEqualTo(NULLABLE)
assertThat(
method.parameters.filter {
it.type.typeArguments.isNotEmpty()
}.map {
Triple(
first = it.name,
second = it.type.nullability,
third = it.type.typeArguments.single().nullability
)
}
).containsExactly(
Triple("nullableGenericWithNonNullType", NULLABLE, NONNULL),
Triple("nullableGenericWithNullableType", NULLABLE, NULLABLE),
Triple("nonNullGenericWithNonNullType", NONNULL, NONNULL),
Triple("nonNullGenericWithNullableType", NONNULL, NULLABLE)
)
}
}
}
@Test
fun changeNullability_primitives() {
runProcessorTest { invocation ->
PRIMITIVE_TYPES.forEach { primitiveTypeName ->
val primitive = invocation.processingEnv.requireType(primitiveTypeName)
assertThat(primitive.nullability).isEqualTo(NONNULL)
val nullable = primitive.makeNullable()
assertThat(nullable.nullability).isEqualTo(NULLABLE)
assertThat(nullable.typeName).isEqualTo(primitiveTypeName.box())
// When a boxed primitive is marked as non-null, it should stay as boxed primitive
// Even though this might be counter-intutive (because making it nullable will box
// it) it is more consistent as it is completely valid to annotate a boxed primitive
// with non-null while you cannot annoteted a primitive with nullable as it is not
// a valid state.
val boxedPrimitive = invocation.processingEnv.requireType(primitiveTypeName.box())
val nonNull = boxedPrimitive.makeNonNullable()
assertThat(nonNull.nullability).isEqualTo(NONNULL)
assertThat(nonNull.typeName).isEqualTo(primitiveTypeName.box())
}
}
}
@Test
fun changeNullability_typeArguments() {
// we need to make sure we don't convert type arguments into primitives!!
val kotlinSrc = Source.kotlin(
"KotlinClas.kt",
"""
class KotlinClass(val subject: List<Int?>)
""".trimIndent()
)
val javaSrc = Source.java(
"JavaClass",
"""
class JavaClass {
java.util.List<Integer> subject;
}
""".trimIndent()
)
runProcessorTest(sources = listOf(javaSrc, kotlinSrc)) { invocation ->
listOf("KotlinClass", "JavaClass").forEach {
val subject = invocation.processingEnv.requireTypeElement(it)
.getField("subject").type
val typeArg = subject.typeArguments.first()
assertThat(typeArg.typeName).isEqualTo(TypeName.INT.box())
typeArg.makeNonNullable().let {
assertThat(it.typeName).isEqualTo(TypeName.INT.box())
assertThat(it.nullability).isEqualTo(NONNULL)
}
typeArg.makeNonNullable().makeNullable().let {
assertThat(it.typeName).isEqualTo(TypeName.INT.box())
assertThat(it.nullability).isEqualTo(NULLABLE)
}
}
}
}
@Test
fun changeNullability_declared() {
runProcessorTest { invocation ->
val subject = invocation.processingEnv.requireType("java.util.List")
subject.makeNullable().let {
assertThat(it.nullability).isEqualTo(NULLABLE)
}
subject.makeNonNullable().let {
assertThat(it.nullability).isEqualTo(NONNULL)
}
// ksp defaults to non-null so we do double conversion here to ensure it flips
// nullability
subject.makeNullable().makeNonNullable().let {
assertThat(it.nullability).isEqualTo(NONNULL)
}
}
}
@Test
fun changeNullability_arrayTypes() {
runProcessorTest { invocation ->
val subject = invocation.processingEnv.getArrayType(
invocation.processingEnv.requireType("java.util.List")
)
subject.makeNullable().let {
assertThat(it.nullability).isEqualTo(NULLABLE)
assertThat(it.isArray()).isTrue()
}
subject.makeNonNullable().let {
assertThat(it.nullability).isEqualTo(NONNULL)
assertThat(it.isArray()).isTrue()
}
// ksp defaults to non-null so we do double conversion here to ensure it flips
// nullability
subject.makeNullable().makeNonNullable().let {
assertThat(it.nullability).isEqualTo(NONNULL)
assertThat(it.isArray()).isTrue()
}
}
}
@Test
fun makeNullable_void() {
val src = Source.java(
"Foo.java",
"""
class Foo {
void subject() {}
}
""".trimIndent()
)
runProcessorTest(sources = listOf(src)) { invocation ->
val voidType = invocation.processingEnv.requireTypeElement("Foo")
.getMethodByJvmName("subject").returnType
assertThat(voidType.typeName).isEqualTo(TypeName.VOID)
voidType.makeNullable().let {
assertThat(it.nullability).isEqualTo(NULLABLE)
assertThat(it.typeName).isEqualTo(TypeName.VOID.box())
}
}
}
companion object {
val PRIMITIVE_TYPES = listOf(
TypeName.BOOLEAN,
TypeName.BYTE,
TypeName.SHORT,
TypeName.INT,
TypeName.LONG,
TypeName.CHAR,
TypeName.FLOAT,
TypeName.DOUBLE,
)
}
}