Invalidate usages of removed classes before round
#KT-23165 fixed
This commit is contained in:
+25
@@ -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) {
|
||||
|
||||
+24
-1
@@ -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(
|
||||
|
||||
+8
-1
@@ -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)
|
||||
}
|
||||
|
||||
+6
@@ -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/");
|
||||
|
||||
+6
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user