From cc2c818341dcc38d52b21c80e37ebd8df3d2b2b8 Mon Sep 17 00:00:00 2001 From: Jesse Wilson Date: Sun, 15 Apr 2018 23:09:04 -0400 Subject: [PATCH] Begin to resolve supertype type parameters --- .../com/squareup/moshi/AdapterGenerator.kt | 42 +++---- .../java/com/squareup/moshi/AppliedType.kt | 71 +++++++++++ .../java/com/squareup/moshi/DelegateKey.kt | 19 +-- .../moshi/JsonClassCodeGenProcessor.kt | 2 +- .../java/com/squareup/moshi/TargetProperty.kt | 6 +- .../java/com/squareup/moshi/TargetType.kt | 49 +++----- .../java/com/squareup/moshi/TypeRenderer.kt | 104 ++++++++++++++++ .../java/com/squareup/moshi/TypeResolver.kt | 60 ++++++++++ .../java/com/squareup/moshi/kotlintypes.kt | 111 ------------------ 9 files changed, 290 insertions(+), 174 deletions(-) create mode 100644 kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AppliedType.kt create mode 100644 kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeRenderer.kt create mode 100644 kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt index 5d71cbf..2e408ac 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AdapterGenerator.kt @@ -18,6 +18,7 @@ package com.squareup.moshi import com.squareup.kotlinpoet.ARRAY import com.squareup.kotlinpoet.AnnotationSpec import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.FileSpec import com.squareup.kotlinpoet.FunSpec import com.squareup.kotlinpoet.KModifier @@ -34,31 +35,28 @@ import me.eugeniomarletti.kotlin.metadata.visibility import org.jetbrains.kotlin.serialization.ProtoBuf.Visibility import java.lang.reflect.Type import javax.lang.model.element.TypeElement -import javax.lang.model.util.Elements /** Generates a JSON adapter for a target type. */ internal class AdapterGenerator( target: TargetType, - private val propertyList: List, - val elements: Elements + private val propertyList: List ) { private val className = target.name private val isDataClass = target.proto.isDataClass private val hasCompanionObject = target.hasCompanionObject private val visibility = target.proto.visibility!! - val genericTypeNames = target.genericTypeNames + private val typeVariables = target.typeVariables private val nameAllocator = NameAllocator() private val adapterName = "${className.simpleNames().joinToString(separator = "_")}JsonAdapter" private val originalTypeName = target.element.asType().asTypeName() - val moshiParam = ParameterSpec.builder( + private val moshiParam = ParameterSpec.builder( nameAllocator.newName("moshi"), Moshi::class).build() - val typesParam = ParameterSpec.builder( + private val typesParam = ParameterSpec.builder( nameAllocator.newName("types"), - ParameterizedTypeName.get(ARRAY, - Type::class.asTypeName())) + ParameterizedTypeName.get(ARRAY, Type::class.asTypeName())) .build() private val readerParam = ParameterSpec.builder( nameAllocator.newName("reader"), @@ -83,12 +81,7 @@ internal class AdapterGenerator( .joinToString(", ") { "\"$it\"" }})", JsonReader.Options::class.asTypeName()) .build() - private val delegateAdapters = propertyList.distinctBy { it.delegateKey } - fun generateFile(generatedOption: TypeElement?): FileSpec { - for (property in delegateAdapters) { - property.delegateKey.reserveName(nameAllocator) - } for (property in propertyList) { property.allocateNames(nameAllocator) } @@ -114,8 +107,8 @@ internal class AdapterGenerator( result.superclass(jsonAdapterTypeName) - if (genericTypeNames.isNotEmpty()) { - result.addTypeVariables(genericTypeNames) + if (typeVariables.isNotEmpty()) { + result.addTypeVariables(typeVariables) } // TODO make this configurable. Right now it just matches the source model @@ -125,9 +118,18 @@ internal class AdapterGenerator( result.primaryConstructor(generateConstructor()) + val typeRenderer: TypeRenderer = object : TypeRenderer() { + override fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock { + val index = typeVariables.indexOfFirst { it == typeVariable } + check(index != -1) { "Unexpected type variable $typeVariable" } + return CodeBlock.of("%N[%L]", typesParam, index) + } + } + result.addProperty(optionsProperty) - for (uniqueAdapter in delegateAdapters) { - result.addProperty(uniqueAdapter.delegateKey.generateProperty(nameAllocator, this)) + for (uniqueAdapter in propertyList.distinctBy { it.delegateKey }) { + result.addProperty(uniqueAdapter.delegateKey.generateProperty( + nameAllocator, typeRenderer, moshiParam)) } result.addFunction(generateToStringFun()) @@ -141,7 +143,7 @@ internal class AdapterGenerator( val result = FunSpec.constructorBuilder() result.addParameter(moshiParam) - if (genericTypeNames.isNotEmpty()) { + if (typeVariables.isNotEmpty()) { result.addParameter(typesParam) } @@ -307,9 +309,9 @@ internal class AdapterGenerator( result.addModifiers(KModifier.INTERNAL) } - if (genericTypeNames.isNotEmpty()) { + if (typeVariables.isNotEmpty()) { result.addParameter(typesParam) - result.addTypeVariables(genericTypeNames) + result.addTypeVariables(typeVariables) result.addStatement("return %N(%N, %N)", adapterName, moshiParam, typesParam) } else { result.addStatement("return %N(%N)", adapterName, moshiParam) diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AppliedType.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AppliedType.kt new file mode 100644 index 0000000..d71beeb --- /dev/null +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/AppliedType.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * 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 com.squareup.moshi + +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.asTypeName +import javax.lang.model.element.TypeElement +import javax.lang.model.type.DeclaredType +import javax.lang.model.util.Types + +/** + * A concrete type like `List` with enough information to know how to resolve its type + * variables. + */ +internal class AppliedType private constructor( + val element: TypeElement, + val resolver: TypeResolver, + private val mirror: DeclaredType +) { + /** Returns all supertypes of this, recursively. Includes both interface and class supertypes. */ + fun supertypes( + types: Types, + result: MutableSet = mutableSetOf() + ): Set { + result.add(this) + for (supertype in types.directSupertypes(mirror)) { + val supertypeDeclaredType = supertype as DeclaredType + val supertypeElement = supertypeDeclaredType.asElement() as TypeElement + val appliedSupertype = AppliedType(supertypeElement, + resolver(supertypeElement, supertypeDeclaredType), supertypeDeclaredType) + appliedSupertype.supertypes(types, result) + } + return result + } + + /** Returns a resolver that uses `element` and `mirror` to resolve type parameters. */ + private fun resolver(element: TypeElement, mirror: DeclaredType): TypeResolver { + return object : TypeResolver() { + override fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName { + val index = element.typeParameters.indexOfFirst { + it.simpleName.toString() == typeVariable.name + } + check(index != -1) { "Unexpected type variable $typeVariable in $mirror" } + val argument = mirror.typeArguments[index] + return argument.asTypeName() + } + } + } + + override fun toString() = mirror.toString() + + companion object { + fun get(typeElement: TypeElement): AppliedType { + return AppliedType(typeElement, TypeResolver(), typeElement.asType() as DeclaredType) + } + } +} \ No newline at end of file diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/DelegateKey.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/DelegateKey.kt index d02b2a9..c39e3c1 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/DelegateKey.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/DelegateKey.kt @@ -19,6 +19,7 @@ import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.KModifier import com.squareup.kotlinpoet.NameAllocator +import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.PropertySpec import com.squareup.kotlinpoet.TypeName @@ -34,25 +35,27 @@ internal data class DelegateKey( ) { val nullable get() = type.nullable || type is TypeVariableName - fun reserveName(nameAllocator: NameAllocator) { + /** Returns an adapter to use when encoding and decoding this property. */ + fun generateProperty( + nameAllocator: NameAllocator, + typeRenderer: TypeRenderer, + moshiParameter: ParameterSpec): PropertySpec { val qualifierNames = jsonQualifiers.joinToString("") { "At${it.annotationType.asElement().simpleName}" } - nameAllocator.newName("${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this) - } + val adapterName = nameAllocator.newName( + "${type.toVariableName().decapitalize()}${qualifierNames}Adapter", this) - /** Returns an adapter to use when encoding and decoding this property. */ - fun generateProperty(nameAllocator: NameAllocator, enclosing: AdapterGenerator): PropertySpec { val adapterTypeName = ParameterizedTypeName.get( JsonAdapter::class.asTypeName(), type) val qualifiers = jsonQualifiers - val standardArgs = arrayOf(enclosing.moshiParam, + val standardArgs = arrayOf(moshiParameter, if (type is ClassName && qualifiers.isEmpty()) { "" } else { CodeBlock.of("<%T>", type) }, - type.makeType(enclosing.elements, enclosing.typesParam, enclosing.genericTypeNames)) + typeRenderer.render(type)) val standardArgsSize = standardArgs.size + 1 val (initializerString, args) = when { qualifiers.isEmpty() -> "" to emptyArray() @@ -77,7 +80,7 @@ internal data class DelegateKey( val nullModifier = if (nullable) ".nullSafe()" else ".nonNull()" - return PropertySpec.builder(nameAllocator.get(this), adapterTypeName, KModifier.PRIVATE) + return PropertySpec.builder(adapterName, adapterTypeName, KModifier.PRIVATE) .initializer("%1N.adapter%2L(%3L$initializerString)$nullModifier", *finalArgs) .build() } diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt index 684bac0..99a6357 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/JsonClassCodeGenProcessor.kt @@ -120,7 +120,7 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils } } - return AdapterGenerator(type, sortedProperties, elementUtils) + return AdapterGenerator(type, sortedProperties) } private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) { diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetProperty.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetProperty.kt index 8a03435..2fad964 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetProperty.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetProperty.kt @@ -36,7 +36,6 @@ import javax.tools.Diagnostic internal data class TargetProperty( val name: String, val type: TypeName, - private val typeWithResolvedAliases: TypeName, private val proto: Property, private val parameter: TargetParameter?, private val annotationHolder: ExecutableElement?, @@ -87,7 +86,7 @@ internal data class TargetProperty( return PropertyGenerator(this) } - fun delegateKey() = DelegateKey(typeWithResolvedAliases, jsonQualifiers()) + fun delegateKey() = DelegateKey(type, jsonQualifiers()) /** Returns the JsonQualifiers on the field and parameter of this property. */ private fun jsonQualifiers(): Set { @@ -107,8 +106,7 @@ internal data class TargetProperty( private val Element?.qualifiers: Set get() { if (this == null) return setOf() - return AnnotationMirrors.getAnnotatedAnnotations(this, - JsonQualifier::class.java) + return AnnotationMirrors.getAnnotatedAnnotations(this, JsonQualifier::class.java) } /** Returns the @Json name of this property, or this property's name if none is provided. */ diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetType.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetType.kt index 36b1b7d..e81db9a 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetType.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TargetType.kt @@ -41,7 +41,6 @@ import javax.lang.model.element.ElementKind import javax.lang.model.element.ExecutableElement import javax.lang.model.element.TypeElement import javax.lang.model.element.VariableElement -import javax.lang.model.type.DeclaredType import javax.lang.model.util.Elements import javax.lang.model.util.Types import javax.tools.Diagnostic.Kind.ERROR @@ -52,7 +51,7 @@ internal data class TargetType( val element: TypeElement, val constructor: TargetConstructor, val properties: Map, - val genericTypeNames: List + val typeVariables: List ) { val name = element.className val hasCompanionObject = proto.hasCompanionObjectName() @@ -93,31 +92,37 @@ internal data class TargetType( } } + val typeVariables = genericTypeNames(proto, typeMetadata.data.nameResolver) + val appliedType = AppliedType.get(element) + val constructor = TargetConstructor.primary(typeMetadata, elements) val properties = mutableMapOf() - for (supertype in element.supertypes(types)) { - if (supertype.asClassName() == OBJECT_CLASS) { + for (supertype in appliedType.supertypes(types)) { + if (supertype.element.asClassName() == OBJECT_CLASS) { continue // Don't load properties for java.lang.Object. } - if (supertype.kind != ElementKind.CLASS) { + if (supertype.element.kind != ElementKind.CLASS) { continue // Don't load properties for interface types. } - if (supertype.kotlinMetadata == null) { + if (supertype.element.kotlinMetadata == null) { messager.printMessage(ERROR, "@JsonClass can't be applied to $element: supertype $supertype is not a Kotlin type", element) + return null } - for ((name, property) in declaredProperties(supertype, constructor)) { + val supertypeProperties = declaredProperties( + supertype.element, supertype.resolver, constructor) + for ((name, property) in supertypeProperties) { properties.putIfAbsent(name, property) } } - val genericTypeNames = genericTypeNames(proto, typeMetadata.data.nameResolver) - return TargetType(proto, element, constructor, properties, genericTypeNames) + return TargetType(proto, element, constructor, properties, typeVariables) } /** Returns the properties declared by `typeElement`. */ private fun declaredProperties( typeElement: TypeElement, + typeResolver: TypeResolver, constructor: TargetConstructor ): Map { val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata @@ -158,13 +163,10 @@ internal data class TargetType( val result = mutableMapOf() for (property in classProto.propertyList) { val name = nameResolver.getString(property.name) - val type = property.returnType.asTypeName( - nameResolver, classProto::getTypeParameter, false) - val typeWithResolvedAliases = property.returnType.asTypeName( - nameResolver, classProto::getTypeParameter, true) - result[name] = TargetProperty(name, type, typeWithResolvedAliases, property, - constructor.parameters[name], annotationHolders[name], fields[name], - setters[name], getters[name]) + val type = typeResolver.resolve(property.returnType.asTypeName( + nameResolver, classProto::getTypeParameter, true)) + result[name] = TargetProperty(name, type, property, constructor.parameters[name], + annotationHolders[name], fields[name], setters[name], getters[name]) } return result @@ -180,19 +182,6 @@ internal data class TargetType( } } - /** Returns all supertypes of this, recursively. Includes interface and class supertypes. */ - private fun TypeElement.supertypes( - types: Types, - result: MutableSet = mutableSetOf() - ): Set { - result.add(this) - for (supertype in types.directSupertypes(asType())) { - val supertypeElement = (supertype as DeclaredType).asElement() as TypeElement - supertypeElement.supertypes(types, result) - } - return result - } - private val Element.name get() = simpleName.toString() private fun genericTypeNames(proto: Class, nameResolver: NameResolver): List { @@ -219,4 +208,4 @@ internal data class TargetType( } } } -} \ No newline at end of file +} diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeRenderer.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeRenderer.kt new file mode 100644 index 0000000..289be39 --- /dev/null +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeRenderer.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * 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 com.squareup.moshi + +import com.squareup.kotlinpoet.ARRAY +import com.squareup.kotlinpoet.BOOLEAN +import com.squareup.kotlinpoet.BYTE +import com.squareup.kotlinpoet.CHAR +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.CodeBlock +import com.squareup.kotlinpoet.DOUBLE +import com.squareup.kotlinpoet.FLOAT +import com.squareup.kotlinpoet.INT +import com.squareup.kotlinpoet.LONG +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.SHORT +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.WildcardTypeName +import com.squareup.kotlinpoet.asTypeName + +/** + * Renders literals like `Types.newParameterizedType(List::class.java, String::class.java)`. + * Rendering is pluggable so that type variables can either be resolved or emitted as other code + * blocks. + */ +abstract class TypeRenderer { + abstract fun renderTypeVariable(typeVariable: TypeVariableName): CodeBlock + + fun render(typeName: TypeName): CodeBlock { + if (typeName.nullable) { + return render(typeName.asNonNullable()) + } + + return when (typeName) { + is ClassName -> CodeBlock.of("%T::class.java", typeName) + + is ParameterizedTypeName -> { + // If it's an Array type, we shortcut this to return Types.arrayOf() + if (typeName.rawType == ARRAY) { + CodeBlock.of("%T.arrayOf(%L)", + Types::class, + render(typeName.typeArguments[0].objectType())) + } else { + val placeholders = typeName.typeArguments.joinToString(", ") { "%L" } + CodeBlock.of( + "%T.newParameterizedType(%T::class.java, $placeholders)", + Types::class, + typeName.rawType.objectType(), + *(typeName.typeArguments.map { render(it.objectType()) }.toTypedArray())) + } + } + + is WildcardTypeName -> { + val target: TypeName + val method: String + when { + typeName.lowerBounds.size == 1 -> { + target = typeName.lowerBounds[0] + method = "supertypeOf" + } + typeName.upperBounds.size == 1 -> { + target = typeName.upperBounds[0] + method = "subtypeOf" + } + else -> throw IllegalArgumentException( + "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") + } + CodeBlock.of("%T.%L(%T::class.java)", Types::class, method, target) + } + + is TypeVariableName -> renderTypeVariable(typeName) + + else -> throw IllegalArgumentException("Unrepresentable type: $typeName") + } + } + + private fun TypeName.objectType(): TypeName { + return when (this) { + BOOLEAN -> Boolean::class.javaObjectType.asTypeName() + BYTE -> Byte::class.javaObjectType.asTypeName() + SHORT -> Short::class.javaObjectType.asTypeName() + INT -> Integer::class.javaObjectType.asTypeName() + LONG -> Long::class.javaObjectType.asTypeName() + CHAR -> Character::class.javaObjectType.asTypeName() + FLOAT -> Float::class.javaObjectType.asTypeName() + DOUBLE -> Double::class.javaObjectType.asTypeName() + else -> this + } + } +} \ No newline at end of file diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt new file mode 100644 index 0000000..99cc29b --- /dev/null +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/TypeResolver.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2018 Square, Inc. + * + * 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 com.squareup.moshi + +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.ParameterizedTypeName +import com.squareup.kotlinpoet.TypeName +import com.squareup.kotlinpoet.TypeVariableName +import com.squareup.kotlinpoet.WildcardTypeName + +/** + * Resolves type parameters against a type declaration. Use this to fill in type variables with + * their actual type parameters. + */ +open class TypeResolver { + open fun resolveTypeVariable(typeVariable: TypeVariableName): TypeName = typeVariable + + fun resolve(typeName: TypeName): TypeName { + return when (typeName) { + is ClassName -> typeName + + is ParameterizedTypeName -> { + ParameterizedTypeName.get( + typeName.rawType, *(typeName.typeArguments.map { resolve(it) }.toTypedArray())) + } + + is WildcardTypeName -> { + when { + typeName.lowerBounds.size == 1 -> { + WildcardTypeName.supertypeOf(resolve(typeName.lowerBounds[0])) + } + typeName.upperBounds.size == 1 -> { + WildcardTypeName.subtypeOf(resolve(typeName.upperBounds[0])) + } + else -> { + throw IllegalArgumentException( + "Unrepresentable wildcard type. Cannot have more than one bound: $typeName") + } + } + } + + is TypeVariableName -> resolveTypeVariable(typeName) + + else -> throw IllegalArgumentException("Unrepresentable type: $typeName") + } + } +} \ No newline at end of file diff --git a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt index 584926b..f5d9580 100644 --- a/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt +++ b/kotlin-codegen/compiler/src/main/java/com/squareup/moshi/kotlintypes.kt @@ -15,25 +15,9 @@ */ package com.squareup.moshi -import com.squareup.kotlinpoet.ARRAY -import com.squareup.kotlinpoet.BOOLEAN -import com.squareup.kotlinpoet.BYTE -import com.squareup.kotlinpoet.CHAR import com.squareup.kotlinpoet.ClassName -import com.squareup.kotlinpoet.CodeBlock -import com.squareup.kotlinpoet.DOUBLE -import com.squareup.kotlinpoet.FLOAT -import com.squareup.kotlinpoet.INT -import com.squareup.kotlinpoet.LONG -import com.squareup.kotlinpoet.ParameterSpec import com.squareup.kotlinpoet.ParameterizedTypeName -import com.squareup.kotlinpoet.SHORT import com.squareup.kotlinpoet.TypeName -import com.squareup.kotlinpoet.TypeVariableName -import com.squareup.kotlinpoet.WildcardTypeName -import com.squareup.kotlinpoet.asTypeName -import javax.lang.model.element.ElementKind -import javax.lang.model.util.Elements internal fun TypeName.rawType(): ClassName { return when (this) { @@ -42,98 +26,3 @@ internal fun TypeName.rawType(): ClassName { else -> throw IllegalArgumentException("Cannot get raw type from $this") } } - -private fun ClassName.isClass(elements: Elements): Boolean { - val fqcn = toString() - if (fqcn.startsWith("kotlin.collections.")) { - // These are special kotlin interfaces are only visible in kotlin, because they're replaced by - // the compiler with concrete java classes/ - return false - } else if (this == ARRAY) { - // This is a "fake" class and not visible to Elements. - return true - } - return elements.getTypeElement(fqcn).kind == ElementKind.INTERFACE -} - -private fun TypeName.objectType(): TypeName { - return when (this) { - BOOLEAN -> Boolean::class.javaObjectType.asTypeName() - BYTE -> Byte::class.javaObjectType.asTypeName() - SHORT -> Short::class.javaObjectType.asTypeName() - INT -> Integer::class.javaObjectType.asTypeName() - LONG -> Long::class.javaObjectType.asTypeName() - CHAR -> Character::class.javaObjectType.asTypeName() - FLOAT -> Float::class.javaObjectType.asTypeName() - DOUBLE -> Double::class.javaObjectType.asTypeName() - else -> this - } -} - -internal fun TypeName.makeType( - elementUtils: Elements, - typesArray: ParameterSpec, - genericTypeNames: List -): CodeBlock { - if (nullable) { - return asNonNullable().makeType(elementUtils, typesArray, genericTypeNames) - } - return when (this) { - is ClassName -> CodeBlock.of( - "%T::class.java", this) - is ParameterizedTypeName -> { - // If it's an Array type, we shortcut this to return Types.arrayOf() - if (rawType == ARRAY) { - return CodeBlock.of("%T.arrayOf(%L)", - Types::class, - typeArguments[0].objectType().makeType(elementUtils, typesArray, genericTypeNames)) - } - // If it's a Class type, we have to specify the generics. - val rawTypeParameters = if (rawType.isClass(elementUtils)) { - CodeBlock.of( - typeArguments.joinTo( - buffer = StringBuilder(), - separator = ", ", - prefix = "<", - postfix = ">") { "%T" } - .toString(), - *(typeArguments.map { objectType() }.toTypedArray()) - ) - } else { - CodeBlock.of("") - } - CodeBlock.of( - "%T.newParameterizedType(%T%L::class.java, ${typeArguments - .joinToString(", ") { "%L" }})", - Types::class, - rawType.objectType(), - rawTypeParameters, - *(typeArguments.map { - it.objectType().makeType(elementUtils, typesArray, genericTypeNames) - }.toTypedArray())) - } - is WildcardTypeName -> { - val target: TypeName - val method: String - when { - lowerBounds.size == 1 -> { - target = lowerBounds[0] - method = "supertypeOf" - } - upperBounds.size == 1 -> { - target = upperBounds[0] - method = "subtypeOf" - } - else -> throw IllegalArgumentException( - "Unrepresentable wildcard type. Cannot have more than one bound: " + this) - } - CodeBlock.of("%T.%L(%T::class.java)", - Types::class, method, target) - } - is TypeVariableName -> { - CodeBlock.of("%N[%L]", typesArray, - genericTypeNames.indexOfFirst { it == this }) - } - else -> throw IllegalArgumentException("Unrepresentable type: " + this) - } -} \ No newline at end of file