diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/MultiverseHighlightingTest.kt b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/MultiverseHighlightingTest.kt index e8e285c28da5..f4bf11a6548b 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/MultiverseHighlightingTest.kt +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/impl/MultiverseHighlightingTest.kt @@ -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 { val contexts = CodeInsightContextManager.getInstance(project).getCodeInsightContexts(virtualFile) assertTrue(contexts.toString(), contexts.all { it is ModuleContext }) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/multiverse/ProjectModelContextBridgeImpl.kt b/platform/lang-impl/src/com/intellij/codeInsight/multiverse/ProjectModelContextBridgeImpl.kt index 313a7e6baecf..484657f9e648 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/multiverse/ProjectModelContextBridgeImpl.kt +++ b/platform/lang-impl/src/com/intellij/codeInsight/multiverse/ProjectModelContextBridgeImpl.kt @@ -23,20 +23,20 @@ internal class ProjectModelContextBridgeImpl(private val project: Project) : Pro private val sdkCachedValue = CachedValueWithParameter { 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? { diff --git a/platform/projectModel-impl/src/com/intellij/multiverse/ProjectModelEntityContextProvider.kt b/platform/projectModel-impl/src/com/intellij/multiverse/ProjectModelEntityContextProvider.kt index 56549d2ed1cf..23d22b77a4bf 100644 --- a/platform/projectModel-impl/src/com/intellij/multiverse/ProjectModelEntityContextProvider.kt +++ b/platform/projectModel-impl/src/com/intellij/multiverse/ProjectModelEntityContextProvider.kt @@ -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() - return ModuleContextImpl(modulePointer, project) + return ModuleContextImpl(entity.contentRoot.module.symbolicId, project) } if (entity is ContentRootEntity) { - val modulePointer = entity.module.createPointer() - 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, + 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, + 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, + 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})" }