diff --git a/python-multiplatform-ksp/fixtures/app/src/commonMain/kotlin/RootPackageDeclarations.kt b/python-multiplatform-ksp/fixtures/app/src/commonMain/kotlin/RootPackageDeclarations.kt new file mode 100644 index 000000000..700f762fd --- /dev/null +++ b/python-multiplatform-ksp/fixtures/app/src/commonMain/kotlin/RootPackageDeclarations.kt @@ -0,0 +1,6 @@ +// No `package` line on purpose (issue #319): a root-package declaration cannot be named from the +// generated fragments, so the processor skips it with a warning instead of breaking this build. + +fun rootPackageProbe(): Int = 7 + +class RootPackageThing diff --git a/python-multiplatform-ksp/fixtures/app/src/desktopTest/kotlin/fixture/app/RootPackageSkipTest.kt b/python-multiplatform-ksp/fixtures/app/src/desktopTest/kotlin/fixture/app/RootPackageSkipTest.kt new file mode 100644 index 000000000..60fa1776a --- /dev/null +++ b/python-multiplatform-ksp/fixtures/app/src/desktopTest/kotlin/fixture/app/RootPackageSkipTest.kt @@ -0,0 +1,22 @@ +package fixture.app + +import python.multiplatform.generated.FunctionTable +import kotlin.test.Test +import kotlin.test.assertTrue + +/** + * Issue #319: `RootPackageDeclarations.kt` has no package. Before the fix the processor emitted an + * unqualified `rootPackageProbe()` call inside `python.multiplatform.generated.fragments`, and this + * module did not compile. Now the root-package declarations are skipped (with a KSP warning), the + * module compiles, and neither appears in the generated table. + */ +class RootPackageSkipTest { + + @Test + fun rootPackageDeclarationsAreNotInTheTable() { + val names = FunctionTable.fragments.flatMap { fragment -> fragment.entries().map { it.name } + fragment.classes().map { it.toString() } } + assertTrue(names.isNotEmpty(), "the table is empty, so this asserts nothing") + val leaked = names.filter { it == "rootPackageProbe" || it.startsWith("RootPackageThing") || it.endsWith(".rootPackageProbe") } + assertTrue(leaked.isEmpty(), "root-package declarations reached the table: $leaked") + } +} diff --git a/python-multiplatform-ksp/src/main/kotlin/python/multiplatform/ksp/FragmentScanner.kt b/python-multiplatform-ksp/src/main/kotlin/python/multiplatform/ksp/FragmentScanner.kt index bdc559d52..e84b23fdb 100644 --- a/python-multiplatform-ksp/src/main/kotlin/python/multiplatform/ksp/FragmentScanner.kt +++ b/python-multiplatform-ksp/src/main/kotlin/python/multiplatform/ksp/FragmentScanner.kt @@ -91,6 +91,20 @@ class FragmentScanner(private val excludePackages: List, private val log val classes = mutableListOf() files.flatMap { it.declarations }.forEach { declaration -> + // Issue #319: Kotlin cannot name a root-package declaration from a named package, and + // every generated fragment and table lives in one, so a binding could only ever be an + // unresolved reference. Skipped with a warning instead of breaking the consumer's build. + if (declaration.packageName.asString().isEmpty()) { + if (declaration is KSFunctionDeclaration || declaration is KSPropertyDeclaration || declaration is KSClassDeclaration) { + logger.warn( + "python-multiplatform-ksp: ${declaration.simpleName.asString()} is in the root package " + + "and is not exposed to Python (generated code in a named package cannot refer to it); " + + "give its file a `package` declaration to expose it.", + declaration, + ) + } + return@forEach + } when (declaration) { is KSFunctionDeclaration -> if (BindingPolicy.isExposedTopLevelFunction(declaration, excludePackages) &&