Skip to content

Commit 2647251

Browse files
authored
Fixed naive toClassName function that was breaking FQ names (#49)
1 parent 0db50f5 commit 2647251

File tree

12 files changed

+160
-29
lines changed

12 files changed

+160
-29
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121
### Other Notes & Contributions
2222
-->
2323

24+
### Fixed
25+
26+
- Fixed naive `toClassName()` function that broke on nested classes - [https://github.com/r0adkll/kimchi/pull/48]
27+
2428
## [0.3.0] - 2024-09-09
2529

2630
### Fixed

circuit/compiler/src/main/kotlin/com/r0adkll/kimchi/circuit/CircuitInjectSymbolProcessor.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ import com.r0adkll.kimchi.util.buildConstructor
2323
import com.r0adkll.kimchi.util.buildFile
2424
import com.r0adkll.kimchi.util.capitalized
2525
import com.r0adkll.kimchi.util.kotlinpoet.toParameterSpec
26+
import com.r0adkll.kimchi.util.ksp.asMemberName
2627
import com.r0adkll.kimchi.util.ksp.directReturnTypeIs
28+
import com.r0adkll.kimchi.util.ksp.findActualType
2729
import com.r0adkll.kimchi.util.ksp.hasAnnotation
2830
import com.r0adkll.kimchi.util.ksp.implements
2931
import com.r0adkll.kimchi.util.ksp.returnTypeIs
30-
import com.r0adkll.kimchi.util.toClassName
3132
import com.squareup.kotlinpoet.AnnotationSpec
3233
import com.squareup.kotlinpoet.ClassName
3334
import com.squareup.kotlinpoet.CodeBlock
@@ -221,7 +222,7 @@ class CircuitInjectSymbolProcessor(
221222
?.mapNotNull { parameter ->
222223
if (parameter.hasAnnotation(Assisted::class)) {
223224
// Validate that injected type is allowed type
224-
val parameterTypeClassName = parameter.type.resolve().declaration.toClassName()
225+
val parameterTypeClassName = parameter.type.findActualType().toClassName()
225226
if (parameterTypeClassName in allowedAssistedTypes) {
226227
ParameterSpec(parameter.name!!.asString(), parameterTypeClassName)
227228
} else {
@@ -362,9 +363,9 @@ class CircuitInjectSymbolProcessor(
362363
.beginControlFlow("return when(screen)")
363364
.beginControlFlow("is %T ->", annotation.screen)
364365
.addStatement(
365-
"%M { %T() }",
366+
"%M { %M() }",
366367
MemberNames.CircuitPresenterOf,
367-
element.toClassName(),
368+
element.asMemberName(),
368369
)
369370
.endControlFlow()
370371
.addStatement("else -> null")
@@ -403,7 +404,7 @@ class CircuitInjectSymbolProcessor(
403404
?.mapNotNull { parameter ->
404405
if (parameter.hasAnnotation(Assisted::class)) {
405406
// Validate that injected type is allowed type
406-
val parameterTypeClassName = parameter.type.resolve().declaration.toClassName()
407+
val parameterTypeClassName = parameter.type.findActualType().toClassName()
407408
if (parameterTypeClassName in allowedAssistedTypes) {
408409
ParameterSpec(parameter.name!!.asString(), parameterTypeClassName)
409410
} else {

circuit/compiler/src/main/kotlin/com/r0adkll/kimchi/circuit/util/UiFactory.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
package com.r0adkll.kimchi.circuit.util
44

55
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
6+
import com.r0adkll.kimchi.util.ksp.asMemberName
67
import com.r0adkll.kimchi.util.ksp.findActualType
78
import com.r0adkll.kimchi.util.ksp.findParameterThatImplements
89
import com.r0adkll.kimchi.util.ksp.implements
9-
import com.r0adkll.kimchi.util.toClassName
1010
import com.squareup.kotlinpoet.ClassName
1111
import com.squareup.kotlinpoet.CodeBlock
12+
import com.squareup.kotlinpoet.ksp.toClassName
1213

1314
fun CodeBlock.Builder.addUiFactoryCreateStatement(
1415
element: KSFunctionDeclaration,
@@ -39,11 +40,12 @@ fun CodeBlock.Builder.addUiFactoryCreateStatement(
3940
// [CircuitUiState] type parameter.
4041
val stateClassName = stateClassParameter?.type?.findActualType()?.toClassName()
4142
?: ClassNames.Circuit.UiState
43+
4244
addStatement(
43-
"%M<%T> { state, modifier -> %T(%L) }",
45+
"%M<%T> { state, modifier -> %M(%L) }",
4446
MemberNames.CircuitUi,
4547
stateClassName,
46-
element.toClassName(),
48+
element.asMemberName(),
4749
callParameters.joinToString(),
4850
)
4951
}

circuit/compiler/src/test/kotlin/com/r0adkll/kimchi/circuit/UiFunctionFactoryTest.kt

Lines changed: 89 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,19 @@ import me.tatarka.inject.annotations.Inject
1818
import me.tatarka.inject.annotations.IntoSet
1919
import me.tatarka.inject.annotations.Provides
2020
import org.junit.jupiter.api.Test
21+
import org.junit.jupiter.api.io.CleanupMode
2122
import org.junit.jupiter.api.io.TempDir
2223
import strikt.api.expectThat
2324
import strikt.assertions.isEqualTo
2425
import strikt.assertions.isNotNull
2526

2627
class UiFunctionFactoryTest {
2728

28-
@TempDir
29+
@TempDir(cleanup = CleanupMode.NEVER)
2930
lateinit var workingDir: File
3031

3132
@Test
3233
fun `ui composable function generates factory and contributed component`() {
33-
println(workingDir.absolutePath)
3434
compileKimchiWithTestSources(
3535
"""
3636
package kimchi
@@ -70,7 +70,6 @@ class UiFunctionFactoryTest {
7070

7171
@Test
7272
fun `ui composable function for static screen generates factory and contributed component`() {
73-
println(workingDir.absolutePath)
7473
compileKimchiWithTestSources(
7574
"""
7675
package kimchi
@@ -110,7 +109,6 @@ class UiFunctionFactoryTest {
110109

111110
@Test
112111
fun `ui composable function with injected parameters injects into factory`() {
113-
println(workingDir.absolutePath)
114112
compileKimchiWithTestSources(
115113
"""
116114
package kimchi
@@ -144,7 +142,6 @@ class UiFunctionFactoryTest {
144142

145143
@Test
146144
fun `ui composable function with injected typealias parameters injects into factory`() {
147-
println(workingDir.absolutePath)
148145
compileKimchiWithTestSources(
149146
"""
150147
package kimchi
@@ -175,4 +172,91 @@ class UiFunctionFactoryTest {
175172
.isTypeOf(injectedComposable)
176173
}
177174
}
175+
176+
@Test
177+
fun `ui composable function with nested screen reference compiles`() {
178+
compileKimchiWithTestSources(
179+
"""
180+
package kimchi
181+
182+
import androidx.compose.runtime.Composable
183+
import androidx.compose.ui.Modifier
184+
import com.r0adkll.kimchi.circuit.annotations.CircuitInject
185+
import com.slack.circuit.runtime.screen.Screen
186+
187+
data object Screens {
188+
data object TestScreen : Screen
189+
}
190+
191+
@CircuitInject(Screens.TestScreen::class, TestScope::class)
192+
@Composable
193+
fun TestUi(
194+
state: TestUiState,
195+
modifier: Modifier = Modifier,
196+
) { }
197+
""".trimIndent(),
198+
workingDir = workingDir,
199+
) {
200+
val factory = kotlinClass("kimchi.TestUiUiFactory")
201+
expectThat(factory)
202+
.hasAnnotation(Inject::class)
203+
.implements(Ui.Factory::class)
204+
205+
val component = kotlinClass("kimchi.TestUiUiFactoryComponent")
206+
expectThat(component)
207+
.withAnnotation<ContributesTo> {
208+
get { scope } isEqualTo testScope
209+
}
210+
.withFunction("bindTestUiUiFactory") {
211+
hasAnnotation(IntoSet::class)
212+
hasAnnotation(Provides::class)
213+
parameter(1)
214+
.isTypeOf(factory)
215+
hasReturnType(Ui.Factory::class)
216+
}
217+
}
218+
}
219+
220+
@Test
221+
fun `nested ui composable function compiles`() {
222+
println(workingDir.absolutePath)
223+
compileKimchiWithTestSources(
224+
"""
225+
package kimchi
226+
227+
import androidx.compose.runtime.Composable
228+
import androidx.compose.ui.Modifier
229+
import com.r0adkll.kimchi.circuit.annotations.CircuitInject
230+
import com.slack.circuit.runtime.screen.Screen
231+
232+
object TestUiScreen {
233+
@CircuitInject(TestScreen::class, TestScope::class)
234+
@Composable
235+
fun TestUi(
236+
state: TestUiState,
237+
modifier: Modifier = Modifier,
238+
) { }
239+
}
240+
""".trimIndent(),
241+
workingDir = workingDir,
242+
) {
243+
val factory = kotlinClass("kimchi.TestUiUiFactory")
244+
expectThat(factory)
245+
.hasAnnotation(Inject::class)
246+
.implements(Ui.Factory::class)
247+
248+
val component = kotlinClass("kimchi.TestUiUiFactoryComponent")
249+
expectThat(component)
250+
.withAnnotation<ContributesTo> {
251+
get { scope } isEqualTo testScope
252+
}
253+
.withFunction("bindTestUiUiFactory") {
254+
hasAnnotation(IntoSet::class)
255+
hasAnnotation(Provides::class)
256+
parameter(1)
257+
.isTypeOf(factory)
258+
hasReturnType(Ui.Factory::class)
259+
}
260+
}
261+
}
178262
}

compiler-utils/src/main/kotlin/com/r0adkll/kimchi/util/KotlinPoetUtils.kt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.r0adkll.kimchi.util
44

5-
import com.google.devtools.ksp.symbol.KSDeclaration
65
import com.squareup.kotlinpoet.ClassName
76
import com.squareup.kotlinpoet.FileSpec
87
import com.squareup.kotlinpoet.FunSpec
@@ -45,8 +44,6 @@ public fun FunSpec.Companion.buildConstructor(
4544
.apply(builder)
4645
.build()
4746

48-
public fun KSDeclaration.toClassName(): ClassName = ClassName(packageName.asString(), simpleName.asString())
49-
5047
public fun <T> T.applyIf(predicate: Boolean, block: T.() -> Unit): T {
5148
return if (predicate) apply(block) else this
5249
}

compiler-utils/src/main/kotlin/com/r0adkll/kimchi/util/ksp/FunctionDeclarations.kt

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package com.r0adkll.kimchi.util.ksp
44

5+
import com.google.devtools.ksp.closestClassDeclaration
56
import com.google.devtools.ksp.getAllSuperTypes
67
import com.google.devtools.ksp.symbol.KSClassDeclaration
78
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
89
import com.google.devtools.ksp.symbol.KSValueParameter
9-
import com.r0adkll.kimchi.util.toClassName
1010
import com.squareup.kotlinpoet.ClassName
11+
import com.squareup.kotlinpoet.MemberName
1112
import com.squareup.kotlinpoet.asClassName
1213
import com.squareup.kotlinpoet.ksp.toClassName
1314
import kotlin.reflect.KClass
@@ -31,7 +32,7 @@ public fun KSValueParameter.implements(className: ClassName): Boolean {
3132
// Check if the direct type implements the passed className
3233
if (classDecl.toClassName() == className) return true
3334
return classDecl.getAllSuperTypes()
34-
.any { it.declaration.toClassName() == className }
35+
.any { it.classDeclaration.toClassName() == className }
3536
}
3637
return false
3738
}
@@ -44,7 +45,7 @@ public fun KSFunctionDeclaration.returnTypeIs(className: ClassName): Boolean {
4445
return returnType
4546
?.findActualType()
4647
?.getAllSuperTypes()
47-
?.any { it.declaration.toClassName() == className } == true
48+
?.any { it.classDeclaration.toClassName() == className } == true
4849
}
4950

5051
public fun KSFunctionDeclaration.directReturnTypeIs(clazz: KClass<*>): Boolean {
@@ -56,3 +57,12 @@ public fun KSFunctionDeclaration.directReturnTypeIs(className: ClassName): Boole
5657
?.findActualType()
5758
?.toClassName() == className
5859
}
60+
61+
public fun KSFunctionDeclaration.asMemberName(): MemberName {
62+
val parentClass = closestClassDeclaration()
63+
return if (parentClass != null) {
64+
MemberName(parentClass.toClassName(), simpleName.asString())
65+
} else {
66+
MemberName(packageName.asString(), simpleName.asString())
67+
}
68+
}

compiler-utils/src/main/kotlin/com/r0adkll/kimchi/util/ksp/KSValueArgument.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ package com.r0adkll.kimchi.util.ksp
44

55
import com.google.devtools.ksp.symbol.KSType
66
import com.google.devtools.ksp.symbol.KSValueArgument
7-
import com.r0adkll.kimchi.util.toClassName
87
import com.squareup.kotlinpoet.ClassName
8+
import com.squareup.kotlinpoet.ksp.toClassName
99

1010
/**
1111
* Get the arguments value as a ClassName, if possible
1212
*/
1313
public val KSValueArgument.valueAsClassName: ClassName?
1414
get() = value
1515
?.let { it as? KSType }
16-
?.declaration
16+
?.classDeclaration
1717
?.toClassName()
1818

1919
/**
@@ -23,5 +23,5 @@ public val KSValueArgument.valueAsClassNameList: List<ClassName>?
2323
get() = value
2424
?.let { it as? List<*> }
2525
?.mapNotNull {
26-
(it as? KSType)?.declaration?.toClassName()
26+
(it as? KSType)?.classDeclaration?.toClassName()
2727
}

compiler-utils/src/main/kotlin/com/r0adkll/kimchi/util/ksp/KspUtil.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.google.devtools.ksp.symbol.ClassKind
66
import com.google.devtools.ksp.symbol.KSAnnotated
77
import com.google.devtools.ksp.symbol.KSAnnotation
88
import com.google.devtools.ksp.symbol.KSClassDeclaration
9+
import com.google.devtools.ksp.symbol.KSType
910
import com.google.devtools.ksp.symbol.KSTypeAlias
1011
import com.google.devtools.ksp.symbol.KSTypeReference
1112
import com.squareup.kotlinpoet.ClassName
@@ -63,5 +64,8 @@ public fun KSTypeReference.findActualType(): KSClassDeclaration {
6364
}
6465
}
6566

67+
public val KSType.classDeclaration: KSClassDeclaration
68+
get() = declaration as KSClassDeclaration
69+
6670
public val KSClassDeclaration.isInterface: Boolean
6771
get() = this.classKind == ClassKind.INTERFACE

compiler/src/main/kotlin/com/r0adkll/kimchi/processors/MergeComponentSymbolProcessor.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import com.r0adkll.kimchi.util.ksp.findQualifier
3232
import com.r0adkll.kimchi.util.ksp.hasAnnotation
3333
import com.r0adkll.kimchi.util.ksp.hasCompanionObject
3434
import com.r0adkll.kimchi.util.ksp.isInterface
35-
import com.r0adkll.kimchi.util.toClassName
3635
import com.squareup.kotlinpoet.AnnotationSpec
3736
import com.squareup.kotlinpoet.ClassName
3837
import com.squareup.kotlinpoet.FileSpec

compiler/src/main/kotlin/com/r0adkll/kimchi/util/kotlinpoet/Bindings.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package com.r0adkll.kimchi.util.kotlinpoet
44

55
import com.google.devtools.ksp.symbol.ClassKind
66
import com.google.devtools.ksp.symbol.KSClassDeclaration
7-
import com.google.devtools.ksp.symbol.KSDeclaration
87
import com.r0adkll.kimchi.annotations.ContributesMultibinding
98
import com.r0adkll.kimchi.util.buildFun
109
import com.r0adkll.kimchi.util.buildProperty
@@ -13,7 +12,6 @@ import com.r0adkll.kimchi.util.ksp.findMapKey
1312
import com.r0adkll.kimchi.util.ksp.findQualifier
1413
import com.r0adkll.kimchi.util.ksp.hasAnnotation
1514
import com.r0adkll.kimchi.util.ksp.pairTypeOf
16-
import com.r0adkll.kimchi.util.toClassName
1715
import com.squareup.kotlinpoet.ClassName
1816
import com.squareup.kotlinpoet.FunSpec
1917
import com.squareup.kotlinpoet.PropertySpec
@@ -135,7 +133,7 @@ private fun TypeSpec.Builder.addProvidesFunction(
135133

136134
private fun TypeSpec.Builder.addMappingProvidesFunction(
137135
boundClass: KSClassDeclaration,
138-
boundType: KSDeclaration,
136+
boundType: KSClassDeclaration,
139137
mapKey: Any,
140138
isBindable: Boolean,
141139
) {

0 commit comments

Comments
 (0)