KSP followups from #1393 (#1448)

* Rename to unwrapTypeAliasInternal + simplify

* Move down isAnyNullable

* Make dynamic explicit

* Clean up supertypes doc and filtering

* Switch to invoke extensions

* Just best guess the annotation

* Clean up redundant sequence and use a regular loop

* element -> type

* supertypes -> superclasses

* Spotless

* Fix copyright

* Add multiple messages check

* Link issue

Co-authored-by: Jesse Wilson <jesse@swank.ca>
This commit is contained in:
Zac Sweers
2021-12-05 20:34:37 -05:00
committed by GitHub
parent f57d7200f3
commit fb5dd08168
8 changed files with 85 additions and 102 deletions

View File

@@ -17,6 +17,7 @@ package com.squareup.moshi.kotlin.codegen.api
import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.Dynamic
import com.squareup.kotlinpoet.LambdaTypeName import com.squareup.kotlinpoet.LambdaTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeName
@@ -26,52 +27,35 @@ import com.squareup.kotlinpoet.tag
import com.squareup.kotlinpoet.tags.TypeAliasTag import com.squareup.kotlinpoet.tags.TypeAliasTag
import java.util.TreeSet import java.util.TreeSet
internal fun TypeName.unwrapTypeAliasReal(): TypeName { private fun TypeName.unwrapTypeAliasInternal(): TypeName? {
return tag<TypeAliasTag>()?.abbreviatedType?.let { unwrappedType -> return tag<TypeAliasTag>()?.abbreviatedType?.let { unwrappedType ->
// If any type is nullable, then the whole thing is nullable
var isAnyNullable = isNullable
// Keep track of all annotations across type levels. Sort them too for consistency. // Keep track of all annotations across type levels. Sort them too for consistency.
val runningAnnotations = TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply { val runningAnnotations = TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
addAll(annotations) addAll(annotations)
} }
val nestedUnwrappedType = unwrappedType.unwrapTypeAlias() val nestedUnwrappedType = unwrappedType.unwrapTypeAlias()
runningAnnotations.addAll(nestedUnwrappedType.annotations) runningAnnotations.addAll(nestedUnwrappedType.annotations)
isAnyNullable = isAnyNullable || nestedUnwrappedType.isNullable // If any type is nullable, then the whole thing is nullable
val isAnyNullable = isNullable || nestedUnwrappedType.isNullable
nestedUnwrappedType.copy(nullable = isAnyNullable, annotations = runningAnnotations.toList()) nestedUnwrappedType.copy(nullable = isAnyNullable, annotations = runningAnnotations.toList())
} ?: this }
} }
internal fun TypeName.unwrapTypeAlias(): TypeName { internal fun TypeName.unwrapTypeAlias(): TypeName {
return when (this) { return when (this) {
is ClassName -> unwrapTypeAliasReal() is ClassName -> unwrapTypeAliasInternal() ?: this
is ParameterizedTypeName -> { is ParameterizedTypeName -> {
if (TypeAliasTag::class in tags) { unwrapTypeAliasInternal() ?: deepCopy(TypeName::unwrapTypeAlias)
unwrapTypeAliasReal()
} else {
deepCopy(TypeName::unwrapTypeAlias)
}
} }
is TypeVariableName -> { is TypeVariableName -> {
if (TypeAliasTag::class in tags) { unwrapTypeAliasInternal() ?: deepCopy(transform = TypeName::unwrapTypeAlias)
unwrapTypeAliasReal()
} else {
deepCopy(transform = TypeName::unwrapTypeAlias)
}
} }
is WildcardTypeName -> { is WildcardTypeName -> {
if (TypeAliasTag::class in tags) { unwrapTypeAliasInternal() ?: deepCopy(TypeName::unwrapTypeAlias)
unwrapTypeAliasReal()
} else {
deepCopy(TypeName::unwrapTypeAlias)
}
} }
is LambdaTypeName -> { is LambdaTypeName -> {
if (TypeAliasTag::class in tags) { unwrapTypeAliasInternal() ?: deepCopy(TypeName::unwrapTypeAlias)
unwrapTypeAliasReal()
} else {
deepCopy(TypeName::unwrapTypeAlias)
}
} }
else -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.") Dynamic -> throw UnsupportedOperationException("Type '${javaClass.simpleName}' is illegal. Only classes, parameterized types, wildcard types, or type variables are allowed.")
} }
} }

View File

@@ -15,10 +15,16 @@
*/ */
package com.squareup.moshi.kotlin.codegen.apt package com.squareup.moshi.kotlin.codegen.apt
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.DelicateKotlinPoetApi
import com.squareup.kotlinpoet.asClassName
import javax.lang.model.element.ElementKind.CLASS
import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType import javax.lang.model.type.DeclaredType
import javax.lang.model.util.Types import javax.lang.model.util.Types
private val OBJECT_CLASS = ClassName("java.lang", "Object")
/** /**
* A concrete type like `List<String>` with enough information to know how to resolve its type * A concrete type like `List<String>` with enough information to know how to resolve its type
* variables. * variables.
@@ -27,8 +33,9 @@ internal class AppliedType private constructor(
val element: TypeElement, val element: TypeElement,
private val mirror: DeclaredType private val mirror: DeclaredType
) { ) {
/** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ /** Returns all supertypes of this, recursively. Only [CLASS] is used as we can't really use other types. */
fun supertypes( @OptIn(DelicateKotlinPoetApi::class)
fun superclasses(
types: Types, types: Types,
result: LinkedHashSet<AppliedType> = LinkedHashSet() result: LinkedHashSet<AppliedType> = LinkedHashSet()
): LinkedHashSet<AppliedType> { ): LinkedHashSet<AppliedType> {
@@ -36,8 +43,14 @@ internal class AppliedType private constructor(
for (supertype in types.directSupertypes(mirror)) { for (supertype in types.directSupertypes(mirror)) {
val supertypeDeclaredType = supertype as DeclaredType val supertypeDeclaredType = supertype as DeclaredType
val supertypeElement = supertypeDeclaredType.asElement() as TypeElement val supertypeElement = supertypeDeclaredType.asElement() as TypeElement
val appliedSupertype = AppliedType(supertypeElement, supertypeDeclaredType) if (supertypeElement.kind != CLASS) {
appliedSupertype.supertypes(types, result) continue
} else if (supertypeElement.asClassName() == OBJECT_CLASS) {
// Don't load properties for java.lang.Object.
continue
}
val appliedSuperclass = AppliedType(supertypeElement, supertypeDeclaredType)
appliedSuperclass.superclasses(types, result)
} }
return result return result
} }
@@ -45,7 +58,7 @@ internal class AppliedType private constructor(
override fun toString() = mirror.toString() override fun toString() = mirror.toString()
companion object { companion object {
fun get(typeElement: TypeElement): AppliedType { operator fun invoke(typeElement: TypeElement): AppliedType {
return AppliedType(typeElement, typeElement.asType() as DeclaredType) return AppliedType(typeElement, typeElement.asType() as DeclaredType)
} }
} }

View File

@@ -52,7 +52,6 @@ import java.lang.annotation.RetentionPolicy
import javax.annotation.processing.Messager import javax.annotation.processing.Messager
import javax.lang.model.element.AnnotationMirror import javax.lang.model.element.AnnotationMirror
import javax.lang.model.element.Element import javax.lang.model.element.Element
import javax.lang.model.element.ElementKind
import javax.lang.model.element.TypeElement import javax.lang.model.element.TypeElement
import javax.lang.model.type.DeclaredType import javax.lang.model.type.DeclaredType
import javax.lang.model.util.Elements import javax.lang.model.util.Elements
@@ -63,7 +62,6 @@ import javax.tools.Diagnostic.Kind.WARNING
private val JSON_QUALIFIER = JsonQualifier::class.java private val JSON_QUALIFIER = JsonQualifier::class.java
private val JSON = Json::class.asClassName() private val JSON = Json::class.asClassName()
private val TRANSIENT = Transient::class.asClassName() private val TRANSIENT = Transient::class.asClassName()
private val OBJECT_CLASS = ClassName("java.lang", "Object")
private val VISIBILITY_MODIFIERS = setOf( private val VISIBILITY_MODIFIERS = setOf(
KModifier.INTERNAL, KModifier.INTERNAL,
KModifier.PRIVATE, KModifier.PRIVATE,
@@ -206,7 +204,7 @@ internal fun targetType(
val kotlinApi = cachedClassInspector.toTypeSpec(kmClass) val kotlinApi = cachedClassInspector.toTypeSpec(kmClass)
val typeVariables = kotlinApi.typeVariables val typeVariables = kotlinApi.typeVariables
val appliedType = AppliedType.get(element) val appliedType = AppliedType(element)
val constructor = primaryConstructor(element, kotlinApi, elements, messager) val constructor = primaryConstructor(element, kotlinApi, elements, messager)
if (constructor == null) { if (constructor == null) {
@@ -230,28 +228,24 @@ internal fun targetType(
val properties = mutableMapOf<String, TargetProperty>() val properties = mutableMapOf<String, TargetProperty>()
val resolvedTypes = mutableListOf<ResolvedTypeMapping>() val resolvedTypes = mutableListOf<ResolvedTypeMapping>()
val superTypes = appliedType.supertypes(types) val superclass = appliedType.superclasses(types)
.filterNot { supertype -> .onEach { superclass ->
supertype.element.asClassName() == OBJECT_CLASS || // Don't load properties for java.lang.Object. if (superclass.element.getAnnotation(Metadata::class.java) == null) {
supertype.element.kind != ElementKind.CLASS // Don't load properties for interface types.
}
.onEach { supertype ->
if (supertype.element.getAnnotation(Metadata::class.java) == null) {
messager.printMessage( messager.printMessage(
ERROR, ERROR,
"@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type", "@JsonClass can't be applied to $element: supertype $superclass is not a Kotlin type",
element element
) )
return null return null
} }
} }
.associateWithTo(LinkedHashMap()) { supertype -> .associateWithTo(LinkedHashMap()) { superclass ->
// Load the kotlin API cache into memory eagerly so we can reuse the parsed APIs // Load the kotlin API cache into memory eagerly so we can reuse the parsed APIs
val api = if (supertype.element == element) { val api = if (superclass.element == element) {
// We've already parsed this api above, reuse it // We've already parsed this api above, reuse it
kotlinApi kotlinApi
} else { } else {
cachedClassInspector.toTypeSpec(supertype.element) cachedClassInspector.toTypeSpec(superclass.element)
} }
val apiSuperClass = api.superclass val apiSuperClass = api.superclass
@@ -268,7 +262,7 @@ internal fun targetType(
// Then when we look at Bar<T> later, we'll look up to the descendent Foo and extract its // Then when we look at Bar<T> later, we'll look up to the descendent Foo and extract its
// materialized type from there. // materialized type from there.
// //
val superSuperClass = supertype.element.superclass as DeclaredType val superSuperClass = superclass.element.superclass as DeclaredType
// Convert to an element and back to wipe the typed generics off of this // Convert to an element and back to wipe the typed generics off of this
val untyped = superSuperClass.asElement().asType().asTypeName() as ParameterizedTypeName val untyped = superSuperClass.asElement().asType().asTypeName() as ParameterizedTypeName
@@ -285,7 +279,7 @@ internal fun targetType(
return@associateWithTo api return@associateWithTo api
} }
for ((localAppliedType, supertypeApi) in superTypes.entries) { for ((localAppliedType, supertypeApi) in superclass.entries) {
val appliedClassName = localAppliedType.element.asClassName() val appliedClassName = localAppliedType.element.asClassName()
val supertypeProperties = declaredProperties( val supertypeProperties = declaredProperties(
constructor = constructor, constructor = constructor,

View File

@@ -35,8 +35,8 @@ internal class AppliedType private constructor(
val typeName: TypeName = type.toClassName() val typeName: TypeName = type.toClassName()
) { ) {
/** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ /** Returns all super classes of this, recursively. Only [CLASS] is used as we can't really use other types. */
fun supertypes( fun superclasses(
resolver: Resolver, resolver: Resolver,
): LinkedHashSet<AppliedType> { ): LinkedHashSet<AppliedType> {
val result: LinkedHashSet<AppliedType> = LinkedHashSet() val result: LinkedHashSet<AppliedType> = LinkedHashSet()
@@ -63,7 +63,7 @@ internal class AppliedType private constructor(
override fun toString() = type.qualifiedName!!.asString() override fun toString() = type.qualifiedName!!.asString()
companion object { companion object {
fun get(type: KSClassDeclaration): AppliedType { operator fun invoke(type: KSClassDeclaration): AppliedType {
return AppliedType(type) return AppliedType(type)
} }
} }

View File

@@ -27,8 +27,8 @@ import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFile import com.google.devtools.ksp.symbol.KSFile
import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.writeTo import com.squareup.kotlinpoet.ksp.writeTo
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator import com.squareup.moshi.kotlin.codegen.api.AdapterGenerator
@@ -66,54 +66,47 @@ private class JsonClassSymbolProcessor(
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
val generatedAnnotation = generatedOption?.let { val generatedAnnotation = generatedOption?.let {
val annotationType = resolver.getClassDeclarationByName(resolver.getKSNameFromString(it)) AnnotationSpec.builder(ClassName.bestGuess(it))
?: run {
logger.error("Generated annotation type doesn't exist: $it")
return emptyList()
}
AnnotationSpec.builder(annotationType.toClassName())
.addMember("value = [%S]", JsonClassSymbolProcessor::class.java.canonicalName) .addMember("value = [%S]", JsonClassSymbolProcessor::class.java.canonicalName)
.addMember("comments = %S", "https://github.com/square/moshi") .addMember("comments = %S", "https://github.com/square/moshi")
.build() .build()
} }
resolver.getSymbolsWithAnnotation(JSON_CLASS_NAME) for (type in resolver.getSymbolsWithAnnotation(JSON_CLASS_NAME)) {
.asSequence() // For the smart cast
.forEach { type -> if (type !is KSDeclaration) {
// For the smart cast logger.error("@JsonClass can't be applied to $type: must be a Kotlin class", type)
if (type !is KSDeclaration) { continue
logger.error("@JsonClass can't be applied to $type: must be a Kotlin class", type)
return@forEach
}
val jsonClassAnnotation = type.findAnnotationWithType<JsonClass>() ?: return@forEach
val generator = jsonClassAnnotation.generator
if (generator.isNotEmpty()) return@forEach
if (!jsonClassAnnotation.generateAdapter) return@forEach
val originatingFile = type.containingFile!!
val adapterGenerator = adapterGenerator(logger, resolver, type) ?: return emptyList()
try {
val preparedAdapter = adapterGenerator
.prepare(generateProguardRules) { spec ->
spec.toBuilder()
.apply {
generatedAnnotation?.let(::addAnnotation)
}
.addOriginatingKSFile(originatingFile)
.build()
}
preparedAdapter.spec.writeTo(codeGenerator, aggregating = false)
preparedAdapter.proguardConfig?.writeTo(codeGenerator, originatingFile)
} catch (e: Exception) {
logger.error(
"Error preparing ${type.simpleName.asString()}: ${e.stackTrace.joinToString("\n")}"
)
}
} }
val jsonClassAnnotation = type.findAnnotationWithType<JsonClass>() ?: continue
val generator = jsonClassAnnotation.generator
if (generator.isNotEmpty()) continue
if (!jsonClassAnnotation.generateAdapter) continue
val originatingFile = type.containingFile!!
val adapterGenerator = adapterGenerator(logger, resolver, type) ?: return emptyList()
try {
val preparedAdapter = adapterGenerator
.prepare(generateProguardRules) { spec ->
spec.toBuilder()
.apply {
generatedAnnotation?.let(::addAnnotation)
}
.addOriginatingKSFile(originatingFile)
.build()
}
preparedAdapter.spec.writeTo(codeGenerator, aggregating = false)
preparedAdapter.proguardConfig?.writeTo(codeGenerator, originatingFile)
} catch (e: Exception) {
logger.error(
"Error preparing ${type.simpleName.asString()}: ${e.stackTrace.joinToString("\n")}"
)
}
}
return emptyList() return emptyList()
} }

View File

@@ -53,7 +53,7 @@ import com.squareup.moshi.kotlin.codegen.api.TargetProperty
import com.squareup.moshi.kotlin.codegen.api.TargetType import com.squareup.moshi.kotlin.codegen.api.TargetType
import com.squareup.moshi.kotlin.codegen.api.unwrapTypeAlias import com.squareup.moshi.kotlin.codegen.api.unwrapTypeAlias
/** Returns a target type for `element`, or null if it cannot be used with code gen. */ /** Returns a target type for [type] or null if it cannot be used with code gen. */
internal fun targetType( internal fun targetType(
type: KSDeclaration, type: KSDeclaration,
resolver: Resolver, resolver: Resolver,
@@ -89,7 +89,7 @@ internal fun targetType(
sourceTypeHint = type.qualifiedName!!.asString() sourceTypeHint = type.qualifiedName!!.asString()
) )
val typeVariables = type.typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) } val typeVariables = type.typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) }
val appliedType = AppliedType.get(type) val appliedType = AppliedType(type)
val constructor = primaryConstructor(resolver, type, classTypeParamsResolver, logger) val constructor = primaryConstructor(resolver, type, classTypeParamsResolver, logger)
?: run { ?: run {
@@ -108,12 +108,12 @@ internal fun targetType(
val properties = mutableMapOf<String, TargetProperty>() val properties = mutableMapOf<String, TargetProperty>()
val originalType = appliedType.type val originalType = appliedType.type
for (supertype in appliedType.supertypes(resolver)) { for (superclass in appliedType.superclasses(resolver)) {
val classDecl = supertype.type val classDecl = superclass.type
if (!classDecl.isKotlinClass()) { if (!classDecl.isKotlinClass()) {
logger.error( logger.error(
""" """
@JsonClass can't be applied to $type: supertype $supertype is not a Kotlin type. @JsonClass can't be applied to $type: supertype $superclass is not a Kotlin type.
Origin=${classDecl.origin} Origin=${classDecl.origin}
Annotations=${classDecl.annotations.joinToString(prefix = "[", postfix = "]") { it.shortName.getShortName() }} Annotations=${classDecl.annotations.joinToString(prefix = "[", postfix = "]") { it.shortName.getShortName() }}
""".trimIndent(), """.trimIndent(),

View File

@@ -430,8 +430,7 @@ class JsonClassSymbolProcessorTest {
) )
assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR) assertThat(result.exitCode).isEqualTo(KotlinCompilation.ExitCode.COMPILATION_ERROR)
assertThat(result.messages).contains("property a is not visible") assertThat(result.messages).contains("property a is not visible")
// TODO we throw eagerly currently and don't collect assertThat(result.messages).contains("property c is not visible")
// assertThat(result.messages).contains("property c is not visible")
} }
@Test @Test

View File

@@ -257,7 +257,7 @@ class DualKotlinTest {
val result = adapter.fromJson(testJson)!! val result = adapter.fromJson(testJson)!!
assertThat(result.i).isEqualTo(6) assertThat(result.i).isEqualTo(6)
// TODO doesn't work yet. // TODO doesn't work yet. https://github.com/square/moshi/issues/1170
// need to invoke the constructor_impl$default static method, invoke constructor with result // need to invoke the constructor_impl$default static method, invoke constructor with result
// val testEmptyJson = // val testEmptyJson =
// """{}""" // """{}"""