Skip to content

Commit 6238ebd

Browse files
committed
Basic IR transformer. Enable in designsystem module.
1 parent 60d8f24 commit 6238ebd

File tree

6 files changed

+129
-15
lines changed

6 files changed

+129
-15
lines changed

android/foundation/designsystem/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id("kstreamlined.android.library")
33
id("kstreamlined.android.screenshot-test")
4+
id("io.github.reactivecircus.cocoon")
45
id("kstreamlined.compose")
56
}
67

@@ -9,6 +10,11 @@ android {
910
androidResources.enable = true
1011
}
1112

13+
cocoon {
14+
annotation.set("io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview.PreviewKStreamlined")
15+
wrappingFunction.set("io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview.KSThemeWithSurface")
16+
}
17+
1218
dependencies {
1319
implementation(libs.androidx.compose.materialIcons)
1420
implementation(libs.androidx.compose.material3)

android/foundation/designsystem/src/main/kotlin/io/github/reactivecircus/kstreamlined/android/foundation/designsystem/component/Button.kt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import androidx.compose.ui.semantics.Role
1111
import androidx.compose.ui.semantics.role
1212
import androidx.compose.ui.semantics.semantics
1313
import androidx.compose.ui.text.font.FontWeight
14-
import androidx.compose.ui.tooling.preview.PreviewLightDark
1514
import androidx.compose.ui.unit.dp
1615
import io.github.reactivecircus.kstreamlined.android.foundation.designsystem.foundation.KSTheme
16+
import io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview.PreviewKStreamlined
1717

1818
@Composable
1919
public fun Button(
@@ -50,15 +50,11 @@ public fun Button(
5050
}
5151

5252
@Composable
53-
@PreviewLightDark
53+
@PreviewKStreamlined
5454
private fun PreviewButton() {
55-
KSTheme {
56-
Surface {
57-
Button(
58-
text = "Button",
59-
onClick = {},
60-
modifier = Modifier.padding(8.dp),
61-
)
62-
}
63-
}
55+
Button(
56+
text = "Button",
57+
onClick = {},
58+
modifier = Modifier.padding(8.dp),
59+
)
6460
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.github.reactivecircus.kstreamlined.android.foundation.designsystem.preview
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.tooling.preview.PreviewLightDark
5+
import io.github.reactivecircus.kstreamlined.android.foundation.designsystem.component.Surface
6+
import io.github.reactivecircus.kstreamlined.android.foundation.designsystem.foundation.KSTheme
7+
8+
@Retention(AnnotationRetention.BINARY)
9+
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.FUNCTION)
10+
@PreviewLightDark
11+
public annotation class PreviewKStreamlined
12+
13+
@Composable
14+
public fun KSThemeWithSurface(
15+
content: @Composable () -> Unit
16+
) {
17+
KSTheme {
18+
Surface {
19+
content()
20+
}
21+
}
22+
}

build-logic/cocoon/cocoon-compiler-plugin/src/main/kotlin/io/github/reactivecircus/cocoon/compiler/CocoonFunctionTransformer.kt

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,106 @@ package io.github.reactivecircus.cocoon.compiler
22

33
import org.jetbrains.kotlin.backend.common.IrElementTransformerVoidWithContext
44
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
5+
import org.jetbrains.kotlin.backend.common.lower.DeclarationIrBuilder
6+
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
57
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
8+
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
9+
import org.jetbrains.kotlin.ir.IrStatement
10+
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
11+
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
12+
import org.jetbrains.kotlin.ir.builders.irBlock
13+
import org.jetbrains.kotlin.ir.builders.irCall
14+
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
15+
import org.jetbrains.kotlin.ir.declarations.IrFunction
16+
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
17+
import org.jetbrains.kotlin.ir.declarations.createBlockBody
18+
import org.jetbrains.kotlin.ir.expressions.IrExpression
19+
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
20+
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionExpressionImpl
21+
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
22+
import org.jetbrains.kotlin.ir.types.IrType
23+
import org.jetbrains.kotlin.ir.util.dump
24+
import org.jetbrains.kotlin.ir.util.hasAnnotation
25+
import org.jetbrains.kotlin.ir.util.statements
626
import org.jetbrains.kotlin.name.CallableId
727
import org.jetbrains.kotlin.name.ClassId
28+
import org.jetbrains.kotlin.name.SpecialNames
829

930
internal class CocoonFunctionTransformer(
1031
private val pluginContext: IrPluginContext,
1132
private val messageCollector: MessageCollector,
12-
private val annotationName: ClassId,
13-
private val wrappingFunctionName: CallableId,
33+
private val annotation: ClassId,
34+
private val wrappingFunction: CallableId,
1435
) : IrElementTransformerVoidWithContext() {
1536

37+
@OptIn(UnsafeDuringIrConstructionAPI::class)
38+
override fun visitFunctionNew(declaration: IrFunction): IrStatement {
39+
if (!declaration.hasAnnotation(annotation) || declaration.body == null) {
40+
return super.visitFunctionNew(declaration)
41+
}
42+
43+
// TODO check for $composer and report error
44+
45+
val originalBody = declaration.body!!
46+
47+
declaration.body = pluginContext.irFactory.createBlockBody(
48+
startOffset = originalBody.startOffset,
49+
endOffset = originalBody.endOffset,
50+
).apply {
51+
val wrappingFunction = pluginContext.referenceFunctions(wrappingFunction).single()
52+
val irBuilder = DeclarationIrBuilder(pluginContext, declaration.symbol)
53+
54+
// TODO move up and check early:
55+
// - must have at least 1 param
56+
// - last must be kotlin.Function0<kotlin.Unit>)
57+
// - move to FIR?
58+
val wrappingFunctionParameters = wrappingFunction.owner.parameters
59+
60+
statements.add(
61+
irBuilder.irBlock {
62+
+irCall(wrappingFunction).apply {
63+
val lambdaExpression = pluginContext.createLambdaIrFunctionExpression(
64+
lambdaReturnType = wrappingFunctionParameters.last().type,
65+
) {
66+
parent = declaration
67+
body = pluginContext.irFactory.createBlockBody(
68+
startOffset,
69+
endOffset,
70+
originalBody.statements,
71+
)
72+
}
73+
arguments[wrappingFunctionParameters.size - 1] = lambdaExpression
74+
}
75+
}
76+
)
77+
}
78+
79+
log("Transformed function IR: \n${declaration.dump()}")
80+
81+
return super.visitFunctionNew(declaration)
82+
}
83+
84+
private fun IrPluginContext.createLambdaIrFunctionExpression(
85+
lambdaReturnType: IrType,
86+
block: IrSimpleFunction.() -> Unit = {},
87+
): IrExpression {
88+
val lambda = irFactory.buildFun {
89+
name = SpecialNames.ANONYMOUS
90+
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
91+
visibility = DescriptorVisibilities.LOCAL
92+
returnType = lambdaReturnType
93+
}.apply(block)
94+
95+
return IrFunctionExpressionImpl(
96+
startOffset = UNDEFINED_OFFSET,
97+
endOffset = UNDEFINED_OFFSET,
98+
type = lambda.returnType,
99+
function = lambda,
100+
origin = IrStatementOrigin.LAMBDA,
101+
)
102+
}
103+
104+
private fun log(message: String) {
105+
messageCollector.report(CompilerMessageSeverity.LOGGING, "Cocoon Compiler Plugin (IR) - $message")
106+
}
16107
}

build-logic/cocoon/cocoon-compiler-plugin/src/main/kotlin/io/github/reactivecircus/cocoon/compiler/CocoonIrGenerationExtension.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
55
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
66
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
77
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
8-
import org.jetbrains.kotlin.ir.symbols.UnsafeDuringIrConstructionAPI
98
import org.jetbrains.kotlin.name.CallableId
109
import org.jetbrains.kotlin.name.ClassId
1110

@@ -14,7 +13,6 @@ internal class CocoonIrGenerationExtension(
1413
private val wrappingFunctionName: CallableId,
1514
private val messageCollector: MessageCollector,
1615
) : IrGenerationExtension {
17-
@OptIn(UnsafeDuringIrConstructionAPI::class)
1816
override fun generate(moduleFragment: IrModuleFragment, pluginContext: IrPluginContext) {
1917
if (pluginContext.referenceClass(annotationName) == null) {
2018
messageCollector.report(CompilerMessageSeverity.ERROR, "Could not find annotation class <$annotationName>.")

detekt.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ style:
3030
ignoreAnnotated:
3131
- Preview
3232
- PreviewLightDark
33+
- PreviewKStreamlined

0 commit comments

Comments
 (0)