Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synthetic classes for top level kotlin files #396

Closed
yigit opened this issue Apr 21, 2021 · 1 comment · Fixed by #402
Closed

Synthetic classes for top level kotlin files #396

yigit opened this issue Apr 21, 2021 · 1 comment · Fixed by #402

Comments

@yigit
Copy link
Collaborator

yigit commented Apr 21, 2021

I'm trying to figure out how to model top level properties in kotlin, specifically the synthetic classes generated for them.

From my testing, it seems like KSP's behavior is a bit inconsistent between pre-compiled code and source core.
Moreover, it fails to resolve the type of a java field if the type of it is inferred from a kotlin file.

see the sample test below:

@Test
    fun preCompiled2() {
        val libKotlin = Source.kotlin("lib/MyFile.kt",
        """
            package lib;
            fun myMethod() {}
        """.trimIndent())
        val libJava = Source.java("lib.MyJava",
        """
            package lib;
            public class MyJava {
                MyFileKt kotlinFileRef;
            }
        """.trimIndent())

        val mainKotlin = Source.kotlin("main/MyFile.kt",
            """
            package main;
            fun myMethod() {}
        """.trimIndent())
        val mainJava = Source.java("main.MyJava",
            """
            package main;
            public class MyJava {
                MyFileKt kotlinFileRef;
            }
        """.trimIndent())
        val classpath = listOf(compileFiles(listOf(libJava, libKotlin)))
        runKspTest(
            sources = listOf(mainJava, mainKotlin),
            classpath = classpath
        ) { invocation ->
            val resolver = invocation.kspResolver
            val readings = listOf("lib", "main").map { pkg ->
                val firstReadOfKotlinFile = resolver.getClassDeclarationByName("$pkg.MyFileKt")
                // now read via java
                val javaClass = resolver.getClassDeclarationByName("$pkg.MyJava")!!
                val javaPropToKotlinFileClass = javaClass.declarations.first {
                    it.simpleName.asString() == "kotlinFileRef"
                } as KSPropertyDeclaration
                val declarationViaJava = javaPropToKotlinFileClass.type.resolve()
                val secondReadOfKotlinFile = resolver.getClassDeclarationByName("$pkg.MyFileKt")
                pkg to Triple(
                    first = firstReadOfKotlinFile,
                    second = declarationViaJava,
                    third = secondReadOfKotlinFile,
                )
            }
            println(readings)

        }
    }

This prints:

lib -> (null, (lib.MyFileKt..lib.MyFileKt?), null)
main -> (null, <ERROR TYPE>, null)

Basically, when a Java class in source references a KSFile synthetic class, it works if it is coming from a compiled dependency whereas KSP fails to resolve it if it is in source.
In both cases, getClassDeclarationByName fails to resolve the synthetic class.

It is possible that not finding it in getClassDeclarationByName can be considered a feature, not a bug. On the other hand, KSP should still be able to resolve those types when java files in source references them.

Moreover, supporting getClassDeclarationByName might also make sense to ease KAPT -> KSP transitions.

cc: @gfreivasc, @elihart

@yigit
Copy link
Collaborator Author

yigit commented Apr 26, 2021

a bit more information on this.

I don't think snythesizing a class in KSP is the right way to go because the KSType does not really exist and it is bound to explode in some cases (at least that is what happened when i tried to do it in Room).

I "think" a nicer solution might be to provide a method in KspResolver to get the qName for that synthetic class.
That way, java code that accesses those methods can be generated without adding classes that do not really exist in the KSP realm.

yigit added a commit to yigit/ksp that referenced this issue Apr 26, 2021
This PR adds a new Resolver API to get the binary class name for the
owner of a function / property declaration.
(resolver.getOwnerJvmClassName)

For top level functions/properties; this value is necessary to be able
to generate Java code.
An alternative solution would be to synthesize KSClassDeclaration for
these top level members but that carries over JVM limitations to KSP
which is not great in the long term. This solution instead provides an
API to unblock Java code generating KSP Processors without taking much of
technical debt for KMP.

Fixes: google#396
Test: topLevelMembers.kt
ting-yuan pushed a commit that referenced this issue Apr 26, 2021
This PR adds a new Resolver API to get the binary class name for the
owner of a function / property declaration.
(resolver.getOwnerJvmClassName)

For top level functions/properties; this value is necessary to be able
to generate Java code.
An alternative solution would be to synthesize KSClassDeclaration for
these top level members but that carries over JVM limitations to KSP
which is not great in the long term. This solution instead provides an
API to unblock Java code generating KSP Processors without taking much of
technical debt for KMP.

Fixes: #396
Test: topLevelMembers.kt
copybara-service bot pushed a commit to androidx/androidx that referenced this issue May 14, 2021
This CL adds support for top level functions/properties in KSP.

KSP does not generate synthetic class for files that include top
level methods or properites.
see: google/ksp#396

For XProcessing, i decided the follow through and not create a fake
XTypeElement for them. Instead, this CL introduces a new XElement
type called XMemberContainer. This interface defines two properties:
className:ClassName and type:XType.
ClassName is mandatory whereas XType is optional (to denote that it
does not have an accessible type in KSP but ClassName can be accessed
for code generation).
This was enough to make the rest of the codebase happy.

For Room, I also removed `XMethod/FieldElement.requireEnclosingTypeElement`
as we didn't really need it once we have className.

XMemberContainer might be extended to provide access to more declarations
but we don't yet have a use case for that.

By not modeling fake XTypeElements, we avoid creating a fake XType for it,
which prevents lots of pitfalls since the type does not really exist in
kotlin world.

Bug: 160322705
Fixes: 184526463
Test: XRoundEnvTest, TopLevelMembersTest
Change-Id: Iea60d2587c9373a96a56f7721d5ea2782b66ba2e
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant