Skip to content
Merged
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
Expand Up @@ -217,6 +217,10 @@ abstract class BaseProcessor(val kspEnvironment: SymbolProcessorEnvironment? = n
// for reuse.
val memoizer = Memoizer(environment, logger)

// Clear resource scanner caches to avoid holding stale KSP element references.
// In KSP2, accessing elements from previous rounds triggers "PSI has changed since creation" errors.
resourceProcessor.clearCachesForNewRound()

val deferredElements: List<XElement> = try {
tryOrPrintError<List<XElement>?> {
timer.markStepCompleted("round initialization")
Expand All @@ -241,7 +245,8 @@ abstract class BaseProcessor(val kspEnvironment: SymbolProcessorEnvironment? = n
// TODO: Potentially generate a single file per model to allow for an isolating processor
kotlinExtensionWriter.generateExtensionsForModels(
generatedModels,
processorName
processorName,
memoizer
)
timer.markStepCompleted("generateKotlinExtensions")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,5 +89,5 @@ internal class BasicGeneratedModelInfo(
)
}

override fun additionalOriginatingElements(): List<XElement> = listOf(superClassElement)
override fun additionalOriginatingElements(currentMemoizer: Memoizer): List<XElement> = listOf(safeSuperClassElement(currentMemoizer))
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,14 @@ class ConfigManager internal constructor(
)
}

fun getDefaultBaseModel(viewElement: XTypeElement): XType? {
return getModelViewConfig(viewElement)?.defaultBaseModel
fun getDefaultBaseModel(viewElement: XTypeElement, currentEnvironment: XProcessingEnv): XType? {
val className = getModelViewConfig(viewElement)?.defaultBaseModelClassName
?: return null
// Resolve the type fresh each round using the current environment to avoid
// accessing stale KSP elements from previous rounds.
// IMPORTANT: Use the currentEnvironment parameter, not the instance field,
// since the instance field was captured at ConfigManager creation time (round 1).
return currentEnvironment.findType(className)
}

fun includeAlternateLayoutsForViews(viewElement: XTypeElement): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ internal class DataBindingAttributeInfo(

init {
fieldName = removeSetPrefix(setterMethod.name)
setXType(setterMethod.parameters[0].type, modelInfo.memoizer)
// Use the memoizer parameter from the current round, not modelInfo.memoizer.
// modelInfo may have been created in a previous round and its memoizer holds stale KSP references.
setXType(setterMethod.parameters[0].type, memoizer)
rootClass = modelInfo.generatedName.simpleName()
packageName = modelInfo.generatedName.packageName()
useInHash = !modelInfo.enableDoNotHash ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,6 @@ internal class DataBindingModelInfo(
)
}

override fun additionalOriginatingElements() =
override fun additionalOriginatingElements(currentMemoizer: Memoizer) =
listOfNotNull(annotatedElement, dataBindingClassElement)
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ class DataBindingProcessor @JvmOverloads constructor(
bindingModelInfo.parseDataBindingClass(logger) ?: return@filter false
createModelWriter(memoizer).generateClassForModel(
bindingModelInfo,
originatingElements = bindingModelInfo.originatingElements()
originatingElements = bindingModelInfo.originatingElements(memoizer)
)
true
}.also { writtenModels ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class EpoxyProcessor @JvmOverloads constructor(
val styleableModels = modelInfos
.filterIsInstance<BasicGeneratedModelInfo>()
.filter { modelInfo ->
modelInfo.superClassElement.getAnnotation(EpoxyModelClass::class)?.getAsInt("layout") == 0 &&
modelInfo.safeSuperClassElement(memoizer).getAnnotation(EpoxyModelClass::class)?.getAsInt("layout") == 0 &&
modelInfo.boundObjectTypeElement?.hasStyleableAnnotation() == true
}
timer.markStepCompleted("check for styleable models")
Expand Down Expand Up @@ -136,7 +136,7 @@ class EpoxyProcessor @JvmOverloads constructor(
private fun writeModel(modelInfo: GeneratedModelInfo, memoizer: Memoizer) {
createModelWriter(memoizer).generateClassForModel(
modelInfo,
originatingElements = modelInfo.originatingElements()
originatingElements = modelInfo.originatingElements(memoizer)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ import javax.lang.model.element.Modifier
abstract class GeneratedModelInfo(val memoizer: Memoizer) {

lateinit var superClassElement: XTypeElement

/**
* Looks up and returns the super class element by FQN, to avoid errors from accessing
* an XTypeElement from a previous processing round.
*
* @param currentMemoizer The memoizer from the current processing round. Must be passed
* explicitly to ensure we use a fresh environment, not the stale one stored in `this.memoizer`.
*/
fun safeSuperClassElement(currentMemoizer: Memoizer): XTypeElement {
return currentMemoizer.environment.requireTypeElement(superClassElement.qualifiedName)
}

lateinit var superClassName: TypeName
lateinit var parameterizedGeneratedName: TypeName
lateinit var generatedName: ClassName
Expand Down Expand Up @@ -64,13 +76,15 @@ abstract class GeneratedModelInfo(val memoizer: Memoizer) {
/**
* The elements that influence the generation of this model.
* eg base model class for @EpoxyModelClass, view class for @ModelView, etc
*
* @param currentMemoizer The memoizer from the current processing round.
*/
fun originatingElements(): List<XElement> {
fun originatingElements(currentMemoizer: Memoizer): List<XElement> {
return listOfNotNull(styleBuilderInfo?.styleBuilderElement)
.plus(additionalOriginatingElements())
.plus(additionalOriginatingElements(currentMemoizer))
}

open fun additionalOriginatingElements(): List<XElement> = emptyList()
open fun additionalOriginatingElements(currentMemoizer: Memoizer): List<XElement> = emptyList()

/**
* Get information about constructors of the original class so we can duplicate them in the
Expand All @@ -86,7 +100,7 @@ abstract class GeneratedModelInfo(val memoizer: Memoizer) {
*/
fun collectMethodsReturningClassType(superModelClass: XTypeElement) {
methodsReturningClassType
.addAll(memoizer.getMethodsReturningClassType(superModelClass.type, memoizer))
.addAll(memoizer.getMethodsReturningClassType(superModelClass.qualifiedName, memoizer))
}

@Synchronized
Expand Down Expand Up @@ -152,8 +166,10 @@ abstract class GeneratedModelInfo(val memoizer: Memoizer) {
* @return True if the super class of this generated model is also extended from a generated
* model.
*/
val isSuperClassAlsoGenerated: Boolean
get() = superClassElement.type.isSubTypeOf(memoizer.generatedModelType)
fun isSuperClassAlsoGenerated(currentMemoizer: Memoizer): Boolean {
val generatedModelType = currentMemoizer.generatedModelType
return safeSuperClassElement(currentMemoizer).type.isSubTypeOf(generatedModelType)
}

data class ConstructorInfo internal constructor(
val modifiers: Set<Modifier>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.airbnb.epoxy.processor
import androidx.annotation.LayoutRes
import androidx.room.compiler.processing.XElement
import androidx.room.compiler.processing.XFiler
import androidx.room.compiler.processing.XMethodElement
import androidx.room.compiler.processing.XProcessingEnv
import androidx.room.compiler.processing.XTypeElement
import androidx.room.compiler.processing.addOriginatingElement
Expand Down Expand Up @@ -161,7 +160,7 @@ class GeneratedModelWriter(

builderHooks?.beforeFinalBuild(this)

addSuperinterface(modelInterfaceWriter.writeInterface(info, this.build().methodSpecs))
addSuperinterface(modelInterfaceWriter.writeInterface(info, this.build().methodSpecs, memoizer))

originatingElements.forEach {
addOriginatingElement(it)
Expand Down Expand Up @@ -608,7 +607,7 @@ class GeneratedModelWriter(
addParameter(boundObjectParam)
addParameter(TypeName.INT, "position")

if (modelInfo.isSuperClassAlsoGenerated) {
if (modelInfo.isSuperClassAlsoGenerated(memoizer)) {
// If a super class is also generated we need to make sure to call through to these
// methods on it as well. This is particularly important for EpoxyModelGroup.
addStatement("super.handlePostBind(\$L, position)", boundObjectParam.name)
Expand Down Expand Up @@ -836,8 +835,8 @@ class GeneratedModelWriter(
// bind!!! So we mustn't do that. So, we only call the super diff binding if we think
// it's a custom implementation.
if (modelImplementsBindWithDiff(
classInfo.superClassElement,
memoizer.baseBindWithDiffMethod
classInfo.safeSuperClassElement(memoizer),
memoizer
)
) {
addStatement(
Expand Down Expand Up @@ -870,7 +869,7 @@ class GeneratedModelWriter(
.addParameter(boundObjectParam)
.addParameter(TypeName.INT, positionParamName, Modifier.FINAL)

if (modelInfo.isSuperClassAlsoGenerated) {
if (modelInfo.isSuperClassAlsoGenerated(memoizer)) {
// If a super class is also generated we need to make sure to call through to these
// methods on it as well. This is particularly important for EpoxyModelGroup.
preBindBuilder.addStatement(
Expand Down Expand Up @@ -1103,7 +1102,7 @@ class GeneratedModelWriter(
methods: MutableList<MethodSpec>
) {

val originalClassElement = modelClassInfo.superClassElement
val originalClassElement = modelClassInfo.safeSuperClassElement(memoizer)
if (!originalClassElement.type.isEpoxyModelWithHolder(memoizer)) {
return
}
Expand Down Expand Up @@ -1179,7 +1178,7 @@ class GeneratedModelWriter(
return modelInfo.getLayoutResource(resourceProcessor)
}

val superClassElement = modelInfo.superClassElement
val superClassElement = modelInfo.safeSuperClassElement(memoizer)
if (implementsMethod(superClassElement, buildDefaultLayoutMethodBase(), environment)) {
return null
}
Expand All @@ -1206,7 +1205,7 @@ class GeneratedModelWriter(
* variables that changed.
*/
private fun generateDataBindingMethodsIfNeeded(info: GeneratedModelInfo): Iterable<MethodSpec> {
if (!info.superClassElement.type.isDataBindingEpoxyModel(memoizer)) {
if (!info.safeSuperClassElement(memoizer).type.isDataBindingEpoxyModel(memoizer)) {
return emptyList()
}

Expand All @@ -1222,7 +1221,7 @@ class GeneratedModelWriter(

// If the base method is already implemented don't bother checking for the payload method
if (implementsMethod(
info.superClassElement,
info.safeSuperClassElement(memoizer),
bindVariablesMethod,
environment
)
Expand All @@ -1233,7 +1232,7 @@ class GeneratedModelWriter(
val generatedModelClass = info.generatedName

val moduleName = (info as? DataBindingModelInfo)?.moduleName
?: dataBindingModuleLookup.getModuleName(info.superClassElement)
?: dataBindingModuleLookup.getModuleName(info.safeSuperClassElement(memoizer))

val baseMethodBuilder = bindVariablesMethod.toBuilder()

Expand Down Expand Up @@ -2102,14 +2101,20 @@ class GeneratedModelWriter(

fun modelImplementsBindWithDiff(
clazz: XTypeElement,
baseBindWithDiffMethod: XMethodElement
currentMemoizer: Memoizer
): Boolean {
return clazz.getAllMethods().any {
// Re-resolve the type element using the current memoizer's environment to avoid
// accessing stale KSP elements from previous rounds. In KSP2, this triggers
// "PSI has changed since creation" errors.
val freshClazz = currentMemoizer.environment.requireType(clazz.qualifiedName).typeElement!!
val baseBindWithDiffMethod = currentMemoizer.baseBindWithDiffMethod

return freshClazz.getAllMethods().any {
it.name == baseBindWithDiffMethod.name &&
!it.isAbstract() &&
it.overrides(
other = baseBindWithDiffMethod,
owner = clazz
owner = freshClazz
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ internal class KotlinModelBuilderExtensionWriter(

fun generateExtensionsForModels(
generatedModels: List<GeneratedModelInfo>,
processorName: String
processorName: String,
memoizer: Memoizer
) {
generatedModels
.filter { it.shouldGenerateModel }
Expand All @@ -29,7 +30,8 @@ internal class KotlinModelBuilderExtensionWriter(
buildExtensionFile(
packageName,
models,
processorName
processorName,
memoizer
)
}.forEach("writeExtensionsForModels", parallel = false) {
// Cannot be done in parallel since filer is not thread safe
Expand All @@ -40,7 +42,8 @@ internal class KotlinModelBuilderExtensionWriter(
private fun buildExtensionFile(
packageName: String,
models: List<GeneratedModelInfo>,
processorName: String
processorName: String,
memoizer: Memoizer
): FileSpec {
val fileBuilder = FileSpec.builder(
packageName,
Expand All @@ -49,10 +52,10 @@ internal class KotlinModelBuilderExtensionWriter(

models.map {
if (it.constructors.isEmpty()) {
listOf(buildExtensionsForModel(it, null))
listOf(buildExtensionsForModel(it, null, memoizer))
} else {
it.constructors.map { constructor ->
buildExtensionsForModel(it, constructor)
buildExtensionsForModel(it, constructor, memoizer)
}
}
}
Expand All @@ -76,7 +79,8 @@ internal class KotlinModelBuilderExtensionWriter(

private fun buildExtensionsForModel(
model: GeneratedModelInfo,
constructor: GeneratedModelInfo.ConstructorInfo?
constructor: GeneratedModelInfo.ConstructorInfo?,
memoizer: Memoizer
): FunSpec {
val constructorIsNotPublic =
constructor != null && Modifier.PUBLIC !in constructor.modifiers
Expand Down Expand Up @@ -118,7 +122,7 @@ internal class KotlinModelBuilderExtensionWriter(
endControlFlow()
addStatement(")")

model.originatingElements().forEach {
model.originatingElements(memoizer).forEach {
addOriginatingElement(it)
}
return build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,16 @@ class Memoizer(

private val methodsReturningClassType = mutableMapOf<String, Set<MethodInfo>>()

fun getMethodsReturningClassType(classType: XType, memoizer: Memoizer): Set<MethodInfo> {
fun getMethodsReturningClassType(classQualifiedName: String, memoizer: Memoizer): Set<MethodInfo> {
// Re-resolve the type fresh using the current memoizer's environment.
// The caller may have been created in a previous round, and accessing properties
// of stale KSP types in KSP2 triggers "PSI has changed since creation" errors.
// By passing the qualified name as a String and resolving fresh each time,
// we ensure we always use current-round types.
val classType = memoizer.environment.requireType(classQualifiedName)
val classElement = classType.typeElement!!
return methodsReturningClassType.getOrPut(classElement.qualifiedName) {

return methodsReturningClassType.getOrPut(classQualifiedName) {

val methodInfos: List<MethodInfo> =
classElement.getDeclaredMethods().mapNotNull { subElement ->
Expand Down Expand Up @@ -164,8 +171,9 @@ class Memoizer(
// Note: Adding super type methods second preserves any overloads in the base
// type that may have changes (ie, a new return type or annotation), since
// Set.plus only adds items that don't already exist.
val superClassType = classElement.superClass ?: return@getOrPut emptySet()
methodInfos.toSet() + getMethodsReturningClassType(superClassType, memoizer)
val superClassElement = classElement.superClass?.typeElement
?: return@getOrPut emptySet()
methodInfos.toSet() + getMethodsReturningClassType(superClassElement.qualifiedName, memoizer)
}
}

Expand Down Expand Up @@ -380,12 +388,12 @@ class Memoizer(
private val implementsModelCollectorMap = mutableMapOf<String, Boolean>()
fun implementsModelCollector(classElement: XTypeElement): Boolean {
return implementsModelCollectorMap.getOrPut(classElement.qualifiedName) {
classElement.getSuperInterfaceElements().any {
it.type.isEpoxyModelCollector(this)
} || classElement.superClass?.typeElement?.let { superClassElement ->
// Also check the class hierarchy
implementsModelCollector(superClassElement)
} ?: false
classElement.getSuperInterfaceElements().any {
it.type.isEpoxyModelCollector(this)
} || classElement.superClass?.typeElement?.let { superClassElement ->
// Also check the class hierarchy
implementsModelCollector(superClassElement)
} ?: false
}
}

Expand Down
Loading
Loading