1 /*
<lambda>null2  * Copyright (C) 2021 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.classinspectors
17 
18 import com.squareup.kotlinpoet.AnnotationSpec
19 import com.squareup.kotlinpoet.AnnotationSpec.UseSiteTarget.FILE
20 import com.squareup.kotlinpoet.ClassName
21 import com.squareup.kotlinpoet.CodeBlock
22 import com.squareup.kotlinpoet.DelicateKotlinPoetApi
23 import com.squareup.kotlinpoet.TypeName
24 import com.squareup.kotlinpoet.asTypeName
25 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.JAVA_DEPRECATED
26 import com.squareup.kotlinpoet.metadata.classinspectors.ClassInspectorUtil.filterOutNullabilityAnnotations
27 import com.squareup.kotlinpoet.metadata.isDeclaration
28 import com.squareup.kotlinpoet.metadata.readKotlinClassMetadata
29 import com.squareup.kotlinpoet.metadata.specs.ClassData
30 import com.squareup.kotlinpoet.metadata.specs.ClassInspector
31 import com.squareup.kotlinpoet.metadata.specs.ConstructorData
32 import com.squareup.kotlinpoet.metadata.specs.ContainerData
33 import com.squareup.kotlinpoet.metadata.specs.EnumEntryData
34 import com.squareup.kotlinpoet.metadata.specs.FieldData
35 import com.squareup.kotlinpoet.metadata.specs.FileData
36 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier
37 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.TRANSIENT
38 import com.squareup.kotlinpoet.metadata.specs.JvmFieldModifier.VOLATILE
39 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier
40 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.DEFAULT
41 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.STATIC
42 import com.squareup.kotlinpoet.metadata.specs.JvmMethodModifier.SYNCHRONIZED
43 import com.squareup.kotlinpoet.metadata.specs.KM_CONSTRUCTOR_COMPARATOR
44 import com.squareup.kotlinpoet.metadata.specs.KM_FUNCTION_COMPARATOR
45 import com.squareup.kotlinpoet.metadata.specs.KM_PROPERTY_COMPARATOR
46 import com.squareup.kotlinpoet.metadata.specs.MethodData
47 import com.squareup.kotlinpoet.metadata.specs.PropertyData
48 import com.squareup.kotlinpoet.metadata.toKmClass
49 import java.lang.reflect.Constructor
50 import java.lang.reflect.Field
51 import java.lang.reflect.Method
52 import java.lang.reflect.Modifier
53 import java.lang.reflect.Parameter
54 import java.util.TreeMap
55 import java.util.concurrent.ConcurrentHashMap
56 import kotlin.LazyThreadSafetyMode.NONE
57 import kotlin.metadata.ClassKind
58 import kotlin.metadata.KmClass
59 import kotlin.metadata.KmDeclarationContainer
60 import kotlin.metadata.KmPackage
61 import kotlin.metadata.hasAnnotations
62 import kotlin.metadata.hasConstant
63 import kotlin.metadata.isConst
64 import kotlin.metadata.isValue
65 import kotlin.metadata.jvm.JvmFieldSignature
66 import kotlin.metadata.jvm.JvmMethodSignature
67 import kotlin.metadata.jvm.KotlinClassMetadata
68 import kotlin.metadata.jvm.fieldSignature
69 import kotlin.metadata.jvm.getterSignature
70 import kotlin.metadata.jvm.setterSignature
71 import kotlin.metadata.jvm.signature
72 import kotlin.metadata.jvm.syntheticMethodForAnnotations
73 import kotlin.metadata.kind
74 
75 public class ReflectiveClassInspector private constructor(
76   private val lenient: Boolean,
77   private val classLoader: ClassLoader?,
78 ) : ClassInspector {
79 
80   private val classCache = ConcurrentHashMap<ClassName, Optional<Class<*>>>()
81   private val methodCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Method>>()
82   private val constructorCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Constructor<*>>>()
83   private val fieldCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Field>>()
84   private val enumCache = ConcurrentHashMap<Pair<Class<*>, String>, Optional<Enum<*>>>()
85 
86   private fun lookupClass(className: ClassName): Class<*>? {
87     return classCache.getOrPut(className) {
88       try {
89         if (classLoader == null) {
90           Class.forName(className.reflectionName())
91         } else {
92           Class.forName(className.reflectionName(), true, classLoader)
93         }
94       } catch (e: ClassNotFoundException) {
95         null
96       }.toOptional()
97     }.nullableValue
98   }
99 
100   override val supportsNonRuntimeRetainedAnnotations: Boolean = false
101 
102   override fun declarationContainerFor(className: ClassName): KmDeclarationContainer {
103     val clazz = lookupClass(className)
104       ?: error("No type element found for: $className.")
105 
106     val metadata = clazz.getAnnotation(Metadata::class.java)
107     return when (val kotlinClassMetadata = metadata.readKotlinClassMetadata(lenient)) {
108       is KotlinClassMetadata.Class -> kotlinClassMetadata.kmClass
109       is KotlinClassMetadata.FileFacade -> kotlinClassMetadata.kmPackage
110       else -> TODO("Not implemented yet: ${kotlinClassMetadata.javaClass.simpleName}")
111     }
112   }
113 
114   override fun isInterface(className: ClassName): Boolean {
115     if (className in ClassInspectorUtil.KOTLIN_INTRINSIC_INTERFACES) {
116       return true
117     }
118     return lookupClass(className)?.isInterface ?: false
119   }
120 
121   private fun Class<*>.lookupField(fieldSignature: JvmFieldSignature): Field? {
122     return try {
123       val signatureString = fieldSignature.toString()
124       fieldCache.getOrPut(this to signatureString) {
125         declaredFields
126           .asSequence()
127           .onEach { it.isAccessible = true }
128           .find { signatureString == it.jvmFieldSignature }.toOptional()
129       }.nullableValue
130     } catch (e: ClassNotFoundException) {
131       null
132     }
133   }
134 
135   private fun Class<*>.lookupMethod(
136     methodSignature: JvmMethodSignature,
137   ): Method? {
138     val signatureString = methodSignature.toString()
139     return methodCache.getOrPut(this to signatureString) {
140       declaredMethods
141         .asSequence()
142         .onEach { it.isAccessible = true }
143         .find { signatureString == it.jvmMethodSignature }.toOptional()
144     }.nullableValue
145   }
146 
147   private fun Class<*>.lookupConstructor(
148     constructorSignature: JvmMethodSignature,
149   ): Constructor<*>? {
150     val signatureString = constructorSignature.toString()
151     return constructorCache.getOrPut(this to signatureString) {
152       declaredConstructors
153         .asSequence()
154         .onEach { it.isAccessible = true }
155         .find { signatureString == it.jvmMethodSignature }.toOptional()
156     }.nullableValue
157   }
158 
159   private fun Field.jvmModifiers(): Set<JvmFieldModifier> {
160     return mutableSetOf<JvmFieldModifier>().apply {
161       if (Modifier.isTransient(modifiers)) {
162         add(TRANSIENT)
163       }
164       if (Modifier.isVolatile(modifiers)) {
165         add(VOLATILE)
166       }
167       if (Modifier.isStatic(modifiers)) {
168         add(JvmFieldModifier.STATIC)
169       }
170     }
171   }
172 
173   @OptIn(DelicateKotlinPoetApi::class)
174   private fun Field.annotationSpecs(): List<AnnotationSpec> {
175     return filterOutNullabilityAnnotations(
176       declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
177     )
178   }
179 
180   @OptIn(DelicateKotlinPoetApi::class)
181   private fun Constructor<*>.annotationSpecs(): List<AnnotationSpec> {
182     return filterOutNullabilityAnnotations(
183       declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, true) },
184     )
185   }
186 
187   private fun Method.jvmModifiers(): Set<JvmMethodModifier> {
188     return methodJvmModifiers(modifiers, isDefault)
189   }
190 
191   private fun Constructor<*>.jvmModifiers(): Set<JvmMethodModifier> {
192     return methodJvmModifiers(modifiers, false)
193   }
194 
195   private fun methodJvmModifiers(modifiers: Int, isDefault: Boolean): Set<JvmMethodModifier> {
196     val jvmMethodModifiers = mutableSetOf<JvmMethodModifier>()
197     if (Modifier.isSynchronized(modifiers)) {
198       jvmMethodModifiers += SYNCHRONIZED
199     }
200     if (Modifier.isStatic(modifiers)) {
201       jvmMethodModifiers += STATIC
202     }
203     if (isDefault) {
204       jvmMethodModifiers += DEFAULT
205     }
206     return jvmMethodModifiers
207   }
208 
209   @OptIn(DelicateKotlinPoetApi::class)
210   private fun Method.annotationSpecs(): List<AnnotationSpec> {
211     return filterOutNullabilityAnnotations(
212       declaredAnnotations.orEmpty().map { AnnotationSpec.get(it, includeDefaultValues = true) },
213     )
214   }
215 
216   @OptIn(DelicateKotlinPoetApi::class)
217   private fun Parameter.annotationSpecs(): List<AnnotationSpec> {
218     return filterOutNullabilityAnnotations(
219       declaredAnnotations.map { AnnotationSpec.get(it, includeDefaultValues = true) },
220     )
221   }
222 
223   private fun Method.exceptionTypeNames(): List<TypeName> {
224     return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
225   }
226 
227   private fun Constructor<*>.exceptionTypeNames(): List<TypeName> {
228     return exceptionTypes.orEmpty().mapTo(mutableListOf()) { it.asTypeName() }
229   }
230 
231   override fun enumEntry(enumClassName: ClassName, memberName: String): EnumEntryData {
232     val clazz = lookupClass(enumClassName)
233       ?: error("No class found for: $enumClassName.")
234     check(clazz.isEnum) {
235       "Class must be an enum but isn't: $clazz"
236     }
237     val enumEntry = enumCache.getOrPut(clazz to memberName) {
238       clazz.enumConstants
239         .asSequence()
240         .map { it as Enum<*> }
241         .find { it.name == memberName }
242         .toOptional()
243     }.nullableValue
244     checkNotNull(enumEntry) {
245       "Could not find $memberName on $enumClassName"
246     }
247     return EnumEntryData(
248       declarationContainer = if (enumEntry.javaClass == clazz) {
249         // For simple enums with no class bodies, the entry class will be the same as the original
250         // class.
251         null
252       } else {
253         enumEntry.javaClass.getAnnotation(Metadata::class.java)?.toKmClass(lenient)
254       },
255       annotations = clazz.getField(enumEntry.name).annotationSpecs(),
256     )
257   }
258 
259   private fun Field.constantValue(): CodeBlock? {
260     if (!Modifier.isStatic(modifiers)) {
261       return null
262     }
263     return get(null) // Constant means we can do a static get on it.
264       .let(ClassInspectorUtil::codeLiteralOf)
265   }
266 
267   private fun JvmMethodSignature.isOverriddenIn(clazz: Class<*>): Boolean {
268     val signatureString = toString()
269     val classPackage = clazz.`package`.name
270     val interfaceMethods = clazz.interfaces.asSequence()
271       .flatMap { it.methods.asSequence() }
272     val superClassMethods = clazz.superclass?.methods.orEmpty().asSequence()
273     return interfaceMethods.plus(superClassMethods)
274       .filterNot { Modifier.isFinal(it.modifiers) }
275       .filterNot { Modifier.isStatic(it.modifiers) }
276       .filterNot { Modifier.isPrivate(it.modifiers) }
277       .filter {
278         Modifier.isPublic(it.modifiers) ||
279           Modifier.isProtected(it.modifiers) ||
280           // Package private
281           it.declaringClass.`package`.name == classPackage
282       }
283       .map { it.jvmMethodSignature }
284       .any { it == signatureString }
285   }
286 
287   override fun methodExists(className: ClassName, methodSignature: JvmMethodSignature): Boolean {
288     return lookupClass(className)?.lookupMethod(methodSignature) != null
289   }
290 
291   @OptIn(DelicateKotlinPoetApi::class)
292   override fun containerData(
293     declarationContainer: KmDeclarationContainer,
294     className: ClassName,
295     parentClassName: ClassName?,
296   ): ContainerData {
297     val targetClass = lookupClass(className) ?: error("No class found for: $className.")
298     val isCompanionObject: Boolean = when (declarationContainer) {
299       is KmClass -> {
300         declarationContainer.kind == ClassKind.COMPANION_OBJECT
301       }
302       is KmPackage -> {
303         false
304       }
305       else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
306     }
307 
308     // Should only be called if parentName has been null-checked
309     val classIfCompanion by lazy(NONE) {
310       if (isCompanionObject && parentClassName != null) {
311         lookupClass(parentClassName)
312           ?: error("No class found for: $parentClassName.")
313       } else {
314         targetClass
315       }
316     }
317 
318     val propertyData = declarationContainer.properties
319       .asSequence()
320       .filter { it.kind.isDeclaration }
321       .associateWithTo(TreeMap(KM_PROPERTY_COMPARATOR)) { property ->
322         val isJvmField = ClassInspectorUtil.computeIsJvmField(
323           property = property,
324           classInspector = this,
325           isCompanionObject = isCompanionObject,
326           hasGetter = property.getterSignature != null,
327           hasSetter = property.setterSignature != null,
328           hasField = property.fieldSignature != null,
329         )
330 
331         val fieldData = property.fieldSignature?.let { fieldSignature ->
332           // Check the field in the parent first. For const/static/jvmField elements, these only
333           // exist in the parent and we want to check that if necessary to avoid looking up a
334           // non-existent field in the companion.
335           val parentModifiers = if (isCompanionObject && parentClassName != null) {
336             classIfCompanion.lookupField(fieldSignature)?.jvmModifiers().orEmpty()
337           } else {
338             emptySet()
339           }
340 
341           val isStatic = JvmFieldModifier.STATIC in parentModifiers
342 
343           // TODO we looked up field once, let's reuse it
344           val classForOriginalField = targetClass.takeUnless {
345             isCompanionObject &&
346               (property.isConst || isJvmField || isStatic)
347           } ?: classIfCompanion
348 
349           val field = classForOriginalField.lookupField(fieldSignature)
350             ?: error("No field $fieldSignature found in $classForOriginalField.")
351           val constant = if (property.hasConstant) {
352             val fieldWithConstant = classIfCompanion.takeIf { it != targetClass }?.let {
353               if (it.isInterface) {
354                 field
355               } else {
356                 // const properties are relocated to the enclosing class
357                 it.lookupField(fieldSignature)
358                   ?: error("No field $fieldSignature found in $it.")
359               }
360             } ?: field
361             fieldWithConstant.constantValue()
362           } else {
363             null
364           }
365 
366           val jvmModifiers = field.jvmModifiers() + parentModifiers
367 
368           // For static, const, or JvmField fields in a companion object, the companion
369           // object's field is marked as synthetic to hide it from Java, but in this case
370           // it's a false positive for this check in kotlin.
371           val isSynthetic = field.isSynthetic &&
372             !(
373               isCompanionObject &&
374                 (property.isConst || isJvmField || JvmFieldModifier.STATIC in jvmModifiers)
375               )
376 
377           FieldData(
378             annotations = field.annotationSpecs(),
379             isSynthetic = isSynthetic,
380             jvmModifiers = jvmModifiers.filterNotTo(mutableSetOf()) {
381               // JvmField companion objects don't need JvmStatic, it's implicit
382               isCompanionObject && isJvmField && it == JvmFieldModifier.STATIC
383             },
384             constant = constant,
385           )
386         }
387 
388         val getterData = property.getterSignature?.let { getterSignature ->
389           val method = classIfCompanion.lookupMethod(getterSignature)
390           method?.methodData(
391             clazz = targetClass,
392             signature = getterSignature,
393             hasAnnotations = property.getter.hasAnnotations,
394             jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
395               ?.lookupMethod(getterSignature) ?: method,
396           )
397             ?: error("No getter method $getterSignature found in $classIfCompanion.")
398         }
399 
400         val setterData = property.setterSignature?.let { setterSignature ->
401           val method = classIfCompanion.lookupMethod(setterSignature)
402           method?.methodData(
403             clazz = targetClass,
404             signature = setterSignature,
405             hasAnnotations = property.setter?.hasAnnotations ?: false,
406             jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }
407               ?.lookupMethod(setterSignature) ?: method,
408             knownIsOverride = getterData?.isOverride,
409           )
410             ?: error("No setter method $setterSignature found in $classIfCompanion.")
411         }
412 
413         val annotations = mutableListOf<AnnotationSpec>()
414         if (property.hasAnnotations) {
415           property.syntheticMethodForAnnotations?.let { annotationsHolderSignature ->
416             targetClass.lookupMethod(annotationsHolderSignature)?.let { method ->
417               annotations += method.annotationSpecs()
418                 // Cover for https://github.com/square/kotlinpoet/issues/1046
419                 .filterNot { it.typeName == JAVA_DEPRECATED }
420             }
421           }
422         }
423 
424         PropertyData(
425           annotations = annotations,
426           fieldData = fieldData,
427           getterData = getterData,
428           setterData = setterData,
429           isJvmField = isJvmField,
430         )
431       }
432 
433     val methodData = declarationContainer.functions
434       .associateWithTo(TreeMap(KM_FUNCTION_COMPARATOR)) { kmFunction ->
435         val signature = kmFunction.signature
436         if (signature != null) {
437           val method = targetClass.lookupMethod(signature)
438           method?.methodData(
439             clazz = targetClass,
440             signature = signature,
441             hasAnnotations = kmFunction.hasAnnotations,
442             jvmInformationMethod = classIfCompanion.takeIf { it != targetClass }?.lookupMethod(signature)
443               ?: method,
444           )
445             ?: error("No method $signature found in $targetClass.")
446         } else {
447           MethodData.EMPTY
448         }
449       }
450 
451     when (declarationContainer) {
452       is KmClass -> {
453         val classAnnotations = if (declarationContainer.hasAnnotations) {
454           ClassInspectorUtil.createAnnotations {
455             addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
456           }
457         } else {
458           emptyList()
459         }
460         val constructorData = declarationContainer.constructors
461           .associateWithTo(TreeMap(KM_CONSTRUCTOR_COMPARATOR)) { kmConstructor ->
462             if (declarationContainer.kind == ClassKind.ANNOTATION_CLASS || declarationContainer.isValue) {
463               //
464               // Annotations are interfaces in reflection, but kotlin metadata will still report a
465               // constructor signature
466               //
467               // Inline classes have no constructors at runtime
468               //
469               return@associateWithTo ConstructorData.EMPTY
470             }
471             val signature = kmConstructor.signature
472             if (signature != null) {
473               val constructor = targetClass.lookupConstructor(signature)
474                 ?: error("No constructor $signature found in $targetClass.")
475               ConstructorData(
476                 annotations = if (kmConstructor.hasAnnotations) {
477                   constructor.annotationSpecs()
478                 } else {
479                   emptyList()
480                 },
481                 parameterAnnotations = constructor.parameters.indexedAnnotationSpecs(),
482                 isSynthetic = constructor.isSynthetic,
483                 jvmModifiers = constructor.jvmModifiers(),
484                 exceptions = constructor.exceptionTypeNames(),
485               )
486             } else {
487               ConstructorData.EMPTY
488             }
489           }
490         return ClassData(
491           declarationContainer = declarationContainer,
492           className = className,
493           annotations = classAnnotations,
494           properties = propertyData,
495           constructors = constructorData,
496           methods = methodData,
497         )
498       }
499       is KmPackage -> {
500         // There's no flag for checking if there are annotations, so we just eagerly check in this
501         // case. All annotations on this class are file: site targets in source. This does not
502         // include @JvmName since it does not have RUNTIME retention. In practice this doesn't
503         // really matter, but it does mean we can't know for certain if the file should be called
504         // FooKt.kt or Foo.kt.
505         val fileAnnotations = ClassInspectorUtil.createAnnotations(FILE) {
506           addAll(targetClass.annotations.map { AnnotationSpec.get(it, includeDefaultValues = true) })
507         }
508         return FileData(
509           declarationContainer = declarationContainer,
510           annotations = fileAnnotations,
511           properties = propertyData,
512           methods = methodData,
513           className = className,
514         )
515       }
516       else -> TODO("Not implemented yet: ${declarationContainer.javaClass.simpleName}")
517     }
518   }
519 
520   private fun Array<Parameter>.indexedAnnotationSpecs(): Map<Int, Collection<AnnotationSpec>> {
521     return withIndex().associate { (index, parameter) ->
522       index to ClassInspectorUtil.createAnnotations { addAll(parameter.annotationSpecs()) }
523     }
524   }
525 
526   private fun Method.methodData(
527     clazz: Class<*>,
528     signature: JvmMethodSignature,
529     hasAnnotations: Boolean,
530     jvmInformationMethod: Method = this,
531     knownIsOverride: Boolean? = null,
532   ): MethodData {
533     return MethodData(
534       annotations = if (hasAnnotations) annotationSpecs() else emptyList(),
535       parameterAnnotations = parameters.indexedAnnotationSpecs(),
536       isSynthetic = isSynthetic,
537       jvmModifiers = jvmInformationMethod.jvmModifiers(),
538       isOverride = knownIsOverride ?: signature.isOverriddenIn(clazz),
539       exceptions = exceptionTypeNames(),
540     )
541   }
542 
543   public companion object {
544     /**
545      * @param lenient see docs on [KotlinClassMetadata.readStrict] and [KotlinClassMetadata.readLenient] for more details.
546      */
547     @JvmStatic
548     public fun create(lenient: Boolean, classLoader: ClassLoader? = null): ClassInspector {
549       return ReflectiveClassInspector(lenient, classLoader)
550     }
551 
552     private val Class<*>.descriptor: String
553       get() {
554         return when {
555           isPrimitive -> when (kotlin) {
556             Byte::class -> "B"
557             Char::class -> "C"
558             Double::class -> "D"
559             Float::class -> "F"
560             Int::class -> "I"
561             Long::class -> "J"
562             Short::class -> "S"
563             Boolean::class -> "Z"
564             Void::class -> "V"
565             else -> throw RuntimeException("Unrecognized primitive $this")
566           }
567           isArray -> name.replace('.', '/')
568           else -> "L$name;".replace('.', '/')
569         }
570       }
571 
572     private val Method.descriptor: String
573       get() = parameterTypes.joinToString(
574         separator = "",
575         prefix = "(",
576         postfix = ")${returnType.descriptor}",
577       ) { it.descriptor }
578 
579     /**
580      * Returns the JVM signature in the form "$Name$MethodDescriptor", for example: `equals(Ljava/lang/Object;)Z`.
581      *
582      * Useful for comparing with [JvmMethodSignature].
583      *
584      * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
585      */
586     private val Method.jvmMethodSignature: String get() = "$name$descriptor"
587 
588     private val Constructor<*>.descriptor: String
589       get() = parameterTypes.joinToString(separator = "", prefix = "(", postfix = ")V") { it.descriptor }
590 
591     /**
592      * Returns the JVM signature in the form "<init>$MethodDescriptor", for example: `"<init>(Ljava/lang/Object;)V")`.
593      *
594      * Useful for comparing with [JvmMethodSignature].
595      *
596      * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
597      */
598     private val Constructor<*>.jvmMethodSignature: String get() = "<init>$descriptor"
599 
600     /**
601      * Returns the JVM signature in the form "$Name:$FieldDescriptor", for example: `"value:Ljava/lang/String;"`.
602      *
603      * Useful for comparing with [JvmFieldSignature].
604      *
605      * For reference, see the [JVM specification, section 4.3](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3).
606      */
607     private val Field.jvmFieldSignature: String get() = "$name:${type.descriptor}"
608   }
609 }
610