1 /*
<lambda>null2 * Copyright (C) 2019 Square, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16 package com.squareup.kotlinpoet.metadata.specs
17
18 import com.squareup.kotlinpoet.ClassName
19 import com.squareup.kotlinpoet.KModifier
20 import com.squareup.kotlinpoet.LambdaTypeName
21 import com.squareup.kotlinpoet.ParameterizedTypeName
22 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
23 import com.squareup.kotlinpoet.STAR
24 import com.squareup.kotlinpoet.TypeName
25 import com.squareup.kotlinpoet.TypeVariableName
26 import com.squareup.kotlinpoet.WildcardTypeName
27 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil
28 import com.squareup.kotlinpoet.metadata.isPrimary
29 import com.squareup.kotlinpoet.tags.TypeAliasTag
30 import kotlin.metadata.KmClass
31 import kotlin.metadata.KmClassifier
32 import kotlin.metadata.KmClassifier.Class
33 import kotlin.metadata.KmClassifier.TypeAlias
34 import kotlin.metadata.KmClassifier.TypeParameter
35 import kotlin.metadata.KmConstructor
36 import kotlin.metadata.KmFlexibleTypeUpperBound
37 import kotlin.metadata.KmFunction
38 import kotlin.metadata.KmProperty
39 import kotlin.metadata.KmType
40 import kotlin.metadata.KmTypeParameter
41 import kotlin.metadata.KmTypeProjection
42 import kotlin.metadata.KmVariance
43 import kotlin.metadata.KmVariance.IN
44 import kotlin.metadata.KmVariance.INVARIANT
45 import kotlin.metadata.KmVariance.OUT
46 import kotlin.metadata.isNullable
47 import kotlin.metadata.isReified
48 import kotlin.metadata.isSuspend
49 import kotlin.metadata.jvm.annotations
50 import kotlin.metadata.jvm.signature
51
52 /**
53 * `true` if this is an extension type (i.e. String.() -> Unit vs (String) -> Unit).
54 *
55 * See details: https://discuss.kotlinlang.org/t/announcing-kotlinx-metadata-jvm-library-for-reading-modifying-metadata-of-kotlin-jvm-class-files/7980/27
56 */
57 public val KmType.isExtensionType: Boolean get() {
58 return annotations.any { it.className == "kotlin/ExtensionFunctionType" }
59 }
60
61 internal val KmClass.primaryConstructor: KmConstructor?
<lambda>null62 get() = constructors.find { it.isPrimary }
63
toKModifiernull64 internal fun KmVariance.toKModifier(): KModifier? {
65 return when (this) {
66 IN -> KModifier.IN
67 OUT -> KModifier.OUT
68 INVARIANT -> null
69 }
70 }
71
toTypeNamenull72 internal fun KmTypeProjection.toTypeName(
73 typeParamResolver: TypeParameterResolver,
74 ): TypeName {
75 val typename = type?.toTypeName(typeParamResolver) ?: STAR
76 return when (variance) {
77 IN -> WildcardTypeName.consumerOf(typename)
78 OUT -> WildcardTypeName.producerOf(typename)
79 INVARIANT -> typename
80 null -> STAR
81 }
82 }
83
84 /**
85 * Converts a given [KmType] into a KotlinPoet representation, attempting to give a correct
86 * "source" representation. This includes converting [functions][kotlin.Function] and `suspend`
87 * types to appropriate [lambda representations][LambdaTypeName].
88 */
toTypeNamenull89 internal fun KmType.toTypeName(
90 typeParamResolver: TypeParameterResolver,
91 ): TypeName {
92 val argumentList = arguments.map { it.toTypeName(typeParamResolver) }
93 val type: TypeName = when (val valClassifier = classifier) {
94 is TypeParameter -> {
95 typeParamResolver[valClassifier.id]
96 }
97 is KmClassifier.Class -> {
98 flexibleTypeUpperBound?.toTypeName(typeParamResolver)?.let { return it }
99 outerType?.toTypeName(typeParamResolver)?.let { return it }
100 var finalType: TypeName = ClassInspectorUtil.createClassName(valClassifier.name)
101 if (argumentList.isNotEmpty()) {
102 val finalTypeString = finalType.toString()
103 if (finalTypeString.startsWith("kotlin.Function")) {
104 // It's a lambda type!
105 finalType = if (finalTypeString == "kotlin.FunctionN") {
106 TODO("unclear how to express this one since it has arity")
107 } else {
108 val (parameters, returnType) = if (isSuspend) {
109 // Coroutines always adds an `Any?` return type, but we kind of just want the
110 // source representation, so we trick it here and ignore the last.
111 argumentList.dropLast(2).toTypedArray() to argumentList.dropLast(1).last().let {
112 // Coroutines makes these a `Continuation<T>` of the type, so we want the parameterized type
113 check(it is ParameterizedTypeName)
114 it.typeArguments[0]
115 }
116 } else {
117 argumentList.dropLast(1).toTypedArray() to argumentList.last()
118 }
119 val lambdaType = if (isExtensionType) {
120 // Extension function type! T.(). First parameter is actually the receiver.
121 LambdaTypeName.get(
122 receiver = parameters[0],
123 parameters = parameters.drop(1).toTypedArray(),
124 returnType = returnType,
125 )
126 } else {
127 LambdaTypeName.get(
128 receiver = null,
129 parameters = parameters,
130 returnType = returnType,
131 )
132 }
133 lambdaType.copy(suspending = isSuspend)
134 }
135 } else {
136 finalType = (finalType as ClassName).parameterizedBy(argumentList)
137 }
138 }
139 finalType
140 }
141 is TypeAlias -> {
142 ClassInspectorUtil.createClassName(valClassifier.name)
143 }
144 }
145
146 val annotations = ClassInspectorUtil.createAnnotations {
147 for (annotation in annotations) {
148 add(annotation.toAnnotationSpec())
149 }
150 }.toList()
151 val finalType = type.copy(nullable = isNullable, annotations = annotations)
152 return abbreviatedType?.let {
153 // This is actually an alias! The "abbreviated type" is the alias and how it's actually
154 // represented in source. So instead - we'll return the abbreviated type but store the "real"
155 // type in tags for reference.
156 val abbreviatedTypeName = it.toTypeName(typeParamResolver)
157 abbreviatedTypeName.copy(
158 tags = mapOf(TypeAliasTag::class to TypeAliasTag(finalType)),
159 )
160 } ?: finalType
161 }
162
toTypeVariableNamenull163 internal fun KmTypeParameter.toTypeVariableName(
164 typeParamResolver: TypeParameterResolver,
165 ): TypeVariableName {
166 val finalVariance = variance.toKModifier()
167 val typeVariableName = TypeVariableName(
168 name = name,
169 bounds = upperBounds.map { it.toTypeName(typeParamResolver) },
170 variance = finalVariance,
171 )
172 val annotations = ClassInspectorUtil.createAnnotations {
173 for (annotation in annotations) {
174 add(annotation.toAnnotationSpec())
175 }
176 }.toList()
177 return typeVariableName.copy(
178 reified = isReified,
179 tags = mapOf(KmTypeParameter::class to this),
180 annotations = annotations,
181 )
182 }
183
toTypeNamenull184 private fun KmFlexibleTypeUpperBound.toTypeName(
185 typeParamResolver: TypeParameterResolver,
186 ): TypeName {
187 // TODO tag typeFlexibilityId somehow?
188 return WildcardTypeName.producerOf(type.toTypeName(typeParamResolver))
189 }
190
191 internal interface TypeParameterResolver {
192 val parametersMap: Map<Int, TypeVariableName>
getnull193 operator fun get(index: Int): TypeVariableName
194
195 companion object {
196 val EMPTY = object : TypeParameterResolver {
197 override val parametersMap: Map<Int, TypeVariableName> = emptyMap()
198
199 override fun get(index: Int): TypeVariableName = throw NoSuchElementException("No type parameters!")
200 }
201 }
202 }
203
toTypeParameterResolvernull204 internal fun List<KmTypeParameter>.toTypeParameterResolver(
205 fallback: TypeParameterResolver? = null,
206 ): TypeParameterResolver {
207 val parametersMap = LinkedHashMap<Int, TypeVariableName>()
208 val typeParamResolver = { id: Int ->
209 parametersMap[id]
210 ?: fallback?.get(id)
211 ?: throw IllegalStateException("No type argument found for $id!")
212 }
213
214 val resolver = object : TypeParameterResolver {
215 override val parametersMap: Map<Int, TypeVariableName> = parametersMap
216
217 override operator fun get(index: Int): TypeVariableName = typeParamResolver(index)
218 }
219
220 // Fill the parametersMap. Need to do sequentially and allow for referencing previously defined params
221 for (typeParam in this) {
222 // Put the simple typevar in first, then it can be referenced in the full toTypeVariable()
223 // replacement later that may add bounds referencing this.
224 parametersMap[typeParam.id] = TypeVariableName(typeParam.name)
225 }
226
227 for (typeParam in this) {
228 // Now replace it with the full version.
229 parametersMap[typeParam.id] = typeParam.toTypeVariableName(resolver)
230 }
231
232 return resolver
233 }
234
o1null235 internal val KM_PROPERTY_COMPARATOR = Comparator<KmProperty> { o1, o2 ->
236 // No need to check fields, getters, etc as properties must have distinct names
237 o1.name.compareTo(o2.name)
238 }
239
o1null240 internal val KM_FUNCTION_COMPARATOR = Comparator<KmFunction> { o1, o2 ->
241 var result = o1.name.compareTo(o2.name)
242 if (result != 0) return@Comparator result
243
244 val signature1 = o1.signature
245 val signature2 = o2.signature
246 if (signature1 != null && signature2 != null) {
247 result = signature1.toString().compareTo(signature2.toString())
248 if (result != 0) return@Comparator result
249 }
250
251 // Fallback - calculate signature
252 val manualSignature1 = o1.computeSignature()
253 val manualSignature2 = o2.computeSignature()
254 manualSignature1.compareTo(manualSignature2)
255 }
256
o1null257 internal val KM_CONSTRUCTOR_COMPARATOR = Comparator<KmConstructor> { o1, o2 ->
258 val signature1 = o1.signature
259 val signature2 = o2.signature
260 if (signature1 != null && signature2 != null) {
261 val result = signature1.toString().compareTo(signature2.toString())
262 if (result != 0) return@Comparator result
263 }
264
265 // Fallback - calculate signature
266 val manualSignature1 = o1.computeSignature()
267 val manualSignature2 = o2.computeSignature()
268 manualSignature1.compareTo(manualSignature2)
269 }
270
271 // Computes a simple signature string good enough for hashing
computeSignaturenull272 private fun KmFunction.computeSignature(): String {
273 return "$name(${valueParameters.joinToString(",") { it.type.simpleName }})${returnType.simpleName}"
274 }
275
computeSignaturenull276 private fun KmConstructor.computeSignature(): String {
277 return "$<init>(${valueParameters.joinToString(",") { it.type.simpleName }})"
278 }
279
280 private val KmType?.simpleName: String get() {
281 if (this == null) return "void"
282 return when (val c = classifier) {
283 is Class -> c.name
284 is TypeParameter -> "Object"
285 is TypeAlias -> c.name
286 }
287 }
288