| /* |
| * 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.kruth.assertThat |
| import androidx.kruth.assertWithMessage |
| import androidx.room.compiler.codegen.XClassName |
| import androidx.room.compiler.codegen.XTypeName |
| import androidx.room.compiler.codegen.XTypeName.Companion.ANY_OBJECT |
| import androidx.room.compiler.codegen.XTypeName.Companion.UNAVAILABLE_KTYPE_NAME |
| import androidx.room.compiler.processing.ksp.ERROR_JTYPE_NAME |
| import androidx.room.compiler.processing.ksp.ERROR_KTYPE_NAME |
| import androidx.room.compiler.processing.util.Source |
| import androidx.room.compiler.processing.util.XTestInvocation |
| import androidx.room.compiler.processing.util.asJClassName |
| import androidx.room.compiler.processing.util.asKClassName |
| import androidx.room.compiler.processing.util.compileFiles |
| import androidx.room.compiler.processing.util.dumpToString |
| import androidx.room.compiler.processing.util.getDeclaredField |
| import androidx.room.compiler.processing.util.getDeclaredMethodByJvmName |
| import androidx.room.compiler.processing.util.getField |
| import androidx.room.compiler.processing.util.getMethodByJvmName |
| import androidx.room.compiler.processing.util.isCollection |
| import androidx.room.compiler.processing.util.javaElementUtils |
| import androidx.room.compiler.processing.util.kspResolver |
| import androidx.room.compiler.processing.util.runKspTest |
| import androidx.room.compiler.processing.util.runProcessorTest |
| import com.google.devtools.ksp.getClassDeclarationByName |
| import com.google.testing.junit.testparameterinjector.TestParameter |
| import com.google.testing.junit.testparameterinjector.TestParameterInjector |
| import com.squareup.javapoet.ClassName |
| import com.squareup.javapoet.ParameterizedTypeName |
| import com.squareup.javapoet.TypeVariableName |
| import com.squareup.javapoet.WildcardTypeName |
| import com.squareup.kotlinpoet.INT |
| import com.squareup.kotlinpoet.MUTABLE_SET |
| import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy |
| import com.squareup.kotlinpoet.STAR |
| import com.squareup.kotlinpoet.javapoet.JClassName |
| import com.squareup.kotlinpoet.javapoet.JParameterizedTypeName |
| import com.squareup.kotlinpoet.javapoet.JTypeName |
| import com.squareup.kotlinpoet.javapoet.JTypeVariableName |
| import com.squareup.kotlinpoet.javapoet.JWildcardTypeName |
| import com.squareup.kotlinpoet.javapoet.KClassName |
| import com.squareup.kotlinpoet.javapoet.KTypeVariableName |
| import org.junit.Test |
| import org.junit.runner.RunWith |
| |
| @RunWith(TestParameterInjector::class) |
| class XTypeTest { |
| @Test |
| fun typeArguments() { |
| val parent = Source.java( |
| "foo.bar.Parent", |
| """ |
| package foo.bar; |
| import java.io.InputStream; |
| import java.util.Set; |
| import java.util.List; |
| class Parent<InputStreamType extends InputStream> { |
| public void wildcardParam(Set<?> param1) {} |
| public void rawParamType(Set param1) {} |
| public void rawParamTypeArgument(List<Set> param1) {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(parent) |
| ) { |
| val type = it.processingEnv.requireType("foo.bar.Parent") |
| assertThat(type.asTypeName().java).isEqualTo( |
| JParameterizedTypeName.get( |
| JClassName.get("foo.bar", "Parent"), |
| JClassName.get("", "InputStreamType") |
| ) |
| ) |
| if (it.isKsp) { |
| assertThat(type.asTypeName().kotlin).isEqualTo( |
| KClassName("foo.bar", "Parent") |
| .parameterizedBy( |
| KTypeVariableName( |
| "InputStreamType", |
| KClassName("java.io", "InputStream") |
| .copy(nullable = true) |
| ) |
| ) |
| ) |
| } |
| |
| val typeArguments = type.typeArguments |
| assertThat(typeArguments).hasSize(1) |
| typeArguments.first().let { firstType -> |
| val expected = TypeVariableName.get( |
| "InputStreamType", |
| JClassName.get("java.io", "InputStream") |
| ) |
| assertThat(firstType.asTypeName().java).isEqualTo(expected) |
| // equals in TypeVariableName just checks the string representation but we want |
| // to assert the upper bound as well |
| assertThat( |
| (firstType.asTypeName().java as JTypeVariableName).bounds |
| ).containsExactly( |
| JClassName.get("java.io", "InputStream") |
| ) |
| } |
| if (it.isKsp) { |
| typeArguments.first().let { firstType -> |
| val expected = KTypeVariableName( |
| "InputStreamType", |
| KClassName("java.io", "InputStream") |
| .copy(nullable = true) |
| ) |
| assertThat(firstType.asTypeName().kotlin).isEqualTo(expected) |
| assertThat( |
| (firstType.asTypeName().kotlin as KTypeVariableName).bounds |
| ).containsExactly( |
| KClassName("java.io", "InputStream") |
| .copy(nullable = true) |
| ) |
| } |
| } |
| |
| type.typeElement!!.getMethodByJvmName("wildcardParam").let { method -> |
| val wildcardParam = method.parameters.first() |
| val extendsBoundOrSelf = wildcardParam.type.extendsBoundOrSelf() |
| assertThat(wildcardParam.type.asTypeName().java).isEqualTo( |
| JParameterizedTypeName.get( |
| JClassName.get("java.util", "Set"), |
| JWildcardTypeName.subtypeOf(Any::class.java) |
| ) |
| ) |
| if (it.isKsp) { |
| assertThat(wildcardParam.type.asTypeName().kotlin).isEqualTo( |
| MUTABLE_SET.parameterizedBy(STAR).copy(nullable = true) |
| ) |
| assertThat(extendsBoundOrSelf.rawType) |
| .isEqualTo( |
| it.processingEnv.requireType("kotlin.collections.MutableSet").rawType |
| ) |
| } else { |
| assertThat(extendsBoundOrSelf.rawType) |
| .isEqualTo( |
| it.processingEnv.requireType("java.util.Set").rawType |
| ) |
| } |
| } |
| type.typeElement!!.getMethodByJvmName("rawParamType").let { method -> |
| val rawParamType = method.parameters.first() |
| assertThat(rawParamType.type.typeArguments).isEmpty() |
| assertThat(rawParamType.type.asTypeName().java).isEqualTo( |
| JClassName.get("java.util", "Set") |
| ) |
| if (it.isKsp) { |
| assertThat(rawParamType.type.asTypeName().kotlin).isEqualTo( |
| KClassName("kotlin.collections", "MutableSet").copy(nullable = true) |
| ) |
| } |
| } |
| type.typeElement!!.getMethodByJvmName("rawParamTypeArgument").let { method -> |
| val rawParamTypeArgument = method.parameters.first() |
| assertThat(rawParamTypeArgument.type.asTypeName().java).isEqualTo( |
| JParameterizedTypeName.get( |
| JClassName.get("java.util", "List"), |
| JClassName.get("java.util", "Set"), |
| ) |
| ) |
| if (it.isKsp) { |
| assertThat(rawParamTypeArgument.type.asTypeName().kotlin).isEqualTo( |
| KClassName("kotlin.collections", "MutableList").parameterizedBy( |
| KClassName("kotlin.collections", "MutableSet").copy(nullable = true) |
| ).copy(nullable = true) |
| ) |
| } |
| val rawTypeArgument = rawParamTypeArgument.type.typeArguments.single() |
| assertThat(rawTypeArgument.asTypeName().java).isEqualTo( |
| JClassName.get("java.util", "Set") |
| ) |
| if (it.isKsp) { |
| assertThat(rawTypeArgument.asTypeName().kotlin).isEqualTo( |
| KClassName("kotlin.collections", "MutableSet").copy(nullable = true) |
| ) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun errorType() { |
| val missingTypeRef = Source.java( |
| "foo.bar.Baz", |
| """ |
| package foo.bar; |
| public class Baz { |
| NotExistingType badField; |
| NotExistingType badMethod() { |
| throw new RuntimeException("Stub"); |
| } |
| } |
| """.trimIndent() |
| ) |
| |
| runProcessorTest( |
| sources = listOf(missingTypeRef) |
| ) { |
| val errorJTypeName = if (it.isKsp) { |
| // In ksp, we lose the name when resolving the type. b/175246617 |
| ERROR_JTYPE_NAME |
| } else { |
| ClassName.get("", "NotExistingType") |
| } |
| val errorKTypeName = if (it.isKsp) { |
| // In ksp, we lose the name when resolving the type. b/175246617 |
| // But otherwise the name is not available in javac / kapt |
| ERROR_KTYPE_NAME |
| } else { |
| UNAVAILABLE_KTYPE_NAME |
| } |
| val element = it.processingEnv.requireTypeElement("foo.bar.Baz") |
| element.getField("badField").let { field -> |
| assertThat(field.type.isError()).isTrue() |
| assertThat(field.type.asTypeName().java).isEqualTo(errorJTypeName) |
| assertThat(field.type.asTypeName().kotlin).isEqualTo(errorKTypeName) |
| } |
| element.getDeclaredMethodByJvmName("badMethod").let { method -> |
| assertThat(method.returnType.isError()).isTrue() |
| assertThat(method.returnType.asTypeName().java).isEqualTo(errorJTypeName) |
| assertThat(method.returnType.asTypeName().kotlin).isEqualTo(errorKTypeName) |
| } |
| it.assertCompilationResult { |
| compilationDidFail() |
| } |
| } |
| } |
| |
| @Test |
| fun sameType() { |
| val subject = Source.java( |
| "foo.bar.Baz", |
| """ |
| package foo.bar; |
| interface Baz { |
| void method(String... inputs); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(subject) |
| ) { |
| val type = it.processingEnv.requireType("foo.bar.Baz") |
| val list = it.processingEnv.requireType("java.util.List") |
| val string = it.processingEnv.requireType("java.lang.String") |
| assertThat(type.isSameType(type)).isTrue() |
| assertThat(type.isSameType(list)).isFalse() |
| assertThat(list.isSameType(string)).isFalse() |
| } |
| } |
| |
| @Test |
| fun sameType_kotlinJava() { |
| val javaSrc = Source.java( |
| "JavaClass", |
| """ |
| class JavaClass { |
| int intField; |
| Integer integerField; |
| } |
| """.trimIndent() |
| ) |
| val kotlinSrc = Source.kotlin( |
| "Foo.kt", |
| """ |
| class KotlinClass { |
| val intProp: Int = 0 |
| val integerProp : Int? = null |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(javaSrc, kotlinSrc) |
| ) { invocation -> |
| val javaElm = invocation.processingEnv.requireTypeElement("JavaClass") |
| val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass") |
| fun XFieldElement.isSameType(other: XFieldElement): Boolean { |
| return type.isSameType(other.type) |
| } |
| val fields = javaElm.getAllFieldsIncludingPrivateSupers() + |
| kotlinElm.getAllFieldsIncludingPrivateSupers() |
| val results = fields.flatMap { f1 -> |
| fields.map { f2 -> |
| f1 to f2 |
| }.filter { (first, second) -> |
| first.isSameType(second) |
| } |
| }.map { (first, second) -> |
| first.name to second.name |
| }.toList() |
| |
| val expected = setOf( |
| "intField" to "intProp", |
| "intProp" to "intField", |
| "integerField" to "integerProp", |
| "integerProp" to "integerField" |
| ) + fields.map { it.name to it.name }.toSet() |
| assertThat(results).containsExactlyElementsIn(expected) |
| } |
| } |
| |
| @Test |
| fun isCollection() { |
| runProcessorTest { |
| it.processingEnv.requireType("java.util.List").let { list -> |
| assertThat(list.isCollection()).isTrue() |
| } |
| it.processingEnv.requireType("java.util.ArrayList").let { list -> |
| // isCollection is overloaded name, it is actually just checking list or set. |
| assertThat(list.isCollection()).isFalse() |
| } |
| it.processingEnv.requireType("java.util.Set").let { list -> |
| assertThat(list.isCollection()).isTrue() |
| } |
| it.processingEnv.requireType("java.util.Map").let { list -> |
| assertThat(list.isCollection()).isFalse() |
| } |
| } |
| } |
| |
| @Test |
| fun isCollection_kotlin() { |
| runKspTest(sources = emptyList()) { invocation -> |
| val subjects = listOf( |
| "Map" to false, |
| "List" to true, |
| "Set" to true |
| ) |
| subjects.forEach { (subject, expected) -> |
| invocation.processingEnv.requireType("kotlin.collections.$subject").let { type -> |
| assertWithMessage(type.asTypeName().java.toString()) |
| .that(type.isCollection()).isEqualTo(expected) |
| if (invocation.isKsp) { |
| assertWithMessage(type.asTypeName().kotlin.toString()) |
| .that(type.isCollection()).isEqualTo(expected) |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun toStringMatchesUnderlyingElement() { |
| runProcessorTest { |
| val subject = "java.lang.String" |
| val expected = if (it.isKsp) { |
| it.kspResolver.getClassDeclarationByName(subject)?.toString() |
| } else { |
| it.javaElementUtils.getTypeElement(subject)?.toString() |
| } |
| val actual = it.processingEnv.requireType(subject).toString() |
| assertThat(actual).isEqualTo(expected) |
| } |
| } |
| |
| @Test |
| fun errorTypeForSuper() { |
| val missingTypeRef = Source.java( |
| "foo.bar.Baz", |
| """ |
| package foo.bar; |
| public class Baz extends IDontExist { |
| NotExistingType foo() { |
| throw new RuntimeException("Stub"); |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(missingTypeRef) |
| ) { |
| val element = it.processingEnv.requireTypeElement("foo.bar.Baz") |
| assertThat(element.superClass?.isError()).isTrue() |
| it.assertCompilationResult { |
| compilationDidFail() |
| } |
| } |
| } |
| |
| @Test |
| fun defaultValues() { |
| runProcessorTest { |
| assertThat( |
| it.processingEnv.requireType("int").defaultValue() |
| ).isEqualTo("0") |
| assertThat( |
| it.processingEnv.requireType("java.lang.String").defaultValue() |
| ).isEqualTo("null") |
| assertThat( |
| it.processingEnv.requireType("double").defaultValue() |
| ).isEqualTo("0.0") |
| assertThat( |
| it.processingEnv.requireType("float").defaultValue() |
| ).isEqualTo("0f") |
| assertThat( |
| it.processingEnv.requireType("char").defaultValue() |
| ).isEqualTo("0") |
| assertThat( |
| it.processingEnv.requireType("long").defaultValue() |
| ).isEqualTo("0L") |
| } |
| } |
| |
| @Test |
| fun boxed() { |
| runProcessorTest { |
| val intBoxed = it.processingEnv.requireType("int").boxed() |
| val stringBoxed = it.processingEnv.requireType("java.lang.String").boxed() |
| assertThat(intBoxed.asTypeName().java) |
| .isEqualTo(java.lang.Integer::class.asJClassName()) |
| assertThat(stringBoxed.asTypeName().java) |
| .isEqualTo(String::class.asJClassName()) |
| if (it.isKsp) { |
| assertThat(intBoxed.asTypeName().kotlin) |
| .isEqualTo(Integer::class.asKClassName()) |
| assertThat(stringBoxed.asTypeName().kotlin) |
| .isEqualTo(String::class.asKClassName()) |
| } |
| } |
| } |
| |
| @Test |
| fun rawType() { |
| runProcessorTest { |
| val subject = it.processingEnv.getDeclaredType( |
| it.processingEnv.requireTypeElement(List::class), |
| it.processingEnv.requireType(String::class) |
| ) |
| assertThat(subject.asTypeName().java).isEqualTo( |
| ParameterizedTypeName.get(List::class.asJClassName(), String::class.asJClassName()) |
| ) |
| assertThat(subject.rawType.asTypeName().java) |
| .isEqualTo(List::class.asJClassName()) |
| if (it.isKsp) { |
| assertThat(subject.asTypeName().kotlin).isEqualTo( |
| KClassName( |
| "kotlin.collections", |
| "MutableList" |
| ).parameterizedBy(String::class.asKClassName()) |
| ) |
| assertThat(subject.rawType.asTypeName().kotlin) |
| .isEqualTo(KClassName("kotlin.collections", "MutableList")) |
| } |
| |
| val listOfInts = it.processingEnv.getDeclaredType( |
| it.processingEnv.requireTypeElement(List::class), |
| it.processingEnv.requireType(Integer::class) |
| ) |
| assertThat(subject.rawType).isEqualTo(listOfInts.rawType) |
| |
| val setOfStrings = it.processingEnv.getDeclaredType( |
| it.processingEnv.requireTypeElement(Set::class), |
| it.processingEnv.requireType(String::class) |
| ) |
| assertThat(subject.rawType).isNotEqualTo(setOfStrings.rawType) |
| } |
| } |
| |
| @Test |
| fun isKotlinUnit() { |
| val kotlinSubject = Source.kotlin( |
| "Subject.kt", |
| """ |
| class KotlinSubject { |
| suspend fun unitSuspend() {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(kotlinSubject)) { invocation -> |
| invocation.processingEnv.requireTypeElement("KotlinSubject").let { |
| val continuationParam = it.getMethodByJvmName("unitSuspend").parameters.last() |
| val typeArg = continuationParam.type.typeArguments.first().let { |
| // KAPT will include the bounds directly whereas in KSP, we use bounds only |
| // when resolving the jvm wildcard type. |
| if (invocation.isKsp) { |
| it |
| } else { |
| checkNotNull(it.extendsBound()) { |
| "In KAPT, continuation should've had an extends bound" |
| } |
| } |
| } |
| assertThat( |
| typeArg.isKotlinUnit() |
| ).isTrue() |
| assertThat( |
| typeArg.extendsBound() |
| ).isNull() |
| } |
| } |
| } |
| |
| @Test |
| fun isVoidObject() { |
| val javaBase = Source.java( |
| "JavaInterface", |
| """ |
| import java.lang.Void; |
| interface JavaInterface { |
| Void getVoid(); |
| Void anotherVoid(); |
| } |
| """.trimIndent() |
| ) |
| val kotlinSubject = Source.kotlin( |
| "Subject.kt", |
| """ |
| abstract class KotlinSubject: JavaInterface { |
| fun voidMethod() {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(javaBase, kotlinSubject)) { invocation -> |
| invocation.processingEnv.requireTypeElement("KotlinSubject").let { |
| it.getMethodByJvmName("voidMethod").returnType.let { |
| assertThat(it.isVoidObject()).isFalse() |
| assertThat(it.isVoid()).isTrue() |
| assertThat(it.isKotlinUnit()).isFalse() |
| } |
| val method = it.getMethodByJvmName("getVoid") |
| method.returnType.let { |
| assertThat(it.isVoidObject()).isTrue() |
| assertThat(it.isVoid()).isFalse() |
| assertThat(it.isKotlinUnit()).isFalse() |
| } |
| it.getMethodByJvmName("anotherVoid").returnType.let { |
| assertThat(it.isVoidObject()).isTrue() |
| assertThat(it.isVoid()).isFalse() |
| assertThat(it.isKotlinUnit()).isFalse() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun selfReferencingType_kotlin() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class SelfReferencing<T : SelfReferencing<T>> { |
| fun method(sr: SelfReferencing<*>) { TODO() } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { invocation -> |
| val typeElement = invocation.processingEnv.requireTypeElement("SelfReferencing") |
| val parameter = typeElement.getMethodByJvmName("method").parameters.single() |
| val expectedTypeStringDump = """ |
| SelfReferencing<T> |
| | T |
| | > SelfReferencing<T> |
| | > | T |
| | > | > SelfReferencing<T> |
| | > | > | T |
| """.trimIndent() |
| assertThat(typeElement.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo(expectedTypeStringDump) |
| if (invocation.isKsp) { |
| assertThat(typeElement.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo(expectedTypeStringDump) |
| } |
| assertThat(parameter.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo( |
| """ |
| SelfReferencing<?> |
| | ? |
| """.trimIndent() |
| ) |
| if (invocation.isKsp) { |
| assertThat(parameter.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo( |
| """ |
| SelfReferencing<*> |
| | * |
| """.trimIndent() |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun selfReferencingType_java() { |
| val src = Source.java( |
| "SelfReferencing", |
| """ |
| class SelfReferencing<T extends SelfReferencing<T>> { |
| static void method(SelfReferencing sr) {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { invocation -> |
| val typeElement = invocation.processingEnv.requireTypeElement("SelfReferencing") |
| val parameter = typeElement.getMethodByJvmName("method").parameters.single() |
| val expectedTypeStringDump = """ |
| SelfReferencing<T> |
| | T |
| | > SelfReferencing<T> |
| | > | T |
| | > | > SelfReferencing<T> |
| | > | > | T |
| """.trimIndent() |
| assertThat(typeElement.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo(expectedTypeStringDump) |
| if (invocation.isKsp) { |
| val expectedTypeStringDumpKotlin = """ |
| SelfReferencing<T> |
| | T |
| | > SelfReferencing<T?>? |
| | > | T? |
| | > | > SelfReferencing<T?>? |
| | > | > | T? |
| """.trimIndent() |
| assertThat(typeElement.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo(expectedTypeStringDumpKotlin) |
| } |
| assertThat(parameter.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo("SelfReferencing") |
| if (invocation.isKsp) { |
| assertThat(parameter.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo("SelfReferencing?") |
| } |
| } |
| } |
| |
| @Test |
| fun multiLevelSelfReferencingType() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| open class Node<TX : Node<TX, RX>, RX : Node<RX, TX>> { |
| fun allStar(node : Node<*, *>) { TODO() } |
| fun secondStar(node : Node<TX, *>) { TODO() } |
| fun firstStar(node : Node<*, RX>) { TODO() } |
| fun noStar(node : Node<TX, RX>) { TODO() } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { invocation -> |
| val nodeElm = invocation.processingEnv.requireTypeElement("Node") |
| val expectedStringDump = """ |
| Node<TX, RX> |
| | TX |
| | > Node<TX, RX> |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | RX |
| | > Node<RX, TX> |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| """.trimIndent() |
| assertThat(nodeElm.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo(expectedStringDump) |
| if (invocation.isKsp) { |
| assertThat(nodeElm.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo(expectedStringDump) |
| } |
| val expectedStringDumps = mapOf( |
| "allStar" to """ |
| Node<?, ?> |
| | ? |
| | ? |
| """.trimIndent(), |
| "firstStar" to """ |
| Node<?, RX> |
| | ? |
| | RX |
| | > Node<RX, TX> |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| """.trimIndent(), |
| "secondStar" to """ |
| Node<TX, ?> |
| | TX |
| | > Node<TX, RX> |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | ? |
| """.trimIndent(), |
| "noStar" to """ |
| Node<TX, RX> |
| | TX |
| | > Node<TX, RX> |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | RX |
| | > Node<RX, TX> |
| | > | RX |
| | > | > Node<RX, TX> |
| | > | > | RX |
| | > | > | TX |
| | > | TX |
| | > | > Node<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| """.trimIndent() |
| ) |
| nodeElm.getDeclaredMethods().associate { |
| it.name to it.parameters.single().type.asTypeName() |
| }.let { |
| assertThat(it.mapValues { entry -> entry.value.java.dumpToString(5) }) |
| .containsExactlyEntriesIn(expectedStringDumps) |
| if (invocation.isKsp) { |
| assertThat(it.mapValues { entry -> entry.value.kotlin.dumpToString(5) }) |
| .containsExactlyEntriesIn( |
| // Quick replace ? to * to correctly compare with KotlinPoet |
| expectedStringDumps.mapValues { entry -> |
| entry.value.replace('?', '*') |
| } |
| ) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun selfReferencing_withGenericClassBounds() { |
| val src = Source.kotlin( |
| "SelfReferencing.kt", |
| """ |
| class SelfReferencing<TX : SelfReferencing<TX, RX>, RX : List<TX>> |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { invocation -> |
| val elm = invocation.processingEnv.requireTypeElement("SelfReferencing") |
| val typeDump = elm.type.typeName.dumpToString(5) |
| // KSP and Javac diverge here when the generic type parameter in the declaration has |
| // variance. This test is kept here to know that the difference is expected. |
| // KAPT generates a wildcard for the List<T> from the variance of it. In XProcessing, |
| // such resolution is expected to happen at code generation time. |
| // |
| // This inconsistency is not great but it is fairly complicated to do variance |
| // resolution (see: OverrideVarianceResolver.kt) and this level of detail almost |
| // never matters. |
| if (invocation.isKsp) { |
| assertThat(typeDump).isEqualTo( |
| """ |
| SelfReferencing<TX, RX> |
| | TX |
| | > SelfReferencing<TX, RX> |
| | > | TX |
| | > | > SelfReferencing<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > java.util.List<TX> |
| | > | > | TX |
| | RX |
| | > java.util.List<TX> |
| | > | TX |
| | > | > SelfReferencing<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| """.trimIndent() |
| ) |
| } else { |
| assertThat(typeDump).isEqualTo( |
| """ |
| SelfReferencing<TX, RX> |
| | TX |
| | > SelfReferencing<TX, RX> |
| | > | TX |
| | > | > SelfReferencing<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > java.util.List<? extends TX> |
| | > | > | ? extends TX |
| | RX |
| | > java.util.List<? extends TX> |
| | > | ? extends TX |
| """.trimIndent() |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun selfReferencing_withGeneric() { |
| val src = Source.kotlin( |
| "SelfReferencing.kt", |
| """ |
| class Generic<T> |
| class SelfReferencing<TX : SelfReferencing<TX, RX>, RX : Generic<TX>> |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { invocation -> |
| val elm = invocation.processingEnv.requireTypeElement("SelfReferencing") |
| val expected = """ |
| SelfReferencing<TX, RX> |
| | TX |
| | > SelfReferencing<TX, RX> |
| | > | TX |
| | > | > SelfReferencing<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| | > | RX |
| | > | > Generic<TX> |
| | > | > | TX |
| | RX |
| | > Generic<TX> |
| | > | TX |
| | > | > SelfReferencing<TX, RX> |
| | > | > | TX |
| | > | > | RX |
| """.trimIndent() |
| assertThat(elm.type.asTypeName().java.dumpToString(5)) |
| .isEqualTo(expected) |
| if (invocation.isKsp) { |
| assertThat(elm.type.asTypeName().kotlin.dumpToString(5)) |
| .isEqualTo(expected) |
| } |
| } |
| } |
| |
| /** |
| * Repro for b/208207043 |
| */ |
| @Test |
| fun selfReferencing_starProjectedJava() { |
| val src = Source.kotlin( |
| "StyleBuilder.kt", |
| """ |
| class StyleApplier<X, Y> |
| class StyleBuilder<out B : StyleBuilder<B, A>, out A : StyleApplier<*, *>> |
| class KotlinSubject { |
| fun subject_1(builder: StyleBuilder<*, *>) { |
| } |
| } |
| """.trimIndent() |
| ) |
| val javaSource = Source.java( |
| "JavaSubject", |
| """ |
| public class JavaSubject { |
| static void subject_1(StyleBuilder<?, ?> builder) { |
| } |
| static void subject_2(StyleBuilder builder) { |
| } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src, javaSource) |
| ) { invocation -> |
| val styleApplier = invocation.processingEnv.requireType("StyleApplier") |
| val styleBuilder = invocation.processingEnv.requireType("StyleBuilder") |
| assertThat(styleApplier.typeName.dumpToString(5)).isEqualTo( |
| """ |
| StyleApplier<X, Y> |
| | X |
| | Y |
| """.trimIndent() |
| ) |
| // we don't match what kapt generates here so this test is kept here to acknowledge the |
| // skew. Otoh, definition of B extending a type that has a type parameter that |
| // extends B is very very weird in practice :). |
| val bArgSignature = if (invocation.isKsp) { |
| "StyleBuilder<B, A>" |
| } else { |
| "StyleBuilder<? extends B, ? extends A>" |
| } |
| assertThat(styleBuilder.typeName.dumpToString(2)).isEqualTo( |
| """ |
| StyleBuilder<B, A> |
| | B |
| | > $bArgSignature |
| | A |
| | > StyleApplier<?, ?> |
| """.trimIndent() |
| ) |
| |
| val javaSubject = invocation.processingEnv.requireTypeElement("JavaSubject") |
| val kotlinSubject = invocation.processingEnv.requireTypeElement("KotlinSubject") |
| // detect raw java types properly to be consistent |
| assertThat( |
| javaSubject.getMethodByJvmName("subject_2").parameters.single() |
| .type.typeName.dumpToString(5) |
| ).isEqualTo("StyleBuilder") |
| |
| val javaTypeName = javaSubject.getMethodByJvmName("subject_1").parameters.single() |
| .type.typeName.dumpToString(5) |
| val kotlinTypeName = kotlinSubject.getMethodByJvmName("subject_1").parameters.single() |
| .type.typeName.dumpToString(5) |
| assertThat(javaTypeName).isEqualTo( |
| """ |
| StyleBuilder<?, ?> |
| | ? |
| | ? |
| """.trimIndent() |
| ) |
| assertThat(kotlinTypeName).isEqualTo( |
| """ |
| StyleBuilder<?, ?> |
| | ? |
| | ? |
| """.trimIndent() |
| ) |
| } |
| } |
| |
| /** |
| * Reproduces the first bug in b/204415667 |
| */ |
| @Test |
| fun starVarianceInParameter() { |
| val libSource = Source.kotlin( |
| "lib.kt", |
| """ |
| class MyClass<R> { |
| fun setLists(starList: List<*>, rList: List<R>) {} |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest(listOf(libSource)) { invocation -> |
| val actual = invocation.processingEnv.requireTypeElement("MyClass") |
| .getDeclaredMethodByJvmName("setLists").parameters.associate { |
| it.name to it.type.asTypeName() |
| } |
| assertThat(actual["starList"]?.java.toString()) |
| .isEqualTo("java.util.List<?>") |
| assertThat(actual["rList"]?.java.toString()) |
| .isEqualTo("java.util.List<? extends R>") |
| if (invocation.isKsp) { |
| assertThat(actual["starList"]?.kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<*>") |
| assertThat(actual["rList"]?.kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<R>") |
| } |
| } |
| } |
| |
| @Test |
| fun superTypes() { |
| val libSource = Source.kotlin( |
| "foo.kt", |
| """ |
| package foo.bar; |
| class Baz : MyInterface, AbstractClass<String>() { |
| } |
| abstract class AbstractClass<T> {} |
| interface MyInterface {} |
| """.trimIndent() |
| ) |
| runProcessorTest(listOf(libSource)) { invocation -> |
| invocation.processingEnv.requireType("foo.bar.Baz").let { |
| val superTypes = it.superTypes |
| assertThat(superTypes).hasSize(2) |
| val superClass = |
| superTypes.first { type -> type.rawType.toString() == "foo.bar.AbstractClass" } |
| val superInterface = |
| superTypes.first { type -> type.rawType.toString() == "foo.bar.MyInterface" } |
| assertThat(superClass.typeArguments).hasSize(1) |
| assertThat(superClass.typeArguments[0].asTypeName().java) |
| .isEqualTo(JClassName.get("java.lang", "String")) |
| if (invocation.isKsp) { |
| assertThat(superClass.typeArguments[0].asTypeName().kotlin) |
| .isEqualTo(KClassName("kotlin", "String")) |
| } |
| assertThat(superInterface.typeArguments).isEmpty() |
| } |
| } |
| } |
| |
| @Test |
| fun arrayTypes() { |
| // Used to test both java and kotlin sources. |
| fun XTestInvocation.checkArrayTypesTest() { |
| val fooElement = processingEnv.requireTypeElement("foo.bar.Foo") |
| val barElement = processingEnv.requireTypeElement("foo.bar.Bar") |
| val barType = fooElement.getField("bar").type |
| val barArrayType = fooElement.getField("barArray").type |
| |
| assertThat(barType.typeElement).isEqualTo(barElement) |
| assertThat(barType.isArray()).isFalse() |
| assertThat(barArrayType.typeElement).isNull() |
| assertThat(barArrayType.isArray()).isTrue() |
| } |
| |
| runProcessorTest(listOf(Source.java( |
| "foo.bar.Foo", |
| """ |
| package foo.bar; |
| class Foo { |
| Bar bar; |
| Bar[] barArray; |
| } |
| class Bar {} |
| """.trimIndent() |
| ))) { it.checkArrayTypesTest() } |
| |
| runProcessorTest(listOf(Source.kotlin( |
| "foo.bar.Foo.kt", |
| """ |
| package foo.bar; |
| class Foo { |
| val bar: Bar = TODO() |
| val barArray: Array<Bar> = TODO() |
| } |
| class Bar |
| """.trimIndent() |
| ))) { it.checkArrayTypesTest() } |
| } |
| |
| @Test |
| fun primitiveTypes() { |
| fun XTestInvocation.checkPrimitiveType() { |
| val fooElement = processingEnv.requireTypeElement("foo.bar.Foo") |
| val primitiveType = fooElement.getField("i").type |
| assertThat(primitiveType.asTypeName().java).isEqualTo(JTypeName.INT) |
| if (isKsp) { |
| assertThat(primitiveType.asTypeName().kotlin).isEqualTo(INT) |
| } |
| assertThat(primitiveType.typeElement).isNull() |
| } |
| |
| runProcessorTest(listOf(Source.java( |
| "foo.bar.Foo", |
| """ |
| package foo.bar; |
| class Foo { |
| int i; |
| } |
| """.trimIndent() |
| ))) { it.checkPrimitiveType() } |
| |
| runProcessorTest(listOf(Source.kotlin( |
| "foo.bar.Foo.kt", |
| """ |
| package foo.bar |
| class Foo { |
| val i: Int = TODO() |
| } |
| """.trimIndent() |
| ))) { it.checkPrimitiveType() } |
| } |
| |
| @Test |
| fun setSuperTypeNames() { |
| fun superTypeHierarchy(type: XType, depth: Int = 0): String { |
| val sb: StringBuilder = StringBuilder() |
| sb.append("${" ".repeat(depth)}> ${type.typeName}") |
| type.superTypes.forEach { |
| sb.append("\n").append(superTypeHierarchy(it, depth + 1)) |
| } |
| return sb.toString() |
| } |
| |
| fun XTestInvocation.checkType() { |
| val fooElement = processingEnv.requireTypeElement("test.Foo") |
| val method1 = fooElement.getMethodByJvmName("method1") |
| val method2 = fooElement.getMethodByJvmName("method2") |
| |
| // Check the return types of the unresolved methods |
| assertThat(method1.returnType.typeName).isEqualTo(TypeVariableName.get("T1")) |
| assertThat(method2.returnType.typeName).isEqualTo(TypeVariableName.get("T2")) |
| |
| // Check the return types of the methods resolved into Usage |
| val usageElement = processingEnv.requireTypeElement("test.Usage") |
| assertThat(method1.asMemberOf(usageElement.type).returnType.typeName.toString()) |
| .isEqualTo("test.Baz<java.lang.Long, java.lang.Number>") |
| assertThat(method2.asMemberOf(usageElement.type).returnType.typeName.toString()) |
| .isEqualTo("java.lang.Integer") |
| |
| // Check the supertypes of the unresolved Foo |
| assertThat(superTypeHierarchy(fooElement.type)).isEqualTo( |
| """ |
| > test.Foo<V1, V2> |
| > java.lang.Object |
| > test.Bar<test.Baz<V1, java.lang.Number>, V2> |
| > java.lang.Object |
| > test.Baz<test.Baz<V1, java.lang.Number>, V2> |
| > java.lang.Object |
| """.trimIndent() |
| ) |
| |
| // Check the supertypes of Foo<Long, Integer> |
| assertThat(superTypeHierarchy(usageElement.type)).isEqualTo( |
| """ |
| > test.Usage |
| > java.lang.Object |
| > test.Foo<java.lang.Long, java.lang.Integer> |
| > java.lang.Object |
| > test.Bar<test.Baz<java.lang.Long, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| > test.Baz<test.Baz<java.lang.Long, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| """.trimIndent() |
| ) |
| |
| // Check the supertypes of Foo<String, Integer> |
| val methodFoo = usageElement.getMethodByJvmName("foo") |
| assertThat(superTypeHierarchy(methodFoo.returnType)).isEqualTo( |
| """ |
| > test.Foo<java.lang.String, java.lang.Integer> |
| > java.lang.Object |
| > test.Bar<test.Baz<java.lang.String, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| > test.Baz<test.Baz<java.lang.String, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| """.trimIndent() |
| ) |
| |
| // Check the supertypes of Foo<Double, Integer> |
| assertThat(superTypeHierarchy(methodFoo.parameters[0].type)).isEqualTo( |
| """ |
| > test.Foo<java.lang.Double, java.lang.Integer> |
| > java.lang.Object |
| > test.Bar<test.Baz<java.lang.Double, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| > test.Baz<test.Baz<java.lang.Double, java.lang.Number>, java.lang.Integer> |
| > java.lang.Object |
| """.trimIndent() |
| ) |
| } |
| |
| runProcessorTest(listOf(Source.java( |
| "test.Usage", |
| """ |
| package test; |
| interface Usage extends Foo<Long, Integer> { |
| Foo<String, Integer> foo(Foo<Double, Integer> param); |
| } |
| interface Foo<V1, V2 extends Integer> extends Bar<Baz<V1, Number>, V2> {} |
| interface Bar<U1, U2 extends Integer> extends Baz<U1, U2> {} |
| interface Baz<T1, T2 extends Number> { |
| T1 method1(); |
| T2 method2(); |
| } |
| """.trimIndent() |
| ))) { it.checkType() } |
| |
| runProcessorTest(listOf(Source.kotlin( |
| "test.Usage.kt", |
| """ |
| package test |
| interface Usage : Foo<Long, Integer> { |
| fun foo(param: Foo<Double, Integer>): Foo<String, Integer> |
| } |
| interface Foo<V1, V2: Integer> : Bar<Baz<V1, Number>, V2> {} |
| interface Bar<U1, U2: Integer> : Baz<U1, U2> {} |
| interface Baz<T1, T2: Number> { |
| fun method1(): T1 |
| fun method2(): T2 |
| } |
| """.trimIndent() |
| ))) { it.checkType() } |
| } |
| |
| @Test |
| fun typeArgumentMissingType() { |
| class TypeArgumentProcessingStep : XProcessingStep { |
| override fun annotations() = setOf("test.Inspect") |
| |
| override fun process( |
| env: XProcessingEnv, |
| elementsByAnnotation: Map<String, Set<XElement>>, |
| isLastRound: Boolean |
| ): Set<XElement> { |
| val barElement = env.requireTypeElement("test.Bar") |
| val missingTypeName = if ( |
| env.backend == XProcessingEnv.Backend.KSP || |
| // There's a bug in KAPT that doesn't replace NonExistentClass even when |
| // correctErrorTypes is enabled, so we account for that here. |
| // https://youtrack.jetbrains.com/issue/KT-34193/Kapt-CorrectErrorTypes-doesnt-work-for-generics |
| barElement.hasAnnotation(Metadata::class) |
| ) { |
| ClassName.get("error", "NonExistentClass") |
| } else { |
| ClassName.get("", "MissingType") |
| } |
| val barType = barElement.type |
| val fooTypeName = ParameterizedTypeName.get( |
| ClassName.get("test", "Foo"), |
| missingTypeName |
| ) |
| |
| val fooType = barType.superTypes.single() |
| assertThat(fooType.typeName).isEqualTo(fooTypeName) |
| assertThat(fooType.isError()).isFalse() |
| |
| val typeArgument = fooType.typeArguments.single() |
| assertThat(typeArgument.typeName).isEqualTo(missingTypeName) |
| assertThat(typeArgument.isError()).isTrue() |
| |
| return emptySet() |
| } |
| } |
| |
| val step = TypeArgumentProcessingStep() |
| runProcessorTest( |
| sources = listOf(Source.java( |
| "test.Foo", |
| """ |
| package test; |
| @Inspect |
| class Bar extends Foo<MissingType> {} |
| class Foo<T> {} |
| @interface Inspect {} |
| """.trimIndent() |
| )), |
| ) { invocation -> |
| val elements = |
| step.annotations() |
| .associateWith { annotation -> |
| invocation.roundEnv.getElementsAnnotatedWith(annotation) |
| .filterIsInstance<XTypeElement>() |
| .toSet() |
| } |
| step.process( |
| env = invocation.processingEnv, |
| elementsByAnnotation = elements, |
| isLastRound = false |
| ) |
| invocation.assertCompilationResult { |
| hasError() |
| hasErrorCount(1) |
| hasErrorContaining("cannot find symbol") |
| } |
| } |
| |
| runProcessorTest( |
| sources = listOf(Source.kotlin( |
| "test.Foo.kt", |
| """ |
| package test |
| class Bar : Foo<MissingType>() |
| open class Foo<T> |
| """.trimIndent() |
| )), |
| kotlincArguments = listOf( |
| "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true" |
| ), |
| createProcessingSteps = { listOf(TypeArgumentProcessingStep()) } |
| ) { result -> |
| result.hasError() |
| result.hasErrorCount(1) |
| result.hasErrorContaining("Unresolved reference") |
| } |
| } |
| |
| @Test |
| fun wildcardWithMissingType() { |
| class WildcardProcessingStep : XProcessingStep { |
| override fun annotations() = setOf("test.Inspect") |
| |
| override fun process( |
| env: XProcessingEnv, |
| elementsByAnnotation: Map<String, Set<XElement>>, |
| isLastRound: Boolean |
| ): Set<XElement> { |
| val missingTypeName = if (env.backend == XProcessingEnv.Backend.KSP) { |
| ClassName.get("error", "NonExistentClass") |
| } else { |
| ClassName.get("", "MissingType") |
| } |
| val wildcardTypeName = WildcardTypeName.subtypeOf(missingTypeName) |
| val fooTypeName = ParameterizedTypeName.get( |
| ClassName.get("test", "Foo"), |
| wildcardTypeName |
| ) |
| |
| val fooElement = env.requireTypeElement("test.Foo") |
| val fooType = fooElement.getField("foo").type |
| assertThat(fooType.typeName).isEqualTo(fooTypeName) |
| assertThat(fooType.isError()).isFalse() |
| |
| val wildcardType = fooType.typeArguments.single() |
| assertThat(wildcardType.typeName).isEqualTo(wildcardTypeName) |
| assertThat(wildcardType.isError()).isFalse() |
| |
| assertThat(wildcardType.extendsBound()).isNotNull() |
| val errorType = wildcardType.extendsBound()!! |
| assertThat(errorType.typeName).isEqualTo(missingTypeName) |
| assertThat(errorType.isError()).isTrue() |
| |
| return emptySet() |
| } |
| } |
| |
| runProcessorTest( |
| sources = listOf(Source.java( |
| "test.Foo", |
| """ |
| package test; |
| @Inspect |
| class Foo<T> { |
| Foo<? extends MissingType> foo; |
| } |
| @interface Inspect {} |
| """.trimIndent() |
| )), |
| createProcessingSteps = { listOf(WildcardProcessingStep()) } |
| ) { result -> |
| result.hasError() |
| result.hasErrorCount(1) |
| result.hasErrorContaining("cannot find symbol") |
| } |
| |
| runProcessorTest( |
| sources = listOf(Source.kotlin( |
| "test.Foo.kt", |
| """ |
| package test |
| class Foo<T> { |
| val foo: Foo<out MissingType> = TODO() |
| } |
| """.trimIndent() |
| )), |
| kotlincArguments = listOf( |
| "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true" |
| ), |
| createProcessingSteps = { listOf(WildcardProcessingStep()) } |
| ) { result -> |
| result.hasError() |
| result.hasErrorCount(1) |
| result.hasErrorContaining("Unresolved reference") |
| } |
| } |
| |
| @Test |
| fun getWildcardType() { |
| fun XTestInvocation.checkType(isJavaSrc: Boolean) { |
| val usageElement = processingEnv.requireTypeElement("test.Usage") |
| val fooElement = processingEnv.requireTypeElement("test.Foo") |
| val barType = processingEnv.requireType("test.Bar").run { |
| if (isJavaSrc) makeNullable() else makeNonNullable() |
| } |
| |
| // Test a manually constructed Foo<Bar> |
| val fooBarType = processingEnv.getDeclaredType(fooElement, barType).run { |
| if (isJavaSrc) makeNullable() else makeNonNullable() |
| } |
| val fooBarUsageType = usageElement.getDeclaredField("fooBar").type |
| assertThat(fooBarUsageType.asTypeName()).isEqualTo(fooBarType.asTypeName()) |
| |
| // Test a manually constructed Foo<? extends Bar> |
| val fooExtendsBarType = processingEnv.getDeclaredType( |
| fooElement, |
| processingEnv.getWildcardType(producerExtends = barType) |
| ).run { |
| if (isJavaSrc) makeNullable() else makeNonNullable() |
| } |
| val fooExtendsBarUsageType = usageElement.getDeclaredField("fooExtendsBar").type |
| assertThat(fooExtendsBarUsageType.asTypeName()) |
| .isEqualTo(fooExtendsBarType.asTypeName()) |
| |
| // Test a manually constructed Foo<? super Bar> |
| val fooSuperBarType = processingEnv.getDeclaredType( |
| fooElement, |
| processingEnv.getWildcardType(consumerSuper = barType) |
| ).run { |
| if (isJavaSrc) makeNullable() else makeNonNullable() |
| } |
| val fooSuperBarUsageType = usageElement.getDeclaredField("fooSuperBar").type |
| assertThat(fooSuperBarUsageType.asTypeName()).isEqualTo(fooSuperBarType.asTypeName()) |
| |
| // Test a manually constructed Foo<?> |
| val fooUnboundedType = processingEnv.getDeclaredType( |
| fooElement, |
| processingEnv.getWildcardType() |
| ).run { |
| if (isJavaSrc) makeNullable() else makeNonNullable() |
| } |
| val fooUnboundedUsageType = usageElement.getDeclaredField("fooUnbounded").type |
| assertThat(fooUnboundedType.asTypeName()).isEqualTo(fooUnboundedUsageType.asTypeName()) |
| } |
| |
| runProcessorTest(listOf(Source.java( |
| "test.Foo", |
| """ |
| package test; |
| class Usage { |
| Foo<?> fooUnbounded; |
| Foo<Bar> fooBar; |
| Foo<? extends Bar> fooExtendsBar; |
| Foo<? super Bar> fooSuperBar; |
| } |
| interface Foo<T> {} |
| interface Bar {} |
| """.trimIndent() |
| ))) { it.checkType(isJavaSrc = true) } |
| |
| runProcessorTest(listOf(Source.kotlin( |
| "Usage.kt", |
| """ |
| package test |
| class Usage { |
| val fooUnbounded: Foo<*> = TODO() |
| val fooBar: Foo<Bar> = TODO() |
| val fooExtendsBar: Foo<out Bar> = TODO() |
| val fooSuperBar: Foo<in Bar> = TODO() |
| } |
| interface Foo<T> |
| interface Bar |
| """.trimIndent() |
| ))) { it.checkType(isJavaSrc = false) } |
| } |
| |
| @Test |
| fun isTypeVariable() { |
| val javaSubject = Source.java( |
| "test.JavaFoo", |
| """ |
| package test; |
| class JavaFoo<T> { |
| T field; |
| T method(T param) { |
| return null; |
| } |
| } |
| """.trimIndent() |
| ) |
| val javaImplSubject = Source.java( |
| "test.JavaFooImpl", |
| """ |
| package test; |
| class JavaFooImpl extends JavaFoo<String> { |
| } |
| """.trimIndent() |
| ) |
| val kotlinSubject = Source.kotlin( |
| "Foo.kt", |
| """ |
| package test |
| open class KotlinFoo<T> { |
| val field: T = TODO(); |
| fun method(param: T): T { |
| TODO() |
| } |
| } |
| |
| class KotlinFooImpl : KotlinFoo<String>() |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(javaSubject, javaImplSubject, kotlinSubject) |
| ) { invocation -> |
| listOf("test.JavaFoo", "test.KotlinFoo").forEach { fqn -> |
| val typeElement = invocation.processingEnv.requireTypeElement(fqn) |
| typeElement.getDeclaredField("field").let { |
| assertThat(it.type.isTypeVariable()).isTrue() |
| val asMemberOf = |
| it.asMemberOf(invocation.processingEnv.requireType(fqn + "Impl")) |
| assertThat(asMemberOf.isTypeVariable()).isFalse() |
| } |
| typeElement.getDeclaredMethodByJvmName("method").let { |
| assertThat(it.returnType.isTypeVariable()).isTrue() |
| assertThat(it.parameters.single().type.isTypeVariable()).isTrue() |
| val asMemberOf = |
| it.asMemberOf(invocation.processingEnv.requireType(fqn + "Impl")) |
| assertThat(asMemberOf.returnType.isTypeVariable()).isFalse() |
| assertThat(asMemberOf.parameterTypes.single().isTypeVariable()).isFalse() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun typeParameter_extendBound() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| class Foo<E> { |
| fun justOneGeneric(): E = TODO() |
| fun listOfGeneric(): List<E> = TODO() |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src) |
| ) { |
| val fooTypeElement = it.processingEnv.requireTypeElement("Foo") |
| fooTypeElement.getMethodByJvmName("justOneGeneric").returnType.let { type -> |
| assertThat(type.extendsBound()).isNull() |
| } |
| fooTypeElement.getMethodByJvmName("listOfGeneric").returnType.let { type -> |
| assertThat(type.extendsBound()).isNull() |
| type.typeArguments.forEach { typeArg -> |
| assertThat(typeArg.extendsBound()).isNull() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun missingTypes_names() { |
| val src = Source.kotlin( |
| "Foo.kt", |
| """ |
| package test |
| |
| class Foo { |
| fun bar(missing: MissingType) = TODO() |
| fun barQualified(missing: bar.MissingType) = TODO() |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf(src), |
| kotlincArguments = listOf( |
| "-P", "plugin:org.jetbrains.kotlin.kapt3:correctErrorTypes=true" |
| ), |
| ) { |
| val fooTypeElement = it.processingEnv.requireTypeElement("test.Foo") |
| fooTypeElement.getMethodByJvmName("bar").parameters.single().let { param -> |
| if (it.isKsp) { |
| // TODO(b/248552462): KSP doesn't expose simple names on error types, see: |
| // https://github.com/google/ksp/issues/1232 |
| assertThat(param.type.asTypeName()) |
| .isEqualTo(XClassName.get("error", "NonExistentClass")) |
| } else { |
| assertThat(param.type.asTypeName()) |
| .isEqualTo(XClassName.get("", "MissingType")) |
| } |
| } |
| fooTypeElement.getMethodByJvmName("barQualified").parameters.single().let { param -> |
| if (it.isKsp) { |
| // TODO(b/248552462): KSP doesn't expose simple names on error types, see: |
| // https://github.com/google/ksp/issues/1232 |
| assertThat(param.type.asTypeName()) |
| .isEqualTo(XClassName.get("error", "NonExistentClass")) |
| } else { |
| assertThat(param.type.asTypeName()) |
| .isEqualTo(XClassName.get("", "bar.MissingType")) |
| } |
| } |
| it.assertCompilationResult { hasErrorContaining("Unresolved reference: MissingType") } |
| } |
| } |
| |
| @Test |
| fun rawTypeNames() { |
| val src = Source.java( |
| "test.Subject", |
| """ |
| package test; |
| import java.util.Set; |
| @SuppressWarnings("rawtypes") |
| class Subject { |
| Foo foo; |
| Foo<Foo> fooFoo; |
| Foo<Foo<Foo>> fooFooFoo; |
| Bar<Foo, Foo> barFooFoo; |
| } |
| class Foo<T> {} |
| class Bar<T1, T2> {} |
| """.trimIndent() |
| ) |
| runProcessorTest(sources = listOf(src)) { invocation -> |
| val fooTypeName = XClassName.get("test", "Foo") |
| val barTypeName = XClassName.get("test", "Bar") |
| |
| fun assertHasTypeName(type: XType, expectedTypeName: XTypeName) { |
| assertThat(type.asTypeName()).isEqualTo(expectedTypeName) |
| } |
| |
| val subject = invocation.processingEnv.requireTypeElement("test.Subject") |
| assertHasTypeName( |
| type = subject.getDeclaredField("foo").type, |
| expectedTypeName = fooTypeName.copy(nullable = true) |
| ) |
| assertHasTypeName( |
| type = subject.getDeclaredField("fooFoo").type, |
| expectedTypeName = fooTypeName.parametrizedBy( |
| fooTypeName.copy(nullable = true) |
| ).copy(nullable = true) |
| ) |
| assertHasTypeName( |
| type = subject.getDeclaredField("fooFooFoo").type, |
| expectedTypeName = fooTypeName.parametrizedBy( |
| fooTypeName.parametrizedBy( |
| fooTypeName.copy(nullable = true) |
| ).copy(nullable = true) |
| ).copy(nullable = true) |
| ) |
| assertHasTypeName( |
| type = subject.getDeclaredField("barFooFoo").type, |
| expectedTypeName = barTypeName.parametrizedBy( |
| fooTypeName.copy(nullable = true), fooTypeName.copy(nullable = true) |
| ).copy(nullable = true) |
| ) |
| |
| // Test manually wrapping raw type using XProcessingEnv#getDeclaredType() |
| subject.getDeclaredField("foo").type.let { foo -> |
| val fooTypeElement = invocation.processingEnv.requireTypeElement("test.Foo") |
| val fooFoo: XType = invocation.processingEnv.getDeclaredType(fooTypeElement, foo) |
| assertHasTypeName( |
| type = fooFoo, |
| expectedTypeName = fooTypeName.parametrizedBy( |
| fooTypeName.copy(nullable = true) |
| ) |
| ) |
| |
| val fooFooFoo: XType = |
| invocation.processingEnv.getDeclaredType(fooTypeElement, fooFoo) |
| assertHasTypeName( |
| type = fooFooFoo, |
| expectedTypeName = fooTypeName.parametrizedBy( |
| fooTypeName.parametrizedBy(fooTypeName.copy(nullable = true)) |
| ) |
| ) |
| |
| val barTypeElement = invocation.processingEnv.requireTypeElement("test.Bar") |
| val barFooFoo: XType = |
| invocation.processingEnv.getDeclaredType(barTypeElement, foo, foo) |
| assertHasTypeName( |
| type = barFooFoo, |
| expectedTypeName = barTypeName.parametrizedBy( |
| fooTypeName.copy(nullable = true), fooTypeName.copy(nullable = true) |
| ) |
| ) |
| } |
| |
| // Test manually unwrapping a type with a raw type argument: |
| subject.getDeclaredField("fooFoo").type.let { fooFoo -> |
| assertHasTypeName( |
| type = fooFoo.typeArguments.single(), |
| expectedTypeName = fooTypeName.copy(nullable = true) |
| ) |
| } |
| subject.getDeclaredField("barFooFoo").type.let { barFooFoo -> |
| assertThat(barFooFoo.typeArguments).hasSize(2) |
| assertHasTypeName( |
| type = barFooFoo.typeArguments[0], |
| expectedTypeName = fooTypeName.copy(nullable = true) |
| ) |
| assertHasTypeName( |
| type = barFooFoo.typeArguments[1], |
| expectedTypeName = fooTypeName.copy(nullable = true) |
| ) |
| } |
| } |
| } |
| |
| @Test |
| fun hasAnnotationWithPackage() { |
| val kotlinSrc = Source.kotlin( |
| "KotlinClass.kt", |
| """ |
| package foo.bar |
| interface KotlinInterface |
| open class KotlinBase |
| @Target(AnnotationTarget.TYPE) |
| annotation class KotlinAnnotation { |
| @Target(AnnotationTarget.TYPE) |
| annotation class KotlinNestedAnnotation |
| } |
| class KotlinClass : @KotlinAnnotation.KotlinNestedAnnotation KotlinBase(), |
| @KotlinAnnotation KotlinInterface { |
| inner class KotlinInner : @KotlinAnnotation KotlinInterface |
| class KotlinNested : @KotlinAnnotation KotlinInterface |
| } |
| """.trimIndent() |
| ) |
| // KSP can't read nested annotations in Java sources if the filename does not match |
| // the outer class. |
| val javaAnnotationSource = Source.java( |
| "foo.bar.JavaAnnotation", |
| """ |
| package foo.bar; |
| import java.lang.annotation.ElementType; |
| import java.lang.annotation.Target; |
| @Target(ElementType.TYPE_USE) |
| @interface JavaAnnotation { |
| @Target(ElementType.TYPE_USE) |
| @interface JavaNestedAnnotation {} |
| } |
| """.trimIndent() |
| ) |
| val javaSrc = Source.java( |
| "foo.bar.JavaClass", |
| """ |
| package foo.bar; |
| interface JavaInterface {} |
| class JavaBase {} |
| class JavaClass extends @JavaAnnotation.JavaNestedAnnotation JavaBase |
| implements @JavaAnnotation JavaInterface {} |
| """.trimIndent() |
| ) |
| fun checkKotlin(invocation: XTestInvocation) { |
| val kotlinTypeElement = invocation.processingEnv.requireTypeElement( |
| "foo.bar.KotlinClass") |
| kotlinTypeElement.superInterfaces.single().let { |
| assertThat(it.getAllAnnotations().single().typeElement.packageName) |
| .isEqualTo("foo.bar") |
| assertThat(it.getAllAnnotations().single().typeElement.qualifiedName) |
| .isEqualTo("foo.bar.KotlinAnnotation") |
| |
| assertThat(it.hasAnnotationWithPackage("foo.bar.KotlinAnnotation")).isFalse() |
| assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue() |
| assertThat(it.hasAnnotationWithPackage("foo")).isFalse() |
| } |
| kotlinTypeElement.superClass!!.let { |
| assertThat(it.getAllAnnotations().single().typeElement.packageName) |
| .isEqualTo("foo.bar") |
| assertThat(it.getAllAnnotations().single().typeElement.qualifiedName) |
| .isEqualTo("foo.bar.KotlinAnnotation.KotlinNestedAnnotation") |
| |
| assertThat(it.getAllAnnotations().single().typeElement.packageName) |
| .isEqualTo("foo.bar") |
| assertThat(it.getAllAnnotations().single().typeElement.qualifiedName) |
| .isEqualTo("foo.bar.KotlinAnnotation.KotlinNestedAnnotation") |
| } |
| } |
| fun checkJava(invocation: XTestInvocation) { |
| val javaTypeElement = invocation.processingEnv.requireTypeElement( |
| "foo.bar.JavaClass") |
| javaTypeElement.superInterfaces.first().let { |
| assertThat(it.getAllAnnotations().single().typeElement.packageName) |
| .isEqualTo("foo.bar") |
| assertThat(it.getAllAnnotations().single().typeElement.qualifiedName) |
| .isEqualTo("foo.bar.JavaAnnotation") |
| |
| assertThat(it.hasAnnotationWithPackage("foo.bar.JavaClass")).isFalse() |
| assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue() |
| assertThat(it.hasAnnotationWithPackage("foo")).isFalse() |
| } |
| javaTypeElement.superClass!!.let { |
| assertThat(it.getAllAnnotations().single().typeElement.packageName) |
| .isEqualTo("foo.bar") |
| assertThat(it.getAllAnnotations().single().typeElement.qualifiedName) |
| .isEqualTo("foo.bar.JavaAnnotation.JavaNestedAnnotation") |
| |
| assertThat(it.hasAnnotationWithPackage("foo.bar.JavaClass")).isFalse() |
| assertThat(it.hasAnnotationWithPackage("foo.bar")).isTrue() |
| assertThat(it.hasAnnotationWithPackage("foo")).isFalse() |
| } |
| } |
| runProcessorTest( |
| sources = listOf(kotlinSrc, javaAnnotationSource, javaSrc), |
| handler = { |
| checkKotlin(it) |
| checkJava(it) |
| } |
| ) |
| runProcessorTest( |
| classpath = compileFiles(listOf(kotlinSrc)), |
| handler = { |
| // We can't see type annotations from precompiled Java classes. Skipping it |
| // for now: https://github.com/google/ksp/issues/1296 |
| checkKotlin(it) |
| } |
| ) |
| } |
| |
| @Test |
| fun selfReferenceTypesDoesNotInfinitelyRecurse() { |
| fun runTest(src: Source) { |
| runProcessorTest( |
| sources = listOf(src), |
| ) { invocation -> |
| val fooTypeElement = invocation.processingEnv.requireTypeElement("test.Usage") |
| val fooType = fooTypeElement.getDeclaredField("foo").type |
| |
| assertThat(fooType.asTypeName().java) |
| .isEqualTo( |
| JParameterizedTypeName.get( |
| JClassName.get("test", "Foo"), |
| JWildcardTypeName.subtypeOf(JClassName.OBJECT) |
| ) |
| ) |
| |
| val typeParam = fooType.typeArguments.single() |
| assertThat(typeParam.asTypeName().java) |
| .isEqualTo(JWildcardTypeName.subtypeOf(JClassName.OBJECT)) |
| |
| assertThat(typeParam.extendsBound()).isNull() |
| } |
| } |
| runTest( |
| Source.java( |
| "test.Usage", |
| """ |
| package test; |
| public final class Usage { |
| private final Foo<?> foo = null; |
| private final Foo<? extends Foo<?>> fooExtendsFoo = null; |
| } |
| abstract class Foo<T extends Foo<T>> {} |
| """.trimIndent() |
| ), |
| ) |
| runTest( |
| Source.kotlin( |
| "test.Foo.kt", |
| """ |
| package test |
| class Usage { |
| val foo: Foo<*> = TODO() |
| val fooExtendsFoo: Foo<out Foo<*>> = TODO() |
| } |
| abstract class Foo<T: Foo<T>> |
| """.trimIndent() |
| ) |
| ) |
| } |
| |
| @Test |
| fun selfReferenceSuperTypesDoesNotInfinitelyRecurse() { |
| val baseInterface = Source.java( |
| "test.BaseInterface", |
| """ |
| package test; |
| public interface BaseInterface<T> {} |
| """.trimIndent() |
| ) |
| val selfReferenceClass = Source.java( |
| "test.SelfRef", |
| """ |
| package test; |
| public abstract class SelfRef<T extends SelfRef<T>> { } |
| """.trimIndent() |
| ) |
| val source = Source.java( |
| "test.Subject", |
| """ |
| package test; |
| public final class Subject implements BaseInterface<SelfRef<?>> { } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = listOf( |
| baseInterface, |
| selfReferenceClass, |
| source |
| ), |
| ) { |
| val subject = it.processingEnv.requireTypeElement("test.Subject") |
| val superType = subject.type.superTypes |
| .map { it.asTypeName() } |
| .filterNot { it == ANY_OBJECT } |
| .single() |
| assertThat(superType.java) |
| .isEqualTo( |
| JParameterizedTypeName.get( |
| JClassName.get("test", "BaseInterface"), |
| JParameterizedTypeName.get( |
| JClassName.get("test", "SelfRef"), |
| JWildcardTypeName.subtypeOf(JClassName.OBJECT) |
| ) |
| ) |
| ) |
| } |
| } |
| |
| @Test |
| fun valueTypes(@TestParameter isPrecompiled: Boolean,) { |
| val kotlinSrc = Source.kotlin( |
| "KotlinClass.kt", |
| """ |
| @JvmInline value class PackageName(val value: String) |
| class KotlinClass { |
| fun getPackageNames(): Set<PackageName> = emptySet() |
| fun setPackageNames(pkgNames: Set<PackageName>) { } |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = if (isPrecompiled) { emptyList() } else { listOf(kotlinSrc) }, |
| classpath = if (isPrecompiled) { compileFiles(listOf(kotlinSrc)) } else { emptyList() } |
| ) { invocation -> |
| val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass") |
| |
| kotlinElm.getMethodByJvmName("getPackageNames").apply { |
| assertThat(returnType.typeName.toString()) |
| .isEqualTo("java.util.Set<PackageName>") |
| assertThat(returnType.typeArguments.single().typeName.toString()) |
| .isEqualTo("PackageName") |
| } |
| kotlinElm.getMethodByJvmName("setPackageNames").apply { |
| val paramType = parameters.single().type |
| assertThat(paramType.typeName.toString()) |
| .isEqualTo("java.util.Set<PackageName>") |
| assertThat(paramType.typeArguments.single().typeName.toString()) |
| .isEqualTo("PackageName") |
| } |
| } |
| } |
| |
| @Test |
| fun jvmTypes(@TestParameter isPrecompiled: Boolean) { |
| val kotlinSrc = Source.kotlin( |
| "KotlinClass.kt", |
| """ |
| @JvmInline value class MyInlineClass(val value: Int) |
| @JvmInline value class MyGenericInlineClass<T: Number>(val value: T) |
| class KotlinClass { |
| // @JvmName disables name mangling for functions that use inline classes directly |
| // and make them visible to Java: |
| // https://kotlinlang.org/docs/inline-classes.html#calling-from-java-code |
| @JvmName("kotlinValueClassDirectUsage") |
| fun kotlinValueClassDirectUsage(): UInt = TODO() |
| fun kotlinValueClassIndirectUsage(): List<UInt> = TODO() |
| fun kotlinNonValueClassDirectUsage(): String = TODO() |
| fun kotlinNonValueClassIndirectUsage(): List<String> = TODO() |
| @JvmName("kotlinGenericValueClassDirectUsage") |
| fun kotlinGenericValueClassDirectUsage(): Result<Int> = TODO() |
| fun kotlinGenericValueClassIndirectUsage(): List<Result<Int>> = TODO() |
| @JvmName("nonKotlinValueClassDirectUsage") |
| fun nonKotlinValueClassDirectUsage(): MyInlineClass = TODO() |
| fun nonKotlinValueClassIndirectUsage(): List<MyInlineClass> = TODO() |
| @JvmName("nonKotlinGenericValueClassDirectUsage") |
| fun nonKotlinGenericValueClassDirectUsage(): MyGenericInlineClass<Int> = TODO() |
| fun nonKotlinGenericValueClassIndirectUsage(): List<MyGenericInlineClass<Int>> = TODO() |
| } |
| """.trimIndent() |
| ) |
| val javaSrc = Source.java( |
| "JavaClass", |
| """ |
| import java.util.List; |
| import kotlin.Result; |
| import kotlin.UInt; |
| interface JavaClass { |
| UInt inlineClassDirectUsage(); |
| List<UInt> inlineClassIndirectUsage(); |
| Result<Integer> genericInlineClassDirectUsage(); |
| List<Result<Integer>> genericInlineClassIndirectUsage(); |
| |
| MyInlineClass customInlineClassDirectUsage(); |
| List<MyInlineClass> customInlineClassIndirectUsage(); |
| MyGenericInlineClass<Integer> customGenericInlineClassDirectUsage(); |
| List<MyGenericInlineClass<Integer>> customGenericInlineClassIndirectUsage(); |
| } |
| """.trimIndent() |
| ) |
| runProcessorTest( |
| sources = if (isPrecompiled) { emptyList() } else { listOf(kotlinSrc, javaSrc) }, |
| classpath = if (isPrecompiled) { |
| compileFiles(listOf(kotlinSrc, javaSrc)) |
| } else { |
| emptyList() |
| } |
| ) { invocation -> |
| val kotlinElm = invocation.processingEnv.requireTypeElement("KotlinClass") |
| kotlinElm.getMethodByJvmName("kotlinValueClassDirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("int") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.UInt") |
| } |
| } |
| kotlinElm.getMethodByJvmName("kotlinValueClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<kotlin.UInt>") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<kotlin.UInt>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("kotlinNonValueClassDirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.lang.String") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.String") |
| } |
| } |
| kotlinElm.getMethodByJvmName("kotlinNonValueClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<java.lang.String>") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<kotlin.String>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("kotlinGenericValueClassDirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.lang.Object") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.Result<kotlin.Int>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("kotlinGenericValueClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<kotlin.Result<java.lang.Integer>>") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<kotlin.Result<kotlin.Int>>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("nonKotlinValueClassDirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("int") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("MyInlineClass") |
| } |
| } |
| kotlinElm.getMethodByJvmName("nonKotlinValueClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<MyInlineClass>") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<MyInlineClass>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("nonKotlinGenericValueClassDirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.lang.Number") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("MyGenericInlineClass<kotlin.Int>") |
| } |
| } |
| kotlinElm.getMethodByJvmName("nonKotlinGenericValueClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<MyGenericInlineClass<java.lang.Integer>>") |
| if (invocation.isKsp) { |
| assertThat(returnType.asTypeName().kotlin.toString()) |
| .isEqualTo("kotlin.collections.List<MyGenericInlineClass<kotlin.Int>>") |
| } |
| } |
| |
| val javaElm = invocation.processingEnv.requireTypeElement("JavaClass") |
| javaElm.getMethodByJvmName("inlineClassDirectUsage").apply { |
| if (invocation.isKsp) { |
| // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace |
| // it with the JVM type. |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("int") |
| } else { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("kotlin.UInt") |
| } |
| } |
| javaElm.getMethodByJvmName("inlineClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<kotlin.UInt>") |
| } |
| javaElm.getMethodByJvmName("customInlineClassDirectUsage").apply { |
| if (invocation.isKsp) { |
| // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace |
| // it with the JVM type. |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("int") |
| } else { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("MyInlineClass") |
| } |
| } |
| javaElm.getMethodByJvmName("customInlineClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<MyInlineClass>") |
| } |
| javaElm.getMethodByJvmName("customGenericInlineClassDirectUsage").apply { |
| if (invocation.isKsp) { |
| // TODO(kuanyingchou): When an inline type is used in Java we shouldn't replace |
| // it with the JVM type. |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.lang.Number") |
| } else { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("MyGenericInlineClass<java.lang.Integer>") |
| } |
| } |
| javaElm.getMethodByJvmName("customGenericInlineClassIndirectUsage").apply { |
| assertThat(returnType.asTypeName().java.toString()) |
| .isEqualTo("java.util.List<MyGenericInlineClass<java.lang.Integer>>") |
| } |
| } |
| } |
| } |