mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IJPL-248901 identify ProjectModel code-insight contexts by symbolic id
ModuleContextImpl/LibraryContextImpl/SdkContextImpl based equals/hashCode (and resolution) on the raw EntityPointer. An EntityPointer's id encodes the entity's slot in the current WorkspaceModel storage (arrayId in the high 32 bits), so the same module/library/SDK resolved against two storage generations produces pointers that are not equal. After a full storage replace (common on the remote-dev backend during project-model sync) the editor's cached context and the file's recomputed context referred to the same module yet compared unequal, producing "PsiFile's context does not match the context of the editor" with two ModuleContextImpl values that differed only by EntityPointer id (e.g. 82 vs 4294967378 = 2^32 + 82 — same module class, different slot). Store the entity's stable symbolic id (ModuleId/LibraryId/SdkId) instead and use it for equals/hashCode and for resolution (storage.resolve(symbolicId)). Identity is now generation-independent, so the two sides compare equal again; as a side benefit contexts become stable map keys and resolution no longer breaks when a context outlives the storage generation it was created in. GitOrigin-RevId: f6638bc8feacbb0d3f54ee54efb88d20d46726ef
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d77fbccddc
commit
20d1b01a3e
+36
@@ -16,11 +16,18 @@ import com.intellij.codeInspection.LocalInspectionTool
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.multiverse.LibraryContextImpl
|
||||
import com.intellij.multiverse.ModuleContextImpl
|
||||
import com.intellij.multiverse.SdkContextImpl
|
||||
import com.intellij.openapi.module.ModuleType
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.DumbAware
|
||||
import com.intellij.openapi.project.rootManager
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryId
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryTableId
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleId
|
||||
import com.intellij.platform.workspace.jps.entities.SdkId
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import com.intellij.psi.PsiFile
|
||||
@@ -167,6 +174,35 @@ class MultiverseHighlightingTest : DaemonAnalyzerTestCase() {
|
||||
}
|
||||
}
|
||||
|
||||
// IJPL-248901: ProjectModel contexts must be identified by their stable symbolic id
|
||||
// (ModuleId / LibraryId / SdkId), NOT by the EntityPointer. An EntityPointer's id encodes the
|
||||
// entity's slot in the current WorkspaceModel storage (arrayId in the high 32 bits), so the same
|
||||
// module resolved against two storage generations (e.g. after a full storage replace on the
|
||||
// remote-dev backend) yields pointers that are not equal. That made the editor's context and the
|
||||
// file's context for the same module compare unequal and triggered
|
||||
// "PsiFile's context does not match the context of the editor" with two ModuleContextImpl values
|
||||
// that differed only by EntityPointer id. Keying on the symbolic id makes them generation-stable.
|
||||
fun testProjectModelContextsAreIdentifiedBySymbolicId() {
|
||||
val module = ModuleContextImpl(ModuleId("m"), project)
|
||||
val sameModule = ModuleContextImpl(ModuleId("m"), project)
|
||||
assertEquals(module, sameModule)
|
||||
assertEquals(module.hashCode(), sameModule.hashCode())
|
||||
assertFalse("different modules must not be equal", module == ModuleContextImpl(ModuleId("other"), project))
|
||||
|
||||
val tableId = LibraryTableId.ProjectLibraryTableId
|
||||
val library = LibraryContextImpl(LibraryId("lib", tableId), project)
|
||||
val sameLibrary = LibraryContextImpl(LibraryId("lib", tableId), project)
|
||||
assertEquals(library, sameLibrary)
|
||||
assertEquals(library.hashCode(), sameLibrary.hashCode())
|
||||
assertFalse("different libraries must not be equal", library == LibraryContextImpl(LibraryId("other", tableId), project))
|
||||
|
||||
val sdk = SdkContextImpl(SdkId("sdk", "JavaSDK"), project)
|
||||
val sameSdk = SdkContextImpl(SdkId("sdk", "JavaSDK"), project)
|
||||
assertEquals(sdk, sameSdk)
|
||||
assertEquals(sdk.hashCode(), sameSdk.hashCode())
|
||||
assertFalse("different SDKs must not be equal", sdk == SdkContextImpl(SdkId("other", "JavaSDK"), project))
|
||||
}
|
||||
|
||||
private fun getContexts(): List<ModuleContext> {
|
||||
val contexts = CodeInsightContextManager.getInstance(project).getCodeInsightContexts(virtualFile)
|
||||
assertTrue(contexts.toString(), contexts.all { it is ModuleContext })
|
||||
|
||||
+3
-3
@@ -23,20 +23,20 @@ internal class ProjectModelContextBridgeImpl(private val project: Project) : Pro
|
||||
|
||||
private val sdkCachedValue = CachedValueWithParameter<Sdk, SdkContext> { storage, parameter ->
|
||||
val sdkEntity = storage.findSdkEntity(parameter)
|
||||
val context = sdkEntity?.let { SdkContextImpl(sdkEntity.createPointer(), project) }
|
||||
val context = sdkEntity?.let { SdkContextImpl(sdkEntity.symbolicId, project) }
|
||||
context ?: NULL
|
||||
}
|
||||
|
||||
override fun getContext(entry: Module): ModuleContext? {
|
||||
val snapshot = currentSnapshot
|
||||
val moduleEntity = entry.findModuleEntity(snapshot) ?: return null
|
||||
return ModuleContextImpl(moduleEntity.createPointer(), project)
|
||||
return ModuleContextImpl(moduleEntity.symbolicId, project)
|
||||
}
|
||||
|
||||
override fun getContext(entry: Library): LibraryContext? {
|
||||
val snapshot = currentSnapshot
|
||||
val lib = snapshot.findLibraryEntity(entry as LibraryBridge) ?: return null
|
||||
return LibraryContextImpl(lib.createPointer(), project)
|
||||
return LibraryContextImpl(lib.symbolicId, project)
|
||||
}
|
||||
|
||||
override fun getContext(entry: Sdk): SdkContext? {
|
||||
|
||||
+22
-22
@@ -16,8 +16,10 @@ import com.intellij.platform.backend.workspace.WorkspaceModelChangeListener
|
||||
import com.intellij.platform.backend.workspace.WorkspaceModelTopics
|
||||
import com.intellij.platform.workspace.jps.entities.ContentRootEntity
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryEntity
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleEntity
|
||||
import com.intellij.platform.workspace.jps.entities.LibraryId
|
||||
import com.intellij.platform.workspace.jps.entities.ModuleId
|
||||
import com.intellij.platform.workspace.jps.entities.SdkEntity
|
||||
import com.intellij.platform.workspace.jps.entities.SdkId
|
||||
import com.intellij.platform.workspace.jps.entities.SourceRootEntity
|
||||
import com.intellij.platform.workspace.storage.EntityPointer
|
||||
import com.intellij.platform.workspace.storage.ImmutableEntityStorage
|
||||
@@ -79,21 +81,19 @@ internal class ProjectModelEntityContextProvider : CodeInsightContextProvider {
|
||||
val entity = entityPointer.resolve(storage) ?: return null
|
||||
|
||||
if (entity is SourceRootEntity) {
|
||||
val modulePointer = entity.contentRoot.module.createPointer<ModuleEntity>()
|
||||
return ModuleContextImpl(modulePointer, project)
|
||||
return ModuleContextImpl(entity.contentRoot.module.symbolicId, project)
|
||||
}
|
||||
|
||||
if (entity is ContentRootEntity) {
|
||||
val modulePointer = entity.module.createPointer<ModuleEntity>()
|
||||
return ModuleContextImpl(modulePointer, project)
|
||||
return ModuleContextImpl(entity.module.symbolicId, project)
|
||||
}
|
||||
|
||||
if (entity is LibraryEntity) {
|
||||
return LibraryContextImpl(entity.createPointer(), project)
|
||||
return LibraryContextImpl(entity.symbolicId, project)
|
||||
}
|
||||
|
||||
if (entity is SdkEntity) {
|
||||
return SdkContextImpl(entity.createPointer(), project)
|
||||
return SdkContextImpl(entity.symbolicId, project)
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -117,68 +117,68 @@ internal class ProjectModelEntityContextProvider : CodeInsightContextProvider {
|
||||
|
||||
@ApiStatus.Internal
|
||||
class ModuleContextImpl(
|
||||
private val modulePointer: EntityPointer<ModuleEntity>,
|
||||
private val moduleId: ModuleId,
|
||||
private val project: Project
|
||||
) : ModuleContext {
|
||||
override fun getModule(): Module? {
|
||||
val storage = WorkspaceModel.getInstance(project).currentSnapshot
|
||||
val entity = modulePointer.resolve(storage) ?: return null
|
||||
val entity = storage.resolve(moduleId) ?: return null
|
||||
return entity.findModule(storage)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return modulePointer == (other as? ModuleContextImpl)?.modulePointer
|
||||
return moduleId == (other as? ModuleContextImpl)?.moduleId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return modulePointer.hashCode()
|
||||
return moduleId.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = "ModuleContextImpl(modulePointer=$modulePointer, project=${project.name})"
|
||||
override fun toString(): String = "ModuleContextImpl(moduleId=$moduleId, project=${project.name})"
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
class LibraryContextImpl(
|
||||
private val libraryPointer: EntityPointer<LibraryEntity>,
|
||||
private val libraryId: LibraryId,
|
||||
private val project: Project,
|
||||
) : LibraryContext {
|
||||
|
||||
override fun getLibrary(): Library? {
|
||||
val storage = WorkspaceModel.getInstance(project).currentSnapshot
|
||||
val entity = libraryPointer.resolve(storage) ?: return null
|
||||
val entity = storage.resolve(libraryId) ?: return null
|
||||
return entity.findLibraryBridge(storage)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return libraryPointer == (other as? LibraryContextImpl)?.libraryPointer
|
||||
return libraryId == (other as? LibraryContextImpl)?.libraryId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return libraryPointer.hashCode()
|
||||
return libraryId.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = "LibraryContextImpl(libraryPointer=$libraryPointer, project=${project.name})"
|
||||
override fun toString(): String = "LibraryContextImpl(libraryId=$libraryId, project=${project.name})"
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
class SdkContextImpl(
|
||||
private val sdkPointer: EntityPointer<SdkEntity>,
|
||||
private val sdkId: SdkId,
|
||||
private val project: Project,
|
||||
) : SdkContext {
|
||||
|
||||
override fun getSdk(): Sdk? {
|
||||
val storage = WorkspaceModel.getInstance(project).currentSnapshot
|
||||
val entity = sdkPointer.resolve(storage) ?: return null
|
||||
val entity = storage.resolve(sdkId) ?: return null
|
||||
return storage.findSdk(entity)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return sdkPointer == (other as? SdkContextImpl)?.sdkPointer
|
||||
return sdkId == (other as? SdkContextImpl)?.sdkId
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return sdkPointer.hashCode()
|
||||
return sdkId.hashCode()
|
||||
}
|
||||
|
||||
override fun toString(): String = "SdkContextImpl(sdkPointer=$sdkPointer, project=${project.name})"
|
||||
override fun toString(): String = "SdkContextImpl(sdkId=$sdkId, project=${project.name})"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user