From c178298ff4d3249c416cc5458418bf1cd5f11239 Mon Sep 17 00:00:00 2001 From: Andreas Rossbacher Date: Wed, 8 Apr 2026 17:13:00 -0700 Subject: [PATCH] Fix flaky KSP error for @DeepLink methods in Kotlin objects XProcessing's isStatic() for KSP relies on annotation type resolution to detect @JvmStatic. This can intermittently fail during incremental compilation with KSP2's Analysis API, causing methods in Kotlin object declarations to appear non-static. Add a fallback check in verifyMethod() that also accepts methods whose enclosing type is a Kotlin object, since all methods in objects are effectively static in JVM bytecode regardless of @JvmStatic. --- .../deeplinkdispatch/DeepLinkProcessor.kt | 10 ++- .../DeepLinkProcessorKspTest.kt | 70 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/deeplinkdispatch-processor/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkProcessor.kt b/deeplinkdispatch-processor/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkProcessor.kt index 792df3ee..16f90ab7 100644 --- a/deeplinkdispatch-processor/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkProcessor.kt +++ b/deeplinkdispatch-processor/src/main/java/com/airbnb/deeplinkdispatch/DeepLinkProcessor.kt @@ -344,7 +344,15 @@ class DeepLinkProcessor( private fun validClassElement(classElement: XTypeElement) = classElement.isActivity() || classElement.isHandler() private fun verifyMethod(methodElement: XMethodElement) { - if (!methodElement.isStatic()) { + // XProcessing's isStatic() for KSP relies on annotation type resolution to detect + // @JvmStatic. This resolution can intermittently fail during incremental compilation + // with KSP2's Analysis API, causing isStatic() to return false for methods that are + // actually @JvmStatic inside a Kotlin object. As a workaround, we also accept methods + // whose enclosing type is a Kotlin object or companion object, since all methods in + // these types are effectively static in the JVM bytecode. + val enclosingElement = methodElement.enclosingElement + val isInKotlinObject = enclosingElement is XTypeElement && enclosingElement.isKotlinObject() + if (!methodElement.isStatic() && !isInKotlinObject) { throw DeepLinkProcessorException( element = methodElement, errorMessage = "Only static methods can be annotated with @${DEEP_LINK_CLASS.simpleName}", diff --git a/deeplinkdispatch-processor/src/test/java/com/airbnb/deeplinkdispatch/DeepLinkProcessorKspTest.kt b/deeplinkdispatch-processor/src/test/java/com/airbnb/deeplinkdispatch/DeepLinkProcessorKspTest.kt index 0b95ead3..25b13f01 100644 --- a/deeplinkdispatch-processor/src/test/java/com/airbnb/deeplinkdispatch/DeepLinkProcessorKspTest.kt +++ b/deeplinkdispatch-processor/src/test/java/com/airbnb/deeplinkdispatch/DeepLinkProcessorKspTest.kt @@ -1449,4 +1449,74 @@ class DeepLinkProcessorKspTest : BaseDeepLinkProcessorTest() { ) } } + + /** + * Verifies that methods inside a Kotlin `object` with @JvmStatic compile successfully. + * This exercises the isInKotlinObject fallback in verifyMethod(), which works around + * intermittent KSP annotation resolution failures where isStatic() returns false + * for @JvmStatic methods in object declarations. + */ + @Test + fun testMethodInKotlinObjectWithJvmStaticCompiles() { + val kotlinObjectSource = + Source.KotlinSource( + "com/example/SampleDeepLinks.kt", + """ + package com.example + import android.content.Context + import android.content.Intent + import com.airbnb.deeplinkdispatch.DeepLink + + object SampleDeepLinks { + @DeepLink("airbnb://example.com/deepLink") + @JvmStatic + fun intentForDeepLink(context: Context): Intent = Intent() + } + """, + ) + val results = + listOf( + compileIncremental( + sourceFiles = listOf(kotlinObjectSource, module, fakeBaseDeeplinkDelegateJava), + useKsp = true, + ), + ) + results.forEach { result -> + assertThat(result.result.exitCode) + .isEqualTo(KotlinCompilation.ExitCode.OK) + } + } + + /** + * Verifies that a non-static method (no @JvmStatic) in a plain class still fails. + */ + @Test + fun testNonStaticMethodInClassStillFails() { + val nonStaticSource = + Source.KotlinSource( + "com/example/SampleActivity.kt", + """ + package com.example + import android.content.Context + import android.content.Intent + import com.airbnb.deeplinkdispatch.DeepLink + + class SampleActivity : android.app.Activity() { + @DeepLink("airbnb://example.com/deepLink") + fun intentFromNoStatic(context: Context): Intent = Intent() + } + """, + ) + val results = + listOf( + compileIncremental( + sourceFiles = listOf(nonStaticSource, module, fakeBaseDeeplinkDelegateJava), + useKsp = true, + ), + ) + assertCompileError( + results = results, + errorMessage = "Only static methods can be annotated", + ) + } }