1 /*
<lambda>null2 * Copyright 2020 Google LLC
3 * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package com.google.devtools.ksp
18
19 import com.google.devtools.ksp.processing.Resolver
20 import com.google.devtools.ksp.symbol.*
21 import com.google.devtools.ksp.visitor.KSValidateVisitor
22 import java.lang.reflect.InvocationHandler
23 import java.lang.reflect.Method
24 import java.lang.reflect.Proxy
25 import java.util.concurrent.ConcurrentHashMap
26 import kotlin.reflect.KClass
27
28 /**
29 * Try to resolve the [KSClassDeclaration] for a class using its fully qualified name.
30 *
31 * @param T The class to resolve a [KSClassDeclaration] for.
32 * @return Resolved [KSClassDeclaration] if found, `null` otherwise.
33 *
34 * @see [Resolver.getClassDeclarationByName]
35 */
36 inline fun <reified T> Resolver.getClassDeclarationByName(): KSClassDeclaration? {
37 return T::class.qualifiedName?.let { fqcn ->
38 getClassDeclarationByName(getKSNameFromString(fqcn))
39 }
40 }
41
42 /**
43 * Find a class in the compilation classpath for the given name.
44 *
45 * @param name fully qualified name of the class to be loaded; using '.' as separator.
46 * @return a KSClassDeclaration, or null if not found.
47 */
getClassDeclarationByNamenull48 fun Resolver.getClassDeclarationByName(name: String): KSClassDeclaration? =
49 getClassDeclarationByName(getKSNameFromString(name))
50
51 /**
52 * Find functions in the compilation classpath for the given name.
53 *
54 * @param name fully qualified name of the function to be loaded; using '.' as separator.
55 * @param includeTopLevel a boolean value indicate if top level functions should be searched. Default false. Note if top level functions are included, this operation can be expensive.
56 * @return a Sequence of KSFunctionDeclaration.
57 */
58 fun Resolver.getFunctionDeclarationsByName(
59 name: String,
60 includeTopLevel: Boolean = false
61 ): Sequence<KSFunctionDeclaration> = getFunctionDeclarationsByName(getKSNameFromString(name), includeTopLevel)
62
63 /**
64 * Find a property in the compilation classpath for the given name.
65 *
66 * @param name fully qualified name of the property to be loaded; using '.' as separator.
67 * @param includeTopLevel a boolean value indicate if top level properties should be searched. Default false. Note if top level properties are included, this operation can be expensive.
68 * @return a KSPropertyDeclaration, or null if not found.
69 */
70 fun Resolver.getPropertyDeclarationByName(name: String, includeTopLevel: Boolean = false): KSPropertyDeclaration? =
71 getPropertyDeclarationByName(getKSNameFromString(name), includeTopLevel)
72
73 /**
74 * Find the containing file of a KSNode.
75 * @return KSFile if the given KSNode has a containing file.
76 * exmample of symbols without a containing file: symbols from class files, synthetic symbols craeted by user.
77 */
78 val KSNode.containingFile: KSFile?
79 get() {
80 var parent = this.parent
81 while (parent != null && parent !is KSFile) {
82 parent = parent.parent
83 }
84 return parent as? KSFile?
85 }
86
87 /**
88 * Get functions directly declared inside the class declaration.
89 *
90 * What are included: member functions, constructors, extension functions declared inside it, etc.
91 * What are NOT included: inherited functions, extension functions declared outside it.
92 */
getDeclaredFunctionsnull93 fun KSClassDeclaration.getDeclaredFunctions(): Sequence<KSFunctionDeclaration> {
94 return this.declarations.filterIsInstance<KSFunctionDeclaration>()
95 }
96
97 /**
98 * Get properties directly declared inside the class declaration.
99 *
100 * What are included: member properties, extension properties declared inside it, etc.
101 * What are NOT included: inherited properties, extension properties declared outside it.
102 */
getDeclaredPropertiesnull103 fun KSClassDeclaration.getDeclaredProperties(): Sequence<KSPropertyDeclaration> {
104 return this.declarations.filterIsInstance<KSPropertyDeclaration>()
105 }
106
getConstructorsnull107 fun KSClassDeclaration.getConstructors(): Sequence<KSFunctionDeclaration> {
108 return getDeclaredFunctions().filter {
109 it.isConstructor()
110 }
111 }
112
113 /**
114 * Check whether this is a local declaration, or namely, declared in a function.
115 */
KSDeclarationnull116 fun KSDeclaration.isLocal(): Boolean {
117 return this.parentDeclaration != null && this.parentDeclaration !is KSClassDeclaration
118 }
119
120 /**
121 * Perform a validation on a given symbol to check if all interested types in symbols enclosed scope are valid, i.e. resolvable.
122 * @param predicate: A lambda for filtering interested symbols for performance purpose. Default checks all.
123 */
KSNodenull124 fun KSNode.validate(predicate: (KSNode?, KSNode) -> Boolean = { _, _ -> true }): Boolean {
125 return this.accept(KSValidateVisitor(predicate), null)
126 }
127
128 /**
129 * Find the KSClassDeclaration that the alias points to recursively.
130 */
KSTypeAliasnull131 fun KSTypeAlias.findActualType(): KSClassDeclaration {
132 val resolvedType = this.type.resolve().declaration
133 return if (resolvedType is KSTypeAlias) {
134 resolvedType.findActualType()
135 } else {
136 resolvedType as KSClassDeclaration
137 }
138 }
139
140 /**
141 * Determine [Visibility] of a [KSDeclaration].
142 */
KSDeclarationnull143 fun KSDeclaration.getVisibility(): Visibility {
144 return when {
145 this.modifiers.contains(Modifier.PUBLIC) -> Visibility.PUBLIC
146 this.modifiers.contains(Modifier.OVERRIDE) -> {
147 when (this) {
148 is KSFunctionDeclaration -> this.findOverridee()?.getVisibility()
149 is KSPropertyDeclaration -> this.findOverridee()?.getVisibility()
150 else -> null
151 } ?: Visibility.PUBLIC
152 }
153 this.isLocal() -> Visibility.LOCAL
154 this.modifiers.contains(Modifier.PRIVATE) -> Visibility.PRIVATE
155 this.modifiers.contains(Modifier.PROTECTED) ||
156 this.modifiers.contains(Modifier.OVERRIDE) -> Visibility.PROTECTED
157 this.modifiers.contains(Modifier.INTERNAL) -> Visibility.INTERNAL
158 else -> if (this.origin != Origin.JAVA && this.origin != Origin.JAVA_LIB)
159 Visibility.PUBLIC else Visibility.JAVA_PACKAGE
160 }
161 }
162
163 /**
164 * get all super types for a class declaration
165 * Calling [getAllSuperTypes] requires type resolution therefore is expensive and should be avoided if possible.
166 */
KSClassDeclarationnull167 fun KSClassDeclaration.getAllSuperTypes(): Sequence<KSType> {
168
169 fun KSTypeParameter.getTypesUpperBound(): Sequence<KSClassDeclaration> =
170 this.bounds.asSequence().flatMap {
171 when (val resolvedDeclaration = it.resolve().declaration) {
172 is KSClassDeclaration -> sequenceOf(resolvedDeclaration)
173 is KSTypeAlias -> sequenceOf(resolvedDeclaration.findActualType())
174 is KSTypeParameter -> resolvedDeclaration.getTypesUpperBound()
175 else -> throw IllegalStateException("unhandled type parameter bound, $ExceptionMessage")
176 }
177 }
178
179 return this.superTypes
180 .asSequence()
181 .map { it.resolve() }
182 .plus(
183 this.superTypes
184 .asSequence()
185 .mapNotNull { it.resolve().declaration }
186 .flatMap {
187 when (it) {
188 is KSClassDeclaration -> it.getAllSuperTypes()
189 is KSTypeAlias -> it.findActualType().getAllSuperTypes()
190 is KSTypeParameter -> it.getTypesUpperBound().flatMap { it.getAllSuperTypes() }
191 else -> throw IllegalStateException("unhandled super type kind, $ExceptionMessage")
192 }
193 }
194 )
195 .distinct()
196 }
197
KSClassDeclarationnull198 fun KSClassDeclaration.isAbstract() =
199 this.classKind == ClassKind.INTERFACE || this.modifiers.contains(Modifier.ABSTRACT)
200
201 fun KSPropertyDeclaration.isAbstract(): Boolean {
202 if (modifiers.contains(Modifier.ABSTRACT)) {
203 return true
204 }
205 val parentClass = parentDeclaration as? KSClassDeclaration ?: return false
206 if (parentClass.classKind != ClassKind.INTERFACE) return false
207 // this is abstract if it does not have setter/getter or setter/getter have abstract modifiers
208 return (getter?.modifiers?.contains(Modifier.ABSTRACT) ?: true) &&
209 (setter?.modifiers?.contains(Modifier.ABSTRACT) ?: true)
210 }
211
isOpennull212 fun KSDeclaration.isOpen() = !this.isLocal() &&
213 (
214 (this as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE ||
215 this.modifiers.contains(Modifier.OVERRIDE) ||
216 this.modifiers.contains(Modifier.ABSTRACT) ||
217 this.modifiers.contains(Modifier.OPEN) ||
218 this.modifiers.contains(Modifier.SEALED) ||
219 (
220 this !is KSClassDeclaration &&
221 (this.parentDeclaration as? KSClassDeclaration)?.classKind == ClassKind.INTERFACE
222 ) ||
223 (!this.modifiers.contains(Modifier.FINAL) && this.origin == Origin.JAVA)
224 )
225
226 fun KSDeclaration.isPublic() = this.getVisibility() == Visibility.PUBLIC
227
228 fun KSDeclaration.isProtected() = this.getVisibility() == Visibility.PROTECTED
229
230 fun KSDeclaration.isInternal() = this.modifiers.contains(Modifier.INTERNAL)
231
232 fun KSDeclaration.isPrivate() = this.modifiers.contains(Modifier.PRIVATE)
233
234 fun KSDeclaration.isJavaPackagePrivate() = this.getVisibility() == Visibility.JAVA_PACKAGE
235
236 fun KSDeclaration.closestClassDeclaration(): KSClassDeclaration? {
237 return if (this is KSClassDeclaration) {
238 this
239 } else {
240 this.parentDeclaration?.closestClassDeclaration()
241 }
242 }
243
244 // TODO: cross module visibility is not handled
isVisibleFromnull245 fun KSDeclaration.isVisibleFrom(other: KSDeclaration): Boolean {
246 fun KSDeclaration.isSamePackage(other: KSDeclaration): Boolean =
247 this.packageName == other.packageName
248
249 // lexical scope for local declaration.
250 fun KSDeclaration.parentDeclarationsForLocal(): List<KSDeclaration> {
251 val parents = mutableListOf<KSDeclaration>()
252
253 var parentDeclaration = this.parentDeclaration!!
254
255 while (parentDeclaration.isLocal()) {
256 parents.add(parentDeclaration)
257 parentDeclaration = parentDeclaration.parentDeclaration!!
258 }
259
260 parents.add(parentDeclaration)
261
262 return parents
263 }
264
265 fun KSDeclaration.isVisibleInPrivate(other: KSDeclaration) =
266 (other.isLocal() && other.parentDeclarationsForLocal().contains(this.parentDeclaration)) ||
267 this.parentDeclaration == other.parentDeclaration ||
268 this.parentDeclaration == other || (
269 this.parentDeclaration == null &&
270 other.parentDeclaration == null &&
271 this.containingFile == other.containingFile
272 )
273
274 return when {
275 // locals are limited to lexical scope
276 this.isLocal() -> this.parentDeclarationsForLocal().contains(other)
277 // file visibility or member
278 // TODO: address nested class.
279 this.isPrivate() -> this.isVisibleInPrivate(other)
280 this.isPublic() -> true
281 this.isInternal() && other.containingFile != null && this.containingFile != null -> true
282 this.isJavaPackagePrivate() -> this.isSamePackage(other)
283 this.isProtected() -> this.isVisibleInPrivate(other) || this.isSamePackage(other) ||
284 other.closestClassDeclaration()?.let {
285 this.closestClassDeclaration()!!.asStarProjectedType().isAssignableFrom(it.asStarProjectedType())
286 } ?: false
287 else -> false
288 }
289 }
290
291 /**
292 * Returns `true` if this is a constructor function.
293 */
KSFunctionDeclarationnull294 fun KSFunctionDeclaration.isConstructor() = this.simpleName.asString() == "<init>"
295
296 const val ExceptionMessage = "please file a bug at https://github.com/google/ksp/issues/new"
297
298 val KSType.outerType: KSType?
299 get() {
300 if (Modifier.INNER !in declaration.modifiers)
301 return null
302 val outerDecl = declaration.parentDeclaration as? KSClassDeclaration ?: return null
303 return outerDecl.asType(arguments.subList(declaration.typeParameters.size, arguments.size))
304 }
305
306 val KSType.innerArguments: List<KSTypeArgument>
307 get() = arguments.subList(0, declaration.typeParameters.size)
308
309 @KspExperimental
Resolvernull310 fun Resolver.getKotlinClassByName(name: KSName): KSClassDeclaration? {
311 val kotlinName = mapJavaNameToKotlin(name) ?: name
312 return getClassDeclarationByName(kotlinName)
313 }
314
315 @KspExperimental
Resolvernull316 fun Resolver.getKotlinClassByName(name: String): KSClassDeclaration? =
317 getKotlinClassByName(getKSNameFromString(name))
318
319 @KspExperimental
320 fun Resolver.getJavaClassByName(name: KSName): KSClassDeclaration? {
321 val javaName = mapKotlinNameToJava(name) ?: name
322 return getClassDeclarationByName(javaName)
323 }
324
325 @KspExperimental
getJavaClassByNamenull326 fun Resolver.getJavaClassByName(name: String): KSClassDeclaration? =
327 getJavaClassByName(getKSNameFromString(name))
328
329 @KspExperimental
330 fun <T : Annotation> KSAnnotated.getAnnotationsByType(annotationKClass: KClass<T>): Sequence<T> {
331 return this.annotations.filter {
332 it.shortName.getShortName() == annotationKClass.simpleName && it.annotationType.resolve().declaration
333 .qualifiedName?.asString() == annotationKClass.qualifiedName
334 }.map { it.toAnnotation(annotationKClass.java) }
335 }
336
337 @KspExperimental
isAnnotationPresentnull338 fun <T : Annotation> KSAnnotated.isAnnotationPresent(annotationKClass: KClass<T>): Boolean =
339 getAnnotationsByType(annotationKClass).firstOrNull() != null
340
341 @KspExperimental
342 @Suppress("UNCHECKED_CAST")
343 private fun <T : Annotation> KSAnnotation.toAnnotation(annotationClass: Class<T>): T {
344 return Proxy.newProxyInstance(
345 annotationClass.classLoader,
346 arrayOf(annotationClass),
347 createInvocationHandler(annotationClass)
348 ) as T
349 }
350
351 @KspExperimental
352 @Suppress("TooGenericExceptionCaught")
KSAnnotationnull353 private fun KSAnnotation.createInvocationHandler(clazz: Class<*>): InvocationHandler {
354 val cache = ConcurrentHashMap<Pair<Class<*>, Any>, Any>(arguments.size)
355 return InvocationHandler { proxy, method, _ ->
356 if (method.name == "toString" && arguments.none { it.name?.asString() == "toString" }) {
357 clazz.canonicalName +
358 arguments.map { argument: KSValueArgument ->
359 // handles default values for enums otherwise returns null
360 val methodName = argument.name?.asString()
361 val value = proxy.javaClass.methods.find { m -> m.name == methodName }?.invoke(proxy)
362 "$methodName=$value"
363 }.toList()
364 } else {
365 val argument = arguments.first { it.name?.asString() == method.name }
366 when (val result = argument.value ?: method.defaultValue) {
367 is Proxy -> result
368 is List<*> -> {
369 val value = { result.asArray(method, clazz) }
370 cache.getOrPut(Pair(method.returnType, result), value)
371 }
372 else -> {
373 when {
374 method.returnType.isEnum -> {
375 val value = { result.asEnum(method.returnType) }
376 cache.getOrPut(Pair(method.returnType, result), value)
377 }
378 method.returnType.isAnnotation -> {
379 val value = { (result as KSAnnotation).asAnnotation(method.returnType) }
380 cache.getOrPut(Pair(method.returnType, result), value)
381 }
382 method.returnType.name == "java.lang.Class" -> {
383 cache.getOrPut(Pair(method.returnType, result)) {
384 when (result) {
385 is KSType -> result.asClass(clazz)
386 // Handles com.intellij.psi.impl.source.PsiImmediateClassType using reflection
387 // since api doesn't contain a reference to this
388 else -> Class.forName(
389 result.javaClass.methods
390 .first { it.name == "getCanonicalText" }
391 .invoke(result, false) as String
392 )
393 }
394 }
395 }
396 method.returnType.name == "byte" -> {
397 val value = { result.asByte() }
398 cache.getOrPut(Pair(method.returnType, result), value)
399 }
400 method.returnType.name == "short" -> {
401 val value = { result.asShort() }
402 cache.getOrPut(Pair(method.returnType, result), value)
403 }
404 method.returnType.name == "long" -> {
405 val value = { result.asLong() }
406 cache.getOrPut(Pair(method.returnType, result), value)
407 }
408 method.returnType.name == "float" -> {
409 val value = { result.asFloat() }
410 cache.getOrPut(Pair(method.returnType, result), value)
411 }
412 method.returnType.name == "double" -> {
413 val value = { result.asDouble() }
414 cache.getOrPut(Pair(method.returnType, result), value)
415 }
416 else -> result // original value
417 }
418 }
419 }
420 }
421 }
422 }
423
424 @KspExperimental
425 @Suppress("UNCHECKED_CAST")
KSAnnotationnull426 private fun KSAnnotation.asAnnotation(
427 annotationInterface: Class<*>,
428 ): Any {
429 return Proxy.newProxyInstance(
430 annotationInterface.classLoader, arrayOf(annotationInterface),
431 this.createInvocationHandler(annotationInterface)
432 ) as Proxy
433 }
434
435 @KspExperimental
436 @Suppress("UNCHECKED_CAST")
asArraynull437 private fun List<*>.asArray(method: Method, proxyClass: Class<*>) =
438 when (method.returnType.componentType.name) {
439 "boolean" -> (this as List<Boolean>).toBooleanArray()
440 "byte" -> (this as List<Byte>).toByteArray()
441 "short" -> (this as List<Short>).toShortArray()
442 "char" -> (this as List<Char>).toCharArray()
443 "double" -> (this as List<Double>).toDoubleArray()
444 "float" -> (this as List<Float>).toFloatArray()
445 "int" -> (this as List<Int>).toIntArray()
446 "long" -> (this as List<Long>).toLongArray()
447 "java.lang.Class" -> (this as List<KSType>).asClasses(proxyClass).toTypedArray()
448 "java.lang.String" -> (this as List<String>).toTypedArray()
449 else -> { // arrays of enums or annotations
450 when {
451 method.returnType.componentType.isEnum -> {
452 this.toArray(method) { result -> result.asEnum(method.returnType.componentType) }
453 }
454 method.returnType.componentType.isAnnotation -> {
455 this.toArray(method) { result ->
456 (result as KSAnnotation).asAnnotation(method.returnType.componentType)
457 }
458 }
459 else -> throw IllegalStateException("Unable to process type ${method.returnType.componentType.name}")
460 }
461 }
462 }
463
464 @Suppress("UNCHECKED_CAST")
toArraynull465 private fun List<*>.toArray(method: Method, valueProvider: (Any) -> Any): Array<Any?> {
466 val array: Array<Any?> = java.lang.reflect.Array.newInstance(
467 method.returnType.componentType,
468 this.size
469 ) as Array<Any?>
470 for (r in 0 until this.size) {
471 array[r] = this[r]?.let { valueProvider.invoke(it) }
472 }
473 return array
474 }
475
476 @Suppress("UNCHECKED_CAST")
asEnumnull477 private fun <T> Any.asEnum(returnType: Class<T>): T =
478 returnType.getDeclaredMethod("valueOf", String::class.java)
479 .invoke(
480 null,
481 if (this is KSType) {
482 this.declaration.simpleName.getShortName()
483 } else {
484 this.toString()
485 }
486 ) as T
487
asBytenull488 private fun Any.asByte(): Byte = if (this is Int) this.toByte() else this as Byte
489
490 private fun Any.asShort(): Short = if (this is Int) this.toShort() else this as Short
491
492 private fun Any.asLong(): Long = if (this is Int) this.toLong() else this as Long
493
494 private fun Any.asFloat(): Float = if (this is Int) this.toFloat() else this as Float
495
496 private fun Any.asDouble(): Double = if (this is Int) this.toDouble() else this as Double
497
498 // for Class/KClass member
499 @KspExperimental
500 class KSTypeNotPresentException(val ksType: KSType, cause: Throwable) : RuntimeException(cause)
501 // for Class[]/Array<KClass<*>> member.
502 @KspExperimental
503 class KSTypesNotPresentException(val ksTypes: List<KSType>, cause: Throwable) : RuntimeException(cause)
504
505 @KspExperimental
506 private fun KSType.asClass(proxyClass: Class<*>) = try {
507 Class.forName(this.declaration.qualifiedName!!.asString(), true, proxyClass.classLoader)
508 } catch (e: Exception) {
509 throw KSTypeNotPresentException(this, e)
510 }
511
512 @KspExperimental
Listnull513 private fun List<KSType>.asClasses(proxyClass: Class<*>) = try {
514 this.map { type -> type.asClass(proxyClass) }
515 } catch (e: Exception) {
516 throw KSTypesNotPresentException(this, e)
517 }
518
isDefaultnull519 fun KSValueArgument.isDefault() = origin == Origin.SYNTHETIC
520