Invalidate usages of removed classes before round

#KT-23165 fixed
This commit is contained in:
Alexey Tsvetkov
2018-03-27 16:29:09 +03:00
parent 8cd0f13f2b
commit 3eb968807e
43 changed files with 610 additions and 212 deletions
@@ -30,6 +30,7 @@ import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.multiproject.ArtifactChangesProvider
import org.jetbrains.kotlin.incremental.multiproject.ChangesRegistry
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.parsing.util.classesFqNames
import org.jetbrains.kotlin.progress.CompilationCanceledStatus
import java.io.File
import java.util.*
@@ -280,6 +281,30 @@ abstract class IncrementalCompilerRunner<
return exitCode
}
protected fun getRemovedClassesChanges(
caches: IncrementalCachesManager<*>,
changedFiles: ChangedFiles.Known
): DirtyData {
val removedClasses = HashSet<String>()
val dirtyFiles = changedFiles.modified.filterTo(HashSet()) { it.isKotlinFile() }
val removedFiles = changedFiles.removed.filterTo(HashSet()) { it.isKotlinFile() }
val existingClasses = classesFqNames(dirtyFiles)
val previousClasses = caches.platformCache
.classesFqNamesBySources(dirtyFiles + removedFiles)
.map { it.asString() }
for (fqName in previousClasses) {
if (fqName !in existingClasses) {
removedClasses.add(fqName)
}
}
val changesCollector = ChangesCollector()
removedClasses.forEach { changesCollector.collectSignature(FqName(it), areSubclassesAffected = true) }
return changesCollector.getDirtyData(listOf(caches.platformCache), reporter)
}
open fun runWithNoDirtyKotlinSources(caches: CacheManager): Boolean = false
protected open fun processChangesAfterBuild(compilationMode: CompilationMode, currentBuildInfo: BuildInfo, dirtyData: DirtyData) {
@@ -27,6 +27,7 @@ import org.jetbrains.kotlin.config.Services
import org.jetbrains.kotlin.incremental.components.ExpectActualTracker
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.js.*
import org.jetbrains.kotlin.name.FqName
import java.io.File
fun makeJsIncrementally(
@@ -92,7 +93,29 @@ class IncrementalJsCompilerRunner(
if (changedLib != null) return CompilationMode.Rebuild { "Library has been changed: $changedLib" }
return CompilationMode.Incremental(getDirtyFiles(changedFiles))
val dirtyFiles = getDirtyFiles(changedFiles)
// todo: unify with JVM calculateSourcesToCompile
fun markDirtyBy(lookupSymbols: Collection<LookupSymbol>) {
if (lookupSymbols.isEmpty()) return
val dirtyFilesFromLookups = mapLookupSymbolsToFiles(caches.lookupCache, lookupSymbols, reporter)
dirtyFiles.addAll(dirtyFilesFromLookups)
}
fun markDirtyBy(dirtyClassesFqNames: Collection<FqName>) {
if (dirtyClassesFqNames.isEmpty()) return
val fqNamesWithSubtypes = dirtyClassesFqNames.flatMap { withSubtypes(it, listOf(caches.platformCache)) }
val dirtyFilesFromFqNames = mapClassesFqNamesToFiles(listOf(caches.platformCache), fqNamesWithSubtypes, reporter)
dirtyFiles.addAll(dirtyFilesFromFqNames)
}
val removedClassesChanges = getRemovedClassesChanges(caches, changedFiles)
markDirtyBy(removedClassesChanges.dirtyLookupSymbols)
markDirtyBy(removedClassesChanges.dirtyClassesFqNames)
return CompilationMode.Incremental(dirtyFiles)
}
override fun makeServices(
@@ -141,7 +141,11 @@ class IncrementalJvmCompilerRunner(
else
null
override fun calculateSourcesToCompile(caches: IncrementalJvmCachesManager, changedFiles: ChangedFiles.Known, args: K2JVMCompilerArguments): CompilationMode {
override fun calculateSourcesToCompile(
caches: IncrementalJvmCachesManager,
changedFiles: ChangedFiles.Known,
args: K2JVMCompilerArguments
): CompilationMode {
val dirtyFiles = getDirtyFiles(changedFiles)
fun markDirtyBy(lookupSymbols: Collection<LookupSymbol>) {
@@ -214,10 +218,13 @@ class IncrementalJvmCompilerRunner(
}
val androidLayoutChanges = processLookupSymbolsForAndroidLayouts(changedFiles)
val removedClassesChanges = getRemovedClassesChanges(caches, changedFiles)
markDirtyBy(androidLayoutChanges)
markDirtyBy(classpathChanges.lookupSymbols)
markDirtyBy(classpathChanges.fqNames)
markDirtyBy(removedClassesChanges.dirtyLookupSymbols)
markDirtyBy(removedClassesChanges.dirtyClassesFqNames)
return CompilationMode.Incremental(dirtyFiles)
}
@@ -756,6 +756,12 @@ public class IncrementalJsCompilerRunnerTestGenerated extends AbstractIncrementa
doTest(fileName);
}
@TestMetadata("classMovedIntoOtherClass")
public void testClassMovedIntoOtherClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/classMovedIntoOtherClass/");
doTest(fileName);
}
@TestMetadata("classRemoved")
public void testClassRemoved() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/classRemoved/");
@@ -756,6 +756,12 @@ public class IncrementalJvmCompilerRunnerTestGenerated extends AbstractIncrement
doTest(fileName);
}
@TestMetadata("classMovedIntoOtherClass")
public void testClassMovedIntoOtherClass() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/classMovedIntoOtherClass/");
doTest(fileName);
}
@TestMetadata("classRemoved")
public void testClassRemoved() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("jps-plugin/testData/incremental/classHierarchyAffected/classRemoved/");
@@ -0,0 +1,103 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parsing.util
import com.intellij.core.CoreApplicationEnvironment
import com.intellij.core.CoreProjectEnvironment
import com.intellij.lang.MetaLanguage
import com.intellij.openapi.Disposable
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VirtualFileManager
import com.intellij.openapi.vfs.local.CoreLocalFileSystem
import com.intellij.psi.PsiManager
import com.intellij.psi.SingleRootFileViewProvider
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtClassOrObject
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.ScriptDefinitionProvider
import java.io.File
import java.util.*
fun classesFqNames(files: Set<File>): Set<String> = withPsiSetup {
val psiManager = PsiManager.getInstance(project)
val fileManager = VirtualFileManager.getInstance()
val localFS = fileManager.getFileSystem(StandardFileSystems.FILE_PROTOCOL) as CoreLocalFileSystem
classesFqNames(files, psiManager, localFS)
}
private fun classesFqNames(files: Collection<File>, psiManager: PsiManager, localFS: CoreLocalFileSystem): Set<String> {
val result = HashSet<String>()
for (file in files) {
if (!file.name.endsWith(".kt", ignoreCase = true)) continue
val virtualFile = localFS.findFileByIoFile(file) ?: continue
for (psiFile in SingleRootFileViewProvider(psiManager, virtualFile).allFiles) {
if (psiFile !is KtFile) continue
val classes = ArrayDeque<KtClassOrObject>()
psiFile.declarations.filterClassesTo(classes)
while (classes.isNotEmpty()) {
val klass = classes.pollFirst()
klass.fqName?.let {
result.add(it.asString())
}
klass.declarations.filterClassesTo(classes)
}
}
}
return result
}
private fun Collection<KtDeclaration>.filterClassesTo(classes: Deque<KtClassOrObject>) {
filterIsInstanceTo<KtClassOrObject, Deque<KtClassOrObject>>(classes)
}
private data class PsiSetup(
val applicationEnvironment: CoreApplicationEnvironment,
val projectEnvironment: CoreProjectEnvironment,
val project: Project,
val disposable: Disposable
)
private inline fun <T> withPsiSetup(fn: PsiSetup.() -> T): T {
val disposable = Disposer.newDisposable()
return try {
val applicationEnvironment = CoreApplicationEnvironment(disposable, false)
val projectEnvironment = CoreProjectEnvironment(disposable, applicationEnvironment)
CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), MetaLanguage.EP_NAME, MetaLanguage::class.java)
applicationEnvironment.registerApplicationService(ScriptDefinitionProvider::class.java, NoopScriptDefinitionProvider())
applicationEnvironment.registerFileType(KotlinFileType.INSTANCE, "kt")
applicationEnvironment.registerParserDefinition(KotlinParserDefinition())
val project = projectEnvironment.project
val setup = PsiSetup(applicationEnvironment, projectEnvironment, project, disposable)
setup.fn()
} finally {
Disposer.dispose(disposable)
}
}
private class NoopScriptDefinitionProvider : ScriptDefinitionProvider {
override fun isScript(fileName: String): Boolean {
return false
}
override fun findScriptDefinition(fileName: String): KotlinScriptDefinition? {
return null
}
}
@@ -0,0 +1,92 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.parsing.utils
import com.intellij.openapi.util.io.FileUtil
import com.intellij.testFramework.UsefulTestCase
import org.jetbrains.kotlin.parsing.util.classesFqNames
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.io.File
import kotlin.test.assertEquals
class ClassesFqNamesTest {
private lateinit var workingDir: File
@Before
fun setUp() {
workingDir = FileUtil.createTempDirectory("ClassesFqNamesTest", null)
}
@After
fun tearDown() {
workingDir.deleteRecursively()
}
@Test
fun testSingleClass() {
doTest(
setOf("test.Foo"),
"""
package test
class Foo""".trimIndent()
)
}
@Test
fun testMultipleClasses() {
doTest(
setOf("test.Fizz", "test.Buzz"),
"""
package test
class Fizz
class Buzz""".trimIndent()
)
}
@Test
fun testInnerClasses() {
doTest(
setOf("test.Foo", "test.Foo.Fizz", "test.Foo.Buzz"),
"""
package test
class Foo {
class Fizz
class Buzz
}""".trimIndent()
)
}
@Test
fun testObject() {
doTest(
setOf("test.Foo", "test.Foo.Fizz", "test.Bar", "test.Bar.Buzz"),
"""
package test
object Foo {
class Fizz
}
class Bar {
object Buzz
}""".trimIndent()
)
}
private fun doTest(expectedClasses: Set<String>, code: String) {
val testKt = File(workingDir, "test.kt")
testKt.writeText(code)
val expected = expectedClasses.sorted().joinToString("\n")
val actual = classesFqNames(setOf(testKt)).sorted().joinToString("\n")
UsefulTestCase.assertEquals(expected, actual)
}
}