JS: several bugfixes in incremental compilation

1. Fix exported packages sometimes being mixed up
2. Fix metadata losing package fragments sometimes
3. Don't serialize empty packages to .meta.js
4. Preserve order of package fragments in .meta.js
5. In IC tests, compare textual representation of metadata rather
   than binary representation
This commit is contained in:
Alexey Andreev
2017-10-19 12:19:34 +03:00
parent 2fbecfdd9c
commit e1f8bac4d5
7 changed files with 84 additions and 17 deletions
@@ -39,7 +39,6 @@ import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.DataOutputStream import java.io.DataOutputStream
import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@@ -109,7 +108,7 @@ object KotlinJavascriptSerializationUtil {
for (fqName in getPackagesFqNames(module)) { for (fqName in getPackagesFqNames(module)) {
val fragment = serializePackageFragment(bindingContext, module, fqName) val fragment = serializePackageFragment(bindingContext, module, fqName)
if (fragment.hasPackage() || fragment.class_Count > 0) { if (!fragment.isEmpty()) {
builder.addPackageFragment(fragment) builder.addPackageFragment(fragment)
} }
} }
@@ -212,13 +211,11 @@ object KotlinJavascriptSerializationUtil {
module: ModuleDescriptor, module: ModuleDescriptor,
languageVersionSettings: LanguageVersionSettings languageVersionSettings: LanguageVersionSettings
): Map<String, ByteArray> { ): Map<String, ByteArray> {
val contentMap = hashMapOf<String, ByteArray>() val contentMap = mutableMapOf<String, ByteArray>()
for (fqName in getPackagesFqNames(module)) { for (fqName in getPackagesFqNames(module)) {
val part = serializePackageFragment(bindingContext, module, fqName) val part = serializePackageFragment(bindingContext, module, fqName)
if (part.class_Count == 0 && part.`package`.let { packageProto -> if (part.isEmpty()) continue
packageProto.functionCount == 0 && packageProto.propertyCount == 0 && packageProto.typeAliasCount == 0
}) continue
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
with(DataOutputStream(stream)) { with(DataOutputStream(stream)) {
@@ -236,6 +233,9 @@ object KotlinJavascriptSerializationUtil {
return contentMap return contentMap
} }
private fun ProtoBuf.PackageFragment.isEmpty(): Boolean =
class_Count == 0 && `package`.let { it.functionCount == 0 && it.propertyCount == 0 && it.typeAliasCount == 0 }
fun serializeHeader(packageFqName: FqName?, languageVersionSettings: LanguageVersionSettings): JsProtoBuf.Header { fun serializeHeader(packageFqName: FqName?, languageVersionSettings: LanguageVersionSettings): JsProtoBuf.Header {
val header = JsProtoBuf.Header.newBuilder() val header = JsProtoBuf.Header.newBuilder()
@@ -253,7 +253,7 @@ object KotlinJavascriptSerializationUtil {
} }
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> { private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
return HashSet<FqName>().apply { return mutableSetOf<FqName>().apply {
getSubPackagesFqNames(module.getPackage(FqName.ROOT), this) getSubPackagesFqNames(module.getPackage(FqName.ROOT), this)
add(FqName.ROOT) add(FqName.ROOT)
} }
@@ -17,13 +17,12 @@
package org.jetbrains.kotlin.serialization.js package org.jetbrains.kotlin.serialization.js
import org.jetbrains.kotlin.contracts.ContractDeserializerImpl import org.jetbrains.kotlin.contracts.ContractDeserializerImpl
import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.NotFoundClasses
import org.jetbrains.kotlin.descriptors.PackageFragmentProvider
import org.jetbrains.kotlin.descriptors.PackageFragmentProviderImpl
import org.jetbrains.kotlin.descriptors.deserialization.PlatformDependentDeclarationFilter import org.jetbrains.kotlin.descriptors.deserialization.PlatformDependentDeclarationFilter
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.incremental.components.LookupTracker import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.parentOrNull
import org.jetbrains.kotlin.serialization.ProtoBuf import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.* import org.jetbrains.kotlin.serialization.deserialization.*
import org.jetbrains.kotlin.storage.StorageManager import org.jetbrains.kotlin.storage.StorageManager
@@ -36,12 +35,26 @@ fun createKotlinJavascriptPackageFragmentProvider(
configuration: DeserializationConfiguration, configuration: DeserializationConfiguration,
lookupTracker: LookupTracker lookupTracker: LookupTracker
): PackageFragmentProvider { ): PackageFragmentProvider {
val packageFragments = packageFragmentProtos.mapNotNull { proto -> val packageFragments: MutableList<PackageFragmentDescriptor> = packageFragmentProtos.mapNotNullTo(mutableListOf()) { proto ->
proto.fqName?.let { fqName -> proto.fqName?.let { fqName ->
KotlinJavascriptPackageFragment(fqName, storageManager, module, proto, header, configuration) KotlinJavascriptPackageFragment(fqName, storageManager, module, proto, header, configuration)
} }
} }
// Generate empty PackageFragmentDescriptor instances for packages that aren't mentioned in compilation units directly.
// For example, if there's `package foo.bar` directive, we'll get only PackageFragmentDescriptor for `foo.bar`, but
// none for `foo`. Various descriptor/scope code relies on presence of such package fragments, and currently we
// don't know if it's possible to fix this.
// TODO: think about fixing issues in descriptors/scopes
val packageFqNames = packageFragmentProtos.mapNotNullTo(mutableSetOf()) { it.fqName }
for (packageFqName in packageFqNames.mapNotNull { it.parentOrNull() }) {
var ancestorFqName = packageFqName
while (!ancestorFqName.isRoot && packageFqNames.add(ancestorFqName)) {
packageFragments += EmptyPackageFragmentDescriptor(module, ancestorFqName)
ancestorFqName = ancestorFqName.parent()
}
}
val provider = PackageFragmentProviderImpl(packageFragments) val provider = PackageFragmentProviderImpl(packageFragments)
val notFoundClasses = NotFoundClasses(storageManager, module) val notFoundClasses = NotFoundClasses(storageManager, module)
@@ -63,7 +76,7 @@ fun createKotlinJavascriptPackageFragmentProvider(
platformDependentDeclarationFilter = PlatformDependentDeclarationFilter.NoPlatformDependent platformDependentDeclarationFilter = PlatformDependentDeclarationFilter.NoPlatformDependent
) )
for (packageFragment in packageFragments) { for (packageFragment in packageFragments.filterIsInstance<KotlinJavascriptPackageFragment>()) {
packageFragment.components = components packageFragment.components = components
} }
+2
View File
@@ -2,6 +2,7 @@
apply { plugin("kotlin") } apply { plugin("kotlin") }
dependencies { dependencies {
testCompile(protobufFull())
testCompile(project(":compiler.tests-common")) testCompile(project(":compiler.tests-common"))
testCompileOnly(project(":compiler:frontend")) testCompileOnly(project(":compiler:frontend"))
testCompileOnly(project(":compiler:cli")) testCompileOnly(project(":compiler:cli"))
@@ -19,6 +20,7 @@ dependencies {
testRuntime(project(":compiler:backend-common")) testRuntime(project(":compiler:backend-common"))
// testRuntime(ideaSdkDeps("*.jar")) // testRuntime(ideaSdkDeps("*.jar"))
testRuntime(commonDep("org.fusesource.jansi", "jansi")) testRuntime(commonDep("org.fusesource.jansi", "jansi"))
testCompile(projectTests(":kotlin-build-common"))
} }
sourceSets { sourceSets {
@@ -56,6 +56,8 @@ import org.jetbrains.kotlin.js.util.TextOutputImpl
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.serialization.DebugProtoBuf
import org.jetbrains.kotlin.serialization.js.JsSerializerProtocol
import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil import org.jetbrains.kotlin.serialization.js.KotlinJavascriptSerializationUtil
import org.jetbrains.kotlin.serialization.js.ModuleKind import org.jetbrains.kotlin.serialization.js.ModuleKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils import org.jetbrains.kotlin.test.InTextDirectivesUtils
@@ -64,6 +66,7 @@ import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactory
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.TargetBackend import org.jetbrains.kotlin.test.TargetBackend
import org.jetbrains.kotlin.utils.DFS import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata
import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils
import java.io.* import java.io.*
import java.nio.charset.Charset import java.nio.charset.Charset
@@ -343,18 +346,30 @@ abstract class BasicBoxTest(
val originalOutput = FileUtil.loadFile(outputFile) val originalOutput = FileUtil.loadFile(outputFile)
val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile)) val recompiledOutput = removeRecompiledSuffix(FileUtil.loadFile(recompiledOutputFile))
TestCase.assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput) assertEquals("Output file changed after recompilation", originalOutput, recompiledOutput)
val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map")) val originalSourceMap = FileUtil.loadFile(File(outputFile.parentFile, outputFile.name + ".map"))
val recompiledSourceMap = removeRecompiledSuffix( val recompiledSourceMap = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map"))) FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.name + ".map")))
TestCase.assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap) assertEquals("Source map file changed after recompilation", originalSourceMap, recompiledSourceMap)
if (multiModule) { if (multiModule) {
val originalMetadata = FileUtil.loadFile(File(outputFile.parentFile, outputFile.nameWithoutExtension + ".meta.js")) val originalMetadata = FileUtil.loadFile(File(outputFile.parentFile, outputFile.nameWithoutExtension + ".meta.js"))
val recompiledMetadata = removeRecompiledSuffix( val recompiledMetadata = removeRecompiledSuffix(
FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + ".meta.js"))) FileUtil.loadFile(File(recompiledOutputFile.parentFile, recompiledOutputFile.nameWithoutExtension + ".meta.js")))
TestCase.assertEquals("Metadata file changed after recompilation", originalMetadata, recompiledMetadata) assertEquals("Metadata file changed after recompilation",
metadataAsString(originalMetadata, module.name),
metadataAsString(recompiledMetadata, module.name))
}
}
private fun metadataAsString(metadata: String, moduleName: String): String {
val containers = mutableListOf<KotlinJavascriptMetadata>()
KotlinJavascriptMetadataUtils.parseMetadata(metadata, containers)
val metadataParts = KotlinJavascriptSerializationUtil.readModuleAsProto(containers.single().body, moduleName).data.body
return metadataParts.joinToString("-----\n") {
val binary = it.toByteArray()
DebugProtoBuf.PackageFragment.parseFrom(binary, JsSerializerProtocol.extensionRegistry).toString()
} }
} }
@@ -3761,6 +3761,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
doTest(fileName); doTest(fileName);
} }
@TestMetadata("packagesWithSameName.kt")
public void testPackagesWithSameName() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/packagesWithSameName.kt");
doTest(fileName);
}
@TestMetadata("simple.kt") @TestMetadata("simple.kt")
public void testSimple() throws Exception { public void testSimple() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/simple.kt"); String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/incremental/simple.kt");
@@ -109,7 +109,7 @@ class Merger(private val rootFunction: JsFunction, val internalModuleName: JsNam
val exportedTag = statement.exportedTag val exportedTag = statement.exportedTag
if (exportedTag != null && !exportedTags.add(exportedTag)) continue if (exportedTag != null && !exportedTags.add(exportedTag)) continue
} }
exportBlock.statements += nameMap.rename(statement) exportBlock.statements += nameMap.rename(statement.deepCopy())
} }
} }
@@ -0,0 +1,31 @@
// EXPECTED_REACHABLE_NODES: 1126
// MODULE: lib
// FILE: a.kt
package a.p
fun foo() = "foo"
// FILE: b1.kt
// RECOMPILE
package b.p
fun bar() = "bar"
// FILE: b2.kt
package b.p
fun baz() = "baz"
// MODULE: main(lib)
// FILE: main.kt
import a.p.*
import b.p.*
fun box(): String {
val r = foo() + bar() + baz()
if (r != "foobarbaz") return "fail: $r"
return "OK"
}