Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.ksp.common

import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSNode
import com.intellij.psi.PsiJavaFile
import org.jetbrains.kotlin.analysis.api.symbols.KaSymbol
import org.jetbrains.kotlin.analysis.api.types.KaType
import java.io.File

/**
* Incremental-processing operations used by symbol processing and resolution.
*
* Cache storage, lookup trackers, and logging implementation details are deliberately excluded,
* so callers do not depend on a particular incremental-context implementation.
*/
interface IncrementalContext {
fun calcDirtyFiles(ksFiles: List<KSFile>): Collection<KSFile>

fun registerGeneratedFiles(newFiles: Collection<KSFile>)

fun <T> closeFilesOnException(f: () -> T): T

fun closeFiles()

fun updateCachesAndOutputs(
dirtyFiles: Collection<KSFile>,
outputs: Set<File>,
sourceToOutputs: Map<File, Set<File>>,
)

fun recordLookup(psiFile: PsiJavaFile, fqn: String)

fun recordLookup(type: KaType, context: KSNode?)

fun recordClassReferenceLookup(type: KaType, declaration: KSDeclaration)

fun recordClassReferenceLookup(fqn: String, declaration: KSDeclaration)

fun recordLookupForPropertyOrMethod(declaration: KSDeclaration)

fun recordLookupForGetAll(supers: List<KaType>, predicate: (KaSymbol) -> Boolean)

fun recordLookupWithSupertypes(
ktType: KaType,
visited: MutableSet<KaType>,
extra: (KaType, PsiJavaFile) -> Unit,
)

fun recordGetSealedSubclasses(classDeclaration: KSClassDeclaration)

/** Lookup traces used by existing debug logging and incremental-processing tests. */
fun dumpLookupRecords(): Map<String, List<String>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ abstract class IncrementalContextBase(
protected val knownModified: List<File>,
protected val knownRemoved: List<File>,
protected val changedClasses: List<String>,
) {
) : IncrementalContext {
// Symbols defined in changed files. This is used to update symbolsMap in the end.
protected val updatedSymbols = MultiMap.createSet<File, LookupSymbolWrapper>()

Expand Down Expand Up @@ -226,7 +226,7 @@ abstract class IncrementalContextBase(
}

// Beware: no side-effects here; Caches should only be touched in updateCaches.
fun calcDirtyFiles(ksFiles: List<KSFile>): Collection<KSFile> = closeFilesOnException {
override fun calcDirtyFiles(ksFiles: List<KSFile>): Collection<KSFile> = closeFilesOnException {
if (!isIncremental) {
return@closeFilesOnException ksFiles
}
Expand Down Expand Up @@ -412,14 +412,14 @@ abstract class IncrementalContextBase(
sealedMap.flush()
}

fun registerGeneratedFiles(newFiles: Collection<KSFile>) = closeFilesOnException {
override fun registerGeneratedFiles(newFiles: Collection<KSFile>) = closeFilesOnException {
if (!isIncremental)
return@closeFilesOnException

collectDefinedSymbols(newFiles)
}

fun <T> closeFilesOnException(f: () -> T): T {
override fun <T> closeFilesOnException(f: () -> T): T {
try {
return f()
} catch (e: Exception) {
Expand All @@ -428,7 +428,7 @@ abstract class IncrementalContextBase(
}
}

fun closeFiles() {
override fun closeFiles() {
symbolsMap.flush()
onDemandImportsCache.clear()
sealedMap.flush()
Expand All @@ -439,7 +439,7 @@ abstract class IncrementalContextBase(
}

// TODO: add a wildcard for outputs with no source and get rid of the outputs parameter.
fun updateCachesAndOutputs(
override fun updateCachesAndOutputs(
dirtyFiles: Collection<KSFile>,
outputs: Set<File>,
sourceToOutputs: Map<File, Set<File>>,
Expand Down Expand Up @@ -516,7 +516,7 @@ abstract class IncrementalContextBase(
* - If `sym` is an on-demand import, then `sym` may invalidate the file.
* - If `sym` is an implicit import, i.e., from the same package, then `sym` may invalidate the file.
*/
fun recordLookup(psiFile: PsiJavaFile, fqn: String) {
override fun recordLookup(psiFile: PsiJavaFile, fqn: String) {
if (!isIncremental)
return

Expand Down Expand Up @@ -552,7 +552,7 @@ abstract class IncrementalContextBase(
?.filter { it.isOnDemand }
?: emptyList()

fun recordGetSealedSubclasses(classDeclaration: KSClassDeclaration) {
override fun recordGetSealedSubclasses(classDeclaration: KSClassDeclaration) {
if (!isIncremental)
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class IncrementalContextAA(
override val classLookupCache = LookupStorageWrapperImpl(LookupStorage(classLookupCacheDir, icContext))

// Debugging and testing only.
fun dumpLookupRecords(): Map<String, List<String>> {
override fun dumpLookupRecords(): Map<String, List<String>> {
val map = mutableMapOf<String, List<String>>()
symbolLookupTracker.lookups.entrySet().forEach { e ->
val key = "${e.key.scope}.${e.key.name}"
Expand Down Expand Up @@ -175,7 +175,7 @@ class IncrementalContextAA(
}
}

fun recordLookup(type: KaType, context: KSNode?) {
override fun recordLookup(type: KaType, context: KSNode?) {
if (!isIncremental)
return

Expand All @@ -191,7 +191,7 @@ class IncrementalContextAA(
* @param declaration the closest enclosing declaration where the reference occurs.
* Callers can use [KSNode.findParentDeclaration] to obtain the enclosing declaration.
*/
fun recordClassReferenceLookup(type: KaType, declaration: KSDeclaration) {
override fun recordClassReferenceLookup(type: KaType, declaration: KSDeclaration) {
if (!isIncremental) {
return
}
Expand All @@ -210,7 +210,7 @@ class IncrementalContextAA(
* @param declaration the closest enclosing declaration where the reference occurs.
* Callers can use [KSNode.findParentDeclaration] to obtain the enclosing declaration.
*/
fun recordClassReferenceLookup(fqn: String, declaration: KSDeclaration) {
override fun recordClassReferenceLookup(fqn: String, declaration: KSDeclaration) {
if (!isIncremental) {
return
}
Expand Down Expand Up @@ -260,7 +260,7 @@ class IncrementalContextAA(
}
}

fun recordLookupForPropertyOrMethod(declaration: KSDeclaration) {
override fun recordLookupForPropertyOrMethod(declaration: KSDeclaration) {
if (!isIncremental)
return

Expand All @@ -275,7 +275,7 @@ class IncrementalContextAA(
recordLookupForDeclaration(symbol, file)
}

internal fun recordLookupForGetAll(supers: List<KaType>, predicate: (KaSymbol) -> Boolean) {
override fun recordLookupForGetAll(supers: List<KaType>, predicate: (KaSymbol) -> Boolean) {
if (!isIncremental)
return

Expand Down Expand Up @@ -314,7 +314,11 @@ class IncrementalContextAA(
}
}

fun recordLookupWithSupertypes(ktType: KaType, visited: MutableSet<KaType>, extra: (KaType, PsiJavaFile) -> Unit) {
override fun recordLookupWithSupertypes(
ktType: KaType,
visited: MutableSet<KaType>,
extra: (KaType, PsiJavaFile) -> Unit,
) {
if (!isIncremental)
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
package com.google.devtools.ksp.impl

import com.google.devtools.ksp.common.AnyChanges
import com.google.devtools.ksp.common.IncrementalContext
import com.google.devtools.ksp.common.KSObjectCacheManager
import com.google.devtools.ksp.common.impl.AAResolutionStrategy
import com.google.devtools.ksp.common.impl.CodeGeneratorImpl
Expand Down Expand Up @@ -556,7 +557,7 @@ class KotlinSymbolProcessing(
)

val dualLookupTracker = DualLookupTracker()
val incrementalContext = IncrementalContextAA(
val incrementalContext: IncrementalContext = IncrementalContextAA(
kspConfig.incremental,
dualLookupTracker,
File(anyChangesWildcard.filePath).relativeTo(kspConfig.projectBaseDir),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.devtools.ksp.InternalKSPException
import com.google.devtools.ksp.InternalKSPException.Companion.render
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.closestClassDeclaration
import com.google.devtools.ksp.common.IncrementalContext
import com.google.devtools.ksp.common.JVM_DEFAULT_ANNOTATION_FQN
import com.google.devtools.ksp.common.JVM_DEFAULT_WITHOUT_COMPATIBILITY_ANNOTATION_FQN
import com.google.devtools.ksp.common.JVM_STATIC_ANNOTATION_FQN
Expand Down Expand Up @@ -157,7 +158,7 @@ import org.jetbrains.org.objectweb.asm.Opcodes
class ResolverAAImpl(
val allKSFiles: List<KSFile>,
val project: Project,
val incrementalContext: IncrementalContextAA,
val incrementalContext: IncrementalContext,
val resolutionStrategy: AnnotationResolutionStrategy,
val processorsRegisteredForNewFeatures: Set<SymbolProcessor>,
) : Resolver {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.devtools.ksp.test

import com.google.devtools.ksp.IncrementalContextLoggingOptions
import com.google.devtools.ksp.common.IncrementalContext
import com.google.devtools.ksp.impl.DualLookupTracker
import com.google.devtools.ksp.impl.IncrementalContextAA
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertSame
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File

class IncrementalContextTest {
@TempDir
lateinit var baseDir: File

@Test
fun incrementalLifecycleThroughInterface() {
val context = createContext(incremental = true)
assertTrue(context.calcDirtyFiles(emptyList()).isEmpty())
context.registerGeneratedFiles(emptyList())
assertTrue(context.dumpLookupRecords().isEmpty())
context.updateCachesAndOutputs(emptyList(), emptySet(), emptyMap())
assertTrue(File(baseDir, "caches/caches.uptodate").exists())
}

@Test
fun nonIncrementalLifecycleThroughInterface() {
val context = createContext(incremental = false)
assertTrue(context.calcDirtyFiles(emptyList()).isEmpty())
context.registerGeneratedFiles(emptyList())
assertTrue(context.dumpLookupRecords().isEmpty())
context.updateCachesAndOutputs(emptyList(), emptySet(), emptyMap())
assertFalse(File(baseDir, "caches/caches.uptodate").exists())
context.closeFiles()
}

@Test
fun exceptionHandlingThroughInterface() {
val context = createContext(incremental = false)
assertEquals("result", context.closeFilesOnException { "result" })
val failure = IllegalStateException("processing failed")
val thrown = assertThrows(IllegalStateException::class.java) {
context.closeFilesOnException { throw failure }
}
assertSame(failure, thrown)
}

private fun createContext(incremental: Boolean): IncrementalContext = IncrementalContextAA(
isIncremental = incremental,
lookupTracker = DualLookupTracker(),
anyChangesWildcard = File("<AnyChanges>"),
loggingOptions = IncrementalContextLoggingOptions(false, null),
baseDir = baseDir,
cachesDir = File(baseDir, "caches").apply { mkdirs() },
kspOutputDir = File(baseDir, "output").apply { mkdirs() },
knownModified = emptyList(),
knownRemoved = emptyList(),
changedClasses = emptyList(),
)
}
Loading