[K/JS] Add warning for ES-modules on the klibgen stage on the uniqueness of the exported names from the module

This commit is contained in:
Artem Kobzar
2023-08-08 15:45:02 +00:00
committed by Space Team
parent 734a3e5716
commit a29fa428b3
15 changed files with 199 additions and 29 deletions
@@ -72,6 +72,7 @@ import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.util.DummyLogger
import org.jetbrains.kotlin.util.Logger
import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.flatGroupBy
import org.jetbrains.kotlin.utils.addToStdlib.ifTrue
import org.jetbrains.kotlin.utils.memoryOptimizedFilter
import java.io.File
@@ -101,6 +102,9 @@ private val CompilerConfiguration.metadataVersion
private val CompilerConfiguration.expectActualLinker: Boolean
get() = get(CommonConfigurationKeys.EXPECT_ACTUAL_LINKER) ?: false
private val SerializedIrFile.fileMetadata: ByteArray
get() = backendSpecificMetadata ?: error("Expect file caches to have backendSpecificMetadata, but '$path' doesn't")
val CompilerConfiguration.resolverLogger: Logger
get() = when (val messageLogger = this[IrMessageLogger.IR_MESSAGE_LOGGER]) {
null -> DummyLogger
@@ -627,6 +631,29 @@ private fun String.parseSerializedIrFileFingerprints(): List<SerializedIrFileFin
return split(FILE_FINGERPRINTS_SEPARATOR).mapNotNull(SerializedIrFileFingerprint::fromString)
}
private fun CompilerConfiguration.assertNoExportedNamesClashes(moduleName: String, files: List<KotlinFileSerializedData>) {
val allExportedNameClashes = files
.flatGroupBy { JsIrFileMetadata.fromByteArray(it.irData.fileMetadata).exportedNames }
.filterValues { it.size > 1 }
if (allExportedNameClashes.isEmpty()) return
val nameClashesString = buildString {
allExportedNameClashes.forEach { (name, files) ->
appendLine(" * Next files contain declarations with @JsExport and name '$name'")
files.forEach { appendLine(" - ${it.irData.path}") }
}
}
val message = """
|There are clashes of declaration names that annotated with @JsExport in module '$moduleName'.
|${nameClashesString}
|Note, that this clash could affect the generated JS code in case of ES module kind usage
""".trimMargin()
irMessageLogger.report(IrMessageLogger.Severity.WARNING, message, null)
}
fun serializeModuleIntoKlib(
moduleName: String,
configuration: CompilerConfiguration,
@@ -674,7 +701,18 @@ fun serializeModuleIntoKlib(
incrementalResultsConsumer?.run {
processPackagePart(ioFile, compiledFile.metadata, empty, empty)
with(compiledFile.irData) {
processIrFile(ioFile, fileData, types, signatures, strings, declarations, bodies, fqName.toByteArray(), debugInfo)
processIrFile(
ioFile,
fileData,
types,
signatures,
strings,
declarations,
bodies,
fqName.toByteArray(),
fileMetadata,
debugInfo,
)
}
}
}
@@ -699,7 +737,11 @@ fun serializeModuleIntoKlib(
processCompiledFileData(ioFile!!, compiledKotlinFile)
}
val compiledKotlinFiles = (cleanFiles + additionalFiles)
val compiledKotlinFiles = (cleanFiles + additionalFiles).also {
if (builtInsPlatform == BuiltInsPlatform.JS) {
configuration.assertNoExportedNamesClashes(moduleName, it)
}
}
val header = serializeKlibHeader(
configuration.languageVersionSettings, moduleDescriptor,
@@ -845,7 +887,8 @@ fun IncrementalDataProvider.getSerializedData(newSources: List<KtSourceFile>): L
strings,
bodies,
declarations,
debugInfo
debugInfo,
fileMetadata,
)
}
storage.add(KotlinFileSerializedData(metaFile.metadata, irFile))
@@ -0,0 +1,27 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.ir.backend.js.lower.serialization.ir
import org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer
import org.jetbrains.kotlin.library.impl.toArray
import org.jetbrains.kotlin.library.encodings.WobblyTF8
import org.jetbrains.kotlin.library.impl.IrMemoryArrayWriter
import org.jetbrains.kotlin.library.impl.IrMemoryStringWriter
import org.jetbrains.kotlin.library.impl.IrArrayMemoryReader
class JsIrFileMetadata(val exportedNames: List<String>) : IrFileSerializer.FileBackendSpecificMetadata {
override fun toByteArray(): ByteArray {
return IrMemoryStringWriter(exportedNames).writeIntoMemory()
}
companion object {
fun fromByteArray(data: ByteArray): JsIrFileMetadata {
return JsIrFileMetadata(
exportedNames = IrArrayMemoryReader(data).toArray().map(WobblyTF8::decode)
)
}
}
}
@@ -5,15 +5,22 @@
package org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir
import org.jetbrains.kotlin.backend.common.ir.isExpect
import org.jetbrains.kotlin.backend.common.serialization.CompatibilityMode
import org.jetbrains.kotlin.backend.common.serialization.DeclarationTable
import org.jetbrains.kotlin.backend.common.serialization.IrFileSerializer
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.symbols.IrSymbol
import org.jetbrains.kotlin.ir.util.IrMessageLogger
import org.jetbrains.kotlin.ir.util.getAnnotation
import org.jetbrains.kotlin.ir.util.hasAnnotation
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.name.FqName
class JsIrFileSerializer(
@@ -38,15 +45,38 @@ class JsIrFileSerializer(
sourceBaseDirs = sourceBaseDirs
) {
companion object {
private val JS_NAME_FQN = FqName("kotlin.js.JsName")
private val JS_EXPORT_FQN = FqName("kotlin.js.JsExport")
private val JS_EXPORT_IGNORE_FQN = FqName("kotlin.js.JsExport.Ignore")
}
override fun backendSpecificExplicitRoot(node: IrAnnotationContainer): Boolean {
return node.annotations.hasAnnotation(JS_EXPORT_FQN) && !backendSpecificExplicitRootExclusion(node)
private fun IrAnnotationContainer.isExportedDeclaration(): Boolean {
return annotations.hasAnnotation(JS_EXPORT_FQN) && !isExportIgnoreDeclaration()
}
override fun backendSpecificExplicitRootExclusion(node: IrAnnotationContainer): Boolean {
return node.annotations.hasAnnotation(JS_EXPORT_IGNORE_FQN)
private fun IrAnnotationContainer.isExportIgnoreDeclaration(): Boolean {
return annotations.hasAnnotation(JS_EXPORT_IGNORE_FQN)
}
private val IrDeclarationWithName.exportedName: String
get() = getAnnotation(JS_NAME_FQN)?.getSingleConstStringArgument() ?: name.toString()
override fun backendSpecificExplicitRoot(node: IrAnnotationContainer) = node.isExportedDeclaration()
override fun backendSpecificExplicitRootExclusion(node: IrAnnotationContainer) = node.isExportIgnoreDeclaration()
override fun backendSpecificMetadata(irFile: IrFile): FileBackendSpecificMetadata {
val isFileExported = irFile.annotations.hasAnnotation(JS_EXPORT_FQN)
val exportedNames = irFile.declarations.asSequence()
.filterIsInstance<IrDeclarationWithName>()
.filter { if (isFileExported) !it.isExportIgnoreDeclaration() else it.isExportedDeclaration() }
.filter { !it.isEffectivelyExternal() && !it.isExpect }
.map { it.exportedName }
.toList()
return JsIrFileMetadata(exportedNames)
}
@Suppress("UNCHECKED_CAST")
private fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
}