aboutsummaryrefslogtreecommitdiff
path: root/common-util
diff options
context:
space:
mode:
authorJiaxiang Chen <jiaxiang@google.com>2022-02-01 16:49:31 -0800
committerlaszio <ting-yuan@users.noreply.github.com>2022-02-06 22:28:03 -0800
commit0047c43e64f19dd9164d3af2f3aa05475719c163 (patch)
tree0fabf0b9f3178f9e249cd0f6cb0106482b3a9f4a /common-util
parentd8792ed2c128350d94c58864691a0e88d6264124 (diff)
downloadksp-0047c43e64f19dd9164d3af2f3aa05475719c163.tar.gz
pack util module into symbol-processing-cmdline artifact
Diffstat (limited to 'common-util')
-rw-r--r--common-util/build.gradle.kts39
-rw-r--r--common-util/src/main/kotlin/com/google/devtools/ksp/DescriptorUtils.kt222
-rw-r--r--common-util/src/main/kotlin/com/google/devtools/ksp/KSObjectCacheManager.kt37
-rw-r--r--common-util/src/main/kotlin/com/google/devtools/ksp/MemoizedSequence.kt31
-rw-r--r--common-util/src/main/kotlin/com/google/devtools/ksp/PsiUtils.kt159
5 files changed, 488 insertions, 0 deletions
diff --git a/common-util/build.gradle.kts b/common-util/build.gradle.kts
new file mode 100644
index 00000000..72eccdea
--- /dev/null
+++ b/common-util/build.gradle.kts
@@ -0,0 +1,39 @@
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+description = "Kotlin Symbol Processing Util"
+
+val kotlinBaseVersion: String by project
+val intellijVersion: String by project
+
+tasks.withType<KotlinCompile> {
+ kotlinOptions.freeCompilerArgs += "-Xjvm-default=compatibility"
+}
+
+plugins {
+ kotlin("jvm")
+ id("org.jetbrains.intellij") version "0.6.4"
+ id("org.jetbrains.dokka") version ("1.4.32")
+}
+
+intellij {
+ version = intellijVersion
+}
+
+dependencies {
+ implementation(kotlin("stdlib", kotlinBaseVersion))
+ implementation("org.jetbrains.kotlin:kotlin-compiler:$kotlinBaseVersion")
+ implementation(project(":api"))
+}
+
+tasks {
+ val sourcesJar by creating(Jar::class) {
+ archiveClassifier.set("sources")
+ from(sourceSets.main.get().allSource)
+ }
+}
+
+val dokkaJavadocJar by tasks.register<Jar>("dokkaJavadocJar") {
+ dependsOn(tasks.dokkaJavadoc)
+ from(tasks.dokkaJavadoc.flatMap { it.outputDirectory })
+ archiveClassifier.set("javadoc")
+}
diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/DescriptorUtils.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/DescriptorUtils.kt
new file mode 100644
index 00000000..d360d263
--- /dev/null
+++ b/common-util/src/main/kotlin/com/google/devtools/ksp/DescriptorUtils.kt
@@ -0,0 +1,222 @@
+package com.google.devtools.ksp
+
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.Modifier
+import com.google.devtools.ksp.symbol.Variance
+import com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
+import org.jetbrains.kotlin.descriptors.ClassDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
+import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource
+import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
+import org.jetbrains.kotlin.descriptors.FunctionDescriptor
+import org.jetbrains.kotlin.descriptors.MemberDescriptor
+import org.jetbrains.kotlin.descriptors.Modality
+import org.jetbrains.kotlin.descriptors.PropertyDescriptor
+import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities
+import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
+import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinarySourceElement
+import org.jetbrains.kotlin.load.kotlin.VirtualFileKotlinClass
+import org.jetbrains.kotlin.load.kotlin.getContainingKotlinJvmBinaryClass
+import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.psi.KtDeclarationWithInitializer
+import org.jetbrains.kotlin.psi.KtParameter
+import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
+import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
+import org.jetbrains.kotlin.resolve.source.getPsi
+import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor
+import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
+import org.jetbrains.kotlin.utils.addToStdlib.safeAs
+import org.jetbrains.org.objectweb.asm.ClassReader
+import org.jetbrains.org.objectweb.asm.ClassVisitor
+import org.jetbrains.org.objectweb.asm.FieldVisitor
+import org.jetbrains.org.objectweb.asm.MethodVisitor
+import org.jetbrains.org.objectweb.asm.Opcodes
+
+fun MemberDescriptor.toKSModifiers(): Set<Modifier> {
+ val modifiers = mutableSetOf<Modifier>()
+ if (this.isActual) {
+ modifiers.add(Modifier.ACTUAL)
+ }
+ if (this.isExpect) {
+ modifiers.add(Modifier.EXPECT)
+ }
+ if (this.isExternal) {
+ modifiers.add(Modifier.EXTERNAL)
+ }
+ // we are not checking for JVM_STATIC annotation here intentionally
+ // see: https://github.com/google/ksp/issues/378
+ val isStatic = (this.containingDeclaration as? ClassDescriptor)?.let { containingClass ->
+ containingClass.staticScope.getContributedDescriptors(
+ nameFilter = {
+ it == this.name
+ }
+ ).any {
+ it == this
+ }
+ } ?: false
+ if (isStatic) {
+ modifiers.add(Modifier.JAVA_STATIC)
+ }
+ when (this.modality) {
+ Modality.SEALED -> modifiers.add(Modifier.SEALED)
+ Modality.FINAL -> modifiers.add(Modifier.FINAL)
+ Modality.OPEN -> {
+ if (!isStatic && this.visibility != DescriptorVisibilities.PRIVATE) {
+ // private methods still show up as OPEN
+ modifiers.add(Modifier.OPEN)
+ }
+ }
+ Modality.ABSTRACT -> modifiers.add(Modifier.ABSTRACT)
+ }
+ when (this.visibility) {
+ DescriptorVisibilities.PUBLIC -> modifiers.add(Modifier.PUBLIC)
+ DescriptorVisibilities.PROTECTED,
+ JavaDescriptorVisibilities.PROTECTED_AND_PACKAGE,
+ JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY,
+ -> modifiers.add(Modifier.PROTECTED)
+ DescriptorVisibilities.PRIVATE, DescriptorVisibilities.LOCAL -> modifiers.add(Modifier.PRIVATE)
+ DescriptorVisibilities.INTERNAL -> modifiers.add(Modifier.INTERNAL)
+ // Since there is no modifier for package-private, use No modifier to tell if a symbol from binary is package private.
+ JavaDescriptorVisibilities.PACKAGE_VISIBILITY, JavaDescriptorVisibilities.PROTECTED_STATIC_VISIBILITY -> Unit
+ else -> throw IllegalStateException("unhandled visibility: ${this.visibility}")
+ }
+
+ return modifiers
+}
+
+fun FunctionDescriptor.toFunctionKSModifiers(): Set<Modifier> {
+ val modifiers = mutableSetOf<Modifier>()
+ if (this.isSuspend) {
+ modifiers.add(Modifier.SUSPEND)
+ }
+ if (this.isTailrec) {
+ modifiers.add(Modifier.TAILREC)
+ }
+ if (this.isInline) {
+ modifiers.add(Modifier.INLINE)
+ }
+ if (this.isInfix) {
+ modifiers.add(Modifier.INFIX)
+ }
+ if (this.isOperator) {
+ modifiers.add(Modifier.OPERATOR)
+ }
+ if (this.overriddenDescriptors.isNotEmpty()) {
+ modifiers.add(Modifier.OVERRIDE)
+ }
+
+ return modifiers
+}
+
+fun org.jetbrains.kotlin.types.Variance.toKSVariance(): Variance {
+ return when (this) {
+ org.jetbrains.kotlin.types.Variance.IN_VARIANCE -> Variance.CONTRAVARIANT
+ org.jetbrains.kotlin.types.Variance.OUT_VARIANCE -> Variance.COVARIANT
+ org.jetbrains.kotlin.types.Variance.INVARIANT -> Variance.INVARIANT
+ else -> throw IllegalStateException("Unexpected variance value $this, $ExceptionMessage")
+ }
+}
+
+fun DeclarationDescriptor.findPsi(): PsiElement? {
+ // For synthetic members.
+ if ((this is CallableMemberDescriptor) && this.kind != CallableMemberDescriptor.Kind.DECLARATION) return null
+ return (this as? DeclarationDescriptorWithSource)?.source?.getPsi()
+}
+
+/**
+ * Custom check for backing fields of descriptors that support properties coming from .class files.
+ * The compiler API always returns true for them even when they don't have backing fields.
+ */
+fun PropertyDescriptor.hasBackingFieldWithBinaryClassSupport(): Boolean {
+ // partially take from https://github.com/JetBrains/kotlin/blob/master/compiler/light-classes/src/org/jetbrains/kotlin/asJava/classes/ultraLightMembersCreator.kt#L104
+ return when {
+ extensionReceiverParameter != null -> false // extension properties do not have backing fields
+ compileTimeInitializer != null -> true // compile time initialization requires backing field
+ isLateInit -> true // lateinit requires property, faster than parsing class declaration
+ modality == Modality.ABSTRACT -> false // abstract means false, faster than parsing class declaration
+ this is DeserializedPropertyDescriptor -> this.hasBackingFieldInBinaryClass() // kotlin class, check binary
+ this.source is KotlinSourceElement -> this.declaresDefaultValue // kotlin source
+ else -> true // Java source or class
+ }
+}
+
+data class BinaryClassInfo(
+ val fieldAccFlags: Map<String, Int>,
+ val methodAccFlags: Map<String, Int>
+)
+
+/**
+ * Lookup cache for field names names for deserialized classes.
+ * To check if a field has backing field, we need to look for binary field names, hence they are cached here.
+ */
+object BinaryClassInfoCache : KSObjectCache<ClassId, BinaryClassInfo>() {
+ fun getCached(
+ kotlinJvmBinaryClass: KotlinJvmBinaryClass,
+ ) = cache.getOrPut(kotlinJvmBinaryClass.classId) {
+ val virtualFileContent = kotlinJvmBinaryClass.safeAs<VirtualFileKotlinClass>()?.file?.contentsToByteArray()
+ val fieldAccFlags = mutableMapOf<String, Int>()
+ val methodAccFlags = mutableMapOf<String, Int>()
+ ClassReader(virtualFileContent).accept(
+ object : ClassVisitor(Opcodes.API_VERSION) {
+ override fun visitField(
+ access: Int,
+ name: String?,
+ descriptor: String?,
+ signature: String?,
+ value: Any?
+ ): FieldVisitor? {
+ if (name != null) {
+ fieldAccFlags.put(name, access)
+ }
+ return null
+ }
+
+ override fun visitMethod(
+ access: Int,
+ name: String?,
+ descriptor: String?,
+ signature: String?,
+ exceptions: Array<out String>?
+ ): MethodVisitor? {
+ if (name != null) {
+ methodAccFlags.put(name + descriptor, access)
+ }
+ return null
+ }
+ },
+ ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES
+ )
+ BinaryClassInfo(fieldAccFlags, methodAccFlags)
+ }
+}
+
+/**
+ * Workaround for backingField in deserialized descriptors.
+ * They always return non-null for backing field even when they don't have a backing field.
+ */
+private fun DeserializedPropertyDescriptor.hasBackingFieldInBinaryClass(): Boolean {
+ val kotlinJvmBinaryClass = if (containingDeclaration.isCompanionObject()) {
+ // Companion objects have backing fields in containing classes.
+ // https://kotlinlang.org/docs/java-to-kotlin-interop.html#static-fields
+ val container = containingDeclaration.containingDeclaration as? DeserializedClassDescriptor
+ container?.source?.safeAs<KotlinJvmBinarySourceElement>()?.binaryClass
+ } else {
+ this.getContainingKotlinJvmBinaryClass()
+ } ?: return false
+ return BinaryClassInfoCache.getCached(kotlinJvmBinaryClass).fieldAccFlags.containsKey(name.asString())
+}
+
+// from: https://github.com/JetBrains/kotlin/blob/92d200e093c693b3c06e53a39e0b0973b84c7ec5/plugins/kotlin-serialization/kotlin-serialization-compiler/src/org/jetbrains/kotlinx/serialization/compiler/resolve/SerializableProperty.kt#L45
+private val PropertyDescriptor.declaresDefaultValue: Boolean
+ get() = when (val declaration = this.source.getPsi()) {
+ is KtDeclarationWithInitializer -> declaration.initializer != null
+ is KtParameter -> declaration.defaultValue != null
+ else -> false
+ }
+
+fun KSAnnotated.hasAnnotation(fqn: String): Boolean =
+ annotations.any {
+ fqn.endsWith(it.shortName.asString()) &&
+ it.annotationType.resolve().declaration.qualifiedName?.asString() == fqn
+ }
diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/KSObjectCacheManager.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/KSObjectCacheManager.kt
new file mode 100644
index 00000000..08a8bd7f
--- /dev/null
+++ b/common-util/src/main/kotlin/com/google/devtools/ksp/KSObjectCacheManager.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2020 Google LLC
+ * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
+ *
+ * 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
+ *
+ * http://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.google.devtools.ksp
+
+class KSObjectCacheManager {
+ companion object {
+ val caches = arrayListOf<KSObjectCache<*, *>>()
+
+ fun register(cache: KSObjectCache<*, *>) = caches.add(cache)
+ fun clear() = caches.forEach { it.clear() }
+ }
+}
+
+abstract class KSObjectCache<K, V> {
+ val cache = mutableMapOf<K, V>()
+
+ init {
+ KSObjectCacheManager.register(this)
+ }
+
+ open fun clear() = cache.clear()
+}
diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/MemoizedSequence.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/MemoizedSequence.kt
new file mode 100644
index 00000000..49bfcecc
--- /dev/null
+++ b/common-util/src/main/kotlin/com/google/devtools/ksp/MemoizedSequence.kt
@@ -0,0 +1,31 @@
+package com.google.devtools.ksp
+
+// TODO: garbage collect underlying sequence after exhaust.
+class MemoizedSequence<T>(sequence: Sequence<T>) : Sequence<T> {
+
+ private val cache = mutableListOf<T>()
+
+ private val iter: Iterator<T> by lazy {
+ sequence.iterator()
+ }
+
+ private inner class CachedIterator(val iterCache: Iterator<T>, var buffer: Int) : Iterator<T> {
+ override fun hasNext(): Boolean {
+ return buffer > 0 || iter.hasNext()
+ }
+
+ override fun next(): T {
+ if (buffer > 0) {
+ buffer --
+ return iterCache.next()
+ }
+ val value = iter.next()
+ cache.add(value)
+ return value
+ }
+ }
+
+ override fun iterator(): Iterator<T> {
+ return CachedIterator(cache.iterator(), cache.size)
+ }
+}
diff --git a/common-util/src/main/kotlin/com/google/devtools/ksp/PsiUtils.kt b/common-util/src/main/kotlin/com/google/devtools/ksp/PsiUtils.kt
new file mode 100644
index 00000000..34d5c2ae
--- /dev/null
+++ b/common-util/src/main/kotlin/com/google/devtools/ksp/PsiUtils.kt
@@ -0,0 +1,159 @@
+package com.google.devtools.ksp
+
+import com.google.devtools.ksp.symbol.ClassKind
+import com.google.devtools.ksp.symbol.Modifier
+import com.intellij.lang.jvm.JvmModifier
+import com.intellij.openapi.project.Project
+import com.intellij.psi.PsiComment
+import com.intellij.psi.PsiDocumentManager
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiModifierListOwner
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.KtClass
+import org.jetbrains.kotlin.psi.KtClassOrObject
+import org.jetbrains.kotlin.psi.KtEnumEntry
+import org.jetbrains.kotlin.psi.KtModifierList
+import org.jetbrains.kotlin.psi.KtModifierListOwner
+import org.jetbrains.kotlin.psi.KtObjectDeclaration
+import org.jetbrains.kotlin.psi.psiUtil.siblings
+
+val jvmModifierMap = mapOf(
+ JvmModifier.PUBLIC to Modifier.PUBLIC,
+ JvmModifier.PRIVATE to Modifier.PRIVATE,
+ JvmModifier.ABSTRACT to Modifier.ABSTRACT,
+ JvmModifier.FINAL to Modifier.FINAL,
+ JvmModifier.PROTECTED to Modifier.PROTECTED,
+ JvmModifier.STATIC to Modifier.JAVA_STATIC,
+ JvmModifier.STRICTFP to Modifier.JAVA_STRICT,
+ JvmModifier.NATIVE to Modifier.JAVA_NATIVE,
+ JvmModifier.SYNCHRONIZED to Modifier.JAVA_SYNCHRONIZED,
+ JvmModifier.TRANSIENT to Modifier.JAVA_TRANSIENT,
+ JvmModifier.VOLATILE to Modifier.JAVA_VOLATILE
+)
+
+val javaModifiers = setOf(
+ Modifier.ABSTRACT,
+ Modifier.FINAL,
+ Modifier.JAVA_DEFAULT,
+ Modifier.JAVA_NATIVE,
+ Modifier.JAVA_STATIC,
+ Modifier.JAVA_STRICT,
+ Modifier.JAVA_SYNCHRONIZED,
+ Modifier.JAVA_TRANSIENT,
+ Modifier.JAVA_VOLATILE,
+ Modifier.PRIVATE,
+ Modifier.PROTECTED,
+ Modifier.PUBLIC,
+)
+
+val modifierMap = mapOf(
+ KtTokens.PUBLIC_KEYWORD to Modifier.PUBLIC,
+ KtTokens.PRIVATE_KEYWORD to Modifier.PRIVATE,
+ KtTokens.INTERNAL_KEYWORD to Modifier.INTERNAL,
+ KtTokens.PROTECTED_KEYWORD to Modifier.PROTECTED,
+ KtTokens.IN_KEYWORD to Modifier.IN,
+ KtTokens.OUT_KEYWORD to Modifier.OUT,
+ KtTokens.OVERRIDE_KEYWORD to Modifier.OVERRIDE,
+ KtTokens.LATEINIT_KEYWORD to Modifier.LATEINIT,
+ KtTokens.ENUM_KEYWORD to Modifier.ENUM,
+ KtTokens.SEALED_KEYWORD to Modifier.SEALED,
+ KtTokens.ANNOTATION_KEYWORD to Modifier.ANNOTATION,
+ KtTokens.DATA_KEYWORD to Modifier.DATA,
+ KtTokens.INNER_KEYWORD to Modifier.INNER,
+ KtTokens.FUN_KEYWORD to Modifier.FUN,
+ KtTokens.VALUE_KEYWORD to Modifier.VALUE,
+ KtTokens.SUSPEND_KEYWORD to Modifier.SUSPEND,
+ KtTokens.TAILREC_KEYWORD to Modifier.TAILREC,
+ KtTokens.OPERATOR_KEYWORD to Modifier.OPERATOR,
+ KtTokens.INFIX_KEYWORD to Modifier.INFIX,
+ KtTokens.INLINE_KEYWORD to Modifier.INLINE,
+ KtTokens.EXTERNAL_KEYWORD to Modifier.EXTERNAL,
+ KtTokens.ABSTRACT_KEYWORD to Modifier.ABSTRACT,
+ KtTokens.FINAL_KEYWORD to Modifier.FINAL,
+ KtTokens.OPEN_KEYWORD to Modifier.OPEN,
+ KtTokens.VARARG_KEYWORD to Modifier.VARARG,
+ KtTokens.NOINLINE_KEYWORD to Modifier.NOINLINE,
+ KtTokens.CROSSINLINE_KEYWORD to Modifier.CROSSINLINE,
+ KtTokens.REIFIED_KEYWORD to Modifier.REIFIED,
+ KtTokens.EXPECT_KEYWORD to Modifier.EXPECT,
+ KtTokens.ACTUAL_KEYWORD to Modifier.ACTUAL,
+ KtTokens.CONST_KEYWORD to Modifier.CONST
+)
+
+fun KtModifierList?.toKSModifiers(): Set<Modifier> {
+ if (this == null)
+ return emptySet()
+ val modifiers = mutableSetOf<Modifier>()
+ modifiers.addAll(
+ modifierMap.entries
+ .filter { hasModifier(it.key) }
+ .map { it.value }
+ )
+ return modifiers
+}
+
+fun KtModifierListOwner.toKSModifiers(): Set<Modifier> {
+ val modifierList = this.modifierList
+ return modifierList.toKSModifiers()
+}
+
+fun PsiModifierListOwner.toKSModifiers(): Set<Modifier> {
+ val modifiers = mutableSetOf<Modifier>()
+ modifiers.addAll(
+ jvmModifierMap.entries.filter { this.hasModifier(it.key) }
+ .map { it.value }
+ .toSet()
+ )
+ if (this.modifierList?.hasExplicitModifier("default") == true) {
+ modifiers.add(Modifier.JAVA_DEFAULT)
+ }
+ return modifiers
+}
+
+fun Project.findLocationString(file: PsiFile, offset: Int): String {
+ val psiDocumentManager = PsiDocumentManager.getInstance(this)
+ val document = psiDocumentManager.getDocument(file) ?: return "<unknown>"
+ val lineNumber = document.getLineNumber(offset)
+ val offsetInLine = offset - document.getLineStartOffset(lineNumber)
+ return "${file.virtualFile.path}: (${lineNumber + 1}, ${offsetInLine + 1})"
+}
+
+private fun parseDocString(raw: String): String? {
+ val t1 = raw.trim()
+ if (!t1.startsWith("/**") || !t1.endsWith("*/"))
+ return null
+ val lineSep = t1.findAnyOf(listOf("\r\n", "\n", "\r"))?.second ?: ""
+ return t1.trim('/').trim('*').lines().joinToString(lineSep) {
+ it.trimStart().trimStart('*')
+ }
+}
+
+fun PsiElement.getDocString(): String? =
+ this.firstChild.siblings().firstOrNull { it is PsiComment }?.let {
+ parseDocString(it.text)
+ }
+
+fun KtClassOrObject.getClassType(): ClassKind {
+ return when (this) {
+ is KtObjectDeclaration -> ClassKind.OBJECT
+ is KtEnumEntry -> ClassKind.ENUM_ENTRY
+ is KtClass -> when {
+ this.isEnum() -> ClassKind.ENUM_CLASS
+ this.isInterface() -> ClassKind.INTERFACE
+ this.isAnnotation() -> ClassKind.ANNOTATION_CLASS
+ else -> ClassKind.CLASS
+ }
+ else -> throw IllegalStateException("Unexpected psi type ${this.javaClass}, $ExceptionMessage")
+ }
+}
+
+inline fun <reified T> PsiElement.findParentOfType(): T? {
+ var parent = this.parent
+ while (parent != null && parent !is T) {
+ parent = parent.parent
+ }
+ return parent as? T
+}
+
+fun <T> Sequence<T>.memoized() = MemoizedSequence(this)