diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt index f3f2da6130c..3a6db530a4c 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JSCompilerArguments.kt @@ -127,7 +127,7 @@ class K2JSCompilerArguments : CommonCompilerArguments() { @Argument( value = "-Xir-dce-runtime-diagnostic", - valueDescription = "{$DCE_RUNTIME_DIAGNOSTIC_LOG|$DCE_RUNTIME_DIAGNOSTIC_EXCEPTION}", + valueDescription = "{$RUNTIME_DIAGNOSTIC_LOG|$RUNTIME_DIAGNOSTIC_EXCEPTION}", description = "Enable runtime diagnostics when performing DCE instead of removing declarations" ) var irDceRuntimeDiagnostic: String? by NullableStringFreezableVar(null) @@ -157,6 +157,19 @@ class K2JSCompilerArguments : CommonCompilerArguments() { @Argument(value = "-Xir-base-class-in-metadata", description = "Write base class into metadata") var irBaseClassInMetadata: Boolean by FreezableVar(false) + @Argument( + value = "-Xir-safe-external-boolean", + description = "Safe access via Boolean() to Boolean properties in externals to safely cast falsy values." + ) + var irSafeExternalBoolean: Boolean by FreezableVar(false) + + @Argument( + value = "-Xir-safe-external-boolean-diagnostic", + valueDescription = "{$RUNTIME_DIAGNOSTIC_LOG|$RUNTIME_DIAGNOSTIC_EXCEPTION}", + description = "Enable runtime diagnostics when access safely to boolean in external declarations" + ) + var irSafeExternalBooleanDiagnostic: String? by NullableStringFreezableVar(null) + @Argument(value = "-Xir-per-module", description = "Splits generated .js per-module") var irPerModule: Boolean by FreezableVar(false) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java index 822423006d4..dcee2ea8b06 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JsArgumentConstants.java @@ -29,6 +29,6 @@ public interface K2JsArgumentConstants { String SOURCE_MAP_SOURCE_CONTENT_NEVER = "never"; String SOURCE_MAP_SOURCE_CONTENT_INLINING = "inlining"; - String DCE_RUNTIME_DIAGNOSTIC_LOG = "log"; - String DCE_RUNTIME_DIAGNOSTIC_EXCEPTION = "exception"; + String RUNTIME_DIAGNOSTIC_LOG = "log"; + String RUNTIME_DIAGNOSTIC_EXCEPTION = "exception"; } diff --git a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt index aad04d39c65..e8b00d321bb 100644 --- a/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt +++ b/compiler/cli/cli-js/src/org/jetbrains/kotlin/cli/js/K2JsIrCompiler.kt @@ -17,8 +17,8 @@ import org.jetbrains.kotlin.cli.common.ExitCode.COMPILATION_ERROR import org.jetbrains.kotlin.cli.common.ExitCode.OK import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants -import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.DCE_RUNTIME_DIAGNOSTIC_EXCEPTION -import org.jetbrains.kotlin.cli.common.arguments.K2JsArgumentConstants.DCE_RUNTIME_DIAGNOSTIC_LOG +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.extensions.ScriptEvaluationExtension import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport @@ -267,7 +267,7 @@ class K2JsIrCompiler : CLICompiler() { mainArguments = mainCallArguments, generateFullJs = !arguments.irDce, generateDceJs = arguments.irDce, - dceRuntimeDiagnostic = DceRuntimeDiagnostic.resolve( + dceRuntimeDiagnostic = RuntimeDiagnostic.resolve( arguments.irDceRuntimeDiagnostic, messageCollector ), @@ -277,6 +277,11 @@ class K2JsIrCompiler : CLICompiler() { propertyLazyInitialization = arguments.irPropertyLazyInitialization, legacyPropertyAccess = arguments.irLegacyPropertyAccess, baseClassIntoMetadata = arguments.irBaseClassInMetadata, + safeExternalBoolean = arguments.irSafeExternalBoolean, + safeExternalBooleanDiagnostic = RuntimeDiagnostic.resolve( + arguments.irSafeExternalBooleanDiagnostic, + messageCollector + ), ) val jsCode = if (arguments.irDce && !arguments.irDceDriven) compiledModule.dceJsCode!! else compiledModule.jsCode!! @@ -436,15 +441,15 @@ class K2JsIrCompiler : CLICompiler() { } } -fun DceRuntimeDiagnostic.Companion.resolve( +fun RuntimeDiagnostic.Companion.resolve( value: String?, messageCollector: MessageCollector -): DceRuntimeDiagnostic? = when (value?.lowercase()) { - DCE_RUNTIME_DIAGNOSTIC_LOG -> DceRuntimeDiagnostic.LOG - DCE_RUNTIME_DIAGNOSTIC_EXCEPTION -> DceRuntimeDiagnostic.EXCEPTION +): RuntimeDiagnostic? = when (value?.lowercase()) { + RUNTIME_DIAGNOSTIC_LOG -> RuntimeDiagnostic.LOG + RUNTIME_DIAGNOSTIC_EXCEPTION -> RuntimeDiagnostic.EXCEPTION null -> null else -> { - messageCollector.report(STRONG_WARNING, "Unknown DCE runtime diagnostic '$value'") + messageCollector.report(STRONG_WARNING, "Unknown runtime diagnostic '$value'") null } } diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt index aa2b39ea620..3e16b3a85b5 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/Dce.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.ir.IrElement import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET import org.jetbrains.kotlin.ir.backend.js.export.isExported import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder -import org.jetbrains.kotlin.ir.backend.js.lower.ClassReferenceLowering import org.jetbrains.kotlin.ir.backend.js.utils.* import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.* @@ -23,9 +22,8 @@ import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid import org.jetbrains.kotlin.ir.visitors.acceptVoid -import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic import org.jetbrains.kotlin.js.config.JSConfigurationKeys -import org.jetbrains.kotlin.js.config.removingBody import org.jetbrains.kotlin.utils.addIfNotNull import java.util.* @@ -109,11 +107,6 @@ private fun buildRoots(modules: Iterable, context: JsIrBackend return rootDeclarations } -private fun DceRuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) = - when (this) { - DceRuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog - DceRuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException - } private fun processUselessDeclarations( modules: Iterable, @@ -173,6 +166,16 @@ private fun IrDeclaration.processUselessDeclaration(context: JsIrBackendContext) } } +private fun RuntimeDiagnostic.unreachableDeclarationMethod(context: JsIrBackendContext) = + when (this) { + RuntimeDiagnostic.LOG -> context.intrinsics.jsUnreachableDeclarationLog + RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsUnreachableDeclarationException + } + +private fun RuntimeDiagnostic.removingBody(): Boolean { + return this != RuntimeDiagnostic.LOG +} + private fun IrDeclaration.processWithDiagnostic(context: JsIrBackendContext) { when (this) { is IrFunction -> processFunctionWithDiagnostic(context) diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt index 5a617bc1116..016cc7a8122 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIntrinsics.kt @@ -164,6 +164,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC val jsUnreachableDeclarationLog = getInternalFunction("unreachableDeclarationLog") val jsUnreachableDeclarationException = getInternalFunction("unreachableDeclarationException") + val jsNativeBoolean = getInternalFunction("nativeBoolean") + val jsBooleanInExternalLog = getInternalFunction("booleanInExternalLog") + val jsBooleanInExternalException = getInternalFunction("booleanInExternalException") + // Coroutines val jsCoroutineContext diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt index dd6d72ab216..613be1982b1 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsIrBackendContext.kt @@ -28,7 +28,7 @@ import org.jetbrains.kotlin.ir.symbols.impl.DescriptorlessExternalPackageFragmen import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.impl.IrDynamicTypeImpl import org.jetbrains.kotlin.ir.util.* -import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic import org.jetbrains.kotlin.js.config.ErrorTolerancePolicy import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.name.FqName @@ -45,10 +45,12 @@ class JsIrBackendContext( override val configuration: CompilerConfiguration, // TODO: remove configuration from backend context override val scriptMode: Boolean = false, override val es6mode: Boolean = false, - val dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, + val dceRuntimeDiagnostic: RuntimeDiagnostic? = null, val propertyLazyInitialization: Boolean = false, val legacyPropertyAccess: Boolean = false, val baseClassIntoMetadata: Boolean = false, + val safeExternalBoolean: Boolean = false, + val safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null, ) : JsCommonBackendContext { val fileToInitializationFuns: MutableMap = mutableMapOf() val fileToInitializerPureness: MutableMap = mutableMapOf() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index dadefdf2e4c..5ddf108391c 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -391,6 +391,12 @@ private val copyPropertyAccessorBodiesLoweringPass = makeDeclarationTransformerP prerequisite = setOf(propertyAccessorInlinerLoweringPhase) ) +private val booleanPropertyInExternalLowering = makeBodyLoweringPhase( + ::BooleanPropertyInExternalLowering, + name = "BooleanPropertyInExternalLowering", + description = "Lowering which wrap boolean in external declarations with Boolean() call and add diagnostic for such cases" +) + private val foldConstantLoweringPhase = makeBodyLoweringPhase( { FoldConstantLowering(it, true) }, name = "FoldConstantLowering", @@ -779,6 +785,7 @@ private val loweringList = listOf( removeInitializersForLazyProperties, propertyAccessorInlinerLoweringPhase, copyPropertyAccessorBodiesLoweringPass, + booleanPropertyInExternalLowering, foldConstantLoweringPhase, privateMembersLoweringPhase, privateMemberUsagesLoweringPhase, diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt index e4c0391d91b..c48aa0dcbc2 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/compiler.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.ir.declarations.StageController import org.jetbrains.kotlin.ir.declarations.persistent.PersistentIrFactory import org.jetbrains.kotlin.ir.util.ExternalDependenciesGenerator import org.jetbrains.kotlin.ir.util.noUnboundLeft -import org.jetbrains.kotlin.js.config.DceRuntimeDiagnostic +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic import org.jetbrains.kotlin.library.KotlinLibrary import org.jetbrains.kotlin.library.resolver.KotlinLibraryResolveResult import org.jetbrains.kotlin.name.FqName @@ -48,7 +48,7 @@ fun compile( generateFullJs: Boolean = true, generateDceJs: Boolean = false, dceDriven: Boolean = false, - dceRuntimeDiagnostic: DceRuntimeDiagnostic? = null, + dceRuntimeDiagnostic: RuntimeDiagnostic? = null, es6mode: Boolean = false, multiModule: Boolean = false, relativeRequirePath: Boolean = false, @@ -56,6 +56,8 @@ fun compile( legacyPropertyAccess: Boolean = false, baseClassIntoMetadata: Boolean = false, lowerPerModule: Boolean = false, + safeExternalBoolean: Boolean = false, + safeExternalBooleanDiagnostic: RuntimeDiagnostic? = null, ): CompilerResult { val (moduleFragment: IrModuleFragment, dependencyModules, irBuiltIns, symbolTable, deserializer, moduleToName) = loadIr(project, mainModule, analyzer, configuration, allDependencies, friendDependencies, irFactory) @@ -78,7 +80,9 @@ fun compile( dceRuntimeDiagnostic = dceRuntimeDiagnostic, propertyLazyInitialization = propertyLazyInitialization, legacyPropertyAccess = legacyPropertyAccess, - baseClassIntoMetadata = baseClassIntoMetadata + baseClassIntoMetadata = baseClassIntoMetadata, + safeExternalBoolean = safeExternalBoolean, + safeExternalBooleanDiagnostic = safeExternalBooleanDiagnostic ) // Load declarations referenced during `context` initialization diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BooleanPropertyInExternalLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BooleanPropertyInExternalLowering.kt new file mode 100644 index 00000000000..3980be44525 --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/BooleanPropertyInExternalLowering.kt @@ -0,0 +1,101 @@ +/* + * Copyright 2010-2020 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 + +import org.jetbrains.kotlin.backend.common.BodyLoweringPass +import org.jetbrains.kotlin.backend.common.lower.createIrBuilder +import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext +import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder +import org.jetbrains.kotlin.ir.builders.createTmpVariable +import org.jetbrains.kotlin.ir.builders.irBlock +import org.jetbrains.kotlin.ir.builders.irGet +import org.jetbrains.kotlin.ir.declarations.IrDeclaration +import org.jetbrains.kotlin.ir.expressions.IrBody +import org.jetbrains.kotlin.ir.expressions.IrCall +import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.util.isEffectivelyExternal +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.js.config.RuntimeDiagnostic + +class BooleanPropertyInExternalLowering( + private val context: JsIrBackendContext +) : BodyLoweringPass { + + override fun lower(irBody: IrBody, container: IrDeclaration) { + irBody.transformChildrenVoid( + ExternalBooleanPropertyProcessor( + context + ) + ) + } + + private class ExternalBooleanPropertyProcessor( + private val context: JsIrBackendContext + ) : IrElementTransformerVoid() { + + private val safeExternalBoolean + get() = context.safeExternalBoolean + + private val safeExternalBooleanDiagnostic + get() = context.safeExternalBooleanDiagnostic + + private val booleanType + get() = context.irBuiltIns.booleanType + + + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + + val symbol = expression.symbol + val callee = symbol.owner + val property = callee.correspondingPropertySymbol?.owner ?: return expression + + if (!property.isEffectivelyExternal()) return expression + + if (callee != property.getter) return expression + + if (callee.returnType != booleanType) return expression + + val function = safeExternalBooleanDiagnostic?.diagnosticMethod() + + if (!safeExternalBoolean && function == null) return expression + + if (safeExternalBoolean && function == null) { + return JsIrBuilder.buildCall( + target = context.intrinsics.jsNativeBoolean + ).apply { + putValueArgument(0, expression) + } + } + + return context.createIrBuilder(symbol).irBlock { + val tmp = createTmpVariable(expression) + val call = JsIrBuilder.buildCall( + target = function!! + ).apply { + putValueArgument(0, irGet(tmp)) + } + + +call + + val newBooleanGet = if (safeExternalBoolean) { + JsIrBuilder.buildCall( + target = this@ExternalBooleanPropertyProcessor.context.intrinsics.jsNativeBoolean + ).apply { + putValueArgument(0, irGet(tmp)) + } + } else irGet(tmp) + +newBooleanGet + } + } + + private fun RuntimeDiagnostic.diagnosticMethod() = when (this) { + RuntimeDiagnostic.LOG -> context.intrinsics.jsBooleanInExternalLog + RuntimeDiagnostic.EXCEPTION -> context.intrinsics.jsBooleanInExternalException + } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt index 325a36f628c..954dde2fb55 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/transformers/irToJs/jsAstUtils.kt @@ -110,7 +110,7 @@ fun translateCall( // @JsName-annotated external property accessors are translated as function calls if (function.getJsName() == null) { val property = function.correspondingPropertySymbol?.owner - if (property != null && (property.isEffectivelyExternal())) { + if (property != null && property.isEffectivelyExternal()) { val nameRef = JsNameRef(context.getNameForProperty(property), jsDispatchReceiver) return when (function) { property.getter -> nameRef @@ -222,22 +222,23 @@ fun translateCall( } else { val defaultResult = JsInvocation(ref, listOfNotNull(jsExtensionReceiver) + arguments) - val alternativeResult = if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) { - val property = function.correspondingPropertySymbol?.owner - if (property != null) { - val propertyName = context.getNameForProperty(property) - val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident)) - val fnName = when (function) { - property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner) - property.setter -> { - args += arguments - context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner) + val alternativeResult = + if (jsDispatchReceiver != null && jsExtensionReceiver == null && context.staticContext.backendContext.legacyPropertyAccess) { + val property = function.correspondingPropertySymbol?.owner + if (property != null) { + val propertyName = context.getNameForProperty(property) + val args = mutableListOf(jsDispatchReceiver, JsStringLiteral(symbolName.ident), JsStringLiteral(propertyName.ident)) + val fnName = when (function) { + property.getter -> context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertyGet.owner) + property.setter -> { + args += arguments + context.getNameForStaticFunction(context.staticContext.backendContext.intrinsics.safePropertySet.owner) + } + else -> error("Function must be an accessor of corresponding property") } - else -> error("Function must be an accessor of corresponding property") - } - JsInvocation(fnName.makeRef(), args) + JsInvocation(fnName.makeRef(), args) + } else null } else null - } else null alternativeResult ?: defaultResult } diff --git a/compiler/testData/cli/js/jsExtraHelp.out b/compiler/testData/cli/js/jsExtraHelp.out index 6d60cd35557..e8fee8f4628 100644 --- a/compiler/testData/cli/js/jsExtraHelp.out +++ b/compiler/testData/cli/js/jsExtraHelp.out @@ -27,6 +27,9 @@ where advanced options include: -Xir-produce-klib-file Generate packed klib into file specified by -output. Disables pre-IR backend -Xir-property-lazy-initialization Perform lazy initialization for properties + -Xir-safe-external-boolean Safe access via Boolean() to Boolean properties in externals to safely cast falsy values. + -Xir-safe-external-boolean-diagnostic={log|exception} + Enable runtime diagnostics when access safely to boolean in external declarations -Xmetadata-only Generate *.meta.js and *.kjsm files only -Xrepositories= Paths to additional places where libraries could be found -Xtyped-arrays Translate primitive arrays to JS typed arrays diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt deleted file mode 100644 index ee78d252589..00000000000 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/DceRuntimeDiagnostic.kt +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2010-2020 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.js.config - -enum class DceRuntimeDiagnostic { - LOG, - EXCEPTION; - - companion object -} - -fun DceRuntimeDiagnostic.removingBody(): Boolean { - return this != DceRuntimeDiagnostic.LOG -} - -fun DceRuntimeDiagnostic.dceRuntimeDiagnosticToArgumentOfUnreachableMethod(): Int { - return when (this) { - DceRuntimeDiagnostic.LOG -> 0 - DceRuntimeDiagnostic.EXCEPTION -> 1 - } -} diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/RuntimeDiagnostic.kt b/js/js.frontend/src/org/jetbrains/kotlin/js/config/RuntimeDiagnostic.kt new file mode 100644 index 00000000000..297bb3512d5 --- /dev/null +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/RuntimeDiagnostic.kt @@ -0,0 +1,13 @@ +/* + * Copyright 2010-2020 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.js.config + +enum class RuntimeDiagnostic { + LOG, + EXCEPTION; + + companion object +} \ No newline at end of file diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 5b3f3357ffa..41a29c8d0d9 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -150,6 +150,7 @@ abstract class BasicBoxTest( val splitPerModule = SPLIT_PER_MODULE.matcher(fileContent).find() val propertyLazyInitialization = PROPERTY_LAZY_INITIALIZATION.matcher(fileContent).find() + val safeExternalBoolean = SAFE_EXTERNAL_BOOLEAN.matcher(fileContent).find() TestFileFactoryImpl().use { testFactory -> val inputFiles = TestFiles.createTestFiles( @@ -214,7 +215,8 @@ abstract class BasicBoxTest( skipDceDriven, splitPerModule, errorPolicy, - propertyLazyInitialization + propertyLazyInitialization, + safeExternalBoolean ) when { @@ -468,6 +470,7 @@ abstract class BasicBoxTest( splitPerModule: Boolean, errorIgnorancePolicy: ErrorTolerancePolicy, propertyLazyInitialization: Boolean, + safeExternalBoolean: Boolean, ) { val kotlinFiles = module.files.filter { it.fileName.endsWith(".kt") } val testFiles = kotlinFiles.map { it.fileName } @@ -518,6 +521,7 @@ abstract class BasicBoxTest( skipDceDriven, splitPerModule, propertyLazyInitialization, + safeExternalBoolean, ) if (incrementalCompilationChecksEnabled && module.hasFilesToRecompile) { @@ -608,10 +612,11 @@ abstract class BasicBoxTest( testPackage, testFunction, needsFullIrRuntime, - isMainModule = false, - skipDceDriven = true, - splitPerModule = false, + isMainModule = false, + skipDceDriven = true, + splitPerModule = false, propertyLazyInitialization = false, + safeExternalBoolean = false, ) val originalOutput = FileUtil.loadFile(outputFile) @@ -691,6 +696,7 @@ abstract class BasicBoxTest( skipDceDriven: Boolean, splitPerModule: Boolean, propertyLazyInitialization: Boolean, + safeExternalBoolean: Boolean, ) { val translator = K2JSTranslator(config, false) val translationResult = translator.translateUnits(ExceptionThrowingReporter, units, mainCallParameters) @@ -1094,6 +1100,8 @@ abstract class BasicBoxTest( private val PROPERTY_LAZY_INITIALIZATION = Pattern.compile("^// *PROPERTY_LAZY_INITIALIZATION *$", Pattern.MULTILINE) + private val SAFE_EXTERNAL_BOOLEAN = Pattern.compile("^// *SAFE_EXTERNAL_BOOLEAN *$", Pattern.MULTILINE) + @JvmStatic protected val runTestInNashorn = getBoolean("kotlin.js.useNashorn") diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt index 4f3e071edb1..733121203a9 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicIrBoxTest.kt @@ -95,6 +95,7 @@ abstract class BasicIrBoxTest( skipDceDriven: Boolean, splitPerModule: Boolean, propertyLazyInitialization: Boolean, + safeExternalBoolean: Boolean, ) { val filesToCompile = units.map { (it as TranslationUnit.SourceFile).file } @@ -150,7 +151,8 @@ abstract class BasicIrBoxTest( es6mode = runEs6Mode, multiModule = splitPerModule || perModule, propertyLazyInitialization = propertyLazyInitialization, - lowerPerModule = lowerPerModule + lowerPerModule = lowerPerModule, + safeExternalBoolean = safeExternalBoolean, ) compiledModule.jsCode!!.writeTo(outputFile, config) @@ -179,7 +181,8 @@ abstract class BasicIrBoxTest( dceDriven = true, es6mode = runEs6Mode, multiModule = splitPerModule || perModule, - propertyLazyInitialization = propertyLazyInitialization + propertyLazyInitialization = propertyLazyInitialization, + safeExternalBoolean = safeExternalBoolean, ).jsCode!!.writeTo(pirOutputFile, config) } } else { diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java index 9623b88f843..394cfd14170 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/es6/semantics/IrBoxJsES6TestGenerated.java @@ -6822,6 +6822,11 @@ public class IrBoxJsES6TestGenerated extends AbstractIrBoxJsES6Test { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/propertyAccess"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR_ES6, true); } + @TestMetadata("booleanInExternals.kt") + public void testBooleanInExternals() throws Exception { + runTest("js/js.translator/testData/box/propertyAccess/booleanInExternals.kt"); + } + @TestMetadata("classUsesPackageProperties.kt") public void testClassUsesPackageProperties() throws Exception { runTest("js/js.translator/testData/box/propertyAccess/classUsesPackageProperties.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java index 0b4f4512d3c..21391fc8900 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/ir/semantics/IrBoxJsTestGenerated.java @@ -6822,6 +6822,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/propertyAccess"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS_IR, true); } + @TestMetadata("booleanInExternals.kt") + public void testBooleanInExternals() throws Exception { + runTest("js/js.translator/testData/box/propertyAccess/booleanInExternals.kt"); + } + @TestMetadata("classUsesPackageProperties.kt") public void testClassUsesPackageProperties() throws Exception { runTest("js/js.translator/testData/box/propertyAccess/classUsesPackageProperties.kt"); diff --git a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 707232bb5eb..df91f1e965d 100644 --- a/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/tests-gen/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -6842,6 +6842,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { KtTestUtil.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("js/js.translator/testData/box/propertyAccess"), Pattern.compile("^([^_](.+))\\.kt$"), null, TargetBackend.JS, true); } + @TestMetadata("booleanInExternals.kt") + public void testBooleanInExternals() throws Exception { + runTest("js/js.translator/testData/box/propertyAccess/booleanInExternals.kt"); + } + @TestMetadata("classUsesPackageProperties.kt") public void testClassUsesPackageProperties() throws Exception { runTest("js/js.translator/testData/box/propertyAccess/classUsesPackageProperties.kt"); diff --git a/js/js.translator/testData/box/propertyAccess/booleanInExternals.kt b/js/js.translator/testData/box/propertyAccess/booleanInExternals.kt new file mode 100644 index 00000000000..fb0a3447e23 --- /dev/null +++ b/js/js.translator/testData/box/propertyAccess/booleanInExternals.kt @@ -0,0 +1,43 @@ +// SAFE_EXTERNAL_BOOLEAN + +fun box(): String { + val interfaceWithBoolean: InterfaceWithBoolean = js("{}") + C().c = interfaceWithBoolean.foo + C().c = interfaceWithBoolean.bar + + return "OK" +} + +abstract class A { + open fun get(): T { + return this.asDynamic()["attr"].unsafeCast() + } + + open fun set(value: T) { + this.asDynamic()["attr"] = value + } +} + +class B : A() { + override fun set(value: Boolean) { + if (value) { + this.asDynamic()["attr"] = value + } + } +} + +val b: A = B() + +class C { + var c: Boolean + get() = b.get() + set(newValue) { + b.set(newValue) + } +} + +external interface InterfaceWithBoolean { + var foo: Boolean + @JsName("goo") + var bar: Boolean +} \ No newline at end of file diff --git a/libraries/stdlib/js-ir/runtime/booleanInExternalHelpers.kt b/libraries/stdlib/js-ir/runtime/booleanInExternalHelpers.kt new file mode 100644 index 00000000000..1baf66f1bd1 --- /dev/null +++ b/libraries/stdlib/js-ir/runtime/booleanInExternalHelpers.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2021 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 kotlin.js + +import JsError + +@JsName("Boolean") +internal external fun nativeBoolean(obj: Any?): Boolean + +internal fun booleanInExternalLog(obj: dynamic) { + if (jsTypeOf(obj) != "boolean") { + console.asDynamic().error("Boolean expected, but actual:", obj) + } +} + +internal fun booleanInExternalException(obj: dynamic) { + if (jsTypeOf(obj) != "boolean") { + throw JsError("Boolean expected, but actual: $obj") + } +} \ No newline at end of file