Repository: evant/kotlin-inject
Pull request #64 (2020-09-23 18:42:09)

Speed up equals/hashcode for type collection
Also removed by lazy as it wasn't really saving anything


Patch:
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
index 5e462d5..c71b07e 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/InjectGenerator.kt
@@ -20,6 +20,8 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
     AstProvider by provider {
 
     fun generate(astClass: AstClass): FileSpec {
+        options.profiler?.onStart()
+
         if (AstModifier.ABSTRACT !in astClass.modifiers) {
             throw FailedToGenerateException("@Component class: $astClass must be abstract", astClass)
         } else if (AstModifier.PRIVATE in astClass.modifiers) {
@@ -31,10 +33,14 @@ class InjectGenerator(provider: AstProvider, private val options: Options) :
         val injectComponent = generateInjectComponent(astClass, constructor)
         val createFunction = generateCreate(astClass, constructor, injectComponent)
 
-        return FileSpec.builder(astClass.packageName, "Inject${astClass.name}")
+        val result =  FileSpec.builder(astClass.packageName, "Inject${astClass.name}")
             .addType(injectComponent)
             .addFunction(createFunction)
             .build()
+
+        options.profiler?.onStop()
+
+        return result
     }
 
     private fun generateInjectComponent(
@@ -540,7 +546,7 @@ fun AstClass.isInject(options: Options): Boolean {
     return hasAnnotation<Inject>()
 }
 
-fun AstElement.qualifier(options: Options): Any? {
+fun AstElement.qualifier(options: Options): AstAnnotation? {
     return if (options.enableJavaxAnnotations) {
         annotationAnnotatedWith("javax.inject.Qualifier")
     } else {
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Options.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Options.kt
index 7dc834b..1cd9597 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Options.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Options.kt
@@ -5,12 +5,14 @@ private const val OPTION_ENABLE_JAVAX_ANNOTATIONS = "me.tatarka.inject.enableJav
 
 data class Options(
         val generateCompanionExtensions: Boolean,
-        val enableJavaxAnnotations: Boolean
+        val enableJavaxAnnotations: Boolean,
+        val profiler: Profiler? = null
 ) {
     companion object {
-        fun from(map: Map<String, String>) = Options(
-                generateCompanionExtensions = map[OPTION_GENERATE_COMPANION_EXTENSIONS]?.toBoolean() ?: false,
-                enableJavaxAnnotations = map[OPTION_ENABLE_JAVAX_ANNOTATIONS]?.toBoolean() ?: false
+        fun from(map: Map<String, String>, profiler: Profiler? = null) = Options(
+            generateCompanionExtensions = map[OPTION_GENERATE_COMPANION_EXTENSIONS]?.toBoolean() ?: false,
+            enableJavaxAnnotations = map[OPTION_ENABLE_JAVAX_ANNOTATIONS]?.toBoolean() ?: false,
+            profiler = profiler
         )
     }
 }
\ No newline at end of file
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Profiler.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Profiler.kt
new file mode 100644
index 0000000..f83ebe2
--- /dev/null
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Profiler.kt
@@ -0,0 +1,7 @@
+package me.tatarka.inject.compiler
+
+interface Profiler {
+    fun onStart()
+
+    fun onStop()
+}
\ No newline at end of file
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeKey.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeKey.kt
index 5d94325..821c6e0 100644
--- a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeKey.kt
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/TypeKey.kt
@@ -1,8 +1,6 @@
 package me.tatarka.inject.compiler
 
-import java.util.*
-
-class TypeKey(val type: AstType, val qualifier: Any? = null) {
+class TypeKey(val type: AstType, val qualifier: AstAnnotation? = null) {
 
     override fun equals(other: Any?): Boolean {
         if (other !is TypeKey) return false
@@ -10,7 +8,10 @@ class TypeKey(val type: AstType, val qualifier: Any? = null) {
     }
 
     override fun hashCode(): Int {
-        return Objects.hash(type, qualifier)
+        return collectHash {
+            hash(type)
+            hash(qualifier)
+        }
     }
 
     override fun toString(): String = StringBuilder().apply {
diff --git a/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Util.kt b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Util.kt
new file mode 100644
index 0000000..7b8b33d
--- /dev/null
+++ b/kotlin-inject-compiler-core/src/main/kotlin/me/tatarka/inject/compiler/Util.kt
@@ -0,0 +1,36 @@
+package me.tatarka.inject.compiler
+
+fun <T> Iterable<T>.eqvItr(other: Iterable<T>, eqv: (T, T) -> Boolean): Boolean {
+    val itr1 = iterator()
+    val itr2 = other.iterator()
+    while (itr1.hasNext()) {
+        if (!itr2.hasNext()) {
+            return false
+        }
+        val v1 = itr1.next()
+        val v2 = itr2.next()
+        if (!eqv(v1, v2)) {
+            return false
+        }
+    }
+    if (itr2.hasNext()) {
+        return false
+    }
+    return true
+}
+
+fun <T : Any> T?.eqv(other: T?, eqv: (T, T) -> Boolean): Boolean =
+    (this == null && other == null) ||
+            (this != null && other != null && eqv(this, other))
+
+class HashCollector() {
+    var hash: Int = 1
+        private set
+
+    fun hash(arg: Any?) {
+        hash = 31 * hash + arg.hashCode()
+    }
+}
+
+inline fun collectHash(collector: HashCollector = HashCollector(), block: HashCollector.() -> Unit): Int =
+    collector.apply(block).hash
\ No newline at end of file
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/BaseInjectCompiler.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/BaseInjectCompiler.kt
index bacb6d7..4fb2542 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/BaseInjectCompiler.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/BaseInjectCompiler.kt
@@ -1,6 +1,7 @@
 package me.tatarka.inject.compiler.kapt
 
 import me.tatarka.inject.compiler.Options
+import me.tatarka.inject.compiler.Profiler
 import javax.annotation.processing.AbstractProcessor
 import javax.annotation.processing.Filer
 import javax.annotation.processing.Messager
@@ -11,7 +12,7 @@ import javax.lang.model.util.Types
 
 private const val OPTION_GENERATE_COMPANION_EXTENSIONS = "me.tatarka.inject.generateCompanionExtensions"
 
-abstract class BaseInjectCompiler : AbstractProcessor(),
+abstract class BaseInjectCompiler(private val profiler: Profiler?) : AbstractProcessor(),
     ModelAstProvider {
 
     protected lateinit var options: Options
@@ -20,7 +21,7 @@ abstract class BaseInjectCompiler : AbstractProcessor(),
 
     override fun init(processingEnv: ProcessingEnvironment) {
         super.init(processingEnv)
-        this.options = Options.from(processingEnv.options)
+        this.options = Options.from(processingEnv.options, profiler)
         this.env = processingEnv
         this.filer = processingEnv.filer
     }
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/InjectCompiler.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/InjectCompiler.kt
index cc12c34..f8442fb 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/InjectCompiler.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/InjectCompiler.kt
@@ -5,7 +5,7 @@ import me.tatarka.inject.compiler.*
 import javax.annotation.processing.RoundEnvironment
 import javax.lang.model.element.TypeElement
 
-class InjectCompiler : BaseInjectCompiler() {
+class InjectCompiler(profiler: Profiler? = null) : BaseInjectCompiler(profiler) {
 
     private val annotationNames = mutableSetOf<String>()
 
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
index 2916cb3..2b99ad7 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/ModelAst.kt
@@ -79,7 +79,7 @@ interface ModelAstProvider :
             qualifiedName = java.canonicalName
             simpleName = java.simpleName
         }
-        val packageName = qualifiedName.removeSuffix(simpleName).replace('.', '/')
+        val packageName = qualifiedName.removeSuffix(".$simpleName").replace('.', '/')
         return "${packageName}/${simpleName}"
     }
 
@@ -192,77 +192,80 @@ private class ModelAstClass(
 
     override val name: String get() = element.simpleName.toString()
 
-    override val modifiers: Set<AstModifier> by lazy {
-        collectModifiers(
-            kmClass?.flags
-        )
-    }
+    override val modifiers: Set<AstModifier>
+        get() = collectModifiers(kmClass?.flags)
 
-    override val companion: AstClass? by lazy {
-        val companionName = kmClass?.companionObject ?: return@lazy null
-        val companionType = ElementFilter.typesIn(element.enclosedElements).firstOrNull { type ->
-            type.simpleName.contentEquals(companionName)
+    override val companion: AstClass?
+        get() {
+            val companionName = kmClass?.companionObject ?: return null
+            val companionType = ElementFilter.typesIn(element.enclosedElements).firstOrNull { type ->
+                type.simpleName.contentEquals(companionName)
+            }
+            return companionType?.toAstClass()
         }
-        companionType?.toAstClass()
-    }
 
-    override val superTypes: List<AstClass> by lazy {
-        mutableListOf<AstClass>().apply {
-            val superclassType = element.superclass
-            if (superclassType !is NoType) {
-                val superclass = provider.types.asElement(superclassType) as TypeElement
-                add(superclass.toAstClass())
+    override val superTypes: List<AstClass>
+        get() {
+            return mutableListOf<AstClass>().apply {
+                val superclassType = element.superclass
+                if (superclassType !is NoType) {
+                    val superclass = types.asElement(superclassType) as TypeElement
+                    add(superclass.toAstClass())
+                }
+                addAll(element.interfaces.mapNotNull { ifaceType ->
+                    val iface = types.asElement(ifaceType) as TypeElement
+                    iface.toAstClass()
+                })
             }
-            addAll(element.interfaces.mapNotNull { ifaceType ->
-                val iface = provider.types.asElement(ifaceType) as TypeElement
-                iface.toAstClass()
-            })
         }
-    }
 
-    override val primaryConstructor: AstConstructor? by lazy {
-        ElementFilter.constructorsIn(element.enclosedElements).mapNotNull { constructor ->
-            //TODO: not sure how to match constructors
-            ModelAstConstructor(
-                this,
-                this,
-                constructor,
-                kmClass?.constructors?.first()
-            )
-        }.firstOrNull()
-    }
+    override val primaryConstructor: AstConstructor?
+        get() {
+            return ElementFilter.constructorsIn(element.enclosedElements).mapNotNull { constructor ->
+                //TODO: not sure how to match constructors
+                ModelAstConstructor(
+                    this,
+                    this,
+                    constructor,
+                    kmClass?.constructors?.first()
+                )
+            }.firstOrNull()
+        }
 
-    override val methods: List<AstMethod> by lazy {
-        ElementFilter.methodsIn(element.enclosedElements).mapNotNull<ExecutableElement, AstMethod> { method ->
-            if (kmClass != null) {
-                for (property in kmClass.properties) {
-                    if (method.matches(property)) {
-                        return@mapNotNull ModelAstProperty(
-                            this,
-                            this,
-                            method,
-                            property
-                        )
-                    }
-                }
-                for (function in kmClass.functions) {
-                    if (method.matches(function)) {
-                        return@mapNotNull ModelAstFunction(
-                            this,
-                            this,
-                            method,
-                            function
-                        )
+    override val methods: List<AstMethod>
+        get() {
+            return ElementFilter.methodsIn(element.enclosedElements)
+                .mapNotNull<ExecutableElement, AstMethod> { method ->
+                    if (kmClass != null) {
+                        for (property in kmClass.properties) {
+                            if (method.matches(property)) {
+                                return@mapNotNull ModelAstProperty(
+                                    this,
+                                    this,
+                                    method,
+                                    property
+                                )
+                            }
+                        }
+                        for (function in kmClass.functions) {
+                            if (method.matches(function)) {
+                                return@mapNotNull ModelAstFunction(
+                                    this,
+                                    this,
+                                    method,
+                                    function
+                                )
+                            }
+                        }
                     }
+                    null
                 }
-            }
-            null
         }
-    }
 
-    override val type: AstType by lazy {
-        ModelAstType(provider, element.asType(), kmClass?.type)
-    }
+    override val type: AstType
+        get() {
+            return ModelAstType(this, element.asType(), kmClass?.type)
+        }
 
     override fun asClassName(): ClassName = element.asClassName()
 
@@ -279,18 +282,19 @@ private class ModelAstConstructor(
 ) : AstConstructor(parent),
     ModelAstElement, ModelAstProvider by provider {
 
-    override val parameters: List<AstParam> by lazy {
-        val params = element.parameters
-        val kmParams: List<KmValueParameter> = kmConstructor?.valueParameters ?: emptyList()
-        params.mapIndexed { index, param ->
-            val kmParam = kmParams.getOrNull(index)
-            ModelAstParam(
-                this,
-                param,
-                kmParam
-            )
+    override val parameters: List<AstParam>
+        get() {
+            val params = element.parameters
+            val kmParams: List<KmValueParameter> = kmConstructor?.valueParameters ?: emptyList()
+            return params.mapIndexed { index, param ->
+                val kmParam = kmParams.getOrNull(index)
+                ModelAstParam(
+                    this,
+                    param,
+                    kmParam
+                )
+            }
         }
-    }
 
     override fun equals(other: Any?): Boolean {
         return other is ModelAstConstructor && element == other.element
@@ -311,11 +315,8 @@ private class ModelAstFunction(
 
     override val name: String get() = kmFunction.name
 
-    override val modifiers: Set<AstModifier> by lazy {
-        collectModifiers(
-            kmFunction.flags
-        )
-    }
+    override val modifiers: Set<AstModifier>
+        get() = collectModifiers(kmFunction.flags)
 
     override val returnType: AstType
         get() = ModelAstType(
@@ -340,23 +341,24 @@ private class ModelAstFunction(
             ModelAstType(this, element.parameters[0].asType(), it)
         }
 
-    override val parameters: List<AstParam> by lazy {
-        val params = if (kmFunction.receiverParameterType != null) {
-            // drop the extension function receiver if present
-            element.parameters.drop(1)
-        } else {
-            element.parameters
-        }
-        val kmParams: List<KmValueParameter> = kmFunction.valueParameters
-        params.mapIndexed { index, param ->
-            val kmParam = kmParams.getOrNull(index)
-            ModelAstParam(
-                this,
-                param,
-                kmParam
-            )
+    override val parameters: List<AstParam>
+        get() {
+            val params = if (kmFunction.receiverParameterType != null) {
+                // drop the extension function receiver if present
+                element.parameters.drop(1)
+            } else {
+                element.parameters
+            }
+            val kmParams: List<KmValueParameter> = kmFunction.valueParameters
+            return params.mapIndexed { index, param ->
+                val kmParam = kmParams.getOrNull(index)
+                ModelAstParam(
+                    this,
+                    param,
+                    kmParam
+                )
+            }
         }
-    }
 
     override fun overrides(other: AstMethod): Boolean {
         require(other is ModelAstMethod)
@@ -386,17 +388,18 @@ private class ModelAstProperty(
 
     override val name: String get() = kmProperty.name
 
-    override val modifiers: Set<AstModifier> by lazy {
-        val result = mutableSetOf<AstModifier>()
-        val flags = kmProperty.flags
-        if (Flag.Common.IS_PRIVATE(flags)) {
-            result.add(AstModifier.PRIVATE)
-        }
-        if (Flag.Common.IS_ABSTRACT(flags)) {
-            result.add(AstModifier.ABSTRACT)
+    override val modifiers: Set<AstModifier>
+        get() {
+            val result = mutableSetOf<AstModifier>()
+            val flags = kmProperty.flags
+            if (Flag.Common.IS_PRIVATE(flags)) {
+                result.add(AstModifier.PRIVATE)
+            }
+            if (Flag.Common.IS_ABSTRACT(flags)) {
+                result.add(AstModifier.ABSTRACT)
+            }
+            return result
         }
-        result
-    }
 
     override val returnType: AstType
         get() = ModelAstType(
@@ -464,28 +467,30 @@ private class ModelAstType(
             return element.simpleName.toString()
         }
 
-    override val annotations: List<AstAnnotation> by lazy {
-        val typeAnnotations = type.annotationMirrors
-        val kmTypeAnnotations = kmType?.annotations
-        typeAnnotations.mapIndexed { index, annotation ->
-            val kmAnnotation = kmTypeAnnotations?.get(index)
-            ModelAstAnnotation(
-                this,
-                annotation,
-                kmAnnotation
-            )
+    override val annotations: List<AstAnnotation>
+        get() {
+            val typeAnnotations = type.annotationMirrors
+            val kmTypeAnnotations = kmType?.annotations
+            return typeAnnotations.mapIndexed { index, annotation ->
+                val kmAnnotation = kmTypeAnnotations?.get(index)
+                ModelAstAnnotation(
+                    this,
+                    annotation,
+                    kmAnnotation
+                )
+            }
         }
-    }
 
-    override val arguments: List<AstType> by lazy {
-        val kmArgs: List<KmType?> = kmType?.arguments?.map { it.type } ?: emptyList()
-        val args: List<TypeMirror> = (type as DeclaredType).typeArguments
-        if (args.size == kmArgs.size) {
-            args.zip(kmArgs) { a1, a2 -> ModelAstType(this, a1, a2) }
-        } else {
-            args.map { ModelAstType(this, it, null) }
+    override val arguments: List<AstType>
+        get() {
+            val kmArgs: List<KmType?> = kmType?.arguments?.map { it.type } ?: emptyList()
+            val args: List<TypeMirror> = (type as DeclaredType).typeArguments
+            return if (args.size == kmArgs.size) {
+                args.zip(kmArgs) { a1, a2 -> ModelAstType(this, a1, a2) }
+            } else {
+                args.map { ModelAstType(this, it, null) }
+            }
         }
-    }
 
     override fun isUnit(): Boolean = type is NoType
 
@@ -528,12 +533,16 @@ private class ModelAstType(
     }
 
     override fun equals(other: Any?): Boolean {
-        if (other !is AstType) return false
-        return asTypeName() == other.asTypeName()
+        if (other !is ModelAstType) return false
+        return if (kmType != null && other.kmType != null) {
+            kmType.eqv(other.kmType)
+        } else {
+            types.isSameType(type, other.type)
+        }
     }
 
     override fun hashCode(): Int {
-        return asTypeName().hashCode()
+        return kmType?.eqvHashCode() ?: type.eqvHashCode()
     }
 }
 
@@ -550,11 +559,15 @@ private class ModelAstAnnotation(
 
     override fun equals(other: Any?): Boolean {
         if (other !is ModelAstAnnotation) return false
-        return kmAnnotation == other.kmAnnotation && mirror.eqv(other.mirror)
+        return if (kmAnnotation != null && other.kmAnnotation != null) {
+            kmAnnotation == other.kmAnnotation
+        } else {
+            mirror.eqv(other.mirror)
+        }
     }
 
     override fun hashCode(): Int {
-        return Objects.hash(kmAnnotation, mirror.annotationType)
+        return kmAnnotation?.hashCode() ?: mirror.eqvHashCode()
     }
 
     override fun toString(): String {
@@ -582,9 +595,8 @@ private class ModelAstParam(
             return kmValueParameter?.name ?: element.simpleName.toString()
         }
 
-    override val type: AstType by lazy {
-        ModelAstType(this, element.asType(), kmValueParameter?.type)
-    }
+    override val type: AstType
+        get() = ModelAstType(this, element.asType(), kmValueParameter?.type)
 
     override fun asParameterSpec(): ParameterSpec {
         return ParameterSpec(name, type.asTypeName())
diff --git a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
index 93b1d75..23c2cff 100644
--- a/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
+++ b/kotlin-inject-compiler-kapt/src/main/kotlin/me/tatarka/inject/compiler/kapt/Util.kt
@@ -5,6 +5,12 @@ import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import kotlinx.metadata.*
 import kotlinx.metadata.jvm.*
+import me.tatarka.inject.compiler.HashCollector
+import me.tatarka.inject.compiler.collectHash
+import me.tatarka.inject.compiler.eqv
+import me.tatarka.inject.compiler.eqvItr
+import java.util.*
+import java.util.function.BinaryOperator
 import javax.lang.model.element.*
 import javax.lang.model.type.*
 import javax.lang.model.util.SimpleTypeVisitor7
@@ -164,50 +170,54 @@ fun TypeMirror.asTypeName(kmType: KmType?): TypeName {
 
 val KmClass.packageName: String get() = name.packageName
 
-val KmType.packageName: String get() {
-    val abbreviatedType = abbreviatedType
-    if (abbreviatedType != null) {
-        return abbreviatedType.packageName
-    }
-    return when (val c = classifier) {
-        is KmClassifier.Class -> c.name.packageName
-        is KmClassifier.TypeAlias -> c.name.packageName
-        is KmClassifier.TypeParameter -> ""
+val KmType.packageName: String
+    get() {
+        val abbreviatedType = abbreviatedType
+        if (abbreviatedType != null) {
+            return abbreviatedType.packageName
+        }
+        return when (val c = classifier) {
+            is KmClassifier.Class -> c.name.packageName
+            is KmClassifier.TypeAlias -> c.name.packageName
+            is KmClassifier.TypeParameter -> ""
+        }
     }
-}
 
-val KmType.simpleName: String get() {
-    val abbreviatedType = abbreviatedType
-    if (abbreviatedType != null) {
-        return abbreviatedType.simpleName
-    }
-    return when (val c = classifier) {
-        is KmClassifier.Class -> c.name.simpleName
-        is KmClassifier.TypeAlias -> c.name.simpleName
-        is KmClassifier.TypeParameter -> ""
+val KmType.simpleName: String
+    get() {
+        val abbreviatedType = abbreviatedType
+        if (abbreviatedType != null) {
+            return abbreviatedType.simpleName
+        }
+        return when (val c = classifier) {
+            is KmClassifier.Class -> c.name.simpleName
+            is KmClassifier.TypeAlias -> c.name.simpleName
+            is KmClassifier.TypeParameter -> ""
+        }
     }
-}
 
-val kotlinx.metadata.ClassName.packageName: String get() {
-    val split = lastIndexOf('/')
-    return if (split == -1) {
-        ""
-    } else {
-        substring(0, split).replace('/', '.')
+val kotlinx.metadata.ClassName.packageName: String
+    get() {
+        val split = lastIndexOf('/')
+        return if (split == -1) {
+            ""
+        } else {
+            substring(0, split).replace('/', '.')
+        }
     }
-}
 
-val kotlinx.metadata.ClassName.simpleName: String get() {
-    val split = lastIndexOf('/')
-    return substring(split + 1)
-}
+val kotlinx.metadata.ClassName.simpleName: String
+    get() {
+        val split = lastIndexOf('/')
+        return substring(split + 1)
+    }
 
 fun KmType.asTypeName(): TypeName? {
     val abbreviatedType = abbreviatedType
     if (abbreviatedType != null) {
         return abbreviatedType.asTypeName()
     }
-    val isNullable =  Flag.Type.IS_NULLABLE(flags)
+    val isNullable = Flag.Type.IS_NULLABLE(flags)
     val className = classifier.asClassName() ?: return null
     return if (arguments.isEmpty()) {
         className
@@ -220,12 +230,56 @@ fun AnnotationMirror.eqv(other: AnnotationMirror): Boolean {
     if (annotationType != other.annotationType) {
         return false
     }
-    val values = elementValues.values
-    val otherValues = other.elementValues.values
+    return elementValues.values.eqvItr(other.elementValues.values) { a, b -> a.value == b.value }
+}
 
-    if (values.size != otherValues.size) {
-        return false
+fun AnnotationMirror.eqvHashCode(): Int {
+    return collectHash {
+        hash(annotationType)
+        for (value in elementValues.values) {
+            hash(value.value)
+        }
     }
+}
 
-    return values.zip(otherValues).all { (a, b) -> a.value == b.value }
+fun KmType.eqv(other: KmType): Boolean {
+    val abbreviatedType = abbreviatedType
+    if (abbreviatedType != null) {
+        val otherAbbreviatedType = other.abbreviatedType
+        return if (otherAbbreviatedType == null) {
+            false
+        } else {
+            abbreviatedType.eqv(otherAbbreviatedType)
+        }
+    }
+    return classifier == other.classifier &&
+            arguments.eqvItr(other.arguments) { a, b ->
+                a.variance == b.variance &&
+                        a.type.eqv(b.type, KmType::eqv)
+            }
+}
+
+fun KmType.eqvHashCode(collector: HashCollector = HashCollector()): Int {
+    return collectHash(collector) {
+        val abbreviatedType = abbreviatedType
+        if (abbreviatedType != null) {
+            abbreviatedType.eqvHashCode(this)
+        } else {
+            hash(classifier)
+            for (argument in arguments) {
+                hash(argument.variance)
+                argument.type?.eqvHashCode(this)
+            }
+        }
+    }
+}
+
+fun TypeMirror.eqvHashCode(collector: HashCollector = HashCollector()): Int = collectHash(collector) {
+    if (this@eqvHashCode is DeclaredType) {
+        val element = asElement()
+        hash(element.simpleName)
+        for (arg in typeArguments) {
+            arg.eqvHashCode(this)
+        }
+    }
 }
\ No newline at end of file
diff --git a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/InjectProcessor.kt b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/InjectProcessor.kt
index 2e0a343..9b2f4a7 100644
--- a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/InjectProcessor.kt
+++ b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/InjectProcessor.kt
@@ -9,7 +9,7 @@ import org.jetbrains.kotlin.ksp.processing.Resolver
 import org.jetbrains.kotlin.ksp.processing.SymbolProcessor
 import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
 
-class InjectProcessor : SymbolProcessor, KSAstProvider {
+class InjectProcessor(private val profiler: Profiler? = null) : SymbolProcessor, KSAstProvider {
 
     private lateinit var options: Options
     private lateinit var codeGenerator: CodeGenerator
@@ -22,7 +22,7 @@ class InjectProcessor : SymbolProcessor, KSAstProvider {
         codeGenerator: CodeGenerator,
         logger: KSPLogger
     ) {
-        this.options = Options.from(options)
+        this.options = Options.from(options, profiler)
         this.codeGenerator = codeGenerator
         this.logger = logger
     }
diff --git a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
index d9bbd4f..ed882a1 100644
--- a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
+++ b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/KSAst.kt
@@ -1,15 +1,44 @@
 package me.tatarka.inject.compiler.ksp
 
-import com.squareup.kotlinpoet.*
-import me.tatarka.inject.compiler.*
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.MemberName
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeSpec
+import me.tatarka.inject.compiler.AstAnnotated
+import me.tatarka.inject.compiler.AstAnnotation
+import me.tatarka.inject.compiler.AstBasicElement
+import me.tatarka.inject.compiler.AstClass
+import me.tatarka.inject.compiler.AstConstructor
+import me.tatarka.inject.compiler.AstElement
+import me.tatarka.inject.compiler.AstFunction
+import me.tatarka.inject.compiler.AstMethod
+import me.tatarka.inject.compiler.AstModifier
+import me.tatarka.inject.compiler.AstParam
+import me.tatarka.inject.compiler.AstProperty
+import me.tatarka.inject.compiler.AstProvider
+import me.tatarka.inject.compiler.AstType
+import me.tatarka.inject.compiler.Messenger
 import org.jetbrains.kotlin.ksp.findActualType
 import org.jetbrains.kotlin.ksp.getDeclaredFunctions
 import org.jetbrains.kotlin.ksp.getDeclaredProperties
 import org.jetbrains.kotlin.ksp.isPrivate
 import org.jetbrains.kotlin.ksp.processing.KSPLogger
 import org.jetbrains.kotlin.ksp.processing.Resolver
-import org.jetbrains.kotlin.ksp.symbol.*
-import java.util.*
+import org.jetbrains.kotlin.ksp.symbol.AnnotationUseSiteTarget
+import org.jetbrains.kotlin.ksp.symbol.ClassKind
+import org.jetbrains.kotlin.ksp.symbol.FileLocation
+import org.jetbrains.kotlin.ksp.symbol.KSAnnotated
+import org.jetbrains.kotlin.ksp.symbol.KSAnnotation
+import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSPropertyDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSType
+import org.jetbrains.kotlin.ksp.symbol.KSTypeAlias
+import org.jetbrains.kotlin.ksp.symbol.KSTypeReference
+import org.jetbrains.kotlin.ksp.symbol.KSVariableParameter
+import org.jetbrains.kotlin.ksp.symbol.Variance
 import kotlin.reflect.KClass
 
 interface KSAstProvider : AstProvider {
@@ -134,35 +163,38 @@ private class KSAstClass(provider: KSAstProvider, override val declaration: KSCl
     override val name: String
         get() = declaration.simpleName.asString()
 
-    override val modifiers: Set<AstModifier> by lazy {
-        collectModifiers(declaration)
-    }
+    override val modifiers: Set<AstModifier>
+        get() = collectModifiers(declaration)
 
     override val companion: AstClass?
         get() = TODO("Not yet implemented")
 
-    override val superTypes: List<AstClass> by lazy {
-        declaration.superTypes.mapNotNull { type -> (type.resolve()?.declaration as? KSClassDeclaration)?.toAstClass() }
-    }
+    override val superTypes: List<AstClass>
+        get() {
+            return declaration.superTypes.mapNotNull { type -> (type.resolve()?.declaration as? KSClassDeclaration)?.toAstClass() }
+        }
 
-    override val primaryConstructor: AstConstructor? by lazy {
-        declaration.primaryConstructor?.let { KSAstConstructor(this, this, it) }
-    }
+    override val primaryConstructor: AstConstructor?
+        get() {
+            return declaration.primaryConstructor?.let { KSAstConstructor(this, this, it) }
+        }
 
-    override val methods: List<AstMethod> by lazy {
-        mutableListOf<AstMethod>().apply {
-            addAll(declaration.getDeclaredProperties().map {
-                KSAstProperty(this@KSAstClass, it)
-            })
-            addAll(declaration.getDeclaredFunctions().map {
-                KSAstFunction(this@KSAstClass, it)
-            })
+    override val methods: List<AstMethod>
+        get() {
+            return mutableListOf<AstMethod>().apply {
+                addAll(declaration.getDeclaredProperties().map {
+                    KSAstProperty(this@KSAstClass, it)
+                })
+                addAll(declaration.getDeclaredFunctions().map {
+                    KSAstFunction(this@KSAstClass, it)
+                })
+            }
         }
-    }
 
-    override val type: AstType by lazy {
-        KSAstType(this, declaration.asStarProjectedType())
-    }
+    override val type: AstType
+        get() {
+            return KSAstType(this, declaration.asStarProjectedType())
+        }
 
     override fun asClassName(): ClassName {
         return ClassName(packageName, name)
@@ -183,9 +215,10 @@ private class KSAstConstructor(
     override val declaration: KSFunctionDeclaration
 ) : AstConstructor(parent), KSAstAnnotated, KSAstProvider by provider {
 
-    override val parameters: List<AstParam> by lazy {
-        declaration.parameters.map { param -> KSAstParam(this, declaration, param) }
-    }
+    override val parameters: List<AstParam>
+        get() {
+            return declaration.parameters.map { param -> KSAstParam(this, declaration, param) }
+        }
 
     override fun equals(other: Any?): Boolean {
         return other is KSAstConstructor && declaration == other.declaration
@@ -207,9 +240,9 @@ private class KSAstFunction(provider: KSAstProvider, override val declaration: K
     override val name: String
         get() = declaration.simpleName.asString()
 
-    override val modifiers: Set<AstModifier> by lazy {
-        collectModifiers(declaration)
-    }
+    override val modifiers: Set<AstModifier>
+        get() = collectModifiers(declaration)
+
     override val receiverParameterType: AstType?
         get() = declaration.extensionReceiver?.let { KSAstType(this, it) }
 
@@ -244,9 +277,8 @@ private class KSAstProperty(provider: KSAstProvider, override val declaration: K
     override val name: String
         get() = declaration.simpleName.asString()
 
-    override val modifiers: Set<AstModifier> by lazy {
-        collectModifiers(declaration)
-    }
+    override val modifiers: Set<AstModifier>
+        get() = collectModifiers(declaration)
 
     override val receiverParameterType: AstType?
         get() = declaration.extensionReceiver?.let { KSAstType(this, it) }
@@ -369,11 +401,11 @@ private class KSAstType(provider: KSAstProvider) : AstType(), KSAstAnnotated, KS
 
     override fun equals(other: Any?): Boolean {
         // don't use type == other.type as the impl declares differing typealias as equal and we don't want that behavior.
-        return other is KSAstType && type.asTypeName() == other.type.asTypeName()
+        return other is KSAstType && type.eqv(other.type)
     }
 
     override fun hashCode(): Int {
-        return asTypeName().hashCode()
+        return type.eqvHashCode()
     }
 
     private fun KSType.actualType(): KSType = when (val declaration = declaration) {
diff --git a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/Util.kt b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/Util.kt
index 40befbb..87bae1e 100644
--- a/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/Util.kt
+++ b/kotlin-inject-compiler-ksp/src/main/kotlin/me/tatarka/inject/compiler/ksp/Util.kt
@@ -5,11 +5,31 @@ import com.squareup.kotlinpoet.KModifier
 import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
 import com.squareup.kotlinpoet.TypeName
 import com.squareup.kotlinpoet.TypeVariableName
+import me.tatarka.inject.compiler.HashCollector
+import me.tatarka.inject.compiler.collectHash
+import me.tatarka.inject.compiler.eqv
+import me.tatarka.inject.compiler.eqvItr
 import org.jetbrains.kotlin.ksp.isAbstract
-import org.jetbrains.kotlin.ksp.symbol.*
+import org.jetbrains.kotlin.ksp.symbol.AnnotationUseSiteTarget
+import org.jetbrains.kotlin.ksp.symbol.KSAnnotated
+import org.jetbrains.kotlin.ksp.symbol.KSAnnotation
+import org.jetbrains.kotlin.ksp.symbol.KSClassDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSFunctionDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSNode
+import org.jetbrains.kotlin.ksp.symbol.KSPropertyDeclaration
+import org.jetbrains.kotlin.ksp.symbol.KSType
+import org.jetbrains.kotlin.ksp.symbol.KSTypeAlias
+import org.jetbrains.kotlin.ksp.symbol.KSTypeParameter
+import org.jetbrains.kotlin.ksp.symbol.KSTypeReference
+import org.jetbrains.kotlin.ksp.symbol.Nullability
+import org.jetbrains.kotlin.ksp.symbol.Variance
 import org.jetbrains.kotlin.ksp.visitor.KSDefaultVisitor
 
-fun KSAnnotated.annotationAnnotatedWith(className: String, useSiteTarget: AnnotationUseSiteTarget? = null): KSAnnotation? {
+fun KSAnnotated.annotationAnnotatedWith(
+    className: String,
+    useSiteTarget: AnnotationUseSiteTarget? = null
+): KSAnnotation? {
     for (annotation in annotations) {
         if (annotation.useSiteTarget == useSiteTarget) {
             val t = annotation.annotationType.resolve()
@@ -101,4 +121,38 @@ fun KSType.asTypeName(): TypeName {
 fun KSAnnotation.eqv(other: KSAnnotation): Boolean {
     return annotationType.resolve() == other.annotationType.resolve() &&
             arguments == other.arguments
-}
\ No newline at end of file
+}
+
+fun KSTypeReference.eqv(other: KSTypeReference): Boolean {
+    val t1 = resolve()
+    val t2 = other.resolve()
+    return if (t1 != null && t2 != null) {
+        t1.eqv(t2)
+    } else {
+        t1 == null && t2 == null
+    }
+}
+
+fun KSType.eqv(other: KSType): Boolean {
+    return declaration.qualifiedName == other.declaration.qualifiedName &&
+            nullability == other.nullability &&
+            arguments.eqvItr(other.arguments) { a, b ->
+                a.variance == b.variance && a.type.eqv(
+                    b.type,
+                    KSTypeReference::eqv
+                )
+            }
+}
+
+fun KSType.eqvHashCode(collector: HashCollector = HashCollector()): Int = collectHash(collector) {
+    hash(declaration.qualifiedName)
+    hash(nullability)
+    for (argument in arguments) {
+        hash(argument.variance)
+        argument.type?.eqvHashCode(this)
+    }
+}
+
+fun KSTypeReference.eqvHashCode(collector: HashCollector): Int = collectHash(collector) {
+    resolve()?.eqvHashCode(collector)
+}


Test patch:
diff --git a/compiler-tests/build.gradle b/compiler-tests/build.gradle
index bb795af..b065db4 100644
--- a/compiler-tests/build.gradle
+++ b/compiler-tests/build.gradle
@@ -5,28 +5,29 @@ plugins {
 dependencies {
     implementation 'org.jetbrains.kotlin:kotlin-stdlib'
 
-    testImplementation "org.jetbrains.kotlin:kotlin-test"
-    testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
-    testImplementation 'org.jetbrains.kotlin:kotlin-reflect'
+    implementation project(':kotlin-inject-compiler-core')
+    implementation project(':kotlin-inject-compiler-kapt')
+    implementation project(':kotlin-inject-compiler-ksp')
 
-    testImplementation project(':kotlin-inject-compiler-core')
-    testImplementation project(':kotlin-inject-compiler-kapt')
-    testImplementation project(':kotlin-inject-compiler-ksp')
-    testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.22'
-    testImplementation 'com.squareup.burst:burst-junit4:1.2.0'
-
-    testImplementation 'com.github.tschuchortdev:kotlin-compile-testing-ksp:1.2.10'
-    testImplementation 'org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.0'
-    testImplementation('org.jetbrains.kotlin:kotlin-ksp') {
+    implementation 'com.github.tschuchortdev:kotlin-compile-testing-ksp:1.2.10'
+    implementation 'org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.0'
+    implementation('org.jetbrains.kotlin:kotlin-ksp') {
         version {
             strictly '1.4.0-dev-experimental-20200914'
         }
     }
-    testImplementation('org.jetbrains.kotlin:kotlin-symbol-processing-api') {
+    implementation('org.jetbrains.kotlin:kotlin-symbol-processing-api') {
         version {
             strictly '1.4.0-dev-experimental-20200914'
         }
     }
+
+    testImplementation "org.jetbrains.kotlin:kotlin-test"
+    testImplementation "org.jetbrains.kotlin:kotlin-test-junit"
+    testImplementation 'org.jetbrains.kotlin:kotlin-reflect'
+
+    testImplementation 'com.willowtreeapps.assertk:assertk-jvm:0.22'
+    testImplementation 'com.squareup.burst:burst-junit4:1.2.0'
 }
 
 sourceCompatibility = 1.8
diff --git a/compiler-tests/src/main/kotlin/me/tatarka/inject/Profile.kt b/compiler-tests/src/main/kotlin/me/tatarka/inject/Profile.kt
new file mode 100644
index 0000000..6b861c6
--- /dev/null
+++ b/compiler-tests/src/main/kotlin/me/tatarka/inject/Profile.kt
@@ -0,0 +1,29 @@
+package me.tatarka.inject
+
+import me.tatarka.inject.compiler.Profiler
+
+fun main() {
+    val compiler = ProjectCompiler(Target.ksp, profiler = object : Profiler {
+        override fun onStart() {
+            println("generate start")
+            readLine()
+        }
+
+        override fun onStop() {
+            println("generate end")
+            readLine()
+        }
+    })
+    compiler.source("MyComponent.kt", """
+        import me.tatarka.inject.annotations.Component
+        import me.tatarka.inject.annotations.Provides
+        
+        @Component abstract class MyComponent {
+            abstract val foo: String
+            
+            @Provides fun provideFoo(): String = "test"
+        }
+    """.trimIndent())
+
+    compiler.compile()
+}
\ No newline at end of file
diff --git a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/ProjectCompiler.kt b/compiler-tests/src/main/kotlin/me/tatarka/inject/ProjectCompiler.kt
similarity index 78%
rename from compiler-tests/src/test/kotlin/me/tatarka/inject/test/ProjectCompiler.kt
rename to compiler-tests/src/main/kotlin/me/tatarka/inject/ProjectCompiler.kt
index 6f94944..75dd88c 100644
--- a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/ProjectCompiler.kt
+++ b/compiler-tests/src/main/kotlin/me/tatarka/inject/ProjectCompiler.kt
@@ -1,18 +1,16 @@
-package me.tatarka.inject.test
+package me.tatarka.inject
 
-import assertk.Assert
-import assertk.assertions.isNotNull
-import assertk.assertions.message
 import com.tschuchort.compiletesting.KotlinCompilation
 import com.tschuchort.compiletesting.SourceFile
 import com.tschuchort.compiletesting.symbolProcessors
+import me.tatarka.inject.compiler.Profiler
 import me.tatarka.inject.compiler.kapt.InjectCompiler
 import me.tatarka.inject.compiler.ksp.InjectProcessor
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.PrintStream
 
-class ProjectCompiler(private val root: File, private val target: Target) {
+class ProjectCompiler(private val target: Target, private val root: File? = null, private val profiler: Profiler? = null) {
 
     private val sourceFiles = mutableListOf<SourceFile>()
 
@@ -29,14 +27,14 @@ class ProjectCompiler(private val root: File, private val target: Target) {
         val result = KotlinCompilation().apply {
             sources = sourceFiles
             messageOutputStream = printOut
-            workingDir = root
+            root?.let { workingDir = it }
             inheritClassPath = true
             when (target) {
                 Target.kapt -> {
-                    annotationProcessors = listOf(InjectCompiler())
+                    annotationProcessors = listOf(InjectCompiler(profiler))
                 }
                 Target.ksp -> {
-                    symbolProcessors = listOf(InjectProcessor())
+                    symbolProcessors = listOf(InjectProcessor(profiler))
                 }
             }
         }.compile()
@@ -48,8 +46,6 @@ class ProjectCompiler(private val root: File, private val target: Target) {
     }
 }
 
-fun Assert<Throwable>.output(): Assert<String> = message().isNotNull()
-
 enum class Target {
     kapt,
     ksp
diff --git a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
index bfbdba0..9125956 100644
--- a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
+++ b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/FailureTest.kt
@@ -3,6 +3,8 @@ package me.tatarka.inject.test
 import assertk.assertThat
 import assertk.assertions.contains
 import assertk.assertions.isFailure
+import me.tatarka.inject.ProjectCompiler
+import me.tatarka.inject.Target
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -27,7 +29,7 @@ class FailureTest(val target: Target) {
 
     @Before
     fun setup() {
-        projectCompiler = ProjectCompiler(tempDir.newFolder(), target)
+        projectCompiler = ProjectCompiler(target, tempDir.newFolder())
     }
 
     @Test
diff --git a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/Utils.kt b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/Utils.kt
index 564831d..4f63b0a 100644
--- a/compiler-tests/src/test/kotlin/me/tatarka/inject/test/Utils.kt
+++ b/compiler-tests/src/test/kotlin/me/tatarka/inject/test/Utils.kt
@@ -1,7 +1,12 @@
 package me.tatarka.inject.test
 
+import assertk.Assert
+import assertk.assertions.isNotNull
+import assertk.assertions.message
 import java.io.File
 
+fun Assert<Throwable>.output(): Assert<String> = message().isNotNull()
+
 fun File.recursiveDelete() {
     val files = listFiles()
     if (files != null) {