• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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