Skip to content

Commit dc8ef63

Browse files
authored
Merge pull request #9 from stslex/dev
Custom actions support
2 parents 34161ce + ce9af36 commit dc8ef63

File tree

13 files changed

+174
-39
lines changed

13 files changed

+174
-39
lines changed

README.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,44 @@
22

33
> Check annotated functions arguments. If they don't change - return last result.
44
> Functions could be logged.
5+
> Support extra custom action on function processing
6+
7+
import compiler plugin:
58

6-
import compiler plugin:
79
```kotlin
810
dependencies {
9-
implementation("io.github.stslex:compiler-plugin:0.0.1")
10-
kotlinCompilerPluginClasspath("io.github.stslex:compiler-plugin:0.0.1")
11+
implementation("io.github.stslex:compiler-plugin:$version")
12+
kotlinCompilerPluginClasspath("io.github.stslex:compiler-plugin:$version")
1113
}
1214
```
1315

14-
in code:
16+
in code (all annotation properties are optional):
17+
1518
```kotlin
19+
1620
import io.github.stslex.compiler_plugin.DistinctUntilChangeFun
1721

18-
@DistinctUntilChangeFun
19-
fun setUserName(username: String){
20-
// function logic
22+
@DistinctUntilChangeFun(
23+
logging = true,
24+
singletonAllow = false,
25+
name = "set_user_second_name",
26+
action = TestLogger::class
27+
)
28+
fun setUserName(username: String) {
29+
// function logic
2130
}
2231
```
32+
33+
for custom actions:
34+
35+
```kotlin
36+
class TestLogger : Action {
37+
38+
override fun invoke(
39+
name: String,
40+
isProcess: Boolean
41+
) {
42+
println("test action $name procession: $isProcess")
43+
}
44+
}
45+
```

app/src/main/kotlin/com/stslex/compiler_app/MainActivity.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,18 @@ class MainActivity : ComponentActivity() {
5454
printUsernameWithSingletonDistinct(user.name)
5555
}
5656

57+
5758
@DistinctUntilChangeFun(true)
5859
private fun setName(name: String) {
5960
logger.log(Level.INFO, "setName: $name")
6061
findViewById<TextView>(R.id.usernameFieldTextView).text = name
6162
}
6263

63-
@DistinctUntilChangeFun(true)
64+
@DistinctUntilChangeFun(
65+
name = "set_user_second_name",
66+
logging = true,
67+
action = TestLogger::class
68+
)
6469
private fun setSecondName(name: String) {
6570
logger.log(Level.INFO, "setSecondName: $name")
6671
findViewById<TextView>(R.id.secondNameFieldTextView).text = name
@@ -74,3 +79,5 @@ class MainActivity : ComponentActivity() {
7479
private fun printUsernameWithSingletonDistinct(name: String) {
7580
println("printUsernameWithSingletonDistinct: $name")
7681
}
82+
83+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.stslex.compiler_app
2+
3+
import io.github.stslex.compiler_plugin.utils.Action
4+
5+
class TestLogger : Action {
6+
7+
override fun invoke(
8+
name: String,
9+
isProcess: Boolean
10+
) {
11+
println("test action $name procession: $isProcess")
12+
}
13+
}
Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package io.github.stslex.compiler_plugin
22

3+
import io.github.stslex.compiler_plugin.model.DistinctChangeConfig
34
import io.github.stslex.compiler_plugin.utils.RuntimeLogger
5+
import org.jetbrains.kotlin.utils.addToStdlib.runIf
46

57
internal class DistinctChangeCache(
68
private val config: DistinctChangeConfig
79
) {
810

911
private val cache = mutableMapOf<String, Pair<List<Any?>, Any?>>()
10-
private val logger = RuntimeLogger.tag("DistinctChangeLogger")
12+
private val logger = runIf(config.logging) { RuntimeLogger.tag("DistinctChangeLogger") }
1113

1214
@Suppress("UNCHECKED_CAST")
1315
internal operator fun <R> invoke(
@@ -17,19 +19,21 @@ internal class DistinctChangeCache(
1719
): R {
1820
val entry = cache[key]
1921

20-
if (config.logging) {
21-
logger.i("key: $key, config:$config, entry: $entry, args: $args")
22-
}
22+
logger?.i("name: ${config.name} key: $key, config:$config, entry: $entry, args: $args")
23+
24+
config.action(
25+
name = config.name,
26+
isProcess = entry != null && entry.first == args
27+
)
2328

2429
if (entry != null && entry.first == args) {
25-
if (config.logging) {
26-
logger.i("$key not change")
27-
}
30+
logger?.i("${config.name} with key $key not change")
2831
return entry.second as R
2932
}
3033

3134
val result = body()
3235
cache[key] = args to result
36+
3337
return result
3438
}
3539
}

compiler-plugin/src/main/kotlin/io/github/stslex/compiler_plugin/DistinctChangeConfig.kt

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,30 @@
11
package io.github.stslex.compiler_plugin
22

3+
import io.github.stslex.compiler_plugin.utils.Action
4+
import io.github.stslex.compiler_plugin.utils.DefaultAction
5+
import kotlin.reflect.KClass
6+
37
/**
48
* @param logging enable logs for Kotlin Compiler Runtime work (useful for debug - don't use in production)
59
* @param singletonAllow if enable - generates distinction for function without classes (so it's singleton)
10+
* @param name set name for function in compiler logs and put in into action, else take function name
11+
* @param action any action to process when function enter - it invokes with state of processing function body info and [name].
12+
* @suppress [action] shouldn't have properties in it's constructor
613
* */
714
@Target(AnnotationTarget.FUNCTION)
815
@Retention(AnnotationRetention.BINARY)
916
public annotation class DistinctUntilChangeFun(
1017
val logging: Boolean = LOGGING_DEFAULT,
11-
val singletonAllow: Boolean = false
18+
val singletonAllow: Boolean = SINGLETON_ALLOW,
19+
val name: String = "",
20+
val action: KClass<out Action> = DefaultAction::class
1221
) {
1322

1423
public companion object {
1524

1625
internal const val LOGGING_DEFAULT = false
1726

27+
internal const val SINGLETON_ALLOW = false
28+
1829
}
1930
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.stslex.compiler_plugin.model
2+
3+
import io.github.stslex.compiler_plugin.utils.Action
4+
5+
internal data class DistinctChangeConfig(
6+
val logging: Boolean,
7+
val action: Action,
8+
val name: String
9+
)

compiler-plugin/src/main/kotlin/io/github/stslex/compiler_plugin/transformers/IrFunctionTransformer.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.github.stslex.compiler_plugin.transformers
22

33
import buildArgsListExpression
4+
import io.github.stslex.compiler_plugin.DistinctUntilChangeFun.Companion.SINGLETON_ALLOW
45
import io.github.stslex.compiler_plugin.utils.CompileLogger.Companion.toCompilerLogger
56
import io.github.stslex.compiler_plugin.utils.buildLambdaForBody
67
import io.github.stslex.compiler_plugin.utils.buildSaveInCacheCall
@@ -29,10 +30,9 @@ internal class IrFunctionTransformer(
2930
val qualifierArgs = pluginContext.readQualifier(declaration, logger)
3031
?: return super.visitSimpleFunction(declaration)
3132

32-
if (
33-
declaration.getQualifierValue("singletonAllow").not() &&
34-
declaration.parentClassOrNull == null
35-
) {
33+
val isSingletonAllow = declaration.getQualifierValue("singletonAllow", SINGLETON_ALLOW)
34+
35+
if (isSingletonAllow.not() && declaration.parentClassOrNull == null) {
3636
error("singleton is not allowed for ${declaration.name} in ${declaration.fileParentOrNull}")
3737
}
3838

compiler-plugin/src/main/kotlin/io/github/stslex/compiler_plugin/utils/CompilerExtensions.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import org.jetbrains.kotlin.ir.util.file
3434
import org.jetbrains.kotlin.ir.util.kotlinFqName
3535
import org.jetbrains.kotlin.ir.util.parentClassOrNull
3636
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
37+
import org.jetbrains.kotlin.name.CallableId
3738
import org.jetbrains.kotlin.name.ClassId
3839
import org.jetbrains.kotlin.name.FqName
3940
import org.jetbrains.kotlin.name.Name
@@ -46,10 +47,18 @@ internal fun IrPluginContext.createIrBuilder(
4647
symbol = declaration.symbol
4748
)
4849

49-
internal inline fun <reified T : Any> KClass<T>.toClassId(): ClassId = ClassId(
50-
FqName(java.`package`.name),
51-
Name.identifier(java.simpleName)
52-
)
50+
internal val <T : Any> KClass<T>.classId: ClassId
51+
get() = ClassId(fqName, name)
52+
53+
internal val <T : Any> KClass<T>.callableId: CallableId
54+
get() = CallableId(fqName, name)
55+
56+
57+
internal val <T : Any> KClass<T>.fqName: FqName
58+
get() = FqName(java.`package`.name)
59+
60+
internal val <T : Any> KClass<T>.name: Name
61+
get() = Name.identifier(java.simpleName)
5362

5463
internal fun IrPluginContext.buildLambdaForBody(
5564
originalBody: IrBody,
@@ -111,7 +120,7 @@ internal fun IrPluginContext.buildSaveInCacheCall(
111120
): IrExpression {
112121
logger.i("buildSaveInCacheCall for ${function.name}, args: ${argsListExpr.dump()}")
113122

114-
val distinctChangeClassSymbol = referenceClass(DistinctChangeCache::class.toClassId())
123+
val distinctChangeClassSymbol = referenceClass(DistinctChangeCache::class.classId)
115124
?: error("Cannot find DistinctChangeCache")
116125

117126
val invokeFunSymbol = distinctChangeClassSymbol.owner.declarations
@@ -177,7 +186,7 @@ internal fun IrPluginContext.generateFields(
177186

178187
val fieldSymbol = IrFieldSymbolImpl()
179188

180-
val distinctChangeClass = referenceClass(DistinctChangeCache::class.toClassId())
189+
val distinctChangeClass = referenceClass(DistinctChangeCache::class.classId)
181190
?: error("couldn't find DistinctChangeCache")
182191

183192
val backingField = irFactory.createField(
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.github.stslex.compiler_plugin.utils
2+
3+
/**
4+
* Use for any action in runtime plugin
5+
* @suppress shouldn't have properties in it's constructor - cause compiling crush
6+
**/
7+
public fun interface Action {
8+
9+
/**
10+
* @param name setted at [io.github.stslex.compiler_plugin.DistinctUntilChangeFun] property name
11+
* @param isProcess show that function block will process
12+
**/
13+
public operator fun invoke(
14+
name: String,
15+
isProcess: Boolean
16+
)
17+
18+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package io.github.stslex.compiler_plugin.utils
2+
3+
internal class DefaultAction : Action {
4+
5+
override fun invoke(
6+
name: String,
7+
isProcess: Boolean
8+
) = Unit
9+
}

compiler-plugin/src/main/kotlin/io/github/stslex/compiler_plugin/utils/ReadQualifierUtil.kt

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
package io.github.stslex.compiler_plugin.utils
22

3-
import io.github.stslex.compiler_plugin.DistinctChangeConfig
43
import io.github.stslex.compiler_plugin.DistinctUntilChangeFun
54
import io.github.stslex.compiler_plugin.DistinctUntilChangeFun.Companion.LOGGING_DEFAULT
5+
import io.github.stslex.compiler_plugin.model.DistinctChangeConfig
66
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
7+
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
78
import org.jetbrains.kotlin.ir.builders.irBoolean
89
import org.jetbrains.kotlin.ir.builders.irCallConstructor
10+
import org.jetbrains.kotlin.ir.builders.irString
911
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
12+
import org.jetbrains.kotlin.ir.expressions.IrClassReference
1013
import org.jetbrains.kotlin.ir.expressions.IrConst
1114
import org.jetbrains.kotlin.ir.expressions.IrConstKind
15+
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
1216
import org.jetbrains.kotlin.ir.expressions.IrExpression
17+
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
1318
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
19+
import org.jetbrains.kotlin.ir.types.defaultType
1420
import org.jetbrains.kotlin.ir.util.constructors
1521
import org.jetbrains.kotlin.ir.util.getAnnotation
1622
import org.jetbrains.kotlin.ir.util.getValueArgument
1723
import org.jetbrains.kotlin.ir.util.patchDeclarationParents
1824
import org.jetbrains.kotlin.name.FqName
1925
import org.jetbrains.kotlin.name.Name
26+
import kotlin.reflect.KClass
2027

2128
@OptIn(UnsafeDuringIrConstructionAPI::class)
2229
internal fun IrPluginContext.readQualifier(
@@ -30,10 +37,13 @@ internal fun IrPluginContext.readQualifier(
3037

3138
val irBuilder = createIrBuilder(function)
3239

33-
val logging = annotation.getValueArgument(0)
40+
val loggingExpr = annotation.getValueArgument(0)
3441
?: irBuilder.irBoolean(LOGGING_DEFAULT)
42+
val actionName = annotation.getValueArgument(2)
43+
?: irBuilder.irString(function.name.identifier)
44+
val actionInstanceExpr = getQualifierAction(annotation, irBuilder)
3545

36-
val constructorSymbol = referenceClass(DistinctChangeConfig::class.toClassId())
46+
val constructorSymbol = referenceClass(DistinctChangeConfig::class.classId)
3747
?.constructors
3848
?.firstOrNull()
3949
?: error("CheckChangesConfig not found in IR")
@@ -45,16 +55,43 @@ internal fun IrPluginContext.readQualifier(
4555
)
4656
.also { it.patchDeclarationParents(function) }
4757
.apply {
48-
putValueArgument(0, logging)
58+
putValueArgument(0, loggingExpr)
59+
putValueArgument(1, actionInstanceExpr)
60+
putValueArgument(2, actionName)
4961
}
5062
}
5163

52-
internal fun IrSimpleFunction.getQualifierValue(name: String): Boolean = getAnnotation(
64+
@OptIn(UnsafeDuringIrConstructionAPI::class)
65+
private fun IrPluginContext.getQualifierAction(
66+
annotation: IrConstructorCall,
67+
irBuilder: DeclarationIrBuilder
68+
): IrExpression {
69+
val defaultActionClass = irBuiltIns
70+
.findClass(DefaultAction::class.name, DefaultAction::class.fqName)
71+
?: error("readQualifier ${DefaultAction::class.java.simpleName} not found")
72+
73+
val actionReference = annotation.getValueArgument(3) as? IrClassReference
74+
75+
val actionClassSymbol = actionReference?.symbol as? IrClassSymbol ?: defaultActionClass
76+
77+
val actionConstructorSymbol = actionClassSymbol.constructors.firstOrNull {
78+
it.owner.valueParameters.isEmpty()
79+
} ?: error("No no-arg constructor for action class: ${actionReference?.symbol?.defaultType}")
80+
81+
return irBuilder.irCallConstructor(actionConstructorSymbol, emptyList())
82+
}
83+
84+
public fun getJavaClassNonInline(kClass: KClass<*>): Class<*> = kClass.java
85+
86+
internal inline fun <reified T> IrSimpleFunction.getQualifierValue(
87+
name: String,
88+
defaultValue: T
89+
): T = getAnnotation(
5390
FqName(DistinctUntilChangeFun::class.qualifiedName!!)
5491
)
5592
?.getValueArgument(Name.identifier(name))
56-
?.parseValue<Boolean>()
57-
?: false
93+
?.parseValue<T>()
94+
?: defaultValue
5895

5996
private inline fun <reified T> IrExpression.parseValue(): T = when (this) {
6097
is IrConst<*> -> when (kind) {

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ activity = "1.10.0"
1414
constraintLayout = "2.2.0"
1515
jetbrainsKotlinJvm = "2.0.20"
1616

17-
stslexCompilerPlugin = "0.0.3"
17+
stslexCompilerPlugin = "0.0.4"
1818

1919
[libraries]
2020
android-desugarJdkLibs = { module = "com.android.tools:desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }

0 commit comments

Comments
 (0)