aboutsummaryrefslogtreecommitdiff
path: root/interop/kotlinx-metadata/src/main/kotlin
diff options
context:
space:
mode:
Diffstat (limited to 'interop/kotlinx-metadata/src/main/kotlin')
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt338
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt112
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt241
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt585
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt194
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt25
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt604
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt117
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt67
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt104
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt64
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt40
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt36
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt33
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt294
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt948
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt91
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt88
-rw-r--r--interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt79
19 files changed, 4060 insertions, 0 deletions
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
new file mode 100644
index 00000000..4745dda0
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/Flags.kt
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2019 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
+ *
+ * https://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.
+ */
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import kotlinx.metadata.Flag
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmValueParameter
+
+// Common flags for any element with flags.
+@KotlinPoetMetadataPreview
+public val Flags.hasAnnotations: Boolean get() = Flag.HAS_ANNOTATIONS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isAbstract: Boolean get() = Flag.IS_ABSTRACT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFinal: Boolean get() = Flag.IS_FINAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInternal: Boolean get() = Flag.IS_INTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isLocal: Boolean get() = Flag.IS_LOCAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOpen: Boolean get() = Flag.IS_OPEN(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate: Boolean get() = Flag.IS_PRIVATE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPrivate_to_this: Boolean get() = Flag.IS_PRIVATE_TO_THIS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isProtected: Boolean get() = Flag.IS_PROTECTED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPublic: Boolean get() = Flag.IS_PUBLIC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSealed: Boolean get() = Flag.IS_SEALED(this)
+
+// Type flags.
+@KotlinPoetMetadataPreview
+public val Flags.isNullableType: Boolean get() = Flag.Type.IS_NULLABLE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendType: Boolean get() = Flag.Type.IS_SUSPEND(this)
+
+// Class flags.
+@KotlinPoetMetadataPreview
+public val Flags.isAnnotationClass: Boolean get() = Flag.Class.IS_ANNOTATION_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isClass: Boolean get() = Flag.Class.IS_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isCompanionObjectClass: Boolean get() = Flag.Class.IS_COMPANION_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDataClass: Boolean get() = Flag.Class.IS_DATA(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumClass: Boolean get() = Flag.Class.IS_ENUM_CLASS(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isEnumEntryClass: Boolean get() = Flag.Class.IS_ENUM_ENTRY(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectClass: Boolean get() = Flag.Class.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalClass: Boolean get() = Flag.Class.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isValueClass: Boolean get() = Flag.Class.IS_VALUE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInnerClass: Boolean get() = Flag.Class.IS_INNER(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isObjectClass: Boolean get() = Flag.Class.IS_OBJECT(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInterface: Boolean get() = Flag.Class.IS_INTERFACE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFun: Boolean get() = Flag.Class.IS_FUN(this)
+
+@KotlinPoetMetadataPreview
+public val KmClass.isAnnotation: Boolean get() = flags.isAnnotationClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isClass: Boolean get() = flags.isClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isCompanionObject: Boolean get() = flags.isCompanionObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isData: Boolean get() = flags.isDataClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnum: Boolean get() = flags.isEnumClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isEnumEntry: Boolean get() = flags.isEnumEntryClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExpect: Boolean get() = flags.isExpectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isExternal: Boolean get() = flags.isExternalClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isValue: Boolean get() = flags.isValueClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInner: Boolean get() = flags.isInnerClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isObject: Boolean get() = flags.isObjectClass
+
+@KotlinPoetMetadataPreview
+public val KmClass.isInterface: Boolean get() = flags.isInterface
+
+@KotlinPoetMetadataPreview
+public val KmClass.isFun: Boolean get() = flags.isFun
+
+@KotlinPoetMetadataPreview
+public val KmType.isSuspend: Boolean get() = flags.isSuspendType
+
+@KotlinPoetMetadataPreview
+public val KmType.isNullable: Boolean get() = flags.isNullableType
+
+// Constructor flags.
+@KotlinPoetMetadataPreview
+public val Flags.isPrimaryConstructor: Boolean get() = !Flag.Constructor.IS_SECONDARY(this)
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isPrimary: Boolean get() = flags.isPrimaryConstructor
+
+@KotlinPoetMetadataPreview
+public val KmConstructor.isSecondary: Boolean get() = !isPrimary
+
+// Function flags.
+@KotlinPoetMetadataPreview
+public val Flags.isDeclarationFunction: Boolean get() = Flag.Function.IS_DECLARATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideFunction: Boolean get() = Flag.Function.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isDelegationFunction: Boolean get() = Flag.Function.IS_DELEGATION(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSynthesizedFunction: Boolean get() = Flag.Function.IS_SYNTHESIZED(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isOperatorFunction: Boolean get() = Flag.Function.IS_OPERATOR(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInfixFunction: Boolean get() = Flag.Function.IS_INFIX(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isInlineFunction: Boolean get() = Flag.Function.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isTailRecFunction: Boolean get() = Flag.Function.IS_TAILREC(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExternalFunction: Boolean get() = Flag.Function.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isSuspendFunction: Boolean get() = Flag.Function.IS_SUSPEND(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isExpectFunction: Boolean get() = Flag.Function.IS_EXPECT(this)
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDeclaration: Boolean get() = flags.isDeclarationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isFakeOverride: Boolean get() = flags.isFakeOverrideFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isDelegation: Boolean get() = flags.isDelegationFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSynthesized: Boolean get() = flags.isSynthesizedFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isOperator: Boolean get() = flags.isOperatorFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInfix: Boolean get() = flags.isInfixFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isInline: Boolean get() = flags.isInlineFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isTailRec: Boolean get() = flags.isTailRecFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExternal: Boolean get() = flags.isExternalFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isSuspend: Boolean get() = flags.isSuspendFunction
+
+@KotlinPoetMetadataPreview
+public val KmFunction.isExpect: Boolean get() = flags.isExpectFunction
+
+// Parameter flags.
+@KotlinPoetMetadataPreview
+public val KmValueParameter.declaresDefaultValue: Boolean get() =
+ Flag.ValueParameter.DECLARES_DEFAULT_VALUE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isCrossInline: Boolean get() = Flag.ValueParameter.IS_CROSSINLINE(flags)
+
+@KotlinPoetMetadataPreview
+public val KmValueParameter.isNoInline: Boolean get() = Flag.ValueParameter.IS_NOINLINE(flags)
+
+// Property flags.
+@KotlinPoetMetadataPreview
+public val Flags.isFakeOverrideProperty: Boolean get() = Flag.Property.IS_FAKE_OVERRIDE(this)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasConstant: Boolean get() = Flag.Property.HAS_CONSTANT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasGetter: Boolean get() = Flag.Property.HAS_GETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.hasSetter: Boolean get() = Flag.Property.HAS_SETTER(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isConst: Boolean get() = Flag.Property.IS_CONST(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDeclaration: Boolean get() = Flag.Property.IS_DECLARATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegated: Boolean get() = Flag.Property.IS_DELEGATED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isDelegation: Boolean get() = Flag.Property.IS_DELEGATION(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExpect: Boolean get() = Flag.Property.IS_EXPECT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isExternal: Boolean get() = Flag.Property.IS_EXTERNAL(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isFakeOverride: Boolean get() = flags.isFakeOverrideProperty
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isLateinit: Boolean get() = Flag.Property.IS_LATEINIT(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isSynthesized: Boolean get() = Flag.Property.IS_SYNTHESIZED(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVar: Boolean get() = Flag.Property.IS_VAR(flags)
+
+@KotlinPoetMetadataPreview
+public val KmProperty.isVal: Boolean get() = !isVar
+
+// Property Accessor Flags
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorExternal: Boolean
+ get() = Flag.PropertyAccessor.IS_EXTERNAL(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorInline: Boolean
+ get() = Flag.PropertyAccessor.IS_INLINE(this)
+
+@KotlinPoetMetadataPreview
+public val Flags.isPropertyAccessorNotDefault: Boolean
+ get() = Flag.PropertyAccessor.IS_NOT_DEFAULT(this)
+
+// TypeParameter flags.
+@KotlinPoetMetadataPreview
+public val KmTypeParameter.isReified: Boolean get() = Flag.TypeParameter.IS_REIFIED(flags)
+
+// Property Accessor Flags
+public enum class PropertyAccessorFlag {
+ IS_EXTERNAL,
+ IS_INLINE,
+ IS_NOT_DEFAULT,
+}
+
+@KotlinPoetMetadataPreview
+public val KmProperty.setterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = setterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val KmProperty.getterPropertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = getterFlags.propertyAccessorFlags
+
+@KotlinPoetMetadataPreview
+public val Flags.propertyAccessorFlags: Set<PropertyAccessorFlag>
+ get() = setOf {
+ if (Flag.PropertyAccessor.IS_EXTERNAL(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_EXTERNAL)
+ }
+ if (Flag.PropertyAccessor.IS_INLINE(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_INLINE)
+ }
+ if (Flag.PropertyAccessor.IS_NOT_DEFAULT(this@propertyAccessorFlags)) {
+ add(PropertyAccessorFlag.IS_NOT_DEFAULT)
+ }
+ }
+
+internal inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+ return mutableSetOf<E>().apply(body).toSet()
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
new file mode 100644
index 00000000..6cb3c91d
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/KotlinPoetMetadata.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2019 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
+ *
+ * https://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.
+ */
+@file:JvmName("KotlinPoetMetadata")
+@file:Suppress("unused")
+
+package com.squareup.kotlinpoet.metadata
+
+import javax.lang.model.element.TypeElement
+import kotlin.annotation.AnnotationTarget.CLASS
+import kotlin.annotation.AnnotationTarget.FUNCTION
+import kotlin.annotation.AnnotationTarget.PROPERTY
+import kotlin.reflect.KClass
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.jvm.KotlinClassHeader
+import kotlinx.metadata.jvm.KotlinClassMetadata
+
+/**
+ * Indicates that a given API is part of the experimental KotlinPoet metadata support. This exists
+ * because kotlinx-metadata is not a stable API, and will remain in place until it is.
+ */
+@RequiresOptIn
+@Retention(AnnotationRetention.BINARY)
+@Target(CLASS, FUNCTION, PROPERTY)
+public annotation class KotlinPoetMetadataPreview
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toKmClass(): KmClass = java.toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] class. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+/** @return a new [KmClass] representation of the Kotlin metadata for [this] type. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toKmClass(): KmClass = readMetadata(::getAnnotation).toKmClass()
+
+@KotlinPoetMetadataPreview
+public fun Metadata.toKmClass(): KmClass {
+ return toKotlinClassMetadata<KotlinClassMetadata.Class>()
+ .toKmClass()
+}
+
+@KotlinPoetMetadataPreview
+public inline fun <reified T : KotlinClassMetadata> Metadata.toKotlinClassMetadata(): T {
+ val expectedType = T::class
+ val metadata = readKotlinClassMetadata()
+ return when (expectedType) {
+ KotlinClassMetadata.Class::class -> {
+ check(metadata is KotlinClassMetadata.Class)
+ metadata as T
+ }
+ KotlinClassMetadata.FileFacade::class -> {
+ check(metadata is KotlinClassMetadata.FileFacade)
+ metadata as T
+ }
+ KotlinClassMetadata.SyntheticClass::class ->
+ throw UnsupportedOperationException("SyntheticClass isn't supported yet!")
+ KotlinClassMetadata.MultiFileClassFacade::class ->
+ throw UnsupportedOperationException("MultiFileClassFacade isn't supported yet!")
+ KotlinClassMetadata.MultiFileClassPart::class ->
+ throw UnsupportedOperationException("MultiFileClassPart isn't supported yet!")
+ KotlinClassMetadata.Unknown::class ->
+ throw RuntimeException("Recorded unknown metadata type! $metadata")
+ else -> TODO("Unrecognized KotlinClassMetadata type: $expectedType")
+ }
+}
+
+/**
+ * Returns the [KotlinClassMetadata] this represents. In general you should only use this function
+ * when you don't know what the underlying [KotlinClassMetadata] subtype is, otherwise you should
+ * use one of the more direct functions like [toKmClass].
+ */
+@KotlinPoetMetadataPreview
+public fun Metadata.readKotlinClassMetadata(): KotlinClassMetadata {
+ val metadata = KotlinClassMetadata.read(asClassHeader())
+ checkNotNull(metadata) {
+ "Could not parse metadata! Try bumping kotlinpoet and/or kotlinx-metadata version."
+ }
+ return metadata
+}
+
+private inline fun readMetadata(lookup: ((Class<Metadata>) -> Metadata?)): Metadata {
+ return checkNotNull(lookup.invoke(Metadata::class.java)) {
+ "No Metadata annotation found! Must be Kotlin code built with the standard library on the classpath."
+ }
+}
+
+private fun Metadata.asClassHeader(): KotlinClassHeader {
+ return KotlinClassHeader(
+ kind = kind,
+ metadataVersion = metadataVersion,
+ data1 = data1,
+ data2 = data2,
+ extraString = extraString,
+ packageName = packageName,
+ extraInt = extraInt,
+ )
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
new file mode 100644
index 00000000..d9caa834
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ClassInspectorUtil.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CHAR_SEQUENCE
+import com.squareup.kotlinpoet.COLLECTION
+import com.squareup.kotlinpoet.COMPARABLE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.ITERABLE
+import com.squareup.kotlinpoet.LIST
+import com.squareup.kotlinpoet.MAP
+import com.squareup.kotlinpoet.MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_COLLECTION
+import com.squareup.kotlinpoet.MUTABLE_ITERABLE
+import com.squareup.kotlinpoet.MUTABLE_LIST
+import com.squareup.kotlinpoet.MUTABLE_MAP
+import com.squareup.kotlinpoet.MUTABLE_MAP_ENTRY
+import com.squareup.kotlinpoet.MUTABLE_SET
+import com.squareup.kotlinpoet.SET
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import java.util.Collections
+import java.util.TreeSet
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.isLocal
+import org.jetbrains.annotations.NotNull
+import org.jetbrains.annotations.Nullable
+
+@KotlinPoetMetadataPreview
+internal object ClassInspectorUtil {
+ val JVM_NAME: ClassName = JvmName::class.asClassName()
+ private val JVM_FIELD = JvmField::class.asClassName()
+ internal val JVM_FIELD_SPEC = AnnotationSpec.builder(JVM_FIELD).build()
+ internal val JVM_SYNTHETIC = JvmSynthetic::class.asClassName()
+ internal val JVM_SYNTHETIC_SPEC = AnnotationSpec.builder(JVM_SYNTHETIC).build()
+ internal val JAVA_DEPRECATED = java.lang.Deprecated::class.asClassName()
+ private val JVM_TRANSIENT = Transient::class.asClassName()
+ private val JVM_VOLATILE = Volatile::class.asClassName()
+ private val IMPLICIT_FIELD_ANNOTATIONS = setOf(
+ JVM_FIELD,
+ JVM_TRANSIENT,
+ JVM_VOLATILE,
+ )
+ private val NOT_NULL = NotNull::class.asClassName()
+ private val NULLABLE = Nullable::class.asClassName()
+ private val EXTENSION_FUNCTION_TYPE = ExtensionFunctionType::class.asClassName()
+ private val KOTLIN_INTRINSIC_ANNOTATIONS = setOf(
+ NOT_NULL,
+ NULLABLE,
+ EXTENSION_FUNCTION_TYPE,
+ )
+
+ val KOTLIN_INTRINSIC_INTERFACES: Set<ClassName> = setOf(
+ CHAR_SEQUENCE,
+ COMPARABLE,
+ ITERABLE,
+ COLLECTION,
+ LIST,
+ SET,
+ MAP,
+ MAP_ENTRY,
+ MUTABLE_ITERABLE,
+ MUTABLE_COLLECTION,
+ MUTABLE_LIST,
+ MUTABLE_SET,
+ MUTABLE_MAP,
+ MUTABLE_MAP_ENTRY,
+ )
+
+ private val KOTLIN_NULLABILITY_ANNOTATIONS = setOf(
+ "org.jetbrains.annotations.NotNull",
+ "org.jetbrains.annotations.Nullable",
+ )
+
+ fun filterOutNullabilityAnnotations(
+ annotations: List<AnnotationSpec>,
+ ): List<AnnotationSpec> {
+ return annotations.filterNot {
+ val typeName = it.typeName
+ return@filterNot typeName is ClassName &&
+ typeName.canonicalName in KOTLIN_NULLABILITY_ANNOTATIONS
+ }
+ }
+
+ /** @return a [CodeBlock] representation of a [literal] value. */
+ fun codeLiteralOf(literal: Any): CodeBlock {
+ return when (literal) {
+ is String -> CodeBlock.of("%S", literal)
+ is Long -> CodeBlock.of("%LL", literal)
+ is Float -> CodeBlock.of("%LF", literal)
+ else -> CodeBlock.of("%L", literal)
+ }
+ }
+
+ /**
+ * Infers if [property] is a jvm field and should be annotated as such given the input
+ * parameters.
+ */
+ fun computeIsJvmField(
+ property: KmProperty,
+ classInspector: ClassInspector,
+ isCompanionObject: Boolean,
+ hasGetter: Boolean,
+ hasSetter: Boolean,
+ hasField: Boolean,
+ ): Boolean {
+ return if (!hasGetter &&
+ !hasSetter &&
+ hasField &&
+ !property.isConst
+ ) {
+ !(classInspector.supportsNonRuntimeRetainedAnnotations && !isCompanionObject)
+ } else {
+ false
+ }
+ }
+
+ /**
+ * @return a new collection of [AnnotationSpecs][AnnotationSpec] with sorting and de-duping
+ * input annotations from [body].
+ */
+ fun createAnnotations(
+ siteTarget: UseSiteTarget? = null,
+ body: MutableCollection<AnnotationSpec>.() -> Unit,
+ ): Collection<AnnotationSpec> {
+ val result = mutableSetOf<AnnotationSpec>()
+ .apply(body)
+ .filterNot { spec ->
+ spec.typeName in KOTLIN_INTRINSIC_ANNOTATIONS
+ }
+ val withUseSiteTarget = if (siteTarget != null) {
+ result.map {
+ if (!(siteTarget == FIELD && it.typeName in IMPLICIT_FIELD_ANNOTATIONS)) {
+ // Some annotations are implicitly only for FIELD, so don't emit those site targets
+ it.toBuilder().useSiteTarget(siteTarget).build()
+ } else {
+ it
+ }
+ }
+ } else {
+ result
+ }
+
+ val sorted = withUseSiteTarget.toTreeSet()
+
+ return Collections.unmodifiableCollection(sorted)
+ }
+
+ /**
+ * @return a [@Throws][Throws] [AnnotationSpec] representation of a given collection of
+ * [exceptions].
+ */
+ fun createThrowsSpec(
+ exceptions: Collection<TypeName>,
+ useSiteTarget: UseSiteTarget? = null,
+ ): AnnotationSpec {
+ return AnnotationSpec.builder(Throws::class)
+ .addMember(
+ "exceptionClasses = %L",
+ exceptions.map { CodeBlock.of("%T::class", it) }
+ .joinToCode(prefix = "[", suffix = "]"),
+ )
+ .useSiteTarget(useSiteTarget)
+ .build()
+ }
+
+ /**
+ * Best guesses a [ClassName] as represented in Metadata's [kotlinx.metadata.ClassName], where
+ * package names in this name are separated by '/' and class names are separated by '.'.
+ *
+ * For example: `"org/foo/bar/Baz.Nested"`.
+ *
+ * Local classes are prefixed with ".", but for KotlinPoetMetadataSpecs' use case we don't deal
+ * with those.
+ */
+ fun createClassName(kotlinMetadataName: String): ClassName {
+ require(!kotlinMetadataName.isLocal) {
+ "Local/anonymous classes are not supported!"
+ }
+ // Top-level: package/of/class/MyClass
+ // Nested A: package/of/class/MyClass.NestedClass
+ val simpleName = kotlinMetadataName.substringAfterLast(
+ '/', // Drop the package name, e.g. "package/of/class/"
+ '.', // Drop any enclosing classes, e.g. "MyClass."
+ )
+ val packageName = kotlinMetadataName.substringBeforeLast(
+ delimiter = "/",
+ missingDelimiterValue = "",
+ )
+ val simpleNames = kotlinMetadataName.removeSuffix(simpleName)
+ .removeSuffix(".") // Trailing "." if any
+ .removePrefix(packageName)
+ .removePrefix("/")
+ .let {
+ if (it.isNotEmpty()) {
+ it.split(".")
+ } else {
+ // Don't split, otherwise we end up with an empty string as the first element!
+ emptyList()
+ }
+ }
+ .plus(simpleName)
+
+ return ClassName(
+ packageName = packageName.replace("/", "."),
+ simpleNames = simpleNames,
+ )
+ }
+
+ fun Iterable<AnnotationSpec>.toTreeSet(): TreeSet<AnnotationSpec> {
+ return TreeSet<AnnotationSpec>(compareBy { it.toString() }).apply {
+ addAll(this@toTreeSet)
+ }
+ }
+
+ private fun String.substringAfterLast(vararg delimiters: Char): String {
+ val index = lastIndexOfAny(delimiters)
+ return if (index == -1) this else substring(index + 1, length)
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
new file mode 100644
index 00000000..d4f77860
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ElementsClassInspector.kt
@@ -0,0 +1,585 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.classinspectors
+
+import com.google.auto.common.MoreElements
+import com.google.auto.common.MoreTypes
+import com.google.auto.common.Visibility
+import com.google.common.collect.LinkedHashMultimap
+import com.google.common.collect.SetMultimap
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JVM_NAME
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind.INTERFACE
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.TypeKind
+import javax.lang.model.util.ElementFilter
+import javax.lang.model.util.Elements
+import javax.lang.model.util.Types
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+private typealias ElementsModifier = javax.lang.model.element.Modifier
+
+/**
+ * An [Elements]-based implementation of [ClassInspector].
+ */
+@KotlinPoetMetadataPreview
+public class ElementsClassInspector private constructor(
+ private val elements: Elements,
+ private val types: Types,
+) : ClassInspector {
+ private val typeElementCache = ConcurrentHashMap<ClassName, Optional<TypeElement>>()
+ private val methodCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<ExecutableElement>>()
+ private val variableElementCache = ConcurrentHashMap<Pair<TypeElement, String>, Optional<VariableElement>>()
+ private val jvmNameType = elements.getTypeElement(JVM_NAME.canonicalName)
+ private val jvmNameName = ElementFilter.methodsIn(jvmNameType.enclosedElements)
+ .first { it.simpleName.toString() == "name" }
+
+ private fun lookupTypeElement(className: ClassName): TypeElement? {
+ return typeElementCache.getOrPut(className) {
+ elements.getTypeElement(className.canonicalName).toOptional()
+ }.nullableValue
+ }
+
+ override val supportsNonRuntimeRetainedAnnotations: Boolean = true
+
+ override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+ val typeElement = lookupTypeElement(className)
+ ?: error("No type element found for: $className.")
+
+ val metadata = typeElement.getAnnotation(Metadata::class.java)
+ return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+ is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+ is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+ else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+ }
+ }
+
+ override fun isInterface(className: ClassName): Boolean {
+ if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+ return true
+ }
+ return lookupTypeElement(className)?.kind == INTERFACE
+ }
+
+ private fun TypeElement.lookupField(fieldSignature: JvmFieldSignature): VariableElement? {
+ val signatureString = fieldSignature.asString()
+ return variableElementCache.getOrPut(this to signatureString) {
+ ElementFilter.fieldsIn(enclosedElements)
+ .find { signatureString == it.jvmFieldSignature(types) }.toOptional()
+ }.nullableValue
+ }
+
+ private fun lookupMethod(
+ className: ClassName,
+ methodSignature: JvmMethodSignature,
+ elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+ ): ExecutableElement? {
+ return lookupTypeElement(className)?.lookupMethod(methodSignature, elementFilter)
+ }
+
+ private fun TypeElement.lookupMethod(
+ methodSignature: JvmMethodSignature,
+ elementFilter: (Iterable<Element>) -> List<ExecutableElement>,
+ ): ExecutableElement? {
+ val signatureString = methodSignature.asString()
+ return methodCache.getOrPut(this to signatureString) {
+ elementFilter(enclosedElements)
+ .find { signatureString == it.jvmMethodSignature(types) }.toOptional()
+ }.nullableValue
+ }
+
+ private fun VariableElement.jvmModifiers(isJvmField: Boolean): Set<JvmFieldModifier> {
+ return modifiers.mapNotNullTo(mutableSetOf()) {
+ when {
+ it == ElementsModifier.TRANSIENT -> TRANSIENT
+ it == ElementsModifier.VOLATILE -> VOLATILE
+ !isJvmField && it == ElementsModifier.STATIC -> JvmFieldModifier.STATIC
+ else -> null
+ }
+ }
+ }
+
+ private fun VariableElement.annotationSpecs(): List<AnnotationSpec> {
+ @Suppress("DEPRECATION")
+ return filterOutNullabilityAnnotations(
+ annotationMirrors.map { AnnotationSpec.get(it) },
+ )
+ }
+
+ private fun ExecutableElement.jvmModifiers(): Set<JvmMethodModifier> {
+ return modifiers.mapNotNullTo(mutableSetOf()) {
+ when (it) {
+ ElementsModifier.SYNCHRONIZED -> SYNCHRONIZED
+ ElementsModifier.STATIC -> STATIC
+ ElementsModifier.DEFAULT -> DEFAULT
+ else -> null
+ }
+ }
+ }
+
+ private fun ExecutableElement.annotationSpecs(): List<AnnotationSpec> {
+ @Suppress("DEPRECATION")
+ return filterOutNullabilityAnnotations(
+ annotationMirrors.map { AnnotationSpec.get(it) },
+ )
+ }
+
+ private fun ExecutableElement.exceptionTypeNames(): List<TypeName> {
+ @Suppress("DEPRECATION")
+ return thrownTypes.map { it.asTypeName() }
+ }
+
+ override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+ val enumType = lookupTypeElement(enumClassName)
+ ?: error("No type element found for: $enumClassName.")
+ val enumTypeAsType = enumType.asType()
+ val member = typeElementCache.getOrPut(enumClassName.nestedClass(memberName)) {
+ ElementFilter.typesIn(enumType.enclosedElements)
+ .asSequence()
+ .filter { types.isSubtype(enumTypeAsType, it.superclass) }
+ .find { it.simpleName.contentEquals(memberName) }.toOptional()
+ }.nullableValue
+ val declarationContainer = member?.getAnnotation(Metadata::class.java)?.toKmClass()
+
+ val entry = ElementFilter.fieldsIn(enumType.enclosedElements)
+ .find { it.simpleName.contentEquals(memberName) }
+ ?: error("Could not find the enum entry for: $enumClassName")
+
+ return EnumEntryData(
+ declarationContainer = declarationContainer,
+ annotations = entry.annotationSpecs(),
+ )
+ }
+
+ private fun VariableElement.constantValue(): CodeBlock? {
+ return constantValue?.let(ClassInspectorUtil::codeLiteralOf)
+ }
+
+ override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+ return lookupMethod(className, methodSignature, ElementFilter::methodsIn) != null
+ }
+
+ /**
+ * Detects whether [this] given method is overridden in [type].
+ *
+ * Adapted and simplified from AutoCommon's private
+ * [MoreElements.getLocalAndInheritedMethods] methods implementations for detecting
+ * overrides.
+ */
+ private fun ExecutableElement.isOverriddenIn(type: TypeElement): Boolean {
+ val methodMap = LinkedHashMultimap.create<String, ExecutableElement>()
+ type.getAllMethods(MoreElements.getPackage(type), methodMap)
+ // Find methods that are overridden using `Elements.overrides`. We reduce the performance
+ // impact by:
+ // (a) grouping methods by name, since a method cannot override another method with a
+ // different name. Since we know the target name, we just inspect the methods with
+ // that name.
+ // (b) making sure that methods in ancestor types precede those in descendant types,
+ // which means we only have to check a method against the ones that follow it in
+ // that order. Below, this means we just need to find the index of our target method
+ // and compare against only preceding ones.
+ val methodList = methodMap.asMap()[simpleName.toString()]?.toList()
+ ?: return false
+ val signature = jvmMethodSignature(types)
+ return methodList.asSequence()
+ .filter { it.jvmMethodSignature(types) == signature }
+ .take(1)
+ .any { elements.overrides(this, it, type) }
+ }
+
+ /**
+ * Add to [methodsAccumulator] the instance methods from [this] that are visible to code in
+ * the package [pkg]. This means all the instance methods from [this] itself and all
+ * instance methods it inherits from its ancestors, except private methods and
+ * package-private methods in other packages. This method does not take overriding into
+ * account, so it will add both an ancestor method and a descendant method that overrides
+ * it. [methodsAccumulator] is a multimap from a method name to all of the methods with
+ * that name, including methods that override or overload one another. Within those
+ * methods, those in ancestor types always precede those in descendant types.
+ *
+ * Adapted from AutoCommon's private [MoreElements.getLocalAndInheritedMethods] methods'
+ * implementations, before overridden methods are stripped.
+ */
+ private fun TypeElement.getAllMethods(
+ pkg: PackageElement,
+ methodsAccumulator: SetMultimap<String, ExecutableElement>,
+ ) {
+ for (superInterface in interfaces) {
+ MoreTypes.asTypeElement(superInterface).getAllMethods(pkg, methodsAccumulator)
+ }
+ if (superclass.kind != TypeKind.NONE) {
+ // Visit the superclass after superinterfaces so we will always see the implementation of a
+ // method after any interfaces that declared it.
+ MoreTypes.asTypeElement(superclass).getAllMethods(pkg, methodsAccumulator)
+ }
+ for (method in ElementFilter.methodsIn(enclosedElements)) {
+ if (ElementsModifier.STATIC !in method.modifiers &&
+ ElementsModifier.FINAL !in method.modifiers &&
+ ElementsModifier.PRIVATE !in method.modifiers &&
+ method.isVisibleFrom(pkg)
+ ) {
+ methodsAccumulator.put(method.simpleName.toString(), method)
+ }
+ }
+ }
+
+ private fun ExecutableElement.isVisibleFrom(pkg: PackageElement): Boolean {
+ // We use Visibility.ofElement rather than [MoreElements.effectiveVisibilityOfElement]
+ // because it doesn't really matter whether the containing class is visible. If you
+ // inherit a public method then you have a public method, regardless of whether you
+ // inherit it from a public class.
+ return when (Visibility.ofElement(this)) {
+ Visibility.PRIVATE -> false
+ Visibility.DEFAULT -> MoreElements.getPackage(this) == pkg
+ else -> true
+ }
+ }
+
+ override fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData {
+ val typeElement: TypeElement = lookupTypeElement(className) ?: error("No class found for: $className.")
+ val isCompanionObject = when (declarationContainer) {
+ is KmClass -> {
+ declarationContainer.isCompanionObject
+ }
+ is KmPackage -> {
+ false
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+
+ // Should only be called if parentName has been null-checked
+ val classIfCompanion by lazy(NONE) {
+ if (isCompanionObject && parentClassName != null) {
+ lookupTypeElement(parentClassName)
+ ?: error("No class found for: $parentClassName.")
+ } else {
+ typeElement
+ }
+ }
+
+ val propertyData = declarationContainer.properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+ val isJvmField = ClassInspectorUtil.computeIsJvmField(
+ property = property,
+ classInspector = this,
+ isCompanionObject = isCompanionObject,
+ hasGetter = property.getterSignature != null,
+ hasSetter = property.setterSignature != null,
+ hasField = property.fieldSignature != null,
+ )
+
+ val fieldData = property.fieldSignature?.let fieldDataLet@{ fieldSignature ->
+ // Check the field in the parent first. For const/static/jvmField elements, these only
+ // exist in the parent and we want to check that if necessary to avoid looking up a
+ // non-existent field in the companion.
+ val parentModifiers = if (isCompanionObject && parentClassName != null) {
+ classIfCompanion.lookupField(fieldSignature)?.jvmModifiers(isJvmField).orEmpty()
+ } else {
+ emptySet()
+ }
+
+ val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+ // TODO we looked up field once, let's reuse it
+ val classForOriginalField = typeElement.takeUnless {
+ isCompanionObject &&
+ (property.isConst || isJvmField || isStatic)
+ } ?: classIfCompanion
+
+ val field = classForOriginalField.lookupField(fieldSignature)
+ ?: return@fieldDataLet FieldData.SYNTHETIC
+ val constant = if (property.hasConstant) {
+ val fieldWithConstant = classIfCompanion.takeIf { it != typeElement }?.let {
+ if (it.kind.isInterface) {
+ field
+ } else {
+ // const properties are relocated to the enclosing class
+ it.lookupField(fieldSignature)
+ ?: return@fieldDataLet FieldData.SYNTHETIC
+ }
+ } ?: field
+ fieldWithConstant.constantValue()
+ } else {
+ null
+ }
+
+ val jvmModifiers = field.jvmModifiers(isJvmField) + parentModifiers
+
+ FieldData(
+ annotations = field.annotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+ // JvmField companion objects don't need JvmStatic, it's implicit
+ isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+ },
+ constant = constant,
+ )
+ }
+
+ val getterData = property.getterSignature?.let { getterSignature ->
+ val method = classIfCompanion.lookupMethod(getterSignature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = property.getterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(getterSignature, ElementFilter::methodsIn)
+ ?: method,
+ )
+ ?: return@let MethodData.SYNTHETIC
+ }
+
+ val setterData = property.setterSignature?.let { setterSignature ->
+ val method = classIfCompanion.lookupMethod(setterSignature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = property.setterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(setterSignature, ElementFilter::methodsIn)
+ ?: method,
+ knownIsOverride = getterData?.isOverride,
+ )
+ ?: return@let MethodData.SYNTHETIC
+ }
+
+ val annotations = mutableListOf<AnnotationSpec>()
+ if (property.flags.hasAnnotations) {
+ property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+ val method = typeElement.lookupMethod(annotationsHolderSignature, ElementFilter::methodsIn)
+ ?: return@let MethodData.SYNTHETIC
+ annotations += method.annotationSpecs()
+ // Cover for https://github.com/square/kotlinpoet/issues/1046
+ .filterNot { it.typeName == JAVA_DEPRECATED }
+ }
+ }
+
+ // If a field is static in a companion object, remove the modifier and add the annotation
+ // directly on the top level. Otherwise this will generate `@field:JvmStatic`, which is
+ // not legal
+ var finalFieldData = fieldData
+ fieldData?.jvmModifiers?.let { modifiers ->
+ if (isCompanionObject && JvmFieldModifier.STATIC in modifiers) {
+ finalFieldData = fieldData.copy(
+ jvmModifiers = fieldData.jvmModifiers
+ .filterNotTo(LinkedHashSet()) { it == JvmFieldModifier.STATIC },
+ )
+ annotations += AnnotationSpec.builder(
+ JVM_STATIC,
+ ).build()
+ }
+ }
+
+ PropertyData(
+ annotations = annotations,
+ fieldData = finalFieldData,
+ getterData = getterData,
+ setterData = setterData,
+ isJvmField = isJvmField,
+ )
+ }
+
+ val methodData = declarationContainer.functions
+ .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+ val signature = kmFunction.signature
+ if (signature != null) {
+ val method = typeElement.lookupMethod(signature, ElementFilter::methodsIn)
+ method?.methodData(
+ typeElement = typeElement,
+ hasAnnotations = kmFunction.flags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != typeElement }
+ ?.lookupMethod(signature, ElementFilter::methodsIn)
+ ?: method,
+ )
+ ?: return@associateWithTo MethodData.SYNTHETIC
+ } else {
+ MethodData.EMPTY
+ }
+ }
+
+ when (declarationContainer) {
+ is KmClass -> {
+ val constructorData = declarationContainer.constructors
+ .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+ if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+ //
+ // Annotations are interfaces in bytecode, but kotlin metadata will still report a
+ // constructor signature
+ //
+ // Inline classes have no constructors at runtime
+ //
+ return@associateWithTo ConstructorData.EMPTY
+ }
+ val signature = kmConstructor.signature
+ if (signature != null) {
+ val constructor = typeElement.lookupMethod(signature, ElementFilter::constructorsIn)
+ ?: return@associateWithTo ConstructorData.EMPTY
+ ConstructorData(
+ annotations = if (kmConstructor.flags.hasAnnotations) {
+ constructor.annotationSpecs()
+ } else {
+ emptyList()
+ },
+ parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = constructor.jvmModifiers(),
+ exceptions = constructor.exceptionTypeNames(),
+ )
+ } else {
+ ConstructorData.EMPTY
+ }
+ }
+ return ClassData(
+ declarationContainer = declarationContainer,
+ className = className,
+ annotations = if (declarationContainer.flags.hasAnnotations) {
+ ClassInspectorUtil.createAnnotations {
+ @Suppress("DEPRECATION")
+ addAll(typeElement.annotationMirrors.map { AnnotationSpec.get(it) })
+ }
+ } else {
+ emptyList()
+ },
+ properties = propertyData,
+ constructors = constructorData,
+ methods = methodData,
+ )
+ }
+ is KmPackage -> {
+ // There's no flag for checking if there are annotations, so we just eagerly check in this
+ // case. All annotations on this class are file: site targets in source. This includes
+ // @JvmName.
+ var jvmName: String? = null
+ val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+ addAll(
+ typeElement.annotationMirrors.map {
+ if (it.annotationType == jvmNameType) {
+ val nameValue = requireNotNull(it.elementValues[jvmNameName]) {
+ "No name property found on $it"
+ }
+ jvmName = nameValue.value as String
+ }
+ @Suppress("DEPRECATION")
+ AnnotationSpec.get(it)
+ },
+ )
+ }
+ return FileData(
+ declarationContainer = declarationContainer,
+ annotations = fileAnnotations,
+ properties = propertyData,
+ methods = methodData,
+ className = className,
+ jvmName = jvmName,
+ )
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+ }
+
+ private fun List<VariableElement>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+ return withIndex().associate { (index, parameter) ->
+ index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+ }
+ }
+
+ private fun ExecutableElement.methodData(
+ typeElement: TypeElement,
+ hasAnnotations: Boolean,
+ jvmInformationMethod: ExecutableElement = this,
+ knownIsOverride: Boolean? = null,
+ ): MethodData {
+ return MethodData(
+ annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+ parameterAnnotations = parameters.indexedAnnotationSpecs(),
+ isSynthetic = false,
+ jvmModifiers = jvmInformationMethod.jvmModifiers(),
+ isOverride = knownIsOverride ?: isOverriddenIn(typeElement),
+ exceptions = exceptionTypeNames(),
+ )
+ }
+
+ public companion object {
+ /** @return an [Elements]-based implementation of [ClassInspector]. */
+ @JvmStatic
+ @KotlinPoetMetadataPreview
+ public fun create(elements: Elements, types: Types): ClassInspector {
+ return ElementsClassInspector(elements, types)
+ }
+
+ private val JVM_STATIC = JvmStatic::class.asClassName()
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
new file mode 100644
index 00000000..235e5e87
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/JvmDescriptorUtils.kt
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.classinspectors
+
+import javax.lang.model.element.Element
+import javax.lang.model.element.ExecutableElement
+import javax.lang.model.element.NestingKind
+import javax.lang.model.element.QualifiedNameable
+import javax.lang.model.element.TypeElement
+import javax.lang.model.element.VariableElement
+import javax.lang.model.type.ArrayType
+import javax.lang.model.type.DeclaredType
+import javax.lang.model.type.ErrorType
+import javax.lang.model.type.ExecutableType
+import javax.lang.model.type.NoType
+import javax.lang.model.type.NullType
+import javax.lang.model.type.PrimitiveType
+import javax.lang.model.type.TypeKind.BOOLEAN
+import javax.lang.model.type.TypeKind.BYTE
+import javax.lang.model.type.TypeKind.CHAR
+import javax.lang.model.type.TypeKind.DOUBLE
+import javax.lang.model.type.TypeKind.FLOAT
+import javax.lang.model.type.TypeKind.INT
+import javax.lang.model.type.TypeKind.LONG
+import javax.lang.model.type.TypeKind.SHORT
+import javax.lang.model.type.TypeMirror
+import javax.lang.model.type.TypeVariable
+import javax.lang.model.type.WildcardType
+import javax.lang.model.util.AbstractTypeVisitor6
+import javax.lang.model.util.Types
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/*
+ * Adapted from
+ * - https://github.com/Takhion/kotlin-metadata/blob/e6de126575ad6ca10b093129b7c30d000c9b0c37/lib/src/main/kotlin/me/eugeniomarletti/kotlin/metadata/jvm/JvmDescriptorUtils.kt
+ * - https://github.com/Takhion/kotlin-metadata/pull/13
+ */
+
+/**
+ * For reference, see the [JVM specification, section 4.2](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.2).
+ *
+ * @return the name of this [Element] in its "internal form".
+ */
+internal val Element.internalName: String
+ get() = when (this) {
+ is TypeElement -> {
+ when (nestingKind) {
+ NestingKind.TOP_LEVEL ->
+ qualifiedName.toString().replace('.', '/')
+ NestingKind.MEMBER ->
+ enclosingElement.internalName + "$" + simpleName
+ NestingKind.LOCAL, NestingKind.ANONYMOUS ->
+ error("Unsupported nesting $nestingKind")
+ null ->
+ error("Unsupported, nestingKind == null")
+ }
+ }
+ is QualifiedNameable -> qualifiedName.toString().replace('.', '/')
+ else -> simpleName.toString()
+ }
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+@Suppress("unused")
+internal val NoType.descriptor: String
+ get() = "V"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val DeclaredType.descriptor: String
+ get() = "L" + asElement().internalName + ";"
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal val PrimitiveType.descriptor: String
+ get() = when (this.kind) {
+ BYTE -> "B"
+ CHAR -> "C"
+ DOUBLE -> "D"
+ FLOAT -> "F"
+ INT -> "I"
+ LONG -> "J"
+ SHORT -> "S"
+ BOOLEAN -> "Z"
+ else -> error("Unknown primitive type $this")
+ }
+
+/**
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeMirror.descriptor(types: Types): String =
+ accept(JvmDescriptorTypeVisitor, types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun WildcardType.descriptor(types: Types): String =
+ types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun TypeVariable.descriptor(types: Types): String =
+ types.erasure(this).descriptor(types)
+
+/**
+ * @return the "field descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ArrayType.descriptor(types: Types): String =
+ "[" + componentType.descriptor(types)
+
+/**
+ * @return the "method descriptor" of this type.
+ * @see [JvmDescriptorTypeVisitor]
+ */
+internal fun ExecutableType.descriptor(types: Types): String {
+ val parameterDescriptors = parameterTypes.joinToString(separator = "") { it.descriptor(types) }
+ val returnDescriptor = returnType.descriptor(types)
+ return "($parameterDescriptors)$returnDescriptor"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun ExecutableElement.jvmMethodSignature(types: Types): String {
+ return "$simpleName${asType().descriptor(types)}"
+}
+
+/**
+ * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+ *
+ * Useful for comparing with [JvmFieldSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal fun VariableElement.jvmFieldSignature(types: Types): String {
+ return "$simpleName:${asType().descriptor(types)}"
+}
+
+/**
+ * When applied over a type, it returns either:
+ * - a "field descriptor", for example: `Ljava/lang/Object;`
+ * - a "method descriptor", for example: `(Ljava/lang/Object;)Z`
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+internal object JvmDescriptorTypeVisitor : AbstractTypeVisitor6<String, Types>() {
+ override fun visitNoType(t: NoType, types: Types): String = t.descriptor
+ override fun visitDeclared(t: DeclaredType, types: Types): String = t.descriptor
+ override fun visitPrimitive(t: PrimitiveType, types: Types): String = t.descriptor
+
+ override fun visitArray(t: ArrayType, types: Types): String = t.descriptor(types)
+ override fun visitWildcard(t: WildcardType, types: Types): String = t.descriptor(types)
+ override fun visitExecutable(t: ExecutableType, types: Types): String = t.descriptor(types)
+ override fun visitTypeVariable(t: TypeVariable, types: Types): String = t.descriptor(types)
+
+ override fun visitNull(t: NullType, types: Types): String = visitUnknown(
+ t,
+ types,
+ )
+ override fun visitError(t: ErrorType, types: Types): String = visitUnknown(
+ t,
+ types,
+ )
+
+ override fun visitUnknown(t: TypeMirror, types: Types): String = error("Unsupported type $t")
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
new file mode 100644
index 00000000..41935e36
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/Optional.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.classinspectors
+
+/**
+ * Simple `Optional` implementation for use in collections that don't allow `null` values.
+ */
+internal data class Optional<out T : Any>(val nullableValue: T?)
+
+internal fun <T : Any> T?.toOptional(): Optional<T> = Optional(
+ this,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
new file mode 100644
index 00000000..fc31ba09
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/classinspectors/ReflectiveClassInspector.kt
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.classinspectors
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasConstant
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
+import com.squareup.kotlinpoet.metadata.specs.ClassData
+import com.squareup.kotlinpoet.metadata.specs.ClassInspector
+import com.squareup.kotlinpoet.metadata.specs.ConstructorData
+import com.squareup.kotlinpoet.metadata.specs.ContainerData
+import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
+import com.squareup.kotlinpoet.metadata.specs.FieldData
+import com.squareup.kotlinpoet.metadata.specs.FileData
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
+import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
+import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
+import com.squareup.kotlinpoet.metadata.specs.MethodData
+import com.squareup.kotlinpoet.metadata.specs.PropertyData
+import com.squareup.kotlinpoet.metadata.toKmClass
+import java.lang.reflect.Constructor
+import java.lang.reflect.Field
+import java.lang.reflect.Method
+import java.lang.reflect.Modifier
+import java.lang.reflect.Parameter
+import java.util.TreeMap
+import java.util.concurrent.ConcurrentHashMap
+import kotlin.LazyThreadSafetyMode.NONE
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.jvm.JvmFieldSignature
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.KotlinClassMetadata
+import kotlinx.metadata.jvm.fieldSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+import kotlinx.metadata.jvm.syntheticMethodForAnnotations
+
+@KotlinPoetMetadataPreview
+public class ReflectiveClassInspector private constructor(
+ private val classLoader: ClassLoader?,
+) : ClassInspector {
+
+ private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>()
+ private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>()
+ private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>()
+ private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>()
+ private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>()
+
+ private fun lookupClass(className: ClassName): Class<*>? {
+ return classCache.getOrPut(className) {
+ try {
+ if (classLoader == null) {
+ Class.forName(className.reflectionName())
+ } else {
+ Class.forName(className.reflectionName(), true, classLoader)
+ }
+ } catch (e: ClassNotFoundException) {
+ null
+ }.toOptional()
+ }.nullableValue
+ }
+
+ override val supportsNonRuntimeRetainedAnnotations: Boolean = false
+
+ override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
+ val clazz = lookupClass(className)
+ ?: error("No type element found for: $className.")
+
+ val metadata = clazz.getAnnotation(Metadata::class.java)
+ return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata()) {
+ is KotlinClassMetadata.Class -> kotlinClassMetadata.toKmClass()
+ is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.toKmPackage()
+ else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
+ }
+ }
+
+ override fun isInterface(className: ClassName): Boolean {
+ if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
+ return true
+ }
+ return lookupClass(className)?.isInterface ?: false
+ }
+
+ private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? {
+ return try {
+ val signatureString = fieldSignature.asString()
+ fieldCache.getOrPut(this to signatureString) {
+ declaredFields
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmFieldSignature }.toOptional()
+ }.nullableValue
+ } catch (e: ClassNotFoundException) {
+ null
+ }
+ }
+
+ private fun Class<*>.lookupMethod(
+ methodSignature: JvmMethodSignature,
+ ): Method? {
+ val signatureString = methodSignature.asString()
+ return methodCache.getOrPut(this to signatureString) {
+ declaredMethods
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmMethodSignature }.toOptional()
+ }.nullableValue
+ }
+
+ private fun Class<*>.lookupConstructor(
+ constructorSignature: JvmMethodSignature,
+ ): Constructor<*>? {
+ val signatureString = constructorSignature.asString()
+ return constructorCache.getOrPut(this to signatureString) {
+ declaredConstructors
+ .asSequence()
+ .onEach { it.isAccessible = true }
+ .find { signatureString == it.jvmMethodSignature }.toOptional()
+ }.nullableValue
+ }
+
+ private fun Field.jvmModifiers(): Set<JvmFieldModifier> {
+ return mutableSetOf<JvmFieldModifier>().apply {
+ if (Modifier.isTransient(modifiers)) {
+ add(TRANSIENT)
+ }
+ if (Modifier.isVolatile(modifiers)) {
+ add(VOLATILE)
+ }
+ if (Modifier.isStatic(modifiers)) {
+ add(JvmFieldModifier.STATIC)
+ }
+ }
+ }
+
+ private fun Field.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) },
+ )
+ }
+
+ private fun Method.jvmModifiers(): Set<JvmMethodModifier> {
+ return methodJvmModifiers(modifiers, isDefault)
+ }
+
+ private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> {
+ return methodJvmModifiers(modifiers, false)
+ }
+
+ private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> {
+ val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>()
+ if (Modifier.isSynchronized(modifiers)) {
+ jvmMethodModifiers += SYNCHRONIZED
+ }
+ if (Modifier.isStatic(modifiers)) {
+ jvmMethodModifiers += STATIC
+ }
+ if (isDefault) {
+ jvmMethodModifiers += DEFAULT
+ }
+ return jvmMethodModifiers
+ }
+
+ private fun Method.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Parameter.annotationSpecs(): List<AnnotationSpec> {
+ return filterOutNullabilityAnnotations(
+ declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) },
+ )
+ }
+
+ private fun Method.exceptionTypeNames(): List<TypeName> {
+ return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+ }
+
+ private fun Constructor<*>.exceptionTypeNames(): List<TypeName> {
+ return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
+ }
+
+ override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
+ val clazz = lookupClass(enumClassName)
+ ?: error("No class found for: $enumClassName.")
+ check(clazz.isEnum) {
+ "Class must be an enum but isn't: $clazz"
+ }
+ val enumEntry = enumCache.getOrPut(clazz to memberName) {
+ clazz.enumConstants
+ .asSequence()
+ .map { it as Enum<*> }
+ .find { it.name == memberName }
+ .toOptional()
+ }.nullableValue
+ checkNotNull(enumEntry) {
+ "Could not find $memberName on $enumClassName"
+ }
+ return EnumEntryData(
+ declarationContainer = if (enumEntry.javaClass == clazz) {
+ // For simple enums with no class bodies, the entry class will be the same as the original
+ // class.
+ null
+ } else {
+ enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass()
+ },
+ annotations = clazz.getField(enumEntry.name).annotationSpecs(),
+ )
+ }
+
+ private fun Field.constantValue(): CodeBlock? {
+ if (!Modifier.isStatic(modifiers)) {
+ return null
+ }
+ return get(null) // Constant means we can do a static get on it.
+ .let(ClassInspectorUtil::codeLiteralOf)
+ }
+
+ private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean {
+ val signatureString = asString()
+ val classPackage = clazz.`package`.name
+ val interfaceMethods = clazz.interfaces.asSequence()
+ .flatMap { it.methods.asSequence() }
+ val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence()
+ return interfaceMethods.plus(superClassMethods)
+ .filterNot { Modifier.isFinal(it.modifiers) }
+ .filterNot { Modifier.isStatic(it.modifiers) }
+ .filterNot { Modifier.isPrivate(it.modifiers) }
+ .filter {
+ Modifier.isPublic(it.modifiers) ||
+ Modifier.isProtected(it.modifiers) ||
+ // Package private
+ it.declaringClass.`package`.name == classPackage
+ }
+ .map { it.jvmMethodSignature }
+ .any { it == signatureString }
+ }
+
+ override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
+ return lookupClass(className)?.lookupMethod(methodSignature) != null
+ }
+
+ override fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData {
+ val targetClass = lookupClass(className) ?: error("No class found for: $className.")
+ val isCompanionObject: Boolean = when (declarationContainer) {
+ is KmClass -> {
+ declarationContainer.isCompanionObject
+ }
+ is KmPackage -> {
+ false
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+
+ // Should only be called if parentName has been null-checked
+ val classIfCompanion by lazy(NONE) {
+ if (isCompanionObject && parentClassName != null) {
+ lookupClass(parentClassName)
+ ?: error("No class found for: $parentClassName.")
+ } else {
+ targetClass
+ }
+ }
+
+ val propertyData = declarationContainer.properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
+ val isJvmField = ClassInspectorUtil.computeIsJvmField(
+ property = property,
+ classInspector = this,
+ isCompanionObject = isCompanionObject,
+ hasGetter = property.getterSignature != null,
+ hasSetter = property.setterSignature != null,
+ hasField = property.fieldSignature != null,
+ )
+
+ val fieldData = property.fieldSignature?.let { fieldSignature ->
+ // Check the field in the parent first. For const/static/jvmField elements, these only
+ // exist in the parent and we want to check that if necessary to avoid looking up a
+ // non-existent field in the companion.
+ val parentModifiers = if (isCompanionObject && parentClassName != null) {
+ classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty()
+ } else {
+ emptySet()
+ }
+
+ val isStatic = JvmFieldModifier.STATIC in parentModifiers
+
+ // TODO we looked up field once, let's reuse it
+ val classForOriginalField = targetClass.takeUnless {
+ isCompanionObject &&
+ (property.isConst || isJvmField || isStatic)
+ } ?: classIfCompanion
+
+ val field = classForOriginalField.lookupField(fieldSignature)
+ ?: error("No field $fieldSignature found in $classForOriginalField.")
+ val constant = if (property.hasConstant) {
+ val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let {
+ if (it.isInterface) {
+ field
+ } else {
+ // const properties are relocated to the enclosing class
+ it.lookupField(fieldSignature)
+ ?: error("No field $fieldSignature found in $it.")
+ }
+ } ?: field
+ fieldWithConstant.constantValue()
+ } else {
+ null
+ }
+
+ val jvmModifiers = field.jvmModifiers() + parentModifiers
+
+ // For static, const, or JvmField fields in a companion object, the companion
+ // object's field is marked as synthetic to hide it from Java, but in this case
+ // it's a false positive for this check in kotlin.
+ val isSynthetic = field.isSynthetic &&
+ !(
+ isCompanionObject &&
+ (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers)
+ )
+
+ FieldData(
+ annotations = field.annotationSpecs(),
+ isSynthetic = isSynthetic,
+ jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
+ // JvmField companion objects don't need JvmStatic, it's implicit
+ isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
+ },
+ constant = constant,
+ )
+ }
+
+ val getterData = property.getterSignature?.let { getterSignature ->
+ val method = classIfCompanion.lookupMethod(getterSignature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = getterSignature,
+ hasAnnotations = property.getterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+ ?.lookupMethod(getterSignature) ?: method,
+ )
+ ?: error("No getter method $getterSignature found in $classIfCompanion.")
+ }
+
+ val setterData = property.setterSignature?.let { setterSignature ->
+ val method = classIfCompanion.lookupMethod(setterSignature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = setterSignature,
+ hasAnnotations = property.setterFlags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
+ ?.lookupMethod(setterSignature) ?: method,
+ knownIsOverride = getterData?.isOverride,
+ )
+ ?: error("No setter method $setterSignature found in $classIfCompanion.")
+ }
+
+ val annotations = mutableListOf<AnnotationSpec>()
+ if (property.flags.hasAnnotations) {
+ property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
+ targetClass.lookupMethod(annotationsHolderSignature)?.let { method ->
+ annotations += method.annotationSpecs()
+ // Cover for https://github.com/square/kotlinpoet/issues/1046
+ .filterNot { it.typeName == JAVA_DEPRECATED }
+ }
+ }
+ }
+
+ PropertyData(
+ annotations = annotations,
+ fieldData = fieldData,
+ getterData = getterData,
+ setterData = setterData,
+ isJvmField = isJvmField,
+ )
+ }
+
+ val methodData = declarationContainer.functions
+ .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
+ val signature = kmFunction.signature
+ if (signature != null) {
+ val method = targetClass.lookupMethod(signature)
+ method?.methodData(
+ clazz = targetClass,
+ signature = signature,
+ hasAnnotations = kmFunction.flags.hasAnnotations,
+ jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature)
+ ?: method,
+ )
+ ?: error("No method $signature found in $targetClass.")
+ } else {
+ MethodData.EMPTY
+ }
+ }
+
+ when (declarationContainer) {
+ is KmClass -> {
+ val classAnnotations = if (declarationContainer.flags.hasAnnotations) {
+ ClassInspectorUtil.createAnnotations {
+ addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+ }
+ } else {
+ emptyList()
+ }
+ val constructorData = declarationContainer.constructors
+ .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
+ if (declarationContainer.isAnnotation || declarationContainer.isValue) {
+ //
+ // Annotations are interfaces in reflection, but kotlin metadata will still report a
+ // constructor signature
+ //
+ // Inline classes have no constructors at runtime
+ //
+ return@associateWithTo ConstructorData.EMPTY
+ }
+ val signature = kmConstructor.signature
+ if (signature != null) {
+ val constructor = targetClass.lookupConstructor(signature)
+ ?: error("No constructor $signature found in $targetClass.")
+ ConstructorData(
+ annotations = if (kmConstructor.flags.hasAnnotations) {
+ constructor.annotationSpecs()
+ } else {
+ emptyList()
+ },
+ parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
+ isSynthetic = constructor.isSynthetic,
+ jvmModifiers = constructor.jvmModifiers(),
+ exceptions = constructor.exceptionTypeNames(),
+ )
+ } else {
+ ConstructorData.EMPTY
+ }
+ }
+ return ClassData(
+ declarationContainer = declarationContainer,
+ className = className,
+ annotations = classAnnotations,
+ properties = propertyData,
+ constructors = constructorData,
+ methods = methodData,
+ )
+ }
+ is KmPackage -> {
+ // There's no flag for checking if there are annotations, so we just eagerly check in this
+ // case. All annotations on this class are file: site targets in source. This does not
+ // include @JvmName since it does not have RUNTIME retention. In practice this doesn't
+ // really matter, but it does mean we can't know for certain if the file should be called
+ // FooKt.kt or Foo.kt.
+ val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
+ addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
+ }
+ return FileData(
+ declarationContainer = declarationContainer,
+ annotations = fileAnnotations,
+ properties = propertyData,
+ methods = methodData,
+ className = className,
+ )
+ }
+ else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
+ }
+ }
+
+ private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
+ return withIndex().associate { (index, parameter) ->
+ index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
+ }
+ }
+
+ private fun Method.methodData(
+ clazz: Class<*>,
+ signature: JvmMethodSignature,
+ hasAnnotations: Boolean,
+ jvmInformationMethod: Method = this,
+ knownIsOverride: Boolean? = null,
+ ): MethodData {
+ return MethodData(
+ annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
+ parameterAnnotations = parameters.indexedAnnotationSpecs(),
+ isSynthetic = isSynthetic,
+ jvmModifiers = jvmInformationMethod.jvmModifiers(),
+ isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz),
+ exceptions = exceptionTypeNames(),
+ )
+ }
+
+ public companion object {
+ @JvmStatic
+ @KotlinPoetMetadataPreview
+ public fun create(classLoader: ClassLoader? = null): ClassInspector {
+ return ReflectiveClassInspector(classLoader)
+ }
+
+ private val Class<*>.descriptor: String
+ get() {
+ return when {
+ isPrimitive -> when (kotlin) {
+ Byte::class -> "B"
+ Char::class -> "C"
+ Double::class -> "D"
+ Float::class -> "F"
+ Int::class -> "I"
+ Long::class -> "J"
+ Short::class -> "S"
+ Boolean::class -> "Z"
+ Void::class -> "V"
+ else -> throw RuntimeException("Unrecognized primitive $this")
+ }
+ isArray -> name.replace('.', '/')
+ else -> "L$name;".replace('.', '/')
+ }
+ }
+
+ private val Method.descriptor: String
+ get() = parameterTypes.joinToString(
+ separator = "",
+ prefix = "(",
+ postfix = ")${returnType.descriptor}",
+ ) { it.descriptor }
+
+ /**
+ * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Method.jvmMethodSignature: String get() = "$name$descriptor"
+
+ private val Constructor<*>.descriptor: String
+ get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }
+
+ /**
+ * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`.
+ *
+ * Useful for comparing with [JvmMethodSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor"
+
+ /**
+ * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
+ *
+ * Useful for comparing with [JvmFieldSignature].
+ *
+ * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
+ */
+ private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}"
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
new file mode 100644
index 00000000..14c019fd
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ClassInspector.kt
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2019 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.jvm.JvmMethodSignature
+
+/** A basic interface for looking up JVM information about a given Class. */
+@KotlinPoetMetadataPreview
+public interface ClassInspector {
+
+ /**
+ * Indicates if this [ClassInspector] supports [AnnotationRetention.RUNTIME]-retained annotations.
+ * This is used to indicate if manual inference of certain non-RUNTIME-retained annotations should
+ * be done, such as [JvmName].
+ */
+ public val supportsNonRuntimeRetainedAnnotations: Boolean
+
+ /**
+ * Creates a new [ContainerData] instance for a given [declarationContainer].
+ *
+ * @param declarationContainer the source [KmDeclarationContainer] to read from.
+ * @param className the [ClassName] of the target class to to read from.
+ * @param parentClassName the parent [ClassName] name if [declarationContainer] is nested, inner,
+ * or is a companion object.
+ */
+ public fun containerData(
+ declarationContainer: KmDeclarationContainer,
+ className: ClassName,
+ parentClassName: ClassName?,
+ ): ContainerData
+
+ /**
+ * Looks up other declaration containers, such as for nested members. Note that this class would
+ * always be Kotlin, so Metadata can be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return the read [KmDeclarationContainer] from its metadata. If no class or facade
+ * file was found, this should throw an exception.
+ */
+ public fun declarationContainerFor(className: ClassName): KmDeclarationContainer
+
+ /**
+ * Looks up a class and returns whether or not it is an interface. Note that this class can be
+ * Java or Kotlin, so Metadata should not be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return whether or not it is an interface.
+ */
+ public fun isInterface(className: ClassName): Boolean
+
+ /**
+ * Looks up the enum entry on a given enum given its member name.
+ *
+ * @param enumClassName The [ClassName] representation of the enum class.
+ * @param memberName The simple member name.
+ * @return the [EnumEntryData]
+ */
+ public fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData
+
+ /**
+ * Looks up if a given [methodSignature] within [className] exists.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @param methodSignature The method signature to check.
+ * @return whether or not the method exists.
+ */
+ public fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean
+}
+
+/**
+ * Creates a new [ContainerData] instance for a given [className].
+ *
+ * @param className the [ClassName] of the target class to to read from.
+ * @param parentClassName the parent [ClassName] name if [className] is nested, inner, or is a
+ * companion object.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.containerData(
+ className: ClassName,
+ parentClassName: ClassName?,
+): ContainerData {
+ return containerData(declarationContainerFor(className), className, parentClassName)
+}
+
+/**
+ * Looks up other classes, such as for nested members. Note that this class would always be
+ * Kotlin, so Metadata can be relied on for this.
+ *
+ * @param className The [ClassName] representation of the class.
+ * @return the read [KmClass] from its metadata. If no class was found, this should throw
+ * an exception.
+ */
+@KotlinPoetMetadataPreview
+public fun ClassInspector.classFor(className: ClassName): KmClass {
+ val container = declarationContainerFor(className)
+ check(container is KmClass) {
+ "Container is not a class! Was ${container.javaClass.simpleName}"
+ }
+ return container
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
new file mode 100644
index 00000000..01c98390
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ConstructorData.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a constructor used for [ClassInspector]. Should only be
+ * associated with constructors of a [ClassData].
+ *
+ * @param annotations declared annotations on this constructor.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this constructor is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this constructor.
+ * @property exceptions list of exceptions thrown by this constructor.
+ */
+@KotlinPoetMetadataPreview
+public data class ConstructorData(
+ private val annotations: List<AnnotationSpec>,
+ val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmMethodModifier>,
+ val exceptions: List<TypeName>,
+) {
+
+ /**
+ * A collection of all annotations on this constructor, including any derived from [jvmModifiers],
+ * [isSynthetic], and [exceptions].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+ addAll(annotations)
+ if (isSynthetic) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull { it.annotationSpec() })
+ exceptions.takeIf { it.isNotEmpty() }
+ ?.let {
+ add(ClassInspectorUtil.createThrowsSpec(it))
+ }
+ }
+
+ public companion object {
+ public val EMPTY: ConstructorData = ConstructorData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = false,
+ jvmModifiers = emptySet(),
+ exceptions = emptyList(),
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
new file mode 100644
index 00000000..f1006d01
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/ContainerData.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmDeclarationContainer
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+
+/**
+ * Represents relevant information on a declaration container used for [ClassInspector]. Can only
+ * ever be applied on a Kotlin type (i.e. is annotated with [Metadata]).
+ *
+ * @property declarationContainer the [KmDeclarationContainer] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property annotations declared annotations on this class.
+ * @property properties the mapping of [declarationContainer]'s properties to parsed [PropertyData].
+ * @property methods the mapping of [declarationContainer]'s methods to parsed [MethodData].
+ */
+@KotlinPoetMetadataPreview
+public interface ContainerData {
+ public val declarationContainer: KmDeclarationContainer
+ public val annotations: Collection<AnnotationSpec>
+ public val properties: Map<KmProperty, PropertyData>
+ public val methods: Map<KmFunction, MethodData>
+}
+
+/**
+ * Represents relevant information on a Kotlin class used for [ClassInspector]. Can only ever be
+ * applied on a class and not file facades.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the class.
+ * @property constructors the mapping of [declarationContainer]'s constructors to parsed
+ * [ConstructorData].
+ */
+@KotlinPoetMetadataPreview
+public data class ClassData(
+ override val declarationContainer: KmClass,
+ val className: ClassName,
+ override val annotations: Collection<AnnotationSpec>,
+ override val properties: Map<KmProperty, PropertyData>,
+ val constructors: Map<KmConstructor, ConstructorData>,
+ override val methods: Map<KmFunction, MethodData>,
+) : ContainerData
+
+/**
+ * Represents relevant information on a file facade used for [ClassInspector].
+ *
+ * @property declarationContainer the [KmClass] as parsed from the class's
+ * [@Metadata][Metadata] annotation.
+ * @property className the KotlinPoet [ClassName] of the underlying facade class in JVM.
+ * @property jvmName the `@JvmName` of the class or null if it does not have a custom name.
+ * Default will try to infer from the [className].
+ */
+@KotlinPoetMetadataPreview
+public data class FileData(
+ override val declarationContainer: KmPackage,
+ override val annotations: Collection<AnnotationSpec>,
+ override val properties: Map<KmProperty, PropertyData>,
+ override val methods: Map<KmFunction, MethodData>,
+ val className: ClassName,
+ val jvmName: String? =
+ if (!className.simpleName.endsWith("Kt")) className.simpleName else null,
+) : ContainerData {
+
+ /**
+ * The file name of the container, defaults to [className]'s simple name + "Kt". If a [jvmName] is
+ * specified, it will always defer to that.
+ */
+ val fileName: String = jvmName ?: className.simpleName.removeSuffix("Kt")
+}
+
+/**
+ * Represents relevant information on a Kotlin enum entry.
+ *
+ * @property declarationContainer the [KmClass] as parsed from the entry's
+ * [@Metadata][Metadata] annotation.
+ * @property annotations the annotations for the entry
+ */
+@KotlinPoetMetadataPreview
+public data class EnumEntryData(
+ val declarationContainer: KmClass?,
+ val annotations: Collection<AnnotationSpec>,
+)
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
new file mode 100644
index 00000000..a000ddef
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/FieldData.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FIELD
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a field used for [ClassInspector]. Should only be
+ * associated with a [PropertyData].
+ *
+ * @param annotations declared annotations on this field.
+ * @property isSynthetic indicates if this field is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this field.
+ * @property constant the constant value of this field, if available. Note that this is does not
+ * strictly imply that the associated property is `const`.
+ */
+@KotlinPoetMetadataPreview
+public data class FieldData(
+ private val annotations: List<AnnotationSpec>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmFieldModifier>,
+ val constant: CodeBlock?,
+) {
+
+ /**
+ * A collection of all annotations on this method, including any derived from [jvmModifiers]
+ * and [isSynthetic].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations(
+ FIELD,
+ ) {
+ addAll(annotations)
+ if (isSynthetic) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull(JvmFieldModifier::annotationSpec))
+ }
+
+ public companion object {
+ public val SYNTHETIC: FieldData = FieldData(
+ annotations = emptyList(),
+ isSynthetic = true,
+ jvmModifiers = emptySet(),
+ constant = null,
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
new file mode 100644
index 00000000..dc2a55bb
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmFieldModifier.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmFieldModifier : JvmModifier {
+ STATIC {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ JvmStatic::class.asClassName(),
+ ).build()
+ },
+ TRANSIENT {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Transient::class.asClassName(),
+ ).build()
+ },
+ VOLATILE {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Volatile::class.asClassName(),
+ ).build()
+ },
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
new file mode 100644
index 00000000..5d63738b
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmMethodModifier.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/** Modifiers that are annotations or implicit in Kotlin but modifier keywords in bytecode. */
+@KotlinPoetMetadataPreview
+public enum class JvmMethodModifier : JvmModifier {
+ STATIC {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ JvmStatic::class.asClassName(),
+ ).build()
+ },
+ SYNCHRONIZED {
+ override fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(
+ Synchronized::class.asClassName(),
+ ).build()
+ },
+ DEFAULT,
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
new file mode 100644
index 00000000..f09e6add
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/JvmModifier.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+
+/**
+ * Represents a JVM modifier that is represented as an annotation in Kotlin but as a modifier in
+ * bytecode. Examples include annotations such as [@JvmStatic][JvmStatic] or
+ * [@JvmSynthetic][JvmSynthetic].
+ *
+ * This API is considered read-only and should not be implemented outside of KotlinPoet.
+ */
+@KotlinPoetMetadataPreview
+public interface JvmModifier {
+ public fun annotationSpec(): AnnotationSpec? {
+ return null
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
new file mode 100644
index 00000000..cd12479c
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KmTypes.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2019 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.LambdaTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.STAR
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeVariableName
+import com.squareup.kotlinpoet.WildcardTypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.isNullable
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.tags.TypeAliasTag
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmClassifier.Class
+import kotlinx.metadata.KmClassifier.TypeAlias
+import kotlinx.metadata.KmClassifier.TypeParameter
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFlexibleTypeUpperBound
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeParameter
+import kotlinx.metadata.KmTypeProjection
+import kotlinx.metadata.KmVariance
+import kotlinx.metadata.KmVariance.IN
+import kotlinx.metadata.KmVariance.INVARIANT
+import kotlinx.metadata.KmVariance.OUT
+import kotlinx.metadata.jvm.annotations
+import kotlinx.metadata.jvm.signature
+
+/**
+ * `true` if this is an extension type (i.e. String.() -> Unit vs (String) -> Unit).
+ *
+ * See details: https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980/27
+ */
+public val KmType.isExtensionType: Boolean get() {
+ return annotations.any { it.className == "kotlin/ExtensionFunctionType" }
+}
+
+@KotlinPoetMetadataPreview
+internal val KmClass.primaryConstructor: KmConstructor?
+ get() = constructors.find { it.isPrimary }
+
+internal fun KmVariance.toKModifier(): KModifier? {
+ return when (this) {
+ IN -> KModifier.IN
+ OUT -> KModifier.OUT
+ INVARIANT -> null
+ }
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeProjection.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ val typename = type?.toTypeName(typeParamResolver) ?: STAR
+ return when (variance) {
+ IN -> WildcardTypeName.consumerOf(typename)
+ OUT -> WildcardTypeName.producerOf(typename)
+ INVARIANT -> typename
+ null -> STAR
+ }
+}
+
+/**
+ * Converts a given [KmType] into a KotlinPoet representation, attempting to give a correct
+ * "source" representation. This includes converting [functions][kotlin.Function] and `suspend`
+ * types to appropriate [lambda representations][LambdaTypeName].
+ */
+@KotlinPoetMetadataPreview
+internal fun KmType.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ val argumentList = arguments.map { it.toTypeName(typeParamResolver) }
+ val type: TypeName = when (val valClassifier = classifier) {
+ is TypeParameter -> {
+ typeParamResolver[valClassifier.id]
+ }
+ is KmClassifier.Class -> {
+ flexibleTypeUpperBound?.toTypeName(typeParamResolver)?.let { return it }
+ outerType?.toTypeName(typeParamResolver)?.let { return it }
+ var finalType: TypeName = ClassInspectorUtil.createClassName(valClassifier.name)
+ if (argumentList.isNotEmpty()) {
+ val finalTypeString = finalType.toString()
+ if (finalTypeString.startsWith("kotlin.Function")) {
+ // It's a lambda type!
+ finalType = if (finalTypeString == "kotlin.FunctionN") {
+ TODO("unclear how to express this one since it has arity")
+ } else {
+ val (parameters, returnType) = if (isSuspend) {
+ // Coroutines always adds an `Any?` return type, but we kind of just want the
+ // source representation, so we trick it here and ignore the last.
+ argumentList.dropLast(2).toTypedArray() to argumentList.dropLast(1).last().let {
+ // Coroutines makes these a `Continuation<T>` of the type, so we want the parameterized type
+ check(it is ParameterizedTypeName)
+ it.typeArguments[0]
+ }
+ } else {
+ argumentList.dropLast(1).toTypedArray() to argumentList.last()
+ }
+ val lambdaType = if (isExtensionType) {
+ // Extension function type! T.(). First parameter is actually the receiver.
+ LambdaTypeName.get(
+ receiver = parameters[0],
+ parameters = parameters.drop(1).toTypedArray(),
+ returnType = returnType,
+ )
+ } else {
+ LambdaTypeName.get(
+ receiver = null,
+ parameters = parameters,
+ returnType = returnType,
+ )
+ }
+ lambdaType.copy(suspending = isSuspend)
+ }
+ } else {
+ finalType = (finalType as ClassName).parameterizedBy(argumentList)
+ }
+ }
+ finalType
+ }
+ is TypeAlias -> {
+ ClassInspectorUtil.createClassName(valClassifier.name)
+ }
+ }
+
+ val annotations = ClassInspectorUtil.createAnnotations {
+ for (annotation in annotations) {
+ add(annotation.toAnnotationSpec())
+ }
+ }.toList()
+ val finalType = type.copy(nullable = isNullable, annotations = annotations)
+ return abbreviatedType?.let {
+ // This is actually an alias! The "abbreviated type" is the alias and how it's actually
+ // represented in source. So instead - we'll return the abbreviated type but store the "real"
+ // type in tags for reference.
+ val abbreviatedTypeName = it.toTypeName(typeParamResolver)
+ abbreviatedTypeName.copy(
+ tags = mapOf(TypeAliasTag::class to TypeAliasTag(finalType)),
+ )
+ } ?: finalType
+}
+
+@KotlinPoetMetadataPreview
+internal fun KmTypeParameter.toTypeVariableName(
+ typeParamResolver: TypeParameterResolver,
+): TypeVariableName {
+ val finalVariance = variance.toKModifier()
+ val typeVariableName = TypeVariableName(
+ name = name,
+ bounds = upperBounds.map { it.toTypeName(typeParamResolver) },
+ variance = finalVariance,
+ )
+ val annotations = ClassInspectorUtil.createAnnotations {
+ for (annotation in annotations) {
+ add(annotation.toAnnotationSpec())
+ }
+ }.toList()
+ return typeVariableName.copy(
+ reified = isReified,
+ tags = mapOf(KmTypeParameter::class to this),
+ annotations = annotations,
+ )
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFlexibleTypeUpperBound.toTypeName(
+ typeParamResolver: TypeParameterResolver,
+): TypeName {
+ // TODO tag typeFlexibilityId somehow?
+ return WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
+}
+
+internal interface TypeParameterResolver {
+ val parametersMap: Map<Int, TypeVariableName>
+ operator fun get(index: Int): TypeVariableName
+
+ companion object {
+ val EMPTY = object : TypeParameterResolver {
+ override val parametersMap: Map<Int, TypeVariableName> = emptyMap()
+
+ override fun get(index: Int): TypeVariableName = throw NoSuchElementException("No type parameters!")
+ }
+ }
+}
+
+@KotlinPoetMetadataPreview
+internal fun List<KmTypeParameter>.toTypeParameterResolver(
+ fallback: TypeParameterResolver? = null,
+): TypeParameterResolver {
+ val parametersMap = LinkedHashMap<Int, TypeVariableName>()
+ val typeParamResolver = { id: Int ->
+ parametersMap[id]
+ ?: fallback?.get(id)
+ ?: throw IllegalStateException("No type argument found for $id!")
+ }
+
+ val resolver = object : TypeParameterResolver {
+ override val parametersMap: Map<Int, TypeVariableName> = parametersMap
+
+ override operator fun get(index: Int): TypeVariableName = typeParamResolver(index)
+ }
+
+ // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
+ for (typeParam in this) {
+ // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
+ // replacement later that may add bounds referencing this.
+ parametersMap[typeParam.id] = TypeVariableName(typeParam.name)
+ }
+
+ for (typeParam in this) {
+ // Now replace it with the full version.
+ parametersMap[typeParam.id] = typeParam.toTypeVariableName(resolver)
+ }
+
+ return resolver
+}
+
+internal val KM_PROPERTY_COMPARATOR = Comparator<KmProperty> { o1, o2 ->
+ // No need to check fields, getters, etc as properties must have distinct names
+ o1.name.compareTo(o2.name)
+}
+
+internal val KM_FUNCTION_COMPARATOR = Comparator<KmFunction> { o1, o2 ->
+ var result = o1.name.compareTo(o2.name)
+ if (result != 0) return@Comparator result
+
+ val signature1 = o1.signature
+ val signature2 = o2.signature
+ if (signature1 != null && signature2 != null) {
+ result = signature1.asString().compareTo(signature2.asString())
+ if (result != 0) return@Comparator result
+ }
+
+ // Fallback - calculate signature
+ val manualSignature1 = o1.computeSignature()
+ val manualSignature2 = o2.computeSignature()
+ manualSignature1.compareTo(manualSignature2)
+}
+
+internal val KM_CONSTRUCTOR_COMPARATOR = Comparator<KmConstructor> { o1, o2 ->
+ val signature1 = o1.signature
+ val signature2 = o2.signature
+ if (signature1 != null && signature2 != null) {
+ val result = signature1.asString().compareTo(signature2.asString())
+ if (result != 0) return@Comparator result
+ }
+
+ // Fallback - calculate signature
+ val manualSignature1 = o1.computeSignature()
+ val manualSignature2 = o2.computeSignature()
+ manualSignature1.compareTo(manualSignature2)
+}
+
+// Computes a simple signature string good enough for hashing
+private fun KmFunction.computeSignature(): String {
+ return "$name(${valueParameters.joinToString(",") { it.type.simpleName }})${returnType.simpleName}"
+}
+
+private fun KmConstructor.computeSignature(): String {
+ return "$<init>(${valueParameters.joinToString(",") { it.type.simpleName }})"
+}
+
+private val KmType?.simpleName: String get() {
+ if (this == null) return "void"
+ return when (val c = classifier) {
+ is Class -> c.name
+ is TypeParameter -> "Object"
+ is TypeAlias -> c.name
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
new file mode 100644
index 00000000..6c0730a6
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/KotlinPoetMetadataSpecs.kt
@@ -0,0 +1,948 @@
+/*
+ * Copyright (C) 2019 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
+ *
+ * https://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.
+ */
+@file:JvmName("KotlinPoetMetadataSpecs")
+
+package com.squareup.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.FunSpec.Builder
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.KModifier.ABSTRACT
+import com.squareup.kotlinpoet.KModifier.CONST
+import com.squareup.kotlinpoet.KModifier.CROSSINLINE
+import com.squareup.kotlinpoet.KModifier.DATA
+import com.squareup.kotlinpoet.KModifier.EXPECT
+import com.squareup.kotlinpoet.KModifier.EXTERNAL
+import com.squareup.kotlinpoet.KModifier.FINAL
+import com.squareup.kotlinpoet.KModifier.INFIX
+import com.squareup.kotlinpoet.KModifier.INLINE
+import com.squareup.kotlinpoet.KModifier.INNER
+import com.squareup.kotlinpoet.KModifier.INTERNAL
+import com.squareup.kotlinpoet.KModifier.LATEINIT
+import com.squareup.kotlinpoet.KModifier.NOINLINE
+import com.squareup.kotlinpoet.KModifier.OPEN
+import com.squareup.kotlinpoet.KModifier.OPERATOR
+import com.squareup.kotlinpoet.KModifier.PRIVATE
+import com.squareup.kotlinpoet.KModifier.PROTECTED
+import com.squareup.kotlinpoet.KModifier.PUBLIC
+import com.squareup.kotlinpoet.KModifier.SEALED
+import com.squareup.kotlinpoet.KModifier.SUSPEND
+import com.squareup.kotlinpoet.KModifier.TAILREC
+import com.squareup.kotlinpoet.KModifier.VALUE
+import com.squareup.kotlinpoet.KModifier.VARARG
+import com.squareup.kotlinpoet.MemberName
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeAliasSpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.UNIT
+import com.squareup.kotlinpoet.asClassName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_EXTERNAL
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_INLINE
+import com.squareup.kotlinpoet.metadata.PropertyAccessorFlag.IS_NOT_DEFAULT
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createAnnotations
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.toTreeSet
+import com.squareup.kotlinpoet.metadata.declaresDefaultValue
+import com.squareup.kotlinpoet.metadata.hasAnnotations
+import com.squareup.kotlinpoet.metadata.hasGetter
+import com.squareup.kotlinpoet.metadata.hasSetter
+import com.squareup.kotlinpoet.metadata.isAbstract
+import com.squareup.kotlinpoet.metadata.isAnnotation
+import com.squareup.kotlinpoet.metadata.isClass
+import com.squareup.kotlinpoet.metadata.isCompanionObject
+import com.squareup.kotlinpoet.metadata.isConst
+import com.squareup.kotlinpoet.metadata.isCrossInline
+import com.squareup.kotlinpoet.metadata.isData
+import com.squareup.kotlinpoet.metadata.isDeclaration
+import com.squareup.kotlinpoet.metadata.isDelegated
+import com.squareup.kotlinpoet.metadata.isDelegation
+import com.squareup.kotlinpoet.metadata.isEnum
+import com.squareup.kotlinpoet.metadata.isEnumEntry
+import com.squareup.kotlinpoet.metadata.isExpect
+import com.squareup.kotlinpoet.metadata.isExternal
+import com.squareup.kotlinpoet.metadata.isFinal
+import com.squareup.kotlinpoet.metadata.isFun
+import com.squareup.kotlinpoet.metadata.isInfix
+import com.squareup.kotlinpoet.metadata.isInline
+import com.squareup.kotlinpoet.metadata.isInner
+import com.squareup.kotlinpoet.metadata.isInterface
+import com.squareup.kotlinpoet.metadata.isInternal
+import com.squareup.kotlinpoet.metadata.isLateinit
+import com.squareup.kotlinpoet.metadata.isNoInline
+import com.squareup.kotlinpoet.metadata.isObject
+import com.squareup.kotlinpoet.metadata.isOpen
+import com.squareup.kotlinpoet.metadata.isOperator
+import com.squareup.kotlinpoet.metadata.isPrimary
+import com.squareup.kotlinpoet.metadata.isPrivate
+import com.squareup.kotlinpoet.metadata.isProtected
+import com.squareup.kotlinpoet.metadata.isPublic
+import com.squareup.kotlinpoet.metadata.isReified
+import com.squareup.kotlinpoet.metadata.isSealed
+import com.squareup.kotlinpoet.metadata.isSuspend
+import com.squareup.kotlinpoet.metadata.isSynthesized
+import com.squareup.kotlinpoet.metadata.isTailRec
+import com.squareup.kotlinpoet.metadata.isVal
+import com.squareup.kotlinpoet.metadata.isValue
+import com.squareup.kotlinpoet.metadata.isVar
+import com.squareup.kotlinpoet.metadata.propertyAccessorFlags
+import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
+import com.squareup.kotlinpoet.metadata.toKmClass
+import com.squareup.kotlinpoet.tag
+import java.util.Locale
+import javax.lang.model.element.Element
+import javax.lang.model.element.ElementKind
+import javax.lang.model.element.PackageElement
+import javax.lang.model.element.TypeElement
+import kotlin.reflect.KClass
+import kotlinx.metadata.Flags
+import kotlinx.metadata.KmClass
+import kotlinx.metadata.KmClassifier
+import kotlinx.metadata.KmConstructor
+import kotlinx.metadata.KmFunction
+import kotlinx.metadata.KmPackage
+import kotlinx.metadata.KmProperty
+import kotlinx.metadata.KmType
+import kotlinx.metadata.KmTypeAlias
+import kotlinx.metadata.KmValueParameter
+import kotlinx.metadata.jvm.JvmMethodSignature
+import kotlinx.metadata.jvm.getterSignature
+import kotlinx.metadata.jvm.jvmInternalName
+import kotlinx.metadata.jvm.setterSignature
+import kotlinx.metadata.jvm.signature
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = java.toTypeSpec(classInspector)
+
+/** @return a [TypeSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [TypeSpec] ABI representation of this [TypeElement]. */
+@Suppress("DEPRECATION")
+@KotlinPoetMetadataPreview
+public fun TypeElement.toTypeSpec(
+ classInspector: ClassInspector? = null,
+): TypeSpec = toKmClass().toTypeSpec(classInspector, asClassName())
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun KClass<*>.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = java.toFileSpec(classInspector)
+
+/** @return a [FileSpec] ABI representation of this [KClass]. */
+@KotlinPoetMetadataPreview
+public fun Class<*>.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(`package`.name, toTypeSpec(classInspector))
+
+/** @return a [FileSpec] ABI representation of this [TypeElement]. */
+@KotlinPoetMetadataPreview
+public fun TypeElement.toFileSpec(
+ classInspector: ClassInspector? = null,
+): FileSpec = FileSpec.get(
+ packageName = packageName,
+ typeSpec = toTypeSpec(classInspector),
+)
+
+/** @return a [TypeSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toTypeSpec(
+ classInspector: ClassInspector?,
+ className: ClassName = createClassName(name),
+): TypeSpec {
+ return toTypeSpec(classInspector, className, null)
+}
+
+/** @return a [FileSpec] ABI representation of this [KmClass]. */
+@KotlinPoetMetadataPreview
+public fun KmClass.toFileSpec(
+ classInspector: ClassInspector?,
+ className: ClassName = createClassName(name),
+): FileSpec {
+ return FileSpec.get(
+ packageName = className.packageName,
+ typeSpec = toTypeSpec(classInspector, className),
+ )
+}
+
+/** @return a [FileSpec] ABI representation of this [KmPackage]. */
+@KotlinPoetMetadataPreview
+public fun KmPackage.toFileSpec(
+ classInspector: ClassInspector?,
+ className: ClassName,
+): FileSpec {
+ val fileData = classInspector?.containerData(className, null)
+ check(fileData is FileData?) {
+ "Unexpected container data type: ${fileData?.javaClass}"
+ }
+ val fileName = fileData?.fileName ?: className.simpleName
+ return FileSpec.builder(className.packageName, fileName)
+ .apply {
+ fileData?.let { data ->
+ data.jvmName?.let { name ->
+ addAnnotation(
+ AnnotationSpec.builder(ClassInspectorUtil.JVM_NAME)
+ .addMember("name = %S", name)
+ .build(),
+ )
+ }
+ val fileAnnotations = createAnnotations(FILE) {
+ addAll(data.annotations.filterNot { it.typeName == METADATA })
+ }
+ for (fileAnnotation in fileAnnotations) {
+ addAnnotation(fileAnnotation)
+ }
+ }
+ for (function in functions) {
+ val methodData = fileData?.methods?.get(function)
+ addFunction(
+ function.toFunSpec(
+ classInspector = classInspector,
+ containerData = fileData,
+ methodData = methodData,
+ isInInterface = false,
+ ),
+ )
+ }
+ for (property in properties) {
+ val propertyData = fileData?.properties?.get(property)
+ addProperty(
+ property.toPropertySpec(
+ classInspector = classInspector,
+ containerData = fileData,
+ propertyData = propertyData,
+ isInInterface = false,
+ ),
+ )
+ }
+ for (alias in typeAliases) {
+ addTypeAlias(alias.toTypeAliasSpec())
+ }
+ }
+ .build()
+}
+
+private const val NOT_IMPLEMENTED = "throw·NotImplementedError(\"Stub!\")"
+
+@KotlinPoetMetadataPreview
+private fun KmClass.toTypeSpec(
+ classInspector: ClassInspector?,
+ className: ClassName,
+ parentClassName: ClassName?,
+): TypeSpec {
+ val classTypeParamsResolver = typeParameters.toTypeParameterResolver()
+ val jvmInternalName = name.jvmInternalName
+ val simpleName = className.simpleName
+ val classData = classInspector?.containerData(className, parentClassName)
+ check(classData is ClassData?) {
+ "Unexpected container data type: ${classData?.javaClass}"
+ }
+
+ val builder = when {
+ isAnnotation -> TypeSpec.annotationBuilder(simpleName)
+ isCompanionObject -> TypeSpec.companionObjectBuilder(companionObjectName(simpleName))
+ isEnum -> TypeSpec.enumBuilder(simpleName)
+ isExpect -> TypeSpec.expectClassBuilder(simpleName)
+ isObject -> TypeSpec.objectBuilder(simpleName)
+ isInterface -> {
+ if (classData?.declarationContainer?.isFun == true) {
+ TypeSpec.funInterfaceBuilder(simpleName)
+ } else {
+ TypeSpec.interfaceBuilder(simpleName)
+ }
+ }
+ isEnumEntry -> TypeSpec.anonymousClassBuilder()
+ else -> TypeSpec.classBuilder(simpleName)
+ }
+
+ classData?.annotations
+ ?.filterNot {
+ it.typeName == METADATA || it.typeName in JAVA_ANNOTATION_ANNOTATIONS
+ }
+ ?.let(builder::addAnnotations)
+
+ if (isEnum) {
+ enumEntries.forEach { entryName ->
+ val typeSpec = if (classInspector != null) {
+ val entry = classInspector.enumEntry(className, entryName)
+ entry.declarationContainer
+ ?.let { enumEntryClass ->
+ val entryClassName = className.nestedClass(entryName)
+ enumEntryClass.toTypeSpec(classInspector, entryClassName, parentClassName = className)
+ }
+ ?: TypeSpec.anonymousClassBuilder()
+ .addAnnotations(entry.annotations)
+ .build()
+ } else {
+ TypeSpec.anonymousClassBuilder()
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this entry may not be reflected accurately if it has a class body.",
+ )
+ .build()
+ }
+ builder.addEnumConstant(entryName, typeSpec)
+ }
+ }
+
+ if (!isEnumEntry) {
+ visibilityFrom(flags) { builder.addModifiers(it) }
+ builder.addModifiers(
+ *flags.modalities
+ .filterNot { it == FINAL } // Default
+ .filterNot { isInterface && it == ABSTRACT } // Abstract is a default on interfaces
+ .toTypedArray(),
+ )
+ if (isData) {
+ builder.addModifiers(DATA)
+ }
+ if (isExternal) {
+ builder.addModifiers(EXTERNAL)
+ }
+ if (isValue) {
+ builder.addModifiers(VALUE)
+ }
+ if (isInner) {
+ builder.addModifiers(INNER)
+ }
+ builder.addTypeVariables(typeParameters.map { it.toTypeVariableName(classTypeParamsResolver) })
+ // If we have an inspector, we can check exactly which "supertype" is an interface vs
+ // class. Without a handler though, we have to best-effort guess. Usually, the flow is:
+ // - First element of a non-interface type is the superclass (can be `Any`)
+ // - First element of an interface type is the first superinterface
+ val superClassFilter = classInspector?.let { handler ->
+ { type: KmType ->
+ !handler.isInterface(createClassName((type.classifier as KmClassifier.Class).name))
+ }
+ } ?: { true }
+ val superClass = supertypes.asSequence()
+ .filter { it.classifier is KmClassifier.Class }
+ .find(superClassFilter)
+ if (superClass != null && !isEnum && !isInterface && !isAnnotation) {
+ superClass.toTypeName(classTypeParamsResolver).takeIf { it != ANY }
+ ?.let(builder::superclass)
+ }
+ builder.addSuperinterfaces(
+ supertypes.asSequence()
+ .filterNot { it == superClass }
+ .map { it.toTypeName(classTypeParamsResolver) }
+ .filterNot { it == ANY }
+ .asIterable(),
+ )
+ val primaryConstructorParams = mutableMapOf<String, ParameterSpec>()
+ if (isClass || isAnnotation || isEnum) {
+ primaryConstructor?.let {
+ it.toFunSpec(classTypeParamsResolver, classData?.constructors?.get(it) ?: return@let)
+ .also { spec ->
+ val finalSpec = if (isEnum && spec.annotations.isEmpty()) {
+ // Metadata specifies the constructor as private, but that's implicit so we can omit it
+ spec.toBuilder().apply { modifiers.remove(PRIVATE) }.build()
+ } else {
+ spec
+ }
+ builder.primaryConstructor(finalSpec)
+ primaryConstructorParams.putAll(spec.parameters.associateBy { it.name })
+ }
+ }
+ constructors.filter { !it.isPrimary }.takeIf { it.isNotEmpty() }?.let { secondaryConstructors ->
+ builder.addFunctions(
+ secondaryConstructors
+ .mapNotNull { kmConstructor ->
+ classData?.constructors?.get(kmConstructor)?.let { kmConstructor to it }
+ }
+ .map { (kmConstructor, constructorData) ->
+ kmConstructor.toFunSpec(classTypeParamsResolver, constructorData)
+ },
+ )
+ }
+ }
+ builder.addProperties(
+ properties
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isSynthesized }
+ .map { it to classData?.properties?.get(it) }
+ .map { (property, propertyData) ->
+ property.toPropertySpec(
+ typeParamResolver = classTypeParamsResolver,
+ isConstructorParam = property.name in primaryConstructorParams,
+ classInspector = classInspector,
+ containerData = classData,
+ propertyData = propertyData,
+ )
+ }
+ .asIterable(),
+ )
+ companionObject?.let { objectName ->
+ val companionType = if (classInspector != null) {
+ val companionClassName = className.nestedClass(objectName)
+ classInspector.classFor(companionClassName)
+ .toTypeSpec(classInspector, companionClassName, parentClassName = className)
+ } else {
+ TypeSpec.companionObjectBuilder(companionObjectName(objectName))
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this companion object's API/contents may not be reflected accurately.",
+ )
+ .build()
+ }
+ builder.addType(companionType)
+ }
+ }
+ builder.addFunctions(
+ functions
+ .asSequence()
+ .filter { it.isDeclaration }
+ .filterNot { it.isDelegation }
+ .filterNot { it.isSynthesized }
+ .map { it to classData?.methods?.get(it) }
+ .map { (func, methodData) ->
+ func.toFunSpec(classTypeParamsResolver, classInspector, classData, methodData)
+ .toBuilder()
+ .apply {
+ // For interface methods, remove any body and mark the methods as abstract
+ fun isKotlinDefaultInterfaceMethod(): Boolean {
+ classInspector?.let { handler ->
+ func.signature?.let { signature ->
+ val suffix = signature.desc.removePrefix("(")
+ return handler.methodExists(
+ className.nestedClass("DefaultImpls"),
+ signature.copy(
+ desc = "(L$jvmInternalName;$suffix",
+ ),
+ )
+ }
+ }
+ return false
+ }
+ // For interface methods, remove any body and mark the methods as abstract
+ // IFF it doesn't have a default interface body.
+ if (isInterface &&
+ annotations.none { it.typeName == JVM_DEFAULT } &&
+ (methodData?.jvmModifiers?.contains(DEFAULT) == false) &&
+ !isKotlinDefaultInterfaceMethod()
+ ) {
+ addModifiers(ABSTRACT)
+ clearBody()
+ } else if (ABSTRACT in modifiers) {
+ // Remove bodies for abstract functions
+ clearBody()
+ }
+ if (methodData?.isSynthetic == true) {
+ addKdoc(
+ "Note: Since this is a synthetic function, some JVM information " +
+ "(annotations, modifiers) may be missing.",
+ )
+ }
+ }
+ .build()
+ }
+ .asIterable(),
+ )
+
+ for (it in nestedClasses) {
+ val nestedClassName = className.nestedClass(it)
+ val nestedClass = classInspector?.classFor(nestedClassName)
+ val nestedType = if (nestedClass != null) {
+ if (nestedClass.isCompanionObject) {
+ // We handle these separately
+ continue
+ } else {
+ nestedClass.toTypeSpec(classInspector, nestedClassName, parentClassName = className)
+ }
+ } else {
+ TypeSpec.classBuilder(it)
+ .addKdoc(
+ "No ClassInspector was available during metadata parsing, so this nested class's API/contents may not be reflected accurately.",
+ )
+ .build()
+ }
+ builder.addType(nestedType)
+ }
+
+ return builder
+ .tag(this)
+ .build()
+}
+
+private fun companionObjectName(name: String): String? {
+ return if (name == "Companion") null else name
+}
+
+@KotlinPoetMetadataPreview
+private fun KmConstructor.toFunSpec(
+ typeParamResolver: TypeParameterResolver,
+ constructorData: ConstructorData?,
+): FunSpec {
+ return FunSpec.constructorBuilder()
+ .apply {
+ addAnnotations(constructorData?.allAnnotations.orEmpty())
+ visibilityFrom(flags) { addModifiers(it) }
+ addParameters(
+ this@toFunSpec.valueParameters.mapIndexed { index, param ->
+ param.toParameterSpec(
+ typeParamResolver,
+ constructorData?.takeIf { it != ConstructorData.EMPTY }
+ ?.parameterAnnotations
+ ?.get(index)
+ .orEmpty(),
+ )
+ },
+ )
+ if (!isPrimary) {
+ // TODO How do we know when to add callSuperConstructor()?
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private val ContainerData.isInterface: Boolean get() {
+ return declarationContainer.let { container ->
+ container is KmClass && container.isInterface
+ }
+}
+
+@KotlinPoetMetadataPreview
+private fun KmFunction.toFunSpec(
+ classTypeParamsResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+ classInspector: ClassInspector? = null,
+ containerData: ContainerData? = null,
+ methodData: MethodData? = null,
+ isInInterface: Boolean = containerData?.isInterface ?: false,
+): FunSpec {
+ val typeParamsResolver = typeParameters.toTypeParameterResolver(
+ fallback = classTypeParamsResolver,
+ )
+ val mutableAnnotations = mutableListOf<AnnotationSpec>()
+ if (classInspector != null && containerData != null) {
+ signature?.let { signature ->
+ if (!containerData.isInterface) {
+ // Infer if JvmName was used
+ // We skip interface types for this because they can't have @JvmName.
+ signature.jvmNameAnnotation(name)?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ val anyReified = typeParameters.any { it.isReified }
+ val isInFacade = containerData is FileData
+ val annotations = mutableAnnotations
+ .plus(methodData?.allAnnotations(containsReifiedTypeParameter = anyReified).orEmpty())
+ .filterNot { isInFacade && it.typeName == JVM_STATIC }
+ .toTreeSet()
+ return FunSpec.builder(name)
+ .apply {
+ addAnnotations(annotations)
+ visibilityFrom(flags) { addModifiers(it) }
+ val isOverride = methodData?.isOverride == true
+ addModifiers(
+ flags.modalities
+ .filterNot { it == FINAL && !isOverride } // Final is the default
+ .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+ .filterNot { it == OPEN && isInInterface }, // interface methods are implicitly open
+ )
+ if (valueParameters.isNotEmpty()) {
+ addParameters(
+ valueParameters.mapIndexed { index, param ->
+ param.toParameterSpec(
+ typeParamsResolver,
+ // This can be empty if the element is synthetic
+ methodData?.parameterAnnotations?.get(index).orEmpty(),
+ )
+ },
+ )
+ }
+ if (typeParameters.isNotEmpty()) {
+ addTypeVariables(typeParameters.map { it.toTypeVariableName(typeParamsResolver) })
+ }
+ if (methodData?.isOverride == true) {
+ addModifiers(KModifier.OVERRIDE)
+ }
+ if (isOperator) {
+ addModifiers(OPERATOR)
+ }
+ if (isInfix) {
+ addModifiers(INFIX)
+ }
+ if (isInline) {
+ addModifiers(INLINE)
+ }
+ if (isTailRec) {
+ addModifiers(TAILREC)
+ }
+ if (isExternal) {
+ addModifiers(EXTERNAL)
+ }
+ if (isExpect) {
+ addModifiers(EXPECT)
+ }
+ if (isSuspend) {
+ addModifiers(SUSPEND)
+ }
+ val returnTypeName = this@toFunSpec.returnType.toTypeName(typeParamsResolver)
+ if (returnTypeName != UNIT) {
+ returns(returnTypeName)
+ if (!flags.isAbstract) {
+ addStatement(NOT_IMPLEMENTED)
+ }
+ }
+ receiverParameterType?.toTypeName(typeParamsResolver)?.let { receiver(it) }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmValueParameter.toParameterSpec(
+ typeParamResolver: TypeParameterResolver,
+ annotations: Collection<AnnotationSpec>,
+): ParameterSpec {
+ val paramType = varargElementType ?: type ?: throw IllegalStateException("No argument type!")
+ return ParameterSpec.builder(name, paramType.toTypeName(typeParamResolver))
+ .apply {
+ addAnnotations(annotations)
+ if (varargElementType != null) {
+ addModifiers(VARARG)
+ }
+ if (isCrossInline) {
+ addModifiers(CROSSINLINE)
+ }
+ if (isNoInline) {
+ addModifiers(NOINLINE)
+ }
+ if (declaresDefaultValue) {
+ defaultValue(NOT_IMPLEMENTED)
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmProperty.toPropertySpec(
+ typeParamResolver: TypeParameterResolver = TypeParameterResolver.EMPTY,
+ isConstructorParam: Boolean = false,
+ classInspector: ClassInspector? = null,
+ containerData: ContainerData? = null,
+ propertyData: PropertyData? = null,
+ isInInterface: Boolean = containerData?.isInterface ?: false,
+): PropertySpec {
+ val isOverride = propertyData?.isOverride ?: false
+ val returnTypeName = returnType.toTypeName(typeParamResolver)
+ val mutableAnnotations = mutableListOf<AnnotationSpec>()
+ if (containerData != null && propertyData != null) {
+ if (hasGetter) {
+ getterSignature?.let { getterSignature ->
+ if (!containerData.isInterface &&
+ !flags.isOpen &&
+ !flags.isAbstract
+ ) {
+ // Infer if JvmName was used
+ // We skip interface types or open/abstract properties because they can't have @JvmName.
+ // For annotation properties, kotlinc puts JvmName annotations by default in
+ // bytecode but they're implicit in source, so we expect the simple name for
+ // annotation types.
+ val expectedMetadataName = if (containerData is ClassData &&
+ containerData.declarationContainer.isAnnotation
+ ) {
+ name
+ } else {
+ "get${name.safeCapitalize(Locale.US)}"
+ }
+ getterSignature.jvmNameAnnotation(
+ metadataName = expectedMetadataName,
+ useSiteTarget = UseSiteTarget.GET,
+ )?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ if (hasSetter) {
+ setterSignature?.let { setterSignature ->
+ if (containerData is ClassData &&
+ !containerData.declarationContainer.isAnnotation &&
+ !containerData.declarationContainer.isInterface &&
+ classInspector?.supportsNonRuntimeRetainedAnnotations == false &&
+ !flags.isOpen &&
+ !flags.isAbstract
+ ) {
+ // Infer if JvmName was used
+ // We skip annotation types for this because they can't have vars.
+ // We skip interface types or open/abstract properties because they can't have @JvmName.
+ setterSignature.jvmNameAnnotation(
+ metadataName = "set${name.safeCapitalize(Locale.US)}",
+ useSiteTarget = UseSiteTarget.SET,
+ )?.let { jvmNameAnnotation ->
+ mutableAnnotations += jvmNameAnnotation
+ }
+ }
+ }
+ }
+ }
+ return PropertySpec.builder(name, returnTypeName)
+ .apply {
+ // If a property annotation doesn't have a custom site target and is used in a constructor
+ // we have to add the property: site target to it.
+
+ val isInFacade = containerData is FileData
+ val finalAnnotations = mutableAnnotations
+ .plus(propertyData?.allAnnotations.orEmpty())
+ .filterNot { (isConst || isInFacade) && it.typeName == JVM_STATIC }
+ .map {
+ if (isConstructorParam && it.useSiteTarget == null) {
+ // TODO Ideally don't do this if the annotation use site is only field?
+ // e.g. JvmField. It's technically fine, but redundant on parameters as it's
+ // automatically applied to the property for these annotation types.
+ // This is another thing ClassInspector *could* tell us
+ it.toBuilder().useSiteTarget(UseSiteTarget.PROPERTY).build()
+ } else {
+ it
+ }
+ }
+ .toTreeSet()
+ addAnnotations(finalAnnotations)
+ visibilityFrom(flags) { addModifiers(it) }
+ addModifiers(
+ flags.modalities
+ .filterNot { it == FINAL && !isOverride } // Final is the default
+ .filterNot { it == OPEN && isOverride } // Overrides are implicitly open
+ .filterNot { it == OPEN && isInInterface } // Interface properties implicitly open
+ .filterNot { it == ABSTRACT && isInInterface }, // Interface properties implicitly abstract
+ )
+ if (isOverride) {
+ addModifiers(KModifier.OVERRIDE)
+ }
+ if (isConst) {
+ addModifiers(CONST)
+ }
+ if (isVar) {
+ mutable(true)
+ } else if (isVal) {
+ mutable(false)
+ }
+ if (isDelegated) {
+ // Placeholders for these are tricky
+ addKdoc("Note: delegation is ABI stub only and not guaranteed to match source code.")
+ if (isVal) {
+ delegate("%M { %L }", MemberName("kotlin", "lazy"), NOT_IMPLEMENTED) // Placeholder
+ } else {
+ if (returnTypeName.isNullable) {
+ delegate(
+ "%T.observable(null) { _, _, _ -> }",
+ ClassName("kotlin.properties", "Delegates"),
+ )
+ } else {
+ delegate("%T.notNull()", ClassName("kotlin.properties", "Delegates")) // Placeholder
+ }
+ }
+ }
+ if (isExpect) {
+ addModifiers(EXPECT)
+ }
+ if (isExternal) {
+ addModifiers(EXTERNAL)
+ }
+ if (isLateinit) {
+ addModifiers(LATEINIT)
+ }
+ if (isConstructorParam || (!isDelegated && !isLateinit)) {
+ val constant = propertyData?.fieldData?.constant
+ when {
+ constant != null -> initializer(constant)
+ isConstructorParam -> initializer(name)
+ returnTypeName.isNullable -> initializer("null")
+ flags.isAbstract || isInInterface -> {
+ // No-op, don't emit an initializer for abstract or interface properties
+ }
+ else -> initializer(NOT_IMPLEMENTED)
+ }
+ }
+ // Delegated properties have setters/getters defined for some reason, ignore here
+ // since the delegate handles it
+ // vals with initialized constants have a getter in bytecode but not a body in kotlin source
+ val modifierSet = modifiers.toSet()
+ if (hasGetter && !isDelegated && !flags.isAbstract) {
+ propertyAccessor(
+ modifierSet,
+ getterFlags,
+ FunSpec.getterBuilder().addStatement(NOT_IMPLEMENTED),
+ isOverride,
+ )?.let(::getter)
+ }
+ if (hasSetter && !isDelegated && !flags.isAbstract) {
+ propertyAccessor(modifierSet, setterFlags, FunSpec.setterBuilder(), isOverride)?.let(::setter)
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@KotlinPoetMetadataPreview
+private fun propertyAccessor(
+ propertyModifiers: Set<KModifier>,
+ flags: Flags,
+ functionBuilder: Builder,
+ isOverride: Boolean,
+): FunSpec? {
+ val visibility = flags.visibility
+ if (visibility == PUBLIC || visibility !in propertyModifiers) {
+ // This is redundant and just a stub
+ // For annotations on this accessor, we declare them on the property with site target instead
+ return null
+ }
+ val modalities = flags.modalities
+ .filterNot { it == FINAL && !isOverride }
+ .filterNot { it == OPEN && isOverride }
+ val propertyAccessorFlags = flags.propertyAccessorFlags
+ return if (visibility != PUBLIC || modalities.isNotEmpty() || propertyAccessorFlags.isNotEmpty()) {
+ functionBuilder
+ .apply {
+ addModifiers(visibility)
+ addModifiers(modalities)
+ addModifiers(*propertyAccessorFlags.toKModifiersArray())
+ }
+ .build()
+ } else {
+ null
+ }
+}
+
+private fun Set<PropertyAccessorFlag>.toKModifiersArray(): Array<KModifier> {
+ return mapNotNull {
+ when (it) {
+ IS_EXTERNAL -> EXTERNAL
+ IS_INLINE -> INLINE
+ IS_NOT_DEFAULT -> null // Gracefully skip over these
+ }
+ }.toTypedArray()
+}
+
+@KotlinPoetMetadataPreview
+private fun KmTypeAlias.toTypeAliasSpec(): TypeAliasSpec {
+ val typeParamResolver = typeParameters.toTypeParameterResolver()
+ return TypeAliasSpec.builder(name, underlyingType.toTypeName(typeParamResolver))
+ .apply {
+ visibilityFrom(flags) {
+ addModifiers(it)
+ }
+ if (flags.hasAnnotations) {
+ val annotationSpecs = this@toTypeAliasSpec.annotations
+ .map { it.toAnnotationSpec() }
+ addAnnotations(annotationSpecs)
+ }
+ }
+ .addTypeVariables(typeParamResolver.parametersMap.values)
+ .build()
+}
+
+private fun JvmMethodSignature.jvmNameAnnotation(
+ metadataName: String,
+ useSiteTarget: UseSiteTarget? = null,
+): AnnotationSpec? {
+ return if (name == metadataName) {
+ null
+ } else {
+ return AnnotationSpec.builder(JvmName::class)
+ .addMember("name = %S", name)
+ .useSiteTarget(useSiteTarget)
+ .build()
+ }
+}
+
+private val JAVA_ANNOTATION_ANNOTATIONS = setOf(
+ java.lang.annotation.Retention::class.asClassName(),
+ java.lang.annotation.Target::class.asClassName(),
+)
+
+@KotlinPoetMetadataPreview
+private val Flags.visibility: KModifier
+ get() = when {
+ isInternal -> INTERNAL
+ isPrivate -> PRIVATE
+ isProtected -> PROTECTED
+ isPublic -> PUBLIC
+ else -> {
+ // IS_PRIVATE_TO_THIS or IS_LOCAL, so just default to public
+ PUBLIC
+ }
+ }
+
+@KotlinPoetMetadataPreview
+private fun visibilityFrom(flags: Flags, body: (KModifier) -> Unit) {
+ val modifierVisibility = flags.visibility
+ if (modifierVisibility != PUBLIC) {
+ body(modifierVisibility)
+ }
+}
+
+private fun String.safeCapitalize(locale: Locale): String {
+ return replaceFirstChar { if (it.isLowerCase()) it.titlecase(locale) else it.toString() }
+}
+
+@KotlinPoetMetadataPreview
+private val Flags.modalities: Set<KModifier>
+ get() = setOf {
+ if (isFinal) {
+ add(FINAL)
+ }
+ if (isOpen) {
+ add(OPEN)
+ }
+ if (isAbstract) {
+ add(ABSTRACT)
+ }
+ if (isSealed) {
+ add(SEALED)
+ }
+ }
+
+private inline fun <E> setOf(body: MutableSet<E>.() -> Unit): Set<E> {
+ return mutableSetOf<E>().apply(body).toSet()
+}
+
+private val METADATA = Metadata::class.asClassName()
+
+@Suppress("DEPRECATION")
+private val JVM_DEFAULT = JvmDefault::class.asClassName()
+private val JVM_STATIC = JvmStatic::class.asClassName()
+
+@PublishedApi
+internal val Element.packageName: String
+ get() {
+ var element = this
+ while (element.kind != ElementKind.PACKAGE) {
+ element = element.enclosingElement
+ }
+ return (element as PackageElement).toString()
+ }
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
new file mode 100644
index 00000000..7319e9d8
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/MethodData.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a method used for [ClassInspector]. Should only be
+ * associated with methods of a [ClassData] or [PropertyData].
+ *
+ * @param annotations declared annotations on this method.
+ * @property parameterAnnotations a mapping of parameter indices to annotations on them.
+ * @property isSynthetic indicates if this method is synthetic or not.
+ * @property jvmModifiers set of [JvmMethodModifiers][JvmMethodModifier] on this method.
+ * @property isOverride indicates if this method overrides one in a supertype.
+ * @property exceptions list of exceptions thrown by this method.
+ */
+@KotlinPoetMetadataPreview
+public data class MethodData(
+ private val annotations: List<AnnotationSpec>,
+ val parameterAnnotations: Map<Int, Collection<AnnotationSpec>>,
+ val isSynthetic: Boolean,
+ val jvmModifiers: Set<JvmMethodModifier>,
+ val isOverride: Boolean,
+ val exceptions: List<TypeName>,
+) {
+
+ /**
+ * A collection of all annotations on this method, including any derived from [jvmModifiers],
+ * [isSynthetic], and [exceptions].
+ *
+ * @param useSiteTarget an optional [UseSiteTarget] that all annotations on this method should
+ * use.
+ * @param containsReifiedTypeParameter an optional boolean indicating if any type parameters on
+ * this function are `reified`, which are implicitly synthetic.
+ */
+ public fun allAnnotations(
+ useSiteTarget: UseSiteTarget? = null,
+ containsReifiedTypeParameter: Boolean = false,
+ ): Collection<AnnotationSpec> {
+ return ClassInspectorUtil.createAnnotations(
+ useSiteTarget,
+ ) {
+ addAll(annotations)
+ if (isSynthetic && !containsReifiedTypeParameter) {
+ add(ClassInspectorUtil.JVM_SYNTHETIC_SPEC)
+ }
+ addAll(jvmModifiers.mapNotNull(JvmMethodModifier::annotationSpec))
+ exceptions.takeIf { it.isNotEmpty() }
+ ?.let {
+ add(ClassInspectorUtil.createThrowsSpec(it, useSiteTarget))
+ }
+ }
+ }
+
+ public companion object {
+ public val SYNTHETIC: MethodData = MethodData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = true,
+ jvmModifiers = emptySet(),
+ isOverride = false,
+ exceptions = emptyList(),
+ )
+ public val EMPTY: MethodData = MethodData(
+ annotations = emptyList(),
+ parameterAnnotations = emptyMap(),
+ isSynthetic = false,
+ jvmModifiers = emptySet(),
+ isOverride = false,
+ exceptions = emptyList(),
+ )
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
new file mode 100644
index 00000000..9fc6d3e4
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/PropertyData.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.GET
+import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.SET
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
+
+/**
+ * Represents relevant information on a property used for [ClassInspector]. Should only be
+ * associated with properties of a [ClassData].
+ *
+ * @param annotations declared annotations on this property.
+ * @property fieldData associated [FieldData] with this property, if any.
+ * @property getterData associated getter (as [MethodData]) with this property, if any.
+ * @property setterData associated setter (as [MethodData]) with this property, if any.
+ * @property isJvmField indicates if this property should be treated as a jvm field.
+ */
+@KotlinPoetMetadataPreview
+public data class PropertyData(
+ private val annotations: List<AnnotationSpec>,
+ val fieldData: FieldData?,
+ val getterData: MethodData?,
+ val setterData: MethodData?,
+ val isJvmField: Boolean,
+) {
+ /** Indicates if this property overrides another from a supertype. */
+ val isOverride: Boolean = (getterData?.isOverride ?: false) || (setterData?.isOverride ?: false)
+
+ /**
+ * A collection of all annotations on this property including declared ones and any derived from
+ * [fieldData], [getterData], [setterData], and [isJvmField].
+ */
+ val allAnnotations: Collection<AnnotationSpec> = ClassInspectorUtil.createAnnotations {
+ // Don't add annotations that are already defined on the parent
+ val higherScopedAnnotations = annotations.associateBy { it.typeName }
+ val fieldAnnotations = fieldData?.allAnnotations.orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations }
+ .associateByTo(LinkedHashMap()) { it.typeName }
+ val getterAnnotations = getterData?.allAnnotations(GET).orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations }
+ .associateByTo(LinkedHashMap()) { it.typeName }
+
+ val finalTopAnnotations = annotations.toMutableList()
+
+ // If this is a val, and annotation is on both getter and field, we can move it to just the
+ // regular annotations
+ if (setterData == null && !isJvmField) {
+ val sharedAnnotations = getterAnnotations.keys.intersect(fieldAnnotations.keys)
+ for (sharedAnnotation in sharedAnnotations) {
+ // Add it to the top-level annotations without a site-target
+ finalTopAnnotations += getterAnnotations.getValue(sharedAnnotation).toBuilder()
+ .useSiteTarget(null)
+ .build()
+
+ // Remove from field and getter
+ fieldAnnotations.remove(sharedAnnotation)
+ getterAnnotations.remove(sharedAnnotation)
+ }
+ }
+
+ addAll(finalTopAnnotations)
+ addAll(fieldAnnotations.values)
+ addAll(getterAnnotations.values)
+ addAll(
+ setterData?.allAnnotations(SET).orEmpty()
+ .filterNot { it.typeName in higherScopedAnnotations },
+ )
+ if (isJvmField) {
+ add(ClassInspectorUtil.JVM_FIELD_SPEC)
+ }
+ }
+}
diff --git a/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
new file mode 100644
index 00000000..f6b44670
--- /dev/null
+++ b/interop/kotlinx-metadata/src/main/kotlin/com/squareup/kotlinpoet/metadata/specs/kmAnnotations.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2021 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
+ *
+ * https://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.kotlinpoet.metadata.specs
+
+import com.squareup.kotlinpoet.AnnotationSpec
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.joinToCode
+import com.squareup.kotlinpoet.metadata.KotlinPoetMetadataPreview
+import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.createClassName
+import com.squareup.kotlinpoet.tag
+import kotlinx.metadata.KmAnnotation
+import kotlinx.metadata.KmAnnotationArgument
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue
+import kotlinx.metadata.KmAnnotationArgument.BooleanValue
+import kotlinx.metadata.KmAnnotationArgument.ByteValue
+import kotlinx.metadata.KmAnnotationArgument.CharValue
+import kotlinx.metadata.KmAnnotationArgument.DoubleValue
+import kotlinx.metadata.KmAnnotationArgument.EnumValue
+import kotlinx.metadata.KmAnnotationArgument.FloatValue
+import kotlinx.metadata.KmAnnotationArgument.IntValue
+import kotlinx.metadata.KmAnnotationArgument.KClassValue
+import kotlinx.metadata.KmAnnotationArgument.LongValue
+import kotlinx.metadata.KmAnnotationArgument.ShortValue
+import kotlinx.metadata.KmAnnotationArgument.StringValue
+import kotlinx.metadata.KmAnnotationArgument.UByteValue
+import kotlinx.metadata.KmAnnotationArgument.UIntValue
+import kotlinx.metadata.KmAnnotationArgument.ULongValue
+import kotlinx.metadata.KmAnnotationArgument.UShortValue
+
+@KotlinPoetMetadataPreview
+internal fun KmAnnotation.toAnnotationSpec(): AnnotationSpec {
+ val cn = createClassName(className)
+ return AnnotationSpec.builder(cn)
+ .apply {
+ arguments.forEach { (name, arg) ->
+ addMember("%L = %L", name, arg.toCodeBlock())
+ }
+ }
+ .tag(this)
+ .build()
+}
+
+@OptIn(ExperimentalUnsignedTypes::class)
+@KotlinPoetMetadataPreview
+internal fun KmAnnotationArgument.toCodeBlock(): CodeBlock {
+ return when (this) {
+ is ByteValue -> CodeBlock.of("%L", value)
+ is CharValue -> CodeBlock.of("'%L'", value)
+ is ShortValue -> CodeBlock.of("%L", value)
+ is IntValue -> CodeBlock.of("%L", value)
+ is LongValue -> CodeBlock.of("%LL", value)
+ is FloatValue -> CodeBlock.of("%LF", value)
+ is DoubleValue -> CodeBlock.of("%L", value)
+ is BooleanValue -> CodeBlock.of("%L", value)
+ is UByteValue -> CodeBlock.of("%Lu", value)
+ is UShortValue -> CodeBlock.of("%Lu", value)
+ is UIntValue -> CodeBlock.of("%Lu", value)
+ is ULongValue -> CodeBlock.of("%Lu", value)
+ is StringValue -> CodeBlock.of("%S", value)
+ is KClassValue -> CodeBlock.of("%T::class", createClassName(className))
+ is EnumValue -> CodeBlock.of("%T.%L", createClassName(enumClassName), enumEntryName)
+ is AnnotationValue -> CodeBlock.of("%L", annotation.toAnnotationSpec())
+ is ArrayValue -> elements.map { it.toCodeBlock() }.joinToCode(", ", "[", "]")
+ }
+}