[JS IR] Run diagnostics by IR before the klib serialization

Implement an infrastructure for checking IR before JS klib serialization.
Implement the EXPORTING_JS_NAME_CLASH and EXPORTING_JS_NAME_CLASH_ES checks.

^KT-61710 Fixed
This commit is contained in:
Alexander Korepanov
2023-09-07 22:08:22 +02:00
parent 13bd627bfa
commit 522952db1f
24 changed files with 385 additions and 87 deletions
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_EXCEPTION
import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.RUNTIME_DIAGNOSTIC_LOG
import org.jetbrains.kotlin.cli.common.config.addKotlinSourceRoot
import org.jetbrains.kotlin.cli.common.fir.reportToMessageCollector
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*
@@ -461,6 +462,7 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
sourceModule.jsFrontEndResult.hasErrors
)
val diagnosticsReporter = DiagnosticReporterFactory.createPendingReporter()
generateKLib(
sourceModule,
outputKlibPath,
@@ -468,10 +470,17 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
jsOutputName = arguments.irPerModuleOutputName,
icData = icData,
moduleFragment = moduleFragment,
diagnosticReporter = diagnosticsReporter,
builtInsPlatform = if (arguments.wasm) BuiltInsPlatform.WASM else BuiltInsPlatform.JS
) { file ->
metadataSerializer.serializeScope(file, sourceModule.jsFrontEndResult.bindingContext, moduleFragment.descriptor)
}
val messageCollector = environmentForJS.configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY)
reportCollectedDiagnostics(environmentForJS.configuration, diagnosticsReporter, messageCollector)
if (diagnosticsReporter.hasErrors) {
throw CompilationErrorException()
}
}
return sourceModule
}
@@ -556,6 +565,11 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
jsOutputName = arguments.irPerModuleOutputName,
useWasmPlatform = arguments.wasm
)
reportCollectedDiagnostics(moduleStructure.compilerConfiguration, diagnosticsReporter, messageCollector)
if (diagnosticsReporter.hasErrors) {
throw CompilationErrorException()
}
}
return moduleStructure
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.cli.common.fir.FirDiagnosticsCompilerResultsReporter
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.constant.EvaluatedConstTracker
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
@@ -116,6 +117,15 @@ inline fun <F> compileModuleToAnalyzedFir(
return outputs
}
internal fun reportCollectedDiagnostics(
compilerConfiguration: CompilerConfiguration,
diagnosticsReporter: BaseDiagnosticsCollector,
messageCollector: MessageCollector
) {
val renderName = compilerConfiguration.getBoolean(CLIConfigurationKeys.RENDER_DIAGNOSTIC_INTERNAL_NAME)
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(diagnosticsReporter, messageCollector, renderName)
}
open class AnalyzedFirOutput(val output: List<ModuleCompilerAnalyzedOutput>) {
protected open fun checkSyntaxErrors(messageCollector: MessageCollector) = false
@@ -125,8 +135,7 @@ open class AnalyzedFirOutput(val output: List<ModuleCompilerAnalyzedOutput>) {
messageCollector: MessageCollector,
): Boolean {
if (checkSyntaxErrors(messageCollector) || diagnosticsReporter.hasErrors) {
val renderName = moduleStructure.compilerConfiguration.getBoolean(CLIConfigurationKeys.RENDER_DIAGNOSTIC_INTERNAL_NAME)
FirDiagnosticsCompilerResultsReporter.reportToMessageCollector(diagnosticsReporter, messageCollector, renderName)
reportCollectedDiagnostics(moduleStructure.compilerConfiguration, diagnosticsReporter, messageCollector)
return true
}
@@ -318,6 +327,7 @@ fun serializeFirKlib(
moduleStructure.compilerConfiguration[CommonConfigurationKeys.MODULE_NAME]!!,
moduleStructure.compilerConfiguration,
moduleStructure.compilerConfiguration.get(IrMessageLogger.IR_MESSAGE_LOGGER) ?: IrMessageLogger.None,
diagnosticsReporter,
fir2KlibSerializer.sourceFiles,
klibPath = outputKlibPath,
moduleStructure.allDependencies,
@@ -0,0 +1,37 @@
/*
* 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.checkers
import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibEsModuleExportsChecker
import org.jetbrains.kotlin.ir.backend.js.checkers.declarations.JsKlibOtherModuleExportsChecker
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.library.SerializedIrFile
object JsKlibCheckers {
private val exportedDeclarationsCheckers = listOf(
JsKlibEsModuleExportsChecker,
JsKlibOtherModuleExportsChecker
)
fun check(
cleanFiles: List<SerializedIrFile>,
dirtyFiles: List<IrFile>,
exportedNames: Map<IrFile, Map<IrDeclarationWithName, String>>,
diagnosticReporter: DiagnosticReporter,
configuration: CompilerConfiguration
) {
val reporter = KtDiagnosticReporterWithImplicitIrBasedContext(diagnosticReporter, configuration.languageVersionSettings)
val exportedDeclarations = JsKlibExportingDeclaration.collectDeclarations(cleanFiles, dirtyFiles, exportedNames)
for (checker in exportedDeclarationsCheckers) {
checker.check(exportedDeclarations, reporter)
}
}
}
@@ -0,0 +1,14 @@
/*
* 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.checkers
import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
interface JsKlibDeclarationsChecker<D> {
fun check(declarations: List<D>, reporter: KtDiagnosticReporterWithImplicitIrBasedContext)
}
typealias JsKlibExportedDeclarationsChecker = JsKlibDeclarationsChecker<JsKlibExportingDeclaration>
@@ -0,0 +1,47 @@
/*
* 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.checkers
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.diagnostics.KtDiagnosticFactoryToRendererMap
import org.jetbrains.kotlin.diagnostics.error2
import org.jetbrains.kotlin.diagnostics.rendering.*
import org.jetbrains.kotlin.diagnostics.warning2
object JsKlibErrors {
val EXPORTING_JS_NAME_CLASH by error2<PsiElement, String, List<JsKlibExport>>()
val EXPORTING_JS_NAME_CLASH_ES by warning2<PsiElement, String, List<JsKlibExport>>()
init {
RootDiagnosticRendererFactory.registerFactory(KtDefaultJsKlibErrorMessages)
}
}
private object KtDefaultJsKlibErrorMessages : BaseDiagnosticRendererFactory() {
@JvmField
val JS_KLIB_EXPORTS = Renderer<List<JsKlibExport>> { exports ->
if (exports.size == 1) {
exports.single().render()
} else {
exports.sortedBy { it.containingFile }.joinToString("\n", "\n", limit = 10) { " ${it.render()}" }
}
}
override val MAP = KtDiagnosticFactoryToRendererMap("KT").also { map ->
map.put(
JsKlibErrors.EXPORTING_JS_NAME_CLASH,
"Exporting name ''{0}'' clashes with {1}",
CommonRenderers.STRING,
JS_KLIB_EXPORTS
)
map.put(
JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES,
"Exporting name ''{0}'' in ES modules may clash with {1}",
CommonRenderers.STRING,
JS_KLIB_EXPORTS,
)
}
}
@@ -0,0 +1,57 @@
/*
* 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.checkers
import org.jetbrains.kotlin.ir.backend.js.fileMetadata
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrFileMetadata
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrDeclarationWithName
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.library.SerializedIrFile
abstract class JsKlibExport(val containingFile: String) {
abstract val fqName: String
abstract fun render(): String
}
class JsKlibExportingPackage(containingFile: String, override val fqName: String) : JsKlibExport(containingFile) {
override fun render() = "package '$fqName' from file '$containingFile'"
}
class JsKlibExportingDeclaration(
val exportingName: String,
containingFile: String,
packageFqName: String,
val declaration: IrDeclaration?,
) : JsKlibExport(containingFile) {
constructor(name: String, file: SerializedIrFile) : this(name, file.path, file.fqName, null)
constructor(name: String, file: IrFile, decl: IrDeclaration) : this(name, file.fileEntry.name, file.packageFqName.toString(), decl)
val containingPackageFqName = packageFqName.takeIf { it != "<root>" } ?: ""
override val fqName = "$containingPackageFqName${".".takeIf { containingPackageFqName.isNotEmpty() } ?: ""}$exportingName"
override fun render() = "exporting name '$exportingName' from file '$containingFile'"
companion object {
fun collectDeclarations(
cleanFiles: List<SerializedIrFile>,
dirtyFiles: List<IrFile>,
exportedNames: Map<IrFile, Map<IrDeclarationWithName, String>>,
) = buildList {
for (serializedFile in cleanFiles) {
val fileMetadata = JsIrFileMetadata.fromByteArray(serializedFile.fileMetadata)
for (exportedName in fileMetadata.exportedNames) {
add(JsKlibExportingDeclaration(exportedName, serializedFile))
}
}
for (dirtyFile in dirtyFiles) {
val exportedDeclarations = exportedNames[dirtyFile] ?: continue
for ((declaration, exportedName) in exportedDeclarations) {
add(JsKlibExportingDeclaration(exportedName, dirtyFile, declaration))
}
}
}
}
}
@@ -0,0 +1,28 @@
/*
* 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.checkers.declarations
import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportingDeclaration
import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibExportedDeclarationsChecker
import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibErrors
object JsKlibEsModuleExportsChecker : JsKlibExportedDeclarationsChecker {
override fun check(
declarations: List<JsKlibExportingDeclaration>,
reporter: KtDiagnosticReporterWithImplicitIrBasedContext,
) {
val allExportedNameClashes = declarations.groupBy { it.exportingName }.filterValues { it.size > 1 }
for (exportedDeclarationClashes in allExportedNameClashes.values) {
for ((index, exportedDeclaration) in exportedDeclarationClashes.withIndex()) {
val declaration = exportedDeclaration.declaration ?: continue
val clashedWith = exportedDeclarationClashes.filterIndexed { i, _ -> i != index }
reporter.at(declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH_ES, exportedDeclaration.exportingName, clashedWith)
}
}
}
}
@@ -0,0 +1,55 @@
/*
* 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.checkers.declarations
import org.jetbrains.kotlin.KtDiagnosticReporterWithImplicitIrBasedContext
import org.jetbrains.kotlin.ir.backend.js.checkers.*
object JsKlibOtherModuleExportsChecker : JsKlibExportedDeclarationsChecker {
private fun <T> MutableMap<T, MutableList<JsKlibExport>>.addExport(key: T, export: JsKlibExport) {
getOrPut(key) { mutableListOf() }.add(export)
}
private fun collectClashesByFqNames(declarations: List<JsKlibExportingDeclaration>): Map<String, List<JsKlibExport>> {
return buildMap<String, MutableList<JsKlibExport>> {
for (declaration in declarations) {
addExport(declaration.fqName, declaration)
var packageFqName = declaration.containingPackageFqName
while (packageFqName.isNotEmpty()) {
addExport(packageFqName, JsKlibExportingPackage(declaration.containingFile, packageFqName))
packageFqName = packageFqName.substringBeforeLast(".", "")
}
}
}
}
private fun collectClashes(declarations: List<JsKlibExportingDeclaration>): Map<JsKlibExportingDeclaration, List<JsKlibExport>> {
val clashesByFqNames = collectClashesByFqNames(declarations)
return buildMap {
for (clashingExports in clashesByFqNames.values) {
for ((index, export) in clashingExports.withIndex()) {
if (export is JsKlibExportingDeclaration) {
val clashedWith = clashingExports.filterIndexed { i, _ -> i != index }
if (clashedWith.isNotEmpty()) {
put(export, clashedWith)
}
}
}
}
}
}
override fun check(declarations: List<JsKlibExportingDeclaration>, reporter: KtDiagnosticReporterWithImplicitIrBasedContext) {
val clashes = collectClashes(declarations)
for ((declaration, clashedWith) in clashes) {
if (declaration.declaration != null) {
reporter.at(declaration.declaration).report(JsKlibErrors.EXPORTING_JS_NAME_CLASH, declaration.exportingName, clashedWith)
}
}
}
}
@@ -32,10 +32,12 @@ import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider
import org.jetbrains.kotlin.ir.IrBuiltIns
import org.jetbrains.kotlin.ir.ObsoleteDescriptorBasedAPI
import org.jetbrains.kotlin.ir.backend.js.checkers.JsKlibCheckers
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.*
import org.jetbrains.kotlin.ir.declarations.IrFactory
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -71,9 +73,9 @@ 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 org.jetbrains.kotlin.utils.toSmartList
import java.io.File
val KotlinLibrary.moduleName: String
@@ -98,7 +100,7 @@ val KotlinLibrary.serializedKlibFingerprint: SerializedKlibFingerprint?
private val CompilerConfiguration.metadataVersion
get() = get(CommonConfigurationKeys.METADATA_VERSION) as? KlibMetadataVersion ?: KlibMetadataVersion.INSTANCE
private val SerializedIrFile.fileMetadata: ByteArray
internal val SerializedIrFile.fileMetadata: ByteArray
get() = backendSpecificMetadata ?: error("Expect file caches to have backendSpecificMetadata, but '$path' doesn't")
val CompilerConfiguration.resolverLogger: Logger
@@ -126,6 +128,7 @@ fun generateKLib(
jsOutputName: String?,
icData: List<KotlinFileSerializedData>,
moduleFragment: IrModuleFragment,
diagnosticReporter: DiagnosticReporter,
builtInsPlatform: BuiltInsPlatform = BuiltInsPlatform.JS,
serializeSingleFile: (KtSourceFile) -> ProtoBuf.PackageFragment
) {
@@ -138,6 +141,7 @@ fun generateKLib(
configuration[CommonConfigurationKeys.MODULE_NAME]!!,
configuration,
messageLogger,
diagnosticReporter,
files,
outputKlibPath,
allDependencies,
@@ -623,33 +627,11 @@ 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,
messageLogger: IrMessageLogger,
diagnosticReporter: DiagnosticReporter,
files: List<KtSourceFile>,
klibPath: String,
dependencies: List<KotlinLibrary>,
@@ -670,6 +652,13 @@ fun serializeModuleIntoKlib(
val absolutePathNormalization = configuration[CommonConfigurationKeys.KLIB_NORMALIZE_ABSOLUTE_PATH] ?: false
val signatureClashChecks = configuration[CommonConfigurationKeys.PRODUCE_KLIB_SIGNATURES_CLASH_CHECKS] ?: false
val moduleExportedNames = moduleFragment.collectExportedNames()
if (builtInsPlatform == BuiltInsPlatform.JS) {
val cleanFilesIrData = cleanFiles.map { it.irData }
JsKlibCheckers.check(cleanFilesIrData, moduleFragment.files, moduleExportedNames, diagnosticReporter, configuration)
}
val serializedIr =
JsIrModuleSerializer(
messageLogger,
@@ -678,7 +667,8 @@ fun serializeModuleIntoKlib(
normalizeAbsolutePaths = absolutePathNormalization,
sourceBaseDirs = sourceBaseDirs,
configuration.languageVersionSettings,
signatureClashChecks
signatureClashChecks,
jsIrFileMetadataFactory = { JsIrFileMetadata(moduleExportedNames[it]?.values?.toSmartList() ?: emptyList()) }
).serializedIrModule(moduleFragment)
val moduleDescriptor = moduleFragment.descriptor
@@ -726,11 +716,7 @@ fun serializeModuleIntoKlib(
processCompiledFileData(ioFile!!, compiledKotlinFile)
}
val compiledKotlinFiles = (cleanFiles + additionalFiles).also {
if (builtInsPlatform == BuiltInsPlatform.JS) {
configuration.assertNoExportedNamesClashes(moduleName, it)
}
}
val compiledKotlinFiles = cleanFiles + additionalFiles
val header = serializeKlibHeader(
configuration.languageVersionSettings, moduleDescriptor,
@@ -0,0 +1,48 @@
/*
* 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.ir.isExpect
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.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
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.JsStandardClassIds
internal fun IrAnnotationContainer.isExportedDeclaration(): Boolean {
return annotations.hasAnnotation(JsStandardClassIds.Annotations.JsExport.asSingleFqName()) && !isExportIgnoreDeclaration()
}
internal fun IrAnnotationContainer.isExportIgnoreDeclaration(): Boolean {
return annotations.hasAnnotation(JsStandardClassIds.Annotations.JsExportIgnore.asSingleFqName())
}
private val IrDeclarationWithName.exportedName: String
get() = getAnnotation(JsStandardClassIds.Annotations.JsName.asSingleFqName())?.getSingleConstStringArgument() ?: name.toString()
@Suppress("UNCHECKED_CAST")
private fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
fun IrModuleFragment.collectExportedNames(): Map<IrFile, Map<IrDeclarationWithName, String>> {
return files.associateWith { irFile ->
val isFileExported = irFile.annotations.hasAnnotation(JsStandardClassIds.Annotations.JsExport.asSingleFqName())
val exportedDeclarations = irFile.declarations.asSequence()
.filterIsInstance<IrDeclarationWithName>()
.filter { if (isFileExported) !it.isExportIgnoreDeclaration() else it.isExportedDeclaration() }
.filter { !it.isEffectivelyExternal() && !it.isExpect }
.map {
it to it.exportedName
}.toMap()
exportedDeclarations
}
}
@@ -8,7 +8,6 @@ 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
@@ -24,4 +23,4 @@ class JsIrFileMetadata(val exportedNames: List<String>) : IrFileSerializer.FileB
)
}
}
}
}
@@ -5,21 +5,21 @@
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.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.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
fun interface JsIrFileMetadataFactory {
fun createJsIrFileMetadata(irFile: IrFile): JsIrFileMetadata
}
object JsIrFileEmptyMetadataFactory : JsIrFileMetadataFactory {
override fun createJsIrFileMetadata(irFile: IrFile) = JsIrFileMetadata(emptyList())
}
class JsIrFileSerializer(
messageLogger: IrMessageLogger,
@@ -28,7 +28,8 @@ class JsIrFileSerializer(
languageVersionSettings: LanguageVersionSettings,
bodiesOnlyForInlines: Boolean = false,
normalizeAbsolutePaths: Boolean,
sourceBaseDirs: Collection<String>
sourceBaseDirs: Collection<String>,
private val jsIrFileMetadataFactory: JsIrFileMetadataFactory
) : IrFileSerializer(
messageLogger,
declarationTable,
@@ -38,39 +39,7 @@ class JsIrFileSerializer(
normalizeAbsolutePaths = normalizeAbsolutePaths,
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")
}
private fun IrAnnotationContainer.isExportedDeclaration(): Boolean {
return annotations.hasAnnotation(JS_EXPORT_FQN) && !isExportIgnoreDeclaration()
}
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
override fun backendSpecificMetadata(irFile: IrFile) = jsIrFileMetadataFactory.createJsIrFileMetadata(irFile)
}
@@ -21,7 +21,8 @@ class JsIrModuleSerializer(
normalizeAbsolutePaths: Boolean,
sourceBaseDirs: Collection<String>,
private val languageVersionSettings: LanguageVersionSettings,
shouldCheckSignaturesOnUniqueness: Boolean = true
shouldCheckSignaturesOnUniqueness: Boolean = true,
private val jsIrFileMetadataFactory: JsIrFileMetadataFactory = JsIrFileEmptyMetadataFactory
) : IrModuleSerializer<JsIrFileSerializer>(messageLogger, compatibilityMode, normalizeAbsolutePaths, sourceBaseDirs) {
private val globalDeclarationTable = JsGlobalDeclarationTable(
@@ -37,5 +38,6 @@ class JsIrModuleSerializer(
normalizeAbsolutePaths = normalizeAbsolutePaths,
sourceBaseDirs = sourceBaseDirs,
languageVersionSettings = languageVersionSettings,
jsIrFileMetadataFactory = jsIrFileMetadataFactory
)
}
}
@@ -5,3 +5,9 @@ Output:
-- JS --
Exit code: OK
Output:
compiler/testData/multiplatform/jsNameClash/common.kt:1:1: warning: 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https://youtrack.jetbrains.com/issue/KT-61573
expect class ClassWithImplByExtension
^
compiler/testData/multiplatform/jsNameClash/js.kt:1:1: warning: 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https://youtrack.jetbrains.com/issue/KT-61573
actual class ClassWithImplByExtension
^
@@ -15,3 +15,6 @@ actual annotation class A
-- JS --
Exit code: OK
Output:
compiler/testData/multiplatform/optionalExpectation/common.kt:5:1: warning: 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https://youtrack.jetbrains.com/issue/KT-61573
expect annotation class A()
^
+6
View File
@@ -15,3 +15,9 @@ actual class Printer {
-- JS --
Exit code: OK
Output:
compiler/testData/multiplatform/simple/common.kt:1:1: warning: 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https://youtrack.jetbrains.com/issue/KT-61573
expect class Printer() {
^
compiler/testData/multiplatform/simple/js.kt:1:1: warning: 'expect'/'actual' classes (including interfaces, objects, annotations, enums, and 'actual' typealiases) are in Beta. You can use -Xexpect-actual-classes flag to suppress this warning. Also see: https://youtrack.jetbrains.com/issue/KT-61573
actual class Printer {
^
@@ -18,6 +18,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.CodegenTestCase
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.incremental.md5
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -76,13 +77,15 @@ class FilePathsInKlibTest : CodegenTestCase() {
val metadataSerializer =
KlibMetadataIncrementalSerializer(module.compilerConfiguration, module.project, module.jsFrontEndResult.hasErrors)
val diagnosticReporter = DiagnosticReporterFactory.createPendingReporter()
generateKLib(
module,
outputKlibPath = destination.path,
nopack = false,
jsOutputName = MODULE_NAME,
icData = icData,
moduleFragment = moduleFragment
moduleFragment = moduleFragment,
diagnosticReporter = diagnosticReporter
) { file ->
metadataSerializer.serializeScope(file, module.jsFrontEndResult.bindingContext, moduleFragment.descriptor)
}
@@ -26,6 +26,7 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.incremental.ChangedFiles
import org.jetbrains.kotlin.incremental.IncrementalJsCompilerRunner
import org.jetbrains.kotlin.incremental.multiproject.EmptyModulesApiHistory
@@ -36,6 +37,7 @@ import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrLinker
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsIrModuleSerializer
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.JsManglerDesc
import org.jetbrains.kotlin.ir.backend.js.lower.serialization.ir.collectExportedNames
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.IrModuleToJsTransformer
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.TranslationMode
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
@@ -490,12 +492,14 @@ class GenerateIrRuntime {
files: List<KtFile>,
perFile: Boolean = false
): String {
val diagnosticReporter = DiagnosticReporterFactory.createPendingReporter()
val tmpKlibDir = createTempDirectory().also { it.toFile().deleteOnExit() }.toString()
val metadataSerializer = KlibMetadataIncrementalSerializer(configuration, project, false)
serializeModuleIntoKlib(
moduleName,
configuration,
IrMessageLogger.None,
diagnosticReporter,
files.map(::KtPsiSourceFile),
tmpKlibDir,
emptyList(),
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.incremental
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.js.klib.generateIrForKlibSerialization
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.diagnostics.DiagnosticReporterFactory
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl
@@ -108,13 +109,15 @@ abstract class IrAbstractInvalidationTest(
val metadataSerializer =
KlibMetadataIncrementalSerializer(configuration, sourceModule.project, sourceModule.jsFrontEndResult.hasErrors)
val diagnosticReporter = DiagnosticReporterFactory.createPendingReporter()
generateKLib(
sourceModule,
outputKlibFile.canonicalPath,
nopack = false,
jsOutputName = moduleName,
icData = icData,
moduleFragment = moduleFragment
moduleFragment = moduleFragment,
diagnosticReporter = diagnosticReporter
) { file ->
metadataSerializer.serializeScope(file, sourceModule.jsFrontEndResult.bindingContext, moduleFragment.descriptor)
}
@@ -62,6 +62,7 @@ class FirJsKlibBackendFacade(
configuration[CommonConfigurationKeys.MODULE_NAME]!!,
configuration,
configuration.get(IrMessageLogger.IR_MESSAGE_LOGGER) ?: IrMessageLogger.None,
inputArtifact.diagnosticReporter,
inputArtifact.sourceFiles,
klibPath = outputFile,
libraries.map { it.library },
@@ -51,6 +51,7 @@ class JsKlibBackendFacade(
configuration[CommonConfigurationKeys.MODULE_NAME]!!,
configuration,
configuration.irMessageLogger,
inputArtifact.diagnosticReporter,
inputArtifact.sourceFiles,
klibPath = outputFile,
JsEnvironmentConfigurator.getAllRecursiveLibrariesFor(module, testServices).keys.toList(),
@@ -112,12 +112,19 @@ abstract class AbstractJsPartialLinkageTestCase(private val compilerType: Compil
"-Xir-produce-klib-file",
"-ir-output-dir", klibFile.parentFile.absolutePath,
"-ir-output-name", moduleName,
"-Werror" // Halt on any unexpected warning.
// Halt on any unexpected warning.
"-Werror",
// Tests suppress the INVISIBLE_REFERENCE check.
// However, JS doesn't produce the INVISIBLE_REFERENCE error;
// As result, it triggers a suppression error warning about the redundant suppression.
// This flag is used to disable the warning.
"-Xdont-warn-on-error-suppression"
),
dependencies.toCompilerArgs(),
listOf(
"-language-version", "2.0",
"-Xsuppress-version-warnings" // Don't fail on language version warnings.
// Don't fail on language version warnings.
"-Xsuppress-version-warnings"
).takeIf { compilerType.useFir },
kotlinSourceFilePaths
)
@@ -364,10 +364,7 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() {
fun testProjectWithExportedNamesClash(gradleVersion: GradleVersion) {
project("kotlin-js-invalid-project-with-exported-clash", gradleVersion) {
build("compileKotlinJs") {
assertOutputContains("""
|There are clashes of declaration names that annotated with @JsExport in module 'kotlin-js-invalid-project-with-exported-clash'.
| * Next files contain declarations with @JsExport and name 'best'
""".trimMargin())
assertOutputContains("Exporting name 'best' in ES modules may clash")
}
}
}
@@ -62,6 +62,7 @@ class FirWasmKlibBackendFacade(
configuration[CommonConfigurationKeys.MODULE_NAME]!!,
configuration,
configuration.get(IrMessageLogger.IR_MESSAGE_LOGGER) ?: IrMessageLogger.None,
inputArtifact.diagnosticReporter,
inputArtifact.sourceFiles,
klibPath = outputFile,
libraries.map { it.library },