Skip to content

Commit b4ed4bc

Browse files
Fix KSP2-generated Java files not visible to Kotlin compiler (#394)
Co-authored-by: Mark Zaiko <[email protected]> Co-authored-by: Zac Sweers <[email protected]>
1 parent e8ce720 commit b4ed4bc

File tree

4 files changed

+127
-1
lines changed

4 files changed

+127
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Changelog
44
**Unreleased**
55
--------------
66

7+
- **Fix**: Feed generated `.java` files from KSP into the subsequent kotlin compilation.
78
- Build against KSP `2.3.0`.
89

910
0.10.1

core/src/main/kotlin/com/tschuchort/compiletesting/AbstractKotlinCompilation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ abstract class AbstractKotlinCompilation<A : CommonCompilerArguments> internal c
206206
}
207207

208208
// Update sources to include any generated in a precursor tool
209-
val generatedSourceFiles = extraGeneratedSources.flatMap(File::listFilesRecursively).filter(File::hasKotlinFileExtension)
209+
val generatedSourceFiles = extraGeneratedSources.flatMap(File::listFilesRecursively).filter(File::hasSourceFileExtension)
210210
.map { it.absoluteFile }
211211
val finalSources = sources + generatedSourceFiles
212212

core/src/main/kotlin/com/tschuchort/compiletesting/Utils.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ internal fun File.hasKotlinFileExtension() = hasFileExtension(listOf("kt", "kts"
3939

4040
internal fun File.hasJavaFileExtension() = hasFileExtension(listOf("java"))
4141

42+
internal fun File.hasSourceFileExtension() = hasFileExtension(listOf("kt", "java"))
43+
4244
internal fun File.hasFileExtension(extensions: List<String>)
4345
= extensions.any{ it.equals(extension, ignoreCase = true) }
4446

ksp/src/test/kotlin/com/tschuchort/compiletesting/KspTest.kt

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -723,6 +723,129 @@ class KspTest {
723723
assertThat(result.classLoader.loadClass("foo.bar.AppCodeDummyKt")).isNotNull()
724724
}
725725

726+
// Regression test: ensures KSP-generated Java files are visible to Kotlin compiler.
727+
// Previously failed with "Unresolved reference" when Kotlin code imported generated Java classes.
728+
@Test
729+
fun `KSP2 cross-language generation - Kotlin imports generated Java`() {
730+
val annotation =
731+
kotlin(
732+
"TestAnnotation.kt",
733+
"""
734+
package com.example
735+
annotation class GenerateProcessor
736+
"""
737+
.trimIndent(),
738+
)
739+
val targetClass =
740+
kotlin(
741+
"InputClass.kt",
742+
"""
743+
package com.example
744+
@GenerateProcessor
745+
class MyInput
746+
"""
747+
.trimIndent(),
748+
)
749+
750+
val result =
751+
newCompilation()
752+
.apply {
753+
sources = listOf(annotation, targetClass)
754+
symbolProcessorProviders += SymbolProcessorProvider { env ->
755+
object : AbstractTestSymbolProcessor(env.codeGenerator) {
756+
override fun process(resolver: Resolver): List<KSAnnotated> {
757+
val symbols = resolver.getSymbolsWithAnnotation("com.example.GenerateProcessor").toList()
758+
759+
if (symbols.isNotEmpty()) {
760+
env.codeGenerator
761+
.createNewFile(
762+
dependencies = Dependencies.ALL_FILES,
763+
packageName = "com.example.generated",
764+
fileName = "MyProcessor",
765+
extensionName = "java",
766+
)
767+
.bufferedWriter()
768+
.use { writer ->
769+
// language=JAVA
770+
writer.write(
771+
"""
772+
package com.example.generated;
773+
774+
public class MyProcessor {
775+
private final String name;
776+
777+
public MyProcessor(String name) {
778+
this.name = name;
779+
}
780+
781+
public void process() {
782+
System.out.println("Processing: " + name);
783+
}
784+
785+
public String getName() {
786+
return name;
787+
}
788+
}
789+
"""
790+
.trimIndent()
791+
)
792+
}
793+
794+
env.codeGenerator
795+
.createNewFile(
796+
dependencies = Dependencies.ALL_FILES,
797+
packageName = "com.example.generated",
798+
fileName = "MyExtensions",
799+
extensionName = "kt",
800+
)
801+
.bufferedWriter()
802+
.use { writer ->
803+
// language=KOTLIN
804+
writer.write(
805+
"""
806+
package com.example.generated
807+
808+
// This import would fail with "Unresolved reference: MyProcessor"
809+
// if Java files are not included in finalSources
810+
import com.example.generated.MyProcessor
811+
812+
fun createProcessor(name: String): MyProcessor {
813+
return MyProcessor(name)
814+
}
815+
816+
fun MyProcessor.execute() {
817+
process()
818+
println("Executed processor: " + name)
819+
}
820+
"""
821+
.trimIndent()
822+
)
823+
}
824+
}
825+
826+
return emptyList()
827+
}
828+
}
829+
}
830+
}
831+
.compile()
832+
833+
assertThat(result.exitCode).isEqualTo(ExitCode.OK)
834+
835+
assertThat(result.classLoader.loadClass("com.example.generated.MyProcessor")).isNotNull()
836+
assertThat(result.classLoader.loadClass("com.example.generated.MyExtensionsKt")).isNotNull()
837+
838+
val processorClass = result.classLoader.loadClass("com.example.generated.MyProcessor")
839+
val constructor = processorClass.getConstructor(String::class.java)
840+
val instance = constructor.newInstance("test-processor")
841+
val getNameMethod = processorClass.getMethod("getName")
842+
assertThat(getNameMethod.invoke(instance)).isEqualTo("test-processor")
843+
844+
val extensionsClass = result.classLoader.loadClass("com.example.generated.MyExtensionsKt")
845+
val createProcessorMethod = extensionsClass.getMethod("createProcessor", String::class.java)
846+
val createdInstance = createProcessorMethod.invoke(null, "kotlin-created")
847+
assertThat(getNameMethod.invoke(createdInstance)).isEqualTo("kotlin-created")
848+
}
726849

727850
@Test
728851
fun `can filter messages by severity`() {

0 commit comments

Comments
 (0)