Merge pull request #506 from square/jwilson.0415.type_resolver

Begin to resolve supertype type parameters
This commit is contained in:
Jesse Wilson
2018-04-16 22:34:03 -04:00
committed by GitHub
9 changed files with 290 additions and 174 deletions

View File

@@ -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<PropertyGenerator>,
val elements: Elements
private val propertyList: List<PropertyGenerator>
) {
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)

View File

@@ -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<String>` 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<AppliedType> = mutableSetOf()
): Set<AppliedType> {
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)
}
}
}

View File

@@ -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()
}

View File

@@ -120,7 +120,7 @@ class JsonClassCodeGenProcessor : KotlinAbstractProcessor(), KotlinMetadataUtils
}
}
return AdapterGenerator(type, sortedProperties, elementUtils)
return AdapterGenerator(type, sortedProperties)
}
private fun AdapterGenerator.generateAndWrite(generatedOption: TypeElement?) {

View File

@@ -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<AnnotationMirror> {
@@ -107,8 +106,7 @@ internal data class TargetProperty(
private val Element?.qualifiers: Set<AnnotationMirror>
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. */

View File

@@ -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<String, TargetProperty>,
val genericTypeNames: List<TypeVariableName>
val typeVariables: List<TypeVariableName>
) {
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<String, TargetProperty>()
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<String, TargetProperty> {
val typeMetadata: KotlinClassMetadata = typeElement.kotlinMetadata as KotlinClassMetadata
@@ -158,13 +163,10 @@ internal data class TargetType(
val result = mutableMapOf<String, TargetProperty>()
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<TypeElement> = mutableSetOf()
): Set<TypeElement> {
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<TypeVariableName> {
@@ -219,4 +208,4 @@ internal data class TargetType(
}
}
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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")
}
}
}

View File

@@ -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<TypeVariableName>
): 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)
}
}