@@ -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