[K/JS] Add support of compilation with ES-classes

This commit is contained in:
Artem Kobzar
2023-01-17 18:14:17 +00:00
committed by Space Team
parent 8d728d4f53
commit 71486a321c
239 changed files with 57567 additions and 1658 deletions
@@ -65,7 +65,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
boolean hasFlags();
@@ -73,7 +73,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
int getFlags();
@@ -433,7 +433,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public boolean hasFlags() {
@@ -443,7 +443,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public int getFlags() {
@@ -1225,7 +1225,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public boolean hasFlags() {
@@ -1235,7 +1235,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public int getFlags() {
@@ -1245,7 +1245,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public Builder setFlags(int value) {
@@ -1258,7 +1258,7 @@ public final class DebugKlibMetadataProtoBuf {
* <code>optional int32 flags = 2;</code>
*
* <pre>
*0x1 = preRelease
*Possible values are listed in KlibMetadataHeaderFlags class.
* </pre>
*/
public Builder clearFlags() {
@@ -291,6 +291,17 @@ class K2JSCompilerArguments : CommonCompilerArguments() {
)
var strictImplicitExportType: Boolean by FreezableVar(false)
@GradleOption(
value = DefaultValue.BOOLEAN_FALSE_DEFAULT,
gradleInputType = GradleInputTypes.INPUT,
shouldGenerateDeprecatedKotlinOptions = true,
)
@Argument(
value = "-Xes-classes",
description = "Generated JavaScript will use ES2015 classes."
)
var useEsClasses: Boolean by FreezableVar(false)
@GradleOption(
value = DefaultValue.BOOLEAN_TRUE_DEFAULT,
gradleInputType = GradleInputTypes.INPUT,
@@ -144,7 +144,8 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
arguments.irSafeExternalBooleanDiagnostic,
messageCollector
),
granularity = arguments.granularity
granularity = arguments.granularity,
es6mode = arguments.useEsClasses
)
}
@@ -668,7 +669,14 @@ class K2JsIrCompiler : CLICompiler<K2JSCompilerArguments>() {
compilerConfiguration = configurationJs,
irFactory = { IrFactoryImplForJsIC(WholeWorldStageController()) },
mainArguments = mainCallArguments,
compilerInterfaceFactory = { mainModule, cfg -> JsIrCompilerWithIC(mainModule, cfg, arguments.granularity) }
compilerInterfaceFactory = { mainModule, cfg ->
JsIrCompilerWithIC(
mainModule,
cfg,
arguments.granularity,
es6mode = arguments.useEsClasses
)
}
)
val artifacts = cacheUpdater.actualizeCaches()
@@ -30,6 +30,6 @@ abstract class AbstractValueRemapper : IrElementTransformerVoid() {
}
}
open class ValueRemapper(private val map: Map<IrValueSymbol, IrValueSymbol>) : AbstractValueRemapper() {
open class ValueRemapper(protected open val map: Map<IrValueSymbol, IrValueSymbol>) : AbstractValueRemapper() {
override fun remapValue(oldValue: IrValueSymbol): IrValueSymbol? = map[oldValue]
}
@@ -125,6 +125,13 @@ class InlineClassLowering(val context: CommonBackendContext) {
return builder.irGet(initFunction.valueParameters.single())
return expression
}
override fun visitSetValue(expression: IrSetValue): IrExpression {
expression.transformChildrenVoid()
if (expression.symbol == origParameterSymbol)
return builder.irSet(initFunction.valueParameters.single(), expression.value)
return expression
}
})
}
+irReturn(unboxedInlineClassValue())
@@ -152,11 +152,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsAnyToString = getInternalFunction("anyToString")
val jsCompareTo = getInternalFunction("compareTo")
val jsEquals = getInternalFunction("equals")
val jsConstruct = getInternalFunction("construct")
val jsNewTarget = getInternalFunction("jsNewTarget")
val jsEmptyObject = getInternalFunction("emptyObject")
val jsOpenInitializerBox = getInternalFunction("openInitializerBox")
val es6DefaultType = getInternalFunction("DefaultType")
val jsImul = getInternalFunction("imul")
@@ -167,6 +165,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsBooleanInExternalLog = getInternalFunction("booleanInExternalLog")
val jsBooleanInExternalException = getInternalFunction("booleanInExternalException")
val jsNewAnonymousClass = getInternalFunction("jsNewAnonymousClass")
// Coroutines
val jsCoroutineContext
@@ -369,7 +369,10 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsPrototypeOfSymbol = getInternalFunction("protoOf")
val jsDefinePropertySymbol = getInternalFunction("defineProp")
val jsObjectCreateSymbol = getInternalFunction("objectCreate") // Object.create
val jsObjectCreateSymbol = getInternalFunction("objectCreate") // Object.create(x)
val jsCreateThisSymbol = getInternalFunction("createThis") // Object.create(x.prototype)
val jsBoxApplySymbol = getInternalFunction("boxApply")
val jsCreateExternalThisSymbol = getInternalFunction("createExternalThis")
// Helpers:
@@ -29,6 +29,12 @@ private fun DeclarationContainerLoweringPass.runOnFilesPostfix(files: Iterable<I
private fun ClassLoweringPass.runOnFilesPostfix(moduleFragment: IrModuleFragment) = moduleFragment.files.forEach { runOnFilePostfix(it) }
private fun List<Lowering>.toCompilerPhase() =
map {
@Suppress("USELESS_CAST")
it.modulePhase as CompilerPhase<JsIrBackendContext, Iterable<IrModuleFragment>, Iterable<IrModuleFragment>>
}.reduce { acc, lowering -> acc.then(lowering) }
private fun makeJsModulePhase(
lowering: (JsIrBackendContext) -> FileLoweringPass,
name: String,
@@ -683,19 +689,6 @@ private val typeOperatorLoweringPhase = makeBodyLoweringPhase(
)
)
private val es6AddInternalParametersToConstructorPhase = makeBodyLoweringPhase(
::ES6AddInternalParametersToConstructorPhase,
name = "ES6CreateInitFunctionPhase",
description = "Add `box` and `resultType` params, create init functions for constructors"
)
private val es6ConstructorLowering = makeBodyLoweringPhase(
::ES6ConstructorLowering,
name = "ES6ConstructorLoweringPhase",
description = "Lower constructors",
prerequisite = setOf(es6AddInternalParametersToConstructorPhase)
)
private val secondaryConstructorLoweringPhase = makeDeclarationTransformerPhase(
::SecondaryConstructorLowering,
name = "SecondaryConstructorLoweringPhase",
@@ -791,10 +784,31 @@ private val invokeStaticInitializersPhase = makeBodyLoweringPhase(
prerequisite = setOf(objectDeclarationLoweringPhase)
)
private val es6AddBoxParameterToConstructorsLowering = makeDeclarationTransformerPhase(
::ES6AddBoxParameterToConstructorsLowering,
name = "ES6AddBoxParameterToConstructorsLowering",
description = "Add box parameter to a constructor if needed",
)
private val es6ConstructorLowering = makeDeclarationTransformerPhase(
::ES6ConstructorLowering,
name = "ES6ConstructorLowering",
description = "Lower constructors declarations to support ES classes",
prerequisite = setOf(es6AddBoxParameterToConstructorsLowering)
)
private val es6ConstructorUsageLowering = makeBodyLoweringPhase(
::ES6ConstructorCallLowering,
name = "ES6ConstructorCallLowering",
description = "Lower constructor usages to support ES classes",
prerequisite = setOf(es6ConstructorLowering)
)
private val objectUsageLoweringPhase = makeBodyLoweringPhase(
::ObjectUsageLowering,
name = "ObjectUsageLowering",
description = "Transform IrGetObjectValue into instance generator call"
description = "Transform IrGetObjectValue into instance generator call",
prerequisite = setOf(primaryConstructorLoweringPhase)
)
private val escapedIdentifiersLowering = makeBodyLoweringPhase(
@@ -809,7 +823,6 @@ private val implicitlyExportedDeclarationsMarkingLowering = makeDeclarationTrans
description = "Add @JsImplicitExport annotation to declarations which are not exported but are used inside other exported declarations as a type"
)
private val cleanupLoweringPhase = makeBodyLoweringPhase(
{ CleanupLowering() },
name = "CleanupLowering",
@@ -912,8 +925,6 @@ val loweringList = listOf<Lowering>(
defaultParameterCleanerPhase,
captureStackTraceInThrowablesPhase,
throwableSuccessorsLoweringPhase,
es6AddInternalParametersToConstructorPhase,
es6ConstructorLowering,
varargLoweringPhase,
multipleCatchesLoweringPhase,
errorExpressionLoweringPhase,
@@ -927,10 +938,13 @@ val loweringList = listOf<Lowering>(
inlineClassDeclarationLoweringPhase,
inlineClassUsageLoweringPhase,
autoboxingTransformerPhase,
blockDecomposerLoweringPhase,
objectDeclarationLoweringPhase,
blockDecomposerLoweringPhase,
invokeStaticInitializersPhase,
objectUsageLoweringPhase,
es6AddBoxParameterToConstructorsLowering,
es6ConstructorLowering,
es6ConstructorUsageLowering,
callsLoweringPhase,
escapedIdentifiersLowering,
implicitlyExportedDeclarationsMarkingLowering,
@@ -947,10 +961,56 @@ val pirLowerings = loweringList.filter { it is DeclarationLowering || it is Body
val jsPhases = SameTypeNamedCompilerPhase(
name = "IrModuleLowering",
description = "IR module lowering",
lower = loweringList.map {
@Suppress("USELESS_CAST")
it.modulePhase as CompilerPhase<JsIrBackendContext, Iterable<IrModuleFragment>, Iterable<IrModuleFragment>>
}.reduce { acc, lowering -> acc.then(lowering) },
lower = loweringList.toCompilerPhase(),
actions = setOf(defaultDumper.toMultiModuleAction(), validationAction.toMultiModuleAction()),
nlevels = 1
)
private val es6CollectConstructorsWhichNeedBoxParameterLowering = makeDeclarationTransformerPhase(
::ES6CollectConstructorsWhichNeedBoxParameters,
name = "ES6CollectConstructorsWhichNeedBoxParameters",
description = "[Optimization] Collect all of the constructors which requires box parameter",
)
private val es6BoxParameterOptimization = makeBodyLoweringPhase(
::ES6ConstructorBoxParameterOptimizationLowering,
name = "ES6ConstructorBoxParameterOptimizationLowering",
description = "[Optimization] Remove box parameter from the constructors which don't require box parameter",
prerequisite = setOf(es6CollectConstructorsWhichNeedBoxParameterLowering)
)
private val es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering = makeDeclarationTransformerPhase(
::ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering,
name = "ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering",
description = "[Optimization] Collect all of the constructors which could be translated into a regular constructor",
)
private val es6PrimaryConstructorOptimizationLowering = makeDeclarationTransformerPhase(
::ES6PrimaryConstructorOptimizationLowering,
name = "ES6PrimaryConstructorOptimizationLowering",
description = "[Optimization] Replace synthetically generated static fabric method with a plain old ES6 constructors whenever it's possible",
prerequisite = setOf(es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering)
)
private val es6PrimaryConstructorUsageOptimizationLowering = makeBodyLoweringPhase(
::ES6PrimaryConstructorUsageOptimizationLowering,
name = "ES6PrimaryConstructorUsageOptimizationLowering",
description = "[Optimization] Replace usage of synthetically generated static fabric method with a plain old ES6 constructors whenever it's possible",
prerequisite = setOf(es6BoxParameterOptimization, es6PrimaryConstructorOptimizationLowering)
)
val optimizationLoweringList = listOf<Lowering>(
es6CollectConstructorsWhichNeedBoxParameterLowering,
es6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering,
es6BoxParameterOptimization,
es6PrimaryConstructorOptimizationLowering,
es6PrimaryConstructorUsageOptimizationLowering,
)
val jsOptimizationPhases = SameTypeNamedCompilerPhase(
name = "IrModuleOptimizationLowering",
description = "IR module optimization lowering",
lower = optimizationLoweringList.toCompilerPhase(),
actions = setOf(defaultDumper.toMultiModuleAction(), validationAction.toMultiModuleAction()),
nlevels = 1
)
@@ -7,9 +7,13 @@ package org.jetbrains.kotlin.ir.backend.js
import org.jetbrains.kotlin.backend.common.DefaultDelegateFactory
import org.jetbrains.kotlin.backend.common.DefaultMapping
import org.jetbrains.kotlin.ir.backend.js.utils.MutableReference
import org.jetbrains.kotlin.ir.declarations.*
class JsMapping : DefaultMapping() {
val esClassWhichNeedBoxParameters = mutableSetOf<IrClass>()
val esClassToPossibilityForOptimization = mutableMapOf<IrClass, MutableReference<Boolean>>()
val outerThisFieldSymbols = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrField>()
val innerClassConstructors = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrConstructor>()
val originalInnerClassPrimaryConstructorByClass = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrConstructor>()
@@ -20,8 +24,6 @@ class JsMapping : DefaultMapping() {
val classToSyntheticPrimaryConstructor = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrClass, IrConstructor>()
val privateMemberToCorrespondingStatic = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrFunction, IrSimpleFunction>()
val constructorToInitFunction = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrSimpleFunction>()
val enumEntryToGetInstanceFun = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrEnumEntry, IrSimpleFunction>()
val enumEntryToInstanceField = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrEnumEntry, IrField>()
val enumConstructorToNewConstructor = DefaultDelegateFactory.newDeclarationToDeclarationMapping<IrConstructor, IrConstructor>()
@@ -9,26 +9,35 @@ import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.lower.isBuiltInClass
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrGetValue
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.serialization.js.ModuleKind
internal class JsUsefulDeclarationProcessor(
override val context: JsIrBackendContext,
printReachabilityInfo: Boolean,
removeUnusedAssociatedObjects: Boolean
) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects) {
private val equalsMethod = getMethodOfAny("equals")
private val hashCodeMethod = getMethodOfAny("hashCode")
private val isEsModules = context.configuration[JSConfigurationKeys.MODULE_KIND] == ModuleKind.ES
override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() {
override fun visitCall(expression: IrCall, data: IrDeclaration) {
super.visitCall(expression, data)
if (expression.usePrototype(data)) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(expression.symbol.owner, "access to super type")
}
when (expression.symbol) {
context.intrinsics.jsBoxIntrinsic -> {
val inlineClass = context.inlineClassesUtils.getInlinedClass(expression.getTypeArgument(0)!!)!!
@@ -68,6 +77,21 @@ internal class JsUsefulDeclarationProcessor(
constructedClasses += classToCreate
}
context.intrinsics.jsCreateThisSymbol -> {
val jsClassOrThis = expression.getValueArgument(0)
val classTypeToCreate = when (jsClassOrThis) {
is IrCall -> jsClassOrThis.getTypeArgument(0)!!
is IrGetValue -> jsClassOrThis.type
else -> error("Unexpected first argument of createThis function call")
}
val classToCreate = classTypeToCreate.classifierOrFail.owner as IrClass
classToCreate.enqueue(data, "intrinsic: jsCreateThis")
constructedClasses += classToCreate
}
context.intrinsics.jsEquals -> {
equalsMethod.enqueue(data, "intrinsic: jsEquals")
}
@@ -86,26 +110,6 @@ internal class JsUsefulDeclarationProcessor(
}
}
context.intrinsics.jsConstruct -> {
val callType = expression.getTypeArgument(0)!!
val constructor = callType.getClass()!!.primaryConstructor
constructor!!.enqueue(data, "ctor call from jsConstruct-intrinsic")
}
context.intrinsics.es6DefaultType -> {
//same as jsClass
val ref = expression.getTypeArgument(0)!!.classifierOrFail.owner as IrDeclaration
ref.enqueue(data, "intrinsic: jsClass")
referencedJsClasses += ref
//Generate klass in `val currResultType = resultType || klass`
val arg = expression.getTypeArgument(0)!!
val klass = arg.getClass()
if (klass != null) {
constructedClasses.add(klass)
}
}
context.intrinsics.jsInvokeSuspendSuperType,
context.intrinsics.jsInvokeSuspendSuperTypeWithReceiver,
context.intrinsics.jsInvokeSuspendSuperTypeWithReceiverAndParam -> {
@@ -137,48 +141,57 @@ internal class JsUsefulDeclarationProcessor(
}
}
if (irClass.containsMetadata()) {
when {
irClass.isObject -> context.intrinsics.metadataObjectConstructorSymbol.owner.enqueue(irClass, "object metadata")
if (!irClass.containsMetadata()) return
irClass.isInterface -> {
context.intrinsics.implementSymbol.owner.enqueue(irClass, "interface metadata")
context.intrinsics.metadataInterfaceConstructorSymbol.owner.enqueue(irClass, "interface metadata")
}
when {
irClass.isObject -> context.intrinsics.metadataObjectConstructorSymbol.owner.enqueue(irClass, "object metadata")
else -> {
context.intrinsics.metadataClassConstructorSymbol.owner.enqueue(irClass, "class metadata")
}
irClass.isInterface -> {
context.intrinsics.implementSymbol.owner.enqueue(irClass, "interface metadata")
context.intrinsics.metadataInterfaceConstructorSymbol.owner.enqueue(irClass, "interface metadata")
}
else -> context.intrinsics.metadataClassConstructorSymbol.owner.enqueue(irClass, "class metadata")
}
if (!irClass.isExpect && !irClass.isExternal && !irClass.defaultType.isAny()) {
if (!irClass.isInterface) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irClass, "class metadata")
}
context.intrinsics.setMetadataForSymbol.owner.enqueue(irClass, "metadata")
if (irClass.superTypes.any { !it.isInterface() }) {
context.intrinsics.jsObjectCreateSymbol.owner.enqueue(irClass, "class metadata")
}
if (irClass.containsInterfaceDefaultImplementation()) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irClass, "interface default implementation")
}
if (irClass.isInner || irClass.isObject) {
context.intrinsics.jsDefinePropertySymbol.owner.enqueue(irClass, "class metadata")
}
if ((!context.es6mode || !isEsModules) && (irClass.isInner || irClass.isObject)) {
context.intrinsics.jsDefinePropertySymbol.owner.enqueue(irClass, "object lazy initialization")
}
context.intrinsics.setMetadataForSymbol.owner.enqueue(irClass, "metadata")
if (context.es6mode) return
if (!irClass.isInterface) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irClass, "class prototype access")
}
if (irClass.superTypes.any { !it.isInterface() }) {
context.intrinsics.jsObjectCreateSymbol.owner.enqueue(irClass, "class inheritance code")
}
}
override fun processSimpleFunction(irFunction: IrSimpleFunction) {
super.processSimpleFunction(irFunction)
if (irFunction.isEs6ConstructorReplacement) {
constructedClasses += irFunction.dispatchReceiverParameter?.type?.classOrNull?.owner!!
}
if (irFunction.isReal && irFunction.body != null) {
irFunction.parentClassOrNull?.takeIf { it.isInterface }?.enqueue(irFunction, "interface default method is used")
}
if (context.es6mode && isEsModules) return
val property = irFunction.correspondingPropertySymbol?.owner ?: return
if (property.isExported(context) || property.isOverriddenExternal()) {
if (property.isExported(context) || property.getJsName() != null || property.isOverriddenExternal()) {
context.intrinsics.jsPrototypeOfSymbol.owner.enqueue(irFunction, "property for export")
context.intrinsics.jsDefinePropertySymbol.owner.enqueue(irFunction, "property for export")
}
}
@@ -225,8 +238,24 @@ internal class JsUsefulDeclarationProcessor(
}
override fun isExported(declaration: IrDeclaration): Boolean = declaration.isExported(context)
}
private fun IrCall.usePrototype(container: IrDeclaration?): Boolean {
if (superQualifierSymbol == null) return false
val currentFun = (container as? IrSimpleFunction)
val currentClass = currentFun?.parentClassOrNull
return !context.es6mode ||
currentFun?.dispatchReceiverParameter == null ||
currentClass != null && (currentClass.isInner || currentClass.isLocal)
}
private fun IrClass.containsInterfaceDefaultImplementation(): Boolean {
return superTypes.any { it.classOrNull?.owner?.isExternal == true } ||
isExported(context) ||
isInterface && declarations.any { it is IrFunction && it.body != null }
}
}
private fun Collection<IrClass>.filterDescendantsOf(bases: Collection<IrClass>): Collection<IrClass> {
val visited = hashSetOf<IrClass>()
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.ir.backend.js.utils.isAssociatedObjectAnnotatedAnnot
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
@@ -13,8 +13,8 @@ import org.jetbrains.kotlin.descriptors.DescriptorVisibility
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.ES6_INIT_BOX_PARAMETER
import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.ES6_RESULT_TYPE_PARAMETER
import org.jetbrains.kotlin.ir.backend.js.lower.isBoxParameter
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
@@ -86,7 +86,7 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
returnType = exportType(function.returnType),
typeParameters = function.typeParameters.map(::exportTypeParameter),
isMember = parent is IrClass,
isStatic = function.isStaticMethodOfClass,
isStatic = function.isStaticMethod,
isAbstract = parent is IrClass && !parent.isInterface && function.modality == Modality.ABSTRACT,
isProtected = function.visibility == DescriptorVisibilities.PROTECTED,
ir = function,
@@ -100,10 +100,9 @@ class ExportModelGenerator(val context: JsIrBackendContext, val generateNamespac
private fun exportConstructor(constructor: IrConstructor): ExportedDeclaration? {
if (!constructor.isPrimary) return null
val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) +
constructor.valueParameters.filterNot { it.origin === ES6_RESULT_TYPE_PARAMETER || it.origin === ES6_INIT_BOX_PARAMETER }
val allValueParameters = listOfNotNull(constructor.extensionReceiverParameter) + constructor.valueParameters
return ExportedConstructor(
parameters = allValueParameters.map { exportParameter(it) },
parameters = allValueParameters.filterNot { it.isBoxParameter }.map { exportParameter(it) },
visibility = constructor.visibility.toExportedVisibility()
)
}
@@ -696,6 +695,9 @@ private class ExportedClassDeclarationsInfo(
private val IrClassifierSymbol.isInterface
get() = (owner as? IrClass)?.isInterface == true
private val IrFunction.isStaticMethod: Boolean
get() = isEs6ConstructorReplacement || isStaticMethodOfClass
private fun getExportCandidate(declaration: IrDeclaration): IrDeclarationWithName? {
// Only actual public declarations with name can be exported
if (declaration !is IrDeclarationWithVisibility ||
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.export
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsAstUtils
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.defineProperty
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsAssignment
@@ -15,13 +16,16 @@ import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.jsElementAccess
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.backend.js.utils.emptyScope
import org.jetbrains.kotlin.ir.backend.js.utils.getJsNameOrKotlinName
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.util.companionObject
import org.jetbrains.kotlin.ir.util.isObject
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
class ExportModelToJsStatements(
private val namer: JsStaticContext,
private val declareNewNamespace: (String) -> String
private val staticContext: JsStaticContext,
private val es6mode: Boolean,
private val declareNewNamespace: (String) -> String,
) {
private val namespaceToRefMap = mutableMapOf<String, JsNameRef>()
@@ -38,7 +42,8 @@ class ExportModelToJsStatements(
fun generateDeclarationExport(
declaration: ExportedDeclaration,
namespace: JsExpression?,
esModules: Boolean
esModules: Boolean,
parentClass: IrClass? = null
): List<JsStatement> {
return when (declaration) {
is ExportedNamespace -> {
@@ -73,7 +78,7 @@ class ExportModelToJsStatements(
}
is ExportedFunction -> {
val name = namer.getNameForStaticDeclaration(declaration.ir)
val name = staticContext.getNameForStaticDeclaration(declaration.ir)
when {
namespace != null ->
listOf(jsAssignment(jsElementAccess(declaration.name, namespace), JsNameRef(name)).makeStmt())
@@ -88,28 +93,29 @@ class ExportModelToJsStatements(
is ExportedProperty -> {
require(namespace != null || esModules) { "Only namespaced properties are allowed" }
val getter = declaration.irGetter?.let { namer.getNameForStaticDeclaration(it) }
val setter = declaration.irSetter?.let { namer.getNameForStaticDeclaration(it) }
if (namespace == null) {
val property = JsVars.JsVar(
JsName(declaration.name, false),
JsObjectLiteral(false).apply {
getter?.let {
val fieldName = when (declaration.irGetter.origin) {
JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION -> "getInstance"
else -> "get"
}
propertyInitializers += JsPropertyInitializer(JsStringLiteral(fieldName), it.makeRef())
}
setter?.let { propertyInitializers += JsPropertyInitializer(JsStringLiteral("set"), it.makeRef()) }
}
)
listOf(
JsVars(property),
JsExport(property.name, JsName(declaration.name, false))
)
} else {
listOf(defineProperty(namespace, declaration.name, getter?.makeRef(), setter?.makeRef(), namer).makeStmt())
when {
namespace == null -> {
val property = declaration.generateTopLevelGetters()
listOf(JsVars(property), JsExport(property.name, JsName(declaration.name, false)))
}
es6mode && declaration.isMember -> {
val jsClass = parentClass?.getCorrespondingJsClass() ?: error("Expect to have parentClass at this point")
jsClass.members += declaration.generateClassMembers()
listOf(JsEmpty)
}
else -> {
val getter = declaration.irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
val setter = declaration.irSetter?.let { staticContext.getNameForStaticDeclaration(it) }
listOf(
defineProperty(
namespace,
declaration.name,
getter?.makeRef(),
setter?.makeRef(),
staticContext
).makeStmt()
)
}
}
}
@@ -120,24 +126,31 @@ class ExportModelToJsStatements(
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
else ->
jsElementAccess(Namer.PROTOTYPE_NAME, namer.getNameForClass(declaration.ir).makeRef())
jsElementAccess(Namer.PROTOTYPE_NAME, staticContext.getNameForClass(declaration.ir).makeRef())
}
val staticsExport = declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
val staticsExport =
declaration.nestedClasses.flatMap { generateDeclarationExport(it, newNameSpace, esModules, declaration.ir) }
val objectExport = when (namespace) {
null -> generateDeclarationExport(
ExportedProperty(declaration.name, ExportedType.Primitive.Any, irGetter = declaration.irGetter),
val objectExport = when {
es6mode || namespace == null -> generateDeclarationExport(
ExportedProperty(
declaration.name,
ExportedType.Primitive.Any,
isStatic = parentClass?.isObject != true,
irGetter = declaration.irGetter,
isMember = parentClass != null
),
namespace,
esModules
esModules,
parentClass
)
else -> listOf(
defineProperty(
namespace,
declaration.name,
namer.getNameForStaticDeclaration(declaration.irGetter).makeRef(),
staticContext.getNameForStaticDeclaration(declaration.irGetter).makeRef(),
null,
namer
staticContext
).makeStmt()
)
}
@@ -147,11 +160,11 @@ class ExportModelToJsStatements(
is ExportedRegularClass -> {
if (declaration.isInterface) return emptyList()
val name = namer.getNameForStaticDeclaration(declaration.ir)
val name = staticContext.getNameForStaticDeclaration(declaration.ir)
val newNameSpace = when {
namespace != null -> jsElementAccess(declaration.name, namespace)
esModules -> name.makeRef()
else -> prototypeOf(namer.getNameForClass(declaration.ir).makeRef(), namer)
else -> prototypeOf(staticContext.getNameForClass(declaration.ir).makeRef(), staticContext)
}
val klassExport = when {
namespace != null -> jsAssignment(newNameSpace, JsNameRef(name)).makeStmt()
@@ -161,7 +174,7 @@ class ExportModelToJsStatements(
// These are only used when exporting secondary constructors annotated with @JsName
val staticFunctions = declaration.members
.filter { it is ExportedFunction && it.isStatic }
.filter { it is ExportedFunction && it.isStatic && !it.ir.isEs6ConstructorReplacement }
.takeIf { !declaration.ir.isInner }.orEmpty()
val enumEntries = declaration.members.filter { it is ExportedProperty && it.isStatic }
@@ -171,22 +184,75 @@ class ExportModelToJsStatements(
.map { it.generateInnerClassAssignment(declaration) }
val staticsExport = (staticFunctions + enumEntries + declaration.nestedClasses)
.flatMap { generateDeclarationExport(it, newNameSpace, esModules) }
.flatMap { generateDeclarationExport(it, newNameSpace, esModules, declaration.ir) }
listOfNotNull(klassExport) + staticsExport + innerClassesAssignments
}
}
}
private fun ExportedProperty.generateTopLevelGetters(): JsVars.JsVar {
val getter = irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
val setter = irSetter?.let { staticContext.getNameForStaticDeclaration(it) }
return JsVars.JsVar(
JsName(name, false),
JsObjectLiteral(false).apply {
getter?.let {
val fieldName = when (irGetter?.origin) {
JsLoweredDeclarationOrigin.OBJECT_GET_INSTANCE_FUNCTION -> "getInstance"
else -> "get"
}
propertyInitializers += JsPropertyInitializer(JsStringLiteral(fieldName), it.makeRef())
}
setter?.let { propertyInitializers += JsPropertyInitializer(JsStringLiteral("set"), it.makeRef()) }
}
)
}
private fun ExportedProperty.generateClassMembers(): List<JsFunction> {
val getter = irGetter?.let { staticContext.getNameForStaticDeclaration(it) }
val setter = irSetter?.let { staticContext.getNameForStaticDeclaration(it) }
return buildList {
if (getter != null) {
add(JsFunction(emptyScope, "").also {
it.name = JsName(name, false)
if (isStatic) {
it.modifiers.add(JsFunction.Modifier.STATIC)
}
it.modifiers.add(JsFunction.Modifier.GET)
it.body = JsBlock().apply {
statements.add(JsReturn(JsInvocation(getter.makeRef())))
}
})
}
if (setter != null) {
add(JsFunction(emptyScope, "").also {
val value = JsName("value", true)
it.name = JsName(name, false)
it.parameters.add(JsParameter(value))
if (isStatic) {
it.modifiers.add(JsFunction.Modifier.STATIC)
}
it.modifiers.add(JsFunction.Modifier.SET)
it.body = JsBlock().apply {
statements.add(JsExpressionStatement(JsInvocation(setter.makeRef(), value.makeRef())))
}
})
}
}
}
private fun ExportedClass.generateInnerClassAssignment(outerClass: ExportedClass): JsStatement {
val innerClassRef = namer.getNameForStaticDeclaration(ir).makeRef()
val outerClassRef = namer.getNameForStaticDeclaration(outerClass.ir).makeRef()
val innerClassRef = staticContext.getNameForStaticDeclaration(ir).makeRef()
val outerClassRef = staticContext.getNameForStaticDeclaration(outerClass.ir).makeRef()
val companionObject = ir.companionObject()
val secondaryConstructors = members.filterIsInstanceAnd<ExportedFunction> { it.isStatic }
val bindConstructor = JsName("__bind_constructor_", false)
val blockStatements = mutableListOf<JsStatement>(
JsVars(JsVars.JsVar(bindConstructor, innerClassRef.bindToThis()))
JsVars(JsVars.JsVar(bindConstructor, innerClassRef.bindToThis(innerClassRef)))
)
if (companionObject != null) {
@@ -200,35 +266,47 @@ class ExportModelToJsStatements(
}
secondaryConstructors.forEach {
val currentFunRef = namer.getNameForStaticDeclaration(it.ir).makeRef()
val currentFunRef = if (it.ir.isEs6ConstructorReplacement) {
JsNameRef(staticContext.getNameForMemberFunction(it.ir), innerClassRef)
} else {
staticContext.getNameForStaticDeclaration(it.ir).makeRef()
}
val assignment = jsAssignment(
JsNameRef(it.name, bindConstructor.makeRef()),
currentFunRef.bindToThis()
currentFunRef.bindToThis(innerClassRef)
).makeStmt()
blockStatements.add(assignment)
}
blockStatements.add(JsReturn(bindConstructor.makeRef()))
val innerClassGetter = JsFunction(emptyScope, JsBlock(*blockStatements.toTypedArray()), "inner class '$name' getter")
return defineProperty(
prototypeOf(outerClassRef, namer),
name,
JsFunction(
emptyScope,
JsBlock(*blockStatements.toTypedArray()),
"inner class '$name' getter"
),
null,
namer
).makeStmt()
return if (es6mode) {
outerClass.ir.getCorrespondingJsClass().members += innerClassGetter.also {
it.name = JsName(name, false)
it.modifiers.add(JsFunction.Modifier.GET)
}
JsEmpty
} else {
defineProperty(
prototypeOf(outerClassRef, staticContext),
name,
innerClassGetter,
null,
staticContext
).makeStmt()
}
}
private fun JsNameRef.bindToThis(): JsInvocation {
return JsInvocation(
JsNameRef("bind", this),
JsNullLiteral(),
JsThisRef()
)
private fun IrClass.getCorrespondingJsClass(): JsClass {
val jsClassModel = staticContext.classModels[symbol] ?: error("Class with name '$name' was not found")
return (jsClassModel.preDeclarationBlock.statements.first() as? JsExpressionStatement)?.expression as? JsClass
?: error("Expect to have JsClass as a first statement inside JsIrClassModel")
}
private fun JsNameRef.bindToThis(bindTo: JsExpression): JsInvocation {
return JsInvocation(JsNameRef("bind", this), bindTo, JsThisRef())
}
}
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
import org.jetbrains.kotlin.ir.backend.js.utils.isJsImplicitExport
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.superTypes
@@ -734,6 +734,7 @@ fun rebuildCacheForDirtyFiles(
irFactory: IrFactory,
exportedDeclarations: Set<FqName>,
mainArguments: List<String>?,
es6mode: Boolean
): Pair<IrModuleFragment, List<Pair<IrFile, JsIrProgramFragment>>> {
val emptyMetadata = object : KotlinSourceFileExports() {
override val inverseDependencies = KotlinSourceFileMap<Set<IdSignature>>(emptyMap())
@@ -753,7 +754,13 @@ fun rebuildCacheForDirtyFiles(
currentIrModule.files.filter { irFile -> irFile.fileEntry.name in files }
} ?: currentIrModule.files
val compilerWithIC = JsIrCompilerWithIC(currentIrModule, configuration, JsGenerationGranularity.PER_MODULE, exportedDeclarations)
val compilerWithIC = JsIrCompilerWithIC(
currentIrModule,
configuration,
JsGenerationGranularity.PER_MODULE,
exportedDeclarations,
es6mode
)
// Load declarations referenced during `context` initialization
jsIrLinker.loadUnboundSymbols()
@@ -15,19 +15,19 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.name.Name
object JsIrBuilder {
object SYNTHESIZED_DECLARATION : IrDeclarationOriginImpl("SYNTHESIZED_DECLARATION")
fun buildCall(
target: IrSimpleFunctionSymbol,
type: IrType? = null,
typeArguments: List<IrType>? = null,
origin: IrStatementOrigin = JsStatementOrigins.SYNTHESIZED_STATEMENT
origin: IrStatementOrigin = JsStatementOrigins.SYNTHESIZED_STATEMENT,
superQualifierSymbol: IrClassSymbol? = null,
): IrCall {
val owner = target.owner
return IrCallImpl(
@@ -35,6 +35,7 @@ object JsIrBuilder {
UNDEFINED_OFFSET,
type ?: owner.returnType,
target,
superQualifierSymbol = superQualifierSymbol,
typeArgumentsCount = owner.typeParameters.size,
valueArgumentsCount = owner.valueParameters.size,
origin = origin
@@ -46,54 +47,95 @@ object JsIrBuilder {
}
}
fun buildConstructorCall(
target: IrConstructorSymbol,
type: IrType? = null,
typeArguments: List<IrType>? = null,
constructorTypeArguments: List<IrType>? = null,
origin: IrStatementOrigin = JsStatementOrigins.SYNTHESIZED_STATEMENT
): IrConstructorCall {
fun buildArray(elements: List<IrExpression>, type: IrType, elementType: IrType): IrExpression {
return IrVarargImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
type,
elementType,
elements
)
}
fun buildDelegatingConstructorCall(target: IrConstructorSymbol, typeArguments: List<IrType?>? = null): IrDelegatingConstructorCall {
val owner = target.owner
val parent = owner.parentAsClass
return IrConstructorCallImpl(
val irClass = owner.parentAsClass
return IrDelegatingConstructorCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
type ?: owner.returnType,
owner.returnType,
target,
typeArgumentsCount = parent.typeParameters.size,
typeArgumentsCount = irClass.typeParameters.size,
valueArgumentsCount = owner.valueParameters.size,
constructorTypeArgumentsCount = owner.typeParameters.size,
origin = origin
).apply {
typeArguments?.let {
assert(typeArguments.size == typeArgumentsCount)
it.withIndex().forEach { (i, t) -> putTypeArgument(i, t) }
}
constructorTypeArguments?.let {
assert(constructorTypeArguments.size == constructorTypeArgumentsCount)
assert(it.size == typeArgumentsCount)
it.withIndex().forEach { (i, t) -> putTypeArgument(i, t) }
}
}
}
fun buildConstructorCall(
target: IrConstructorSymbol,
typeArguments: List<IrType?>? = null,
constructorTypeArguments: List<IrType?>? = null,
origin: IrStatementOrigin = JsStatementOrigins.SYNTHESIZED_STATEMENT
): IrConstructorCall {
val owner = target.owner
val irClass = owner.parentAsClass
return IrConstructorCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
owner.returnType,
target,
typeArgumentsCount = irClass.typeParameters.size,
constructorTypeArgumentsCount = owner.typeParameters.size,
valueArgumentsCount = owner.valueParameters.size,
origin = origin
).apply {
typeArguments?.let {
assert(it.size == typeArgumentsCount)
it.withIndex().forEach { (i, t) -> putTypeArgument(i, t) }
}
constructorTypeArguments?.let {
assert(it.size == typeArgumentsCount)
it.withIndex().forEach { (i, t) -> putTypeArgument(i, t) }
}
}
}
fun buildRawReference(targetSymbol: IrFunctionSymbol, type: IrType): IrRawFunctionReference =
IrRawFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, targetSymbol)
fun buildReturn(targetSymbol: IrFunctionSymbol, value: IrExpression, type: IrType) =
IrReturnImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, targetSymbol, value)
fun buildThrow(type: IrType, value: IrExpression) = IrThrowImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, value)
fun buildValueParameter(parent: IrFunction, name: String, index: Int, type: IrType): IrValueParameter =
fun buildValueParameter(
parent: IrFunction,
name: String,
index: Int,
type: IrType,
isAssignable: Boolean = false,
origin: IrDeclarationOrigin = SYNTHESIZED_DECLARATION
): IrValueParameter =
buildValueParameter(parent) {
this.origin = SYNTHESIZED_DECLARATION
this.origin = origin
this.name = Name.identifier(name)
this.index = index
this.type = type
this.isAssignable = isAssignable
}
fun buildGetObjectValue(type: IrType, classSymbol: IrClassSymbol) =
IrGetObjectValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, classSymbol)
fun buildGetValue(symbol: IrValueSymbol) =
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol.owner.type, symbol, JsStatementOrigins.SYNTHESIZED_STATEMENT)
fun buildGetValue(symbol: IrValueSymbol, origin: IrStatementOrigin = JsStatementOrigins.SYNTHESIZED_STATEMENT) =
IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol.owner.type, symbol, origin)
fun buildSetValue(symbol: IrValueSymbol, value: IrExpression) =
IrSetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, symbol.owner.type, symbol, value, JsStatementOrigins.SYNTHESIZED_STATEMENT)
@@ -5,17 +5,20 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.parentAsClass
class AnnotationConstructorLowering(context: CommonBackendContext) : DeclarationTransformer {
class AnnotationConstructorLowering(val context: JsCommonBackendContext) : DeclarationTransformer {
private val unitType = context.irBuiltIns.unitType
private val anyConstructor = context.irBuiltIns.anyClass.constructors.first()
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration !is IrConstructor || !declaration.isPrimary) return null
@@ -27,6 +30,9 @@ class AnnotationConstructorLowering(context: CommonBackendContext) : Declaration
// put empty body to make sure proper initializer is generated
// TODO what about its previous body?
declaration.body = declaration.factory.createBlockBody(declaration.startOffset, declaration.endOffset) {
if (context.es6mode) {
statements += IrDelegatingConstructorCallImpl(startOffset, endOffset, context.irBuiltIns.anyType, anyConstructor, 0, 0)
}
statements += IrInstanceInitializerCallImpl(startOffset, endOffset, irClass.symbol, unitType)
}
@@ -36,9 +36,7 @@ class JsBlockDecomposerLowering(val context: JsIrBackendContext) : AbstractBlock
JsIrBuilder.buildCall(context.intrinsics.unreachable, context.irBuiltIns.nothingType)
}
abstract class AbstractBlockDecomposerLowering(
private val context: JsCommonBackendContext
) : BodyLoweringPass {
abstract class AbstractBlockDecomposerLowering(private val context: JsCommonBackendContext) : BodyLoweringPass {
private val nothingType = context.irBuiltIns.nothingType
@@ -179,7 +179,11 @@ class CallableReferenceLowering(private val context: CommonBackendContext) : Bod
origin = if (isKReference || !isLambda) FUNCTION_REFERENCE_IMPL else LAMBDA_IMPL
name = makeContextDependentName()
}.apply {
superTypes = listOfNotNull(superClass, referenceType, secondFunctionInterface?.symbol?.typeWithArguments(referenceType.arguments))
superTypes = listOfNotNull(
this@CallableReferenceBuilder.superClass,
referenceType,
secondFunctionInterface?.symbol?.typeWithArguments(referenceType.arguments)
)
// if (samSuperType == null)
// superTypes += functionSuperClass.typeWith(parameterTypes)
// if (irFunctionReference.isSuspend) superTypes += context.ir.symbols.suspendFunctionInterface.defaultType
@@ -6,18 +6,23 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrDelegatingConstructorCall
import org.jetbrains.kotlin.ir.expressions.impl.IrRawFunctionReferenceImpl
import org.jetbrains.kotlin.ir.util.isSubclassOf
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.name.Name
object ES6_THROWABLE_CONSTRUCTOR_SLOT : IrDeclarationOriginImpl("ES6_THROWABLE_CONSTRUCTOR_SLOT")
/**
* Capture stack trace in primary constructors of Throwable
*/
@@ -31,15 +36,36 @@ class CaptureStackTraceInThrowables(val context: JsIrBackendContext) : BodyLower
if (!klass.isSubclassOf(context.irBuiltIns.throwableClass.owner))
return
val statements = (irBody as IrBlockBody).statements
val statements = (irBody as? IrBlockBody)?.statements ?: return
val delegatingConstructorCallIndex = statements.indexOfLast { it is IrDelegatingConstructorCall }
statements.add(delegatingConstructorCallIndex + 1, JsIrBuilder.buildCall(context.intrinsics.captureStack).also { call ->
call.putValueArgument(0, JsIrBuilder.buildGetValue(klass.thisReceiver!!.symbol))
call.putValueArgument(
1,
IrRawFunctionReferenceImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, context.irBuiltIns.anyType, container.symbol)
)
val self = klass.thisReceiver!!.symbol
val constructorRef = if (context.es6mode) {
JsIrBuilder.buildGetField(klass.addThrowableConstructorSlot().symbol, JsIrBuilder.buildGetValue(self))
} else {
JsIrBuilder.buildRawReference(container.symbol, context.irBuiltIns.anyType)
}
call.putValueArgument(0, JsIrBuilder.buildGetValue(self))
call.putValueArgument(1, constructorRef)
})
}
private fun IrClass.addThrowableConstructorSlot(): IrField {
return factory.buildField {
type = context.dynamicType
isFinal = false
isExternal = false
isStatic = false
metadata = null
name = Name.identifier(Namer.THROWABLE_CONSTRUCTOR)
visibility = DescriptorVisibilities.PRIVATE
origin = ES6_THROWABLE_CONSTRUCTOR_SLOT
}.apply {
parent = this@addThrowableConstructorSlot
declarations.add(this)
}
}
}
@@ -8,14 +8,20 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.declarations.IrConstructor
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstKind
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.classifierOrNull
import org.jetbrains.kotlin.ir.types.defaultType
@@ -33,7 +39,13 @@ class ConstTransformer(private val context: JsIrBackendContext) : IrElementTrans
): IrExpression {
val constructor = irClass.constructors.single { it.owner.isPrimary }
val argType = constructor.owner.valueParameters.first().type
return IrConstructorCallImpl.fromSymbolOwner(expression.startOffset, expression.endOffset, irClass.defaultType, constructor).apply {
return IrConstructorCallImpl.fromSymbolOwner(
expression.startOffset,
expression.endOffset,
irClass.defaultType,
constructor
).apply {
for (i in args.indices) {
putValueArgument(i, carrierFactory(startOffset, endOffset, argType, args[i]))
}
@@ -0,0 +1,182 @@
/*
* 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.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.ValueRemapper
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.utils.hasStrictSignature
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
object ES6_BOX_PARAMETER : IrDeclarationOriginImpl("ES6_BOX_PARAMETER")
object ES6_BOX_PARAMETER_DEFAULT_RESOLUTION : IrStatementOriginImpl("ES6_BOX_PARAMETER_DEFAULT_RESOLUTION")
val IrValueParameter.isBoxParameter: Boolean
get() = origin == ES6_BOX_PARAMETER
val IrWhen.isBoxParameterDefaultResolution: Boolean
get() = origin == ES6_BOX_PARAMETER_DEFAULT_RESOLUTION
val IrFunction.boxParameter: IrValueParameter?
get() = valueParameters.lastOrNull()?.takeIf { it.isBoxParameter }
class ES6AddBoxParameterToConstructorsLowering(val context: JsIrBackendContext) : DeclarationTransformer {
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.es6mode || declaration !is IrConstructor || declaration.hasStrictSignature(context)) return null
hackEnums(declaration)
hackExceptions(declaration)
hackSimpleClassWithCapturing(declaration)
if (!declaration.isSyntheticPrimaryConstructor) {
declaration.addBoxParameter()
}
return null
}
private fun IrConstructor.addBoxParameter() {
val irClass = parentAsClass
val boxParameter = generateBoxParameter(irClass).also { valueParameters += it }
val body = body as? IrBlockBody ?: return
val isBoxUsed = body.replaceThisWithBoxBeforeSuperCall(irClass, boxParameter.symbol)
if (isBoxUsed) {
body.statements.add(0, boxParameter.generateDefaultResolution())
}
}
private fun createJsObjectLiteral(): IrExpression {
return JsIrBuilder.buildCall(context.intrinsics.jsEmptyObject)
}
private fun IrConstructor.generateBoxParameter(irClass: IrClass): IrValueParameter {
return JsIrBuilder.buildValueParameter(
parent = this,
name = Namer.ES6_BOX_PARAMETER_NAME,
index = valueParameters.size,
type = irClass.defaultType.makeNullable(),
origin = ES6_BOX_PARAMETER,
isAssignable = true
)
}
private fun IrValueParameter.generateDefaultResolution(): IrExpression {
return with(context.createIrBuilder(symbol, UNDEFINED_OFFSET, UNDEFINED_OFFSET)) {
irIfThen(
context.irBuiltIns.unitType,
irEqeqeqWithoutBox(irGet(type, symbol), this@ES6AddBoxParameterToConstructorsLowering.context.getVoid()),
irSet(symbol, createJsObjectLiteral()),
ES6_BOX_PARAMETER_DEFAULT_RESOLUTION
)
}
}
private fun IrBody.replaceThisWithBoxBeforeSuperCall(irClass: IrClass, boxParameterSymbol: IrValueSymbol): Boolean {
var meetCapturing = false
var meetDelegatingConstructor = false
val selfParameterSymbol = irClass.thisReceiver!!.symbol
transformChildrenVoid(object : ValueRemapper(mapOf(selfParameterSymbol to boxParameterSymbol)) {
override fun visitGetValue(expression: IrGetValue): IrExpression {
return if (meetDelegatingConstructor) {
expression
} else {
super.visitGetValue(expression)
}
}
override fun visitSetField(expression: IrSetField): IrExpression {
if (meetDelegatingConstructor) return expression
val newExpression = super.visitSetField(expression)
val receiver = expression.receiver as? IrGetValue
if (receiver?.symbol == boxParameterSymbol) {
meetCapturing = true
}
return newExpression
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
meetDelegatingConstructor = true
return super.visitDelegatingConstructorCall(expression)
}
})
return meetCapturing
}
private fun hackEnums(constructor: IrConstructor) {
constructor.transformChildren(object : IrElementTransformerVoid() {
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
return (expression.argument as? IrDelegatingConstructorCall) ?: expression
}
}, null)
}
private fun hackSimpleClassWithCapturing(constructor: IrConstructor) {
val irClass = constructor.parentAsClass
if (irClass.superClass != null || (!irClass.isInner && !irClass.isLocal)) return
val statements = (constructor.body as? IrBlockBody)?.statements ?: return
val delegationConstructorIndex = statements.indexOfFirst { it is IrDelegatingConstructorCall }
if (delegationConstructorIndex == -1) return
val firstClassFieldAssignment = statements.indexOfFirst { statement ->
statement is IrSetField && statement.receiver?.let { it is IrGetValue && it.symbol == irClass.thisReceiver?.symbol } == true
}
if (firstClassFieldAssignment == -1 || firstClassFieldAssignment > delegationConstructorIndex) return
statements.add(firstClassFieldAssignment, statements[delegationConstructorIndex])
statements.removeAt(delegationConstructorIndex + 1)
}
/**
* Swap call synthetic primary ctor and call extendThrowable
*/
private fun hackExceptions(constructor: IrConstructor) {
val setPropertiesSymbol = context.setPropertiesToThrowableInstanceSymbol
val statements = (constructor.body as? IrBlockBody)?.statements ?: return
var callIndex = -1
var superCallIndex = -1
for (i in statements.indices) {
val s = statements[i]
if (s is IrCall && s.symbol === setPropertiesSymbol) {
callIndex = i
}
if (s is IrDelegatingConstructorCall && s.symbol.owner.origin === PrimaryConstructorLowering.SYNTHETIC_PRIMARY_CONSTRUCTOR) {
superCallIndex = i
}
}
if (callIndex != -1 && superCallIndex != -1) {
val tmp = statements[callIndex]
statements[callIndex] = statements[superCallIndex]
statements[superCallIndex] = tmp
}
}
}
@@ -1,216 +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.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering.SYNTHETIC_PRIMARY_CONSTRUCTOR
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.name.Name
/**
* Add ES6_INIT_BOX_PARAMETER for each constructor (except constructors
* of inline, external and primitive (string, arrays) classes).
* Uses: Inline class's constructor initialize field of outer class before
* call `super`, but its impossible in ES6. ES6_INIT_BOX_PARAMETER - object,
* that collect these values and pass to super constructor. In the last
* constructor of delegation chain ES6_INIT_BOX_PARAMETER will be open
* with `Object.assign(this, box)`
*
* Add ES6_RESULT_TYPE PARAMETER for each secondary ctor (except
* constructors of external and primitive (string, arrays) classes).
* Uses: Pass information about type of new object to `Reflect.construct()`
*
* Transform SYNTHETIC_PRIMARY_CONSTRUCTOR:
* constructor() {
* //statements
* }
* ==>
* constructor() {
* init(this)
* }
* init($this$) {
* //statements from ctor
* }
*/
class ES6AddInternalParametersToConstructorPhase(val context: JsIrBackendContext) : BodyLoweringPass {
object ES6_SYNTHETIC_PRIMARY_INIT_FUNCTION : IrDeclarationOriginImpl("ES6_SYNTHETIC_PRIMARY_INIT_FUNCTION")
object ES6_INIT_BOX_PARAMETER : IrDeclarationOriginImpl("ES6_INIT_BOX_PARAMETER")
object ES6_RESULT_TYPE_PARAMETER : IrDeclarationOriginImpl("ES6_RESULT_TYPE_PARAMETER")
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.es6mode) return
container.transform(CallSiteTransformer(), null)
if (container !is IrConstructor) return
if (!container.hasStrictSignature()) container.addInternalValueParameters()
if (container.origin === SYNTHETIC_PRIMARY_CONSTRUCTOR) {
createInitFunction(container)
openInitializerBox(container)
}
}
private fun IrConstructor.addInternalValueParameters() {
addValueParameter("box", context.dynamicType, ES6_INIT_BOX_PARAMETER)
if (!isPrimary) {
addValueParameter("resultType", context.dynamicType, ES6_RESULT_TYPE_PARAMETER)
}
}
private fun createInitFunction(constructor: IrConstructor) {
val irClass = constructor.parentAsClass
val initFunction = buildInitFunction(constructor, irClass)
irClass.declarations += initFunction
context.mapping.constructorToInitFunction[constructor] = initFunction
initFunction.transformChildren(object : IrElementTransformerVoid() {
override fun visitDeclaration(declaration: IrDeclarationBase): IrStatement {
declaration.parent = initFunction
return declaration
}
}, null)
}
private fun openInitializerBox(constructor: IrConstructor) = with(constructor.body as IrBlockBody) {
statements.clear()
statements += JsIrBuilder.buildCall(context.intrinsics.jsOpenInitializerBox).also {
it.putValueArgument(0, JsIrBuilder.buildGetValue(constructor.parentAsClass.thisReceiver!!.symbol))
it.putValueArgument(1, JsIrBuilder.buildGetValue(constructor.valueParameters.last().symbol))
}
}
private fun buildInitFunction(constructor: IrConstructor, irClass: IrClass): IrSimpleFunction {
val functionName = "${irClass.name}_init"
return context.irFactory.buildFun {
name = Name.identifier(functionName)
returnType = context.irBuiltIns.unitType
visibility = DescriptorVisibilities.PROTECTED
modality = Modality.FINAL
isInline = constructor.isInline
isExternal = constructor.isExternal
origin = ES6_SYNTHETIC_PRIMARY_INIT_FUNCTION
}.apply {
parent = irClass
addValueParameter("\$this\$", context.dynamicType)
body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET, constructor.body?.statements.orEmpty())
transformChildren(object : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
return if (expression.symbol.owner === constructor.parentAsClass.thisReceiver!!) {
with(expression) { IrGetValueImpl(startOffset, endOffset, type, valueParameters.single().symbol) }
} else {
expression
}
}
}, null)
}
}
/**
* Pass `null` for ES6_INIT_BOX_PARAMETER and ES6_RESULT_TYPE_PARAMETER
*/
inner class CallSiteTransformer : IrElementTransformerVoid() {
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
val constructor = expression.symbol.owner
val parent = constructor.parentAsClass
if (constructor.hasStrictSignature() || parent.defaultType.isAny()) return expression
val newArgsCount = if (constructor.isPrimary) 1 else 2
return expression.run {
IrConstructorCallImpl(
startOffset,
endOffset,
type,
symbol,
typeArgumentsCount,
constructorTypeArgumentsCount,
valueArgumentsCount + newArgsCount
).also {
for (i in 0 until valueArgumentsCount) {
it.putValueArgument(i, getValueArgument(i))
}
it.putValueArgument(
valueArgumentsCount,
JsIrBuilder.buildNull(context.dynamicType)
)
if (!constructor.isPrimary) {
it.putValueArgument(
valueArgumentsCount + 1,
JsIrBuilder.buildNull(context.dynamicType)
)
}
}
}
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
val constructor = expression.symbol.owner
val parent = constructor.parentAsClass
if (constructor.hasStrictSignature() || parent.defaultType.isAny()) return expression
val newArgsCount = if (constructor.isPrimary) 1 else 2
return expression.run {
IrDelegatingConstructorCallImpl(
startOffset,
endOffset,
type,
symbol,
typeArgumentsCount,
valueArgumentsCount + newArgsCount
).also {
for (i in 0 until valueArgumentsCount) {
it.putValueArgument(i, getValueArgument(i))
}
it.putValueArgument(
valueArgumentsCount,
JsIrBuilder.buildNull(context.dynamicType)
)
if (!constructor.isPrimary) {
it.putValueArgument(
valueArgumentsCount + 1,
JsIrBuilder.buildNull(context.dynamicType)
)
}
}
}
}
}
private fun IrConstructor.hasStrictSignature(): Boolean {
val primitives = with(context.irBuiltIns) { primitiveTypesToPrimitiveArrays.values + stringClass }
return with(parentAsClass) { isExternal || context.inlineClassesUtils.isClassInlineLike(this) || symbol in primitives }
}
}
@@ -0,0 +1,140 @@
/*
* Copyright 2010-2022 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.DeclarationTransformer
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.backend.js.utils.irEmpty
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.util.isLocal
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.superClass
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
class ES6ConstructorBoxParameterOptimizationLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.es6mode) return
val containerFunction = container as? IrFunction
val shouldRemoveBoxRelatedDeclarationsAndStatements =
containerFunction?.isEs6ConstructorReplacement == true && !containerFunction.parentAsClass.requiredToHaveBoxParameter()
if (containerFunction != null && shouldRemoveBoxRelatedDeclarationsAndStatements && irBody is IrBlockBody) {
containerFunction.valueParameters = containerFunction.valueParameters.filter { !it.isBoxParameter }
}
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitWhen(expression: IrWhen): IrExpression {
return if (shouldRemoveBoxRelatedDeclarationsAndStatements && expression.isBoxParameterDefaultResolution) {
irEmpty(context)
} else {
super.visitWhen(expression)
}
}
override fun visitCall(expression: IrCall): IrExpression {
val callee = expression.symbol.owner
return when {
shouldRemoveBoxRelatedDeclarationsAndStatements && (callee.symbol == context.intrinsics.jsCreateThisSymbol || callee.symbol == context.intrinsics.jsCreateExternalThisSymbol) -> {
expression.putValueArgument(expression.valueArgumentsCount - 1, context.getVoid())
super.visitCall(expression)
}
callee.isEs6ConstructorReplacement && (!callee.parentAsClass.requiredToHaveBoxParameter() || shouldRemoveBoxRelatedDeclarationsAndStatements) -> {
val newArgumentsSize = expression.valueArgumentsCount - 1
super.visitCall(IrCallImpl(
expression.startOffset,
expression.endOffset,
expression.type,
expression.symbol,
expression.typeArgumentsCount,
newArgumentsSize,
expression.origin,
superQualifierSymbol = expression.superQualifierSymbol
).apply {
copyTypeArgumentsFrom(expression)
dispatchReceiver = expression.dispatchReceiver
extensionReceiver = expression.extensionReceiver
for (i in 0 until newArgumentsSize) {
putValueArgument(i, expression.getValueArgument(i))
}
})
}
else -> super.visitCall(expression)
}
}
})
}
private fun IrClass.requiredToHaveBoxParameter(): Boolean {
return esClassWhichNeedBoxParameters.contains(this)
}
}
class ES6CollectConstructorsWhichNeedBoxParameters(private val context: JsIrBackendContext) : DeclarationTransformer {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.es6mode || declaration !is IrClass) return null
val hasSuperClass = declaration.superClass != null
if (hasSuperClass && declaration.isInner) {
declaration.addToClassListWhichNeedBoxParameter()
}
if (hasSuperClass && declaration.isLocal && declaration.containsCapturedValues()) {
declaration.addToClassListWhichNeedBoxParameter()
}
return null
}
private fun IrClass.containsCapturedValues(): Boolean {
if (superClass == null) return false
declarations
.filterIsInstanceAnd<IrFunction> { it.isEs6ConstructorReplacement }
.forEach {
var meetCapturing = false
val boxParameter = it.boxParameter
it.body?.acceptChildrenVoid(object : IrElementVisitorVoid {
override fun visitSetField(expression: IrSetField) {
val receiver = expression.receiver as? IrGetValue
if (receiver != null && receiver.symbol == boxParameter?.symbol) {
meetCapturing = true
}
super.visitSetField(expression)
}
})
if (meetCapturing) return true
}
return false
}
private fun IrClass.addToClassListWhichNeedBoxParameter() {
if (isExternal) return
esClassWhichNeedBoxParameters.add(this)
superClass?.addToClassListWhichNeedBoxParameter()
}
}
@@ -0,0 +1,73 @@
/*
* 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.IrElementTransformerVoidWithContext
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.hasStrictSignature
import org.jetbrains.kotlin.ir.backend.js.utils.jsConstructorReference
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class ES6ConstructorCallLowering(val context: JsIrBackendContext) : BodyLoweringPass {
private var IrConstructor.constructorFactory by context.mapping.secondaryConstructorToFactory
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.es6mode) return
val containerFunction = container as? IrFunction
irBody.transformChildrenVoid(object : IrElementTransformerVoidWithContext() {
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression {
val currentConstructor = expression.symbol.owner
val irClass = currentConstructor.parentAsClass
val currentFunction = currentFunction?.irElement as? IrFunction ?: containerFunction
if (irClass.symbol == context.irBuiltIns.anyClass || currentConstructor.hasStrictSignature(context)) {
return super.visitConstructorCall(expression)
}
val factoryFunction = currentConstructor.constructorFactory ?: error("Replacement for the constructor is not found")
if (expression.isInitCall) {
assert(factoryFunction.isInitFunction) { "Expect to have init function replacement" }
return JsIrBuilder.buildCall(factoryFunction.symbol).apply {
copyValueArgumentsFrom(expression, factoryFunction)
}
}
val isDelegatingCall =
expression.isSyntheticDelegatingReplacement && currentFunction != null && currentFunction.parentAsClass != irClass
val factoryFunctionCall = JsIrBuilder.buildCall(
factoryFunction.symbol,
superQualifierSymbol = irClass.symbol.takeIf { isDelegatingCall },
origin = if (isDelegatingCall) ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT else JsStatementOrigins.SYNTHESIZED_STATEMENT
).apply {
copyValueArgumentsFrom(expression, factoryFunction)
if (expression.isSyntheticDelegatingReplacement) {
currentFunction?.boxParameter?.let {
putValueArgument(valueArgumentsCount - 1, JsIrBuilder.buildGetValue(it.symbol))
}
if (superQualifierSymbol == null) {
dispatchReceiver = JsIrBuilder.buildGetValue(factoryFunction.dispatchReceiverParameter!!.symbol)
}
} else {
dispatchReceiver = irClass.jsConstructorReference(context)
}
}
return super.visitCall(factoryFunctionCall)
}
})
}
}
@@ -5,568 +5,253 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.ValueRemapper
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
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.ES6AddInternalParametersToConstructorPhase.ES6_INIT_BOX_PARAMETER
import org.jetbrains.kotlin.ir.backend.js.lower.ES6AddInternalParametersToConstructorPhase.ES6_RESULT_TYPE_PARAMETER
import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering.SYNTHETIC_PRIMARY_CONSTRUCTOR
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.IrVariableSymbol
import org.jetbrains.kotlin.ir.types.isAny
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.statements
import org.jetbrains.kotlin.ir.util.transformInPlace
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitor
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.runIf
object ES6_THIS_VARIABLE_ORIGIN : IrDeclarationOriginImpl("ES6_THIS_VARIABLE_ORIGIN")
object ES6_INIT_CALL : IrStatementOriginImpl("ES6_INIT_CALL")
object ES6_CONSTRUCTOR_REPLACEMENT : IrDeclarationOriginImpl("ES6_CONSTRUCTOR_REPLACEMENT")
object ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT : IrDeclarationOriginImpl("ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT")
object ES6_INIT_FUNCTION : IrDeclarationOriginImpl("ES6_INIT_FUNCTION")
object ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT : IrStatementOriginImpl("ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT")
class ES6ConstructorLowering(val context: JsIrBackendContext) : BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.es6mode) return
val IrDeclaration.isEs6ConstructorReplacement: Boolean
get() = origin == ES6_CONSTRUCTOR_REPLACEMENT || origin == ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT
if (container !is IrConstructor) return
val IrDeclaration.isEs6PrimaryConstructorReplacement: Boolean
get() = origin == ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT
if (container.hasStrictSignature()) return
val IrFunctionAccessExpression.isSyntheticDelegatingReplacement: Boolean
get() = origin == ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT
hackEnums(container)
hackExceptions(context, container)
val IrDeclaration.isInitFunction: Boolean
get() = origin == ES6_INIT_FUNCTION
val superCall = getSuperCall(container) ?: return
val superCtor = superCall.symbol.owner
val helper = LowerCtorHelper(context, container, superCtor)
val IrFunctionAccessExpression.isInitCall: Boolean
get() = origin == ES6_INIT_CALL
if (container.isPrimary) {
if (superCtor.isPrimary) primaryToPrimary(helper)
else primaryToSecondary(helper)
class ES6ConstructorLowering(val context: JsIrBackendContext) : DeclarationTransformer {
private var IrConstructor.constructorFactory by context.mapping.secondaryConstructorToFactory
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.es6mode || declaration !is IrConstructor || declaration.hasStrictSignature(context)) return null
return if (declaration.isSyntheticPrimaryConstructor) {
listOf(declaration.generateInitFunction())
} else {
if (superCtor.isPrimary) secondaryToPrimary(helper)
else secondaryToSecondary(helper)
replaceCallToDefaultPrimary(context, container)
changeIrConstructorToIrFunction(context, container)
val factoryFunction = declaration.generateCreateFunction()
listOfNotNull(factoryFunction, declaration.generateExportedConstructorIfNeed(factoryFunction))
}
}
/**
* constructor(args, box) {
* var currBox = box || {}
*
* //1. Superclass is Any
* Object.assign(this, currBox)
* //body
*
* //2. Base class isInline or isExternal or array/string
* super(args)
* Object.assign(this, currBox)
* //body
*
* //3. Base class !isInline and !isExternal and !(array/string)
* //fill initialization box
* super(args, currBox)
* //body
* }
*/
private fun primaryToPrimary(helper: LowerCtorHelper) = with(helper) {
statements.add(0, boxOrEmptyObject)
if (superCtor.parentAsClass.defaultType.isAny()) {
//after superCall
statements.add(1, openBoxStatement(thisSymbol, boxSymbol))
return
}
if (superCtor.hasStrictSignature()) {
//after superCall
statements.add(2, openBoxStatement(thisSymbol, boxSymbol))
} else {
fillInitializerBox(boxSymbol, constructor)
putBoxToSuperCall(boxSymbol, constructor)
}
}
/**
* constructor(args, box) {
* var currBox = box || {}
*
* //1. Base class isExternal or array/string
* var $this$ = Base_constructor(.., currBox)
* Object.assign(this, currBox)
* return $this$
*
* //2. Base class !isInline and !isExternal and !(array/string)
* //fill initialization box
* var $this$ = Base_constructor(.., currBox, new.target)
* //body
* return $this$
* }
*/
private fun primaryToSecondary(helper: LowerCtorHelper) = with(helper) {
statements.add(0, boxOrEmptyObject)
if (superCtor.hasStrictSignature()) {
fun createNewSuperCall(): IrCall {
val callType = JsIrBuilder.buildCall(context.intrinsics.jsClass, context.dynamicType, listOf(superCtor.returnType))
val newTarget = JsIrBuilder.buildCall(context.intrinsics.jsNewTarget)
val args = IrVarargImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
context.dynamicType,
context.dynamicType,
getElements(constructor)
)
return JsIrBuilder.buildCall(context.intrinsics.jsConstruct).apply {
putValueArgument(0, callType)
putValueArgument(1, newTarget)
putValueArgument(2, args)
putTypeArgument(0, superCtor.returnType)
private fun IrConstructor.generateExportedConstructorIfNeed(factoryFunction: IrSimpleFunction): IrConstructor? {
return runIf(isExported(context) && isPrimary) {
apply {
valueParameters = valueParameters.filterNot { it.isBoxParameter }
(body as? IrBlockBody)?.let {
val selfReplacedConstructorCall = JsIrBuilder.buildCall(factoryFunction.symbol).apply {
valueParameters.forEachIndexed { i, it -> putValueArgument(i, JsIrBuilder.buildGetValue(it.symbol)) }
dispatchReceiver = JsIrBuilder.buildCall(context.intrinsics.jsNewTarget)
}
it.statements.add(JsIrBuilder.buildReturn(symbol, selfReplacedConstructorCall, returnType))
}
}
val newThis = createThisVariable(createNewSuperCall())
changeReturnUnitToReturnInstance(newThis)
statements.add(2, openBoxStatement(newThis.symbol, boxSymbol))
statements += returnThis(constructor, newThis)
redirectOldThisToNewOne(constructor, newThis)
} else {
putBoxToSuperCall(boxSymbol, constructor)
fillInitializerBox(boxSymbol, constructor)
val newTarget = JsIrBuilder.buildCall(context.intrinsics.jsNewTarget)
val newThis = createThisVariable(createNewSuperCallPrimaryToSecondary(superCall, newTarget))
changeReturnUnitToReturnInstance(newThis)
statements += returnThis(constructor, newThis)
redirectOldThisToNewOne(constructor, newThis)
}
}
/**
* Derived_constructor(.., box, resultType) {
* var currBox = box || {}
* var currResultType = resultType || Derived
*
* //1. Base class isInline or isExternal or array/string
* var $this$ = construct(Base, currResultType, [..])
* Object.assign(this, currBox)
* //body
* return $this$
*
* //2. Base class !isInline and !isExternal and !(array/string)
* //fill initialization box
* var $this$ = construct(Base, currResultType, [.., currBox])
* //body
* return $this$
* }
*/
private fun secondaryToPrimary(helper: LowerCtorHelper) = with(helper) {
statements.add(0, boxOrEmptyObject)
statements.add(1, resultTypeOrDefaultType())
val resultTypeSymbol = (statements[1] as IrVariable).symbol
private fun IrConstructor.generateInitFunction(): IrSimpleFunction {
val constructor = this
val irClass = parentAsClass
val constructorName = "init_${irClass.constructorPostfix}"
return context.irFactory.buildFun {
name = Name.identifier(constructorName)
returnType = context.irBuiltIns.unitType
visibility = DescriptorVisibilities.PRIVATE
modality = Modality.FINAL
isInline = constructor.isInline
isExternal = constructor.isExternal
origin = ES6_INIT_FUNCTION
}.also { factory ->
factory.parent = irClass
factory.copyTypeParametersFrom(irClass)
factory.annotations = annotations
factory.extensionReceiverParameter = irClass.thisReceiver?.copyTo(factory)
if (superCtor.hasStrictSignature()) {
val newThis = createThisVariable(createNewSuperCallSecondaryToPrimary(constructor, superCtor, resultTypeSymbol))
changeReturnUnitToReturnInstance(newThis)
statements.add(3, openBoxStatement(newThis.symbol, boxSymbol))
statements.add(returnThis(constructor, newThis))
redirectOldThisToNewOne(constructor, newThis)
} else {
fillInitializerBox(boxSymbol, constructor)
val newThis = createThisVariable(createNewSuperCallSecondaryToPrimary(constructor, superCtor, resultTypeSymbol, boxSymbol))
changeReturnUnitToReturnInstance(newThis)
statements.add(returnThis(constructor, newThis))
redirectOldThisToNewOne(constructor, newThis)
}
}
/**
* Derived_constructor(.., box, resultType) {
* var currBox = box || {}
* var currResultType = resultType || Derived
* //fill initialization box
*
* //1. Base class isInline or isExternal or array/string
* var $this$ = Base_constructor(..)
* Object.assign(this, currBox)
* //body
* return $this$
*
* //2. Base class !isInline and !isExternal and !(array/string)
* var $this$ = Base_constructor(.., currBox, currResultType)
* //body
* return $this$
* }
*/
private fun secondaryToSecondary(helper: LowerCtorHelper) = with(helper) {
statements.add(0, boxOrEmptyObject)
statements.add(1, resultTypeOrDefaultType())
val resultTypeSymbol = (statements[1] as IrVariable).symbol
putBoxToSuperCall(boxSymbol, constructor)
fillInitializerBox(boxSymbol, constructor)
if (superCtor.hasStrictSignature()) {
val newThis = createThisVariable(superCall)
changeReturnUnitToReturnInstance(newThis)
statements.add(3, openBoxStatement(newThis.symbol, boxSymbol))
statements += returnThis(constructor, newThis)
redirectOldThisToNewOne(constructor, newThis)
} else {
val newThis = createThisVariable(createNewSuperCallSecondaryToSecondary(superCall, boxSymbol, resultTypeSymbol))
changeReturnUnitToReturnInstance(newThis)
statements += returnThis(constructor, newThis)
redirectOldThisToNewOne(constructor, newThis)
}
}
//superCallBuilder
private fun createNewSuperCallSecondaryToSecondary(
superCall: IrDelegatingConstructorCall,
boxSymbol: IrValueSymbol,
resultTypeSymbol: IrVariableSymbol
) = superCall.apply {
putValueArgument(ES6_INIT_BOX_PARAMETER, JsIrBuilder.buildGetValue(boxSymbol))
putValueArgument(ES6_RESULT_TYPE_PARAMETER, JsIrBuilder.buildGetValue(resultTypeSymbol))
}
//superCallBuilder
private fun createNewSuperCallPrimaryToSecondary(
superCall: IrDelegatingConstructorCall,
newTarget: IrCall? = null
) = superCall.apply { putValueArgument(ES6_RESULT_TYPE_PARAMETER, newTarget) }
private fun IrDelegatingConstructorCall.putValueArgument(origin: IrDeclarationOrigin, value: IrExpression?) {
val valueParameters = symbol.owner.valueParameters
for (i in valueParameters.indices) {
if (valueParameters[i].origin === origin) {
putValueArgument(i, value)
factory.body = constructor.body?.deepCopyWithSymbols(factory)?.apply {
transformChildrenVoid(ValueRemapper(mapOf(irClass.thisReceiver!!.symbol to factory.extensionReceiverParameter!!.symbol)))
}
constructorFactory = factory
}
}
//superCallBuilder
private fun createNewSuperCallSecondaryToPrimary(
constructor: IrConstructor,
superCtor: IrConstructor,
resultTypeSymbol: IrVariableSymbol,
boxSymbol: IrVariableSymbol? = null
): IrCall {
val callType = JsIrBuilder.buildCall(context.intrinsics.jsClass, context.dynamicType, listOf(superCtor.returnType))
val resultType = JsIrBuilder.buildGetValue(resultTypeSymbol)
val arguments = IrVarargImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
context.irBuiltIns.anyNType,
context.irBuiltIns.anyNType,
getElements(constructor, boxSymbol)
private fun IrConstructor.generateCreateFunction(): IrSimpleFunction {
val constructor = this
val irClass = parentAsClass
val type = irClass.defaultType
val constructorName = "new_${irClass.constructorPostfix}"
return context.irFactory.buildFun {
name = Name.identifier(constructorName)
returnType = type
visibility = constructor.visibility
modality = Modality.FINAL
isInline = constructor.isInline
isExternal = constructor.isExternal
origin = when {
constructor.isPrimary -> ES6_PRIMARY_CONSTRUCTOR_REPLACEMENT
else -> ES6_CONSTRUCTOR_REPLACEMENT
}
}.also { factory ->
factory.parent = irClass
factory.copyTypeParametersFrom(irClass)
factory.copyValueParametersFrom(constructor)
factory.annotations = annotations
factory.dispatchReceiverParameter = irClass.thisReceiver?.copyTo(factory)
if (irClass.isExported(context) && constructor.isPrimary) {
factory.excludeFromExport()
}
factory.body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
val bodyCopy = constructor.body?.deepCopyWithSymbols(factory) ?: return@createBlockBody
val self = bodyCopy.replaceSuperCallsAndThisUsages(irClass, factory, constructor)
statements.addAll(bodyCopy.statements)
statements.add(JsIrBuilder.buildReturn(factory.symbol, JsIrBuilder.buildGetValue(self), irClass.defaultType))
}
constructorFactory = factory
}
}
private fun IrFunction.generateThisVariable(irClass: IrClass, initializer: IrExpression): IrVariable {
return JsIrBuilder.buildVar(
type = irClass.defaultType,
parent = this,
name = Namer.SYNTHETIC_RECEIVER_NAME,
initializer = initializer
)
if (superCtor.parentAsClass.defaultType.isAny()) {
//superType is Any, and we have default primary --> create call to default primary
return JsIrBuilder.buildCall(context.intrinsics.jsConstruct, context.irBuiltIns.nothingType).apply {
arguments.elements.clear()
arguments.addElement(JsIrBuilder.buildGetValue(boxSymbol!!))
putValueArgument(0, JsIrBuilder.buildCall(context.intrinsics.jsClass, context.dynamicType, listOf(constructor.returnType)))
putValueArgument(1, resultType)
putValueArgument(2, arguments)
putTypeArgument(0, constructor.returnType)
}
}
return JsIrBuilder.buildCall(context.intrinsics.jsConstruct, context.irBuiltIns.nothingType).apply {
putValueArgument(0, callType)
putValueArgument(1, resultType)
putValueArgument(2, arguments)
putTypeArgument(0, superCtor.returnType)
}
}
/**
* Copy arguments from superCall
*/
private fun getElements(constructor: IrConstructor, boxSymbol: IrValueSymbol? = null): List<IrVarargElement> {
val result = mutableListOf<IrVarargElement>()
private val IrClass.constructorPostfix: String
get() = fqNameWhenAvailable?.asString()?.replace('.', '_') ?: name.toString()
val superCall = constructor.body!!.statements.filterIsInstance<IrDelegatingConstructorCall>().first()
repeat(superCall.valueArgumentsCount) { i ->
val arg = superCall.getValueArgument(i) ?: return@repeat
if (superCall.symbol.owner.valueParameters[i].origin === ES6_INIT_BOX_PARAMETER) {
result += JsIrBuilder.buildGetValue(boxSymbol!!)
} else {
if (context.inlineClassesUtils.getInlinedClass(arg.type) != null) {
val any = context.irBuiltIns.anyNType
result += JsIrBuilder.buildTypeOperator(any, IrTypeOperator.REINTERPRET_CAST, arg, any)
private fun irAnyArray(elements: List<IrExpression>): IrExpression {
return JsIrBuilder.buildArray(
elements,
context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.anyNType),
context.irBuiltIns.anyNType,
)
}
private fun IrBody.replaceSuperCallsAndThisUsages(
irClass: IrClass,
constructorReplacement: IrSimpleFunction,
currentConstructor: IrConstructor,
): IrValueSymbol {
var generatedThisValueSymbol: IrValueSymbol? = null
val selfParameterSymbol = irClass.thisReceiver!!.symbol
val boxParameterSymbol = constructorReplacement.boxParameter
transformChildrenVoid(object : ValueRemapper(emptyMap()) {
override val map: MutableMap<IrValueSymbol, IrValueSymbol> = currentConstructor.valueParameters
.zip(constructorReplacement.valueParameters)
.associate { it.first.symbol to it.second.symbol }
.toMutableMap()
override fun visitReturn(expression: IrReturn): IrExpression {
return if (expression.returnTargetSymbol == currentConstructor.symbol) {
super.visitReturn(
JsIrBuilder.buildReturn(
constructorReplacement.symbol,
JsIrBuilder.buildGetValue(selfParameterSymbol),
irClass.defaultType
)
)
} else {
result += arg
}
}
}
return result
}
//builder
private fun returnThis(constructor: IrConstructor, newThis: IrVariable): IrReturn {
return JsIrBuilder.buildReturn(
constructor.symbol,
JsIrBuilder.buildGetValue(newThis.symbol),
context.irBuiltIns.nothingType
)
}
/**
* Transform statements like `this.x = y` to `box.x = y`
*/
private fun fillInitializerBox(boxSymbol: IrValueSymbol, constructor: IrConstructor) {
val statements = (constructor.body as IrBlockBody).statements
for (i in statements.indices) {
val current = statements[i]
if (current is IrSetField) {
if ((current.receiver as? IrGetValue)?.symbol?.owner === constructor.parentAsClass.thisReceiver) {
current.receiver = JsIrBuilder.buildGetValue(boxSymbol)
super.visitReturn(expression)
}
}
if (statements[i] is IrDelegatingConstructorCall) {
break
}
}
}
//transformer
private fun putBoxToSuperCall(boxSymbol: IrValueSymbol, constructor: IrConstructor) {
constructor.transformChildren(object : IrElementTransformerVoid() {
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
return expression.also { it.putValueArgument(ES6_INIT_BOX_PARAMETER, JsIrBuilder.buildGetValue(boxSymbol)) }
val constructor = expression.symbol.owner
if (constructor.isSyntheticPrimaryConstructor) {
return JsIrBuilder.buildConstructorCall(expression.symbol, origin = ES6_INIT_CALL)
.apply {
copyValueArgumentsFrom(expression, constructor)
extensionReceiver = JsIrBuilder.buildGetValue(selfParameterSymbol)
}
.run { visitConstructorCall(this) }
}
val boxParameterGetter = boxParameterSymbol?.let { JsIrBuilder.buildGetValue(it.symbol) } ?: context.getVoid()
val newThisValue = when {
constructor.isEffectivelyExternal() ->
JsIrBuilder.buildCall(context.intrinsics.jsCreateExternalThisSymbol)
.apply {
putValueArgument(0, irClass.getCurrentConstructorReference(constructorReplacement))
putValueArgument(1, expression.symbol.owner.parentAsClass.jsConstructorReference(context))
putValueArgument(2, irAnyArray(expression.valueArguments.map { it ?: context.getVoid() }))
putValueArgument(3, boxParameterGetter)
}
constructor.parentAsClass.symbol == context.irBuiltIns.anyClass ->
JsIrBuilder.buildCall(context.intrinsics.jsCreateThisSymbol)
.apply {
putValueArgument(0, irClass.getCurrentConstructorReference(constructorReplacement))
putValueArgument(1, boxParameterGetter)
}
else ->
JsIrBuilder.buildConstructorCall(
expression.symbol,
null,
expression.typeArguments,
ES6_DELEGATING_CONSTRUCTOR_REPLACEMENT
).apply {
copyValueArgumentsFrom(expression, constructor)
}
}
val newThisVariable = constructorReplacement.generateThisVariable(irClass, newThisValue)
.also {
generatedThisValueSymbol = it.symbol
map[selfParameterSymbol] = it.symbol
}
return super.visitComposite(JsIrBuilder.buildComposite(context.irBuiltIns.unitType, listOf(newThisVariable)))
}
}, null)
})
return generatedThisValueSymbol!!
}
//builder
private fun openBoxStatement(thisSymbol: IrValueSymbol, boxSymbol: IrValueSymbol): IrCall {
return JsIrBuilder.buildCall(context.intrinsics.jsOpenInitializerBox).also {
it.putValueArgument(0, JsIrBuilder.buildGetValue(thisSymbol))
it.putValueArgument(1, JsIrBuilder.buildGetValue(boxSymbol))
private fun IrClass.getCurrentConstructorReference(currentFactoryFunction: IrSimpleFunction): IrExpression {
return if (isFinalClass) {
jsConstructorReference(context)
} else {
JsIrBuilder.buildGetValue(currentFactoryFunction.dispatchReceiverParameter!!.symbol)
}
}
//util
private fun IrConstructor.hasStrictSignature(): Boolean {
val primitives = with(context.irBuiltIns) { primitiveTypesToPrimitiveArrays.values + stringClass }
return with(parentAsClass) { isExternal || context.inlineClassesUtils.isClassInlineLike(this) || symbol in primitives }
private fun IrDeclaration.excludeFromExport() {
val jsExportIgnoreClass = context.intrinsics.jsExportIgnoreAnnotationSymbol.owner
val jsExportIgnoreCtor = jsExportIgnoreClass.primaryConstructor ?: return
annotations += JsIrBuilder.buildConstructorCall(jsExportIgnoreCtor.symbol)
}
}
//transformer
private fun replaceCallToDefaultPrimary(context: JsIrBackendContext, constructor: IrConstructor) {
val thisSymbol = (((constructor.body as IrBlockBody).statements
.find { it is IrVariable && it.origin === ES6_THIS_VARIABLE_ORIGIN }) as IrVariable?)?.symbol ?: return
(constructor.body as IrBlockBody).statements.transformInPlace {
if (it is IrDelegatingConstructorCall) {
val superCtor = it.symbol.owner
val initFunc = context.mapping.constructorToInitFunction[superCtor]!!
JsIrBuilder.buildCall(initFunc.symbol).apply {
putValueArgument(0, JsIrBuilder.buildGetValue(thisSymbol))
}
} else it
}
}
//transformer
private fun redirectOldThisToNewOne(constructor: IrConstructor, newThis: IrVariable) {
constructor.transformChildren(object : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
return if (expression.symbol.owner === constructor.parentAsClass.thisReceiver!!) {
with(expression) { IrGetValueImpl(startOffset, endOffset, type, newThis.symbol) }
} else {
expression
}
}
}, null)
}
/**
* Change `return Unit` to `return $this$`
*/
private fun LowerCtorHelper.changeReturnUnitToReturnInstance(newThis: IrVariable) {
constructor.transformChildren(object : IrElementTransformerVoid() {
override fun visitReturn(expression: IrReturn): IrExpression {
return JsIrBuilder.buildReturn(
constructor.symbol,
JsIrBuilder.buildGetValue(newThis.symbol),
expression.type
)
}
}, null)
}
private fun hackEnums(constructor: IrConstructor) {
constructor.transformChildren(object : IrElementTransformerVoid() {
override fun visitTypeOperator(expression: IrTypeOperatorCall): IrExpression {
return (expression.argument as? IrDelegatingConstructorCall) ?: expression
}
}, null)
}
/**
* Swap call synthetic primary ctor and call extendThrowable
*/
private fun hackExceptions(context: JsIrBackendContext, constructor: IrConstructor) {
val setPropertiesSymbol = context.setPropertiesToThrowableInstanceSymbol
val statements = (constructor.body as IrBlockBody).statements
var callIndex = -1
var superCallIndex = -1
for (i in statements.indices) {
val s = statements[i]
if (s is IrCall && s.symbol === setPropertiesSymbol) callIndex = i
if (s is IrDelegatingConstructorCall && s.symbol.owner.origin === SYNTHETIC_PRIMARY_CONSTRUCTOR) superCallIndex = i
}
if (callIndex != -1 && superCallIndex != -1) {
val tmp = statements[callIndex]
statements[callIndex] = statements[superCallIndex]
statements[superCallIndex] = tmp
}
}
private class LowerCtorHelper(
private val context: JsIrBackendContext,
val constructor: IrConstructor,
val superCtor: IrConstructor
) {
private val boxParameterSymbol = constructor.valueParameters.find { it.origin === ES6_INIT_BOX_PARAMETER }!!.symbol
val resultTypeParameterSymbol by lazy {
constructor.valueParameters.find { it.origin === ES6_RESULT_TYPE_PARAMETER }!!.symbol
}
val statements = (constructor.body as IrBlockBody).statements
/**
* var currBox = box || {}
*/
val boxOrEmptyObject = boxOrEmptyObject(boxParameterSymbol, constructor)
private fun boxOrEmptyObject(boxSymbol: IrValueSymbol, parent: IrConstructor): IrVariable {
val emptyObject = JsIrBuilder.buildCall(context.intrinsics.jsEmptyObject)
val or = JsIrBuilder.buildCall(context.intrinsics.jsOr, context.dynamicType).apply {
putValueArgument(0, JsIrBuilder.buildGetValue(boxSymbol))
putValueArgument(1, emptyObject)
}
return JsIrBuilder.buildVar(context.dynamicType, parent, "currBox", initializer = or)
}
val boxSymbol = boxOrEmptyObject.symbol
val thisSymbol = constructor.parentAsClass.thisReceiver!!.symbol
val superCall: IrDelegatingConstructorCall by lazy { getSuperCall(constructor)!! }
/**
* var $this$ = `newSuperCall`
*/
fun createThisVariable(newSuperCall: IrExpression): IrVariable {
val newThis = JsIrBuilder.buildVar(
context.dynamicType,
constructor,
"\$this\$",
initializer = newSuperCall
).apply {
origin = ES6_THIS_VARIABLE_ORIGIN
}
(constructor.body as IrBlockBody).statements.transformInPlace {
if (it === superCall) newThis
else it
}
return newThis
}
/**
* var currResultType = resultType || D
*/
fun resultTypeOrDefaultType(): IrStatement {
val defaultType = JsIrBuilder.buildCall(context.intrinsics.es6DefaultType).apply {
putTypeArgument(0, constructor.parentAsClass.defaultType)
}
val or = JsIrBuilder.buildCall(context.intrinsics.jsOr, context.dynamicType).apply {
putValueArgument(0, JsIrBuilder.buildGetValue(resultTypeParameterSymbol))
putValueArgument(1, defaultType)
}
return JsIrBuilder.buildVar(context.dynamicType, constructor, "currResultType", initializer = or)
}
}
private fun getSuperCall(constructor: IrConstructor): IrDelegatingConstructorCall? {
var result: IrDelegatingConstructorCall? = null
(constructor.body as IrBlockBody).acceptChildren(object : IrElementVisitor<Unit, Any?> {
override fun visitElement(element: IrElement, data: Any?) { }
override fun visitBlock(expression: IrBlock, data: Any?) {
expression.statements.forEach { it.accept(this, data) }
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall, data: Any?) {
result = result ?: expression
}
}, null)
return result
}
private fun changeIrConstructorToIrFunction(context: JsIrBackendContext, container: IrConstructor) {
val newConstructor = context.irFactory.buildFun {
name = Name.identifier("${container.parentAsClass.name}_constructor")
returnType = container.returnType
origin = JsIrBuilder.SYNTHESIZED_DECLARATION
}.apply {
parent = container.parent
container.valueParameters.forEach { param ->
addValueParameter(param.name.asString(), param.type, param.origin)
}
val parametersMap = container.valueParameters.zip(valueParameters).toMap()
body = container.body
transformChildren(object : IrElementTransformerVoid() {
override fun visitGetValue(expression: IrGetValue): IrExpression {
val newParam = parametersMap[expression.symbol.owner]
return if (newParam != null) JsIrBuilder.buildGetValue(newParam.symbol) else expression
}
}, null)
}
container.parentAsClass.declarations.transformInPlace {
if (it === container) newConstructor else it
}
context.mapping.secondaryConstructorToDelegate[container] = newConstructor
}
@@ -0,0 +1,258 @@
/*
* 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.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.ValueRemapper
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
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.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.MutableReference
import org.jetbrains.kotlin.ir.backend.js.utils.irEmpty
import org.jetbrains.kotlin.ir.backend.js.utils.mutableReferenceOf
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.util.collectionUtils.filterIsInstanceAnd
const val CREATE_EXTERNAL_THIS_CONSTRUCTOR_PARAMETERS = 2
class ES6PrimaryConstructorOptimizationLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (!context.es6mode || declaration !is IrFunction || !declaration.shouldBeConvertedToPlainConstructor(context)) {
return null
}
val irClass = declaration.parentAsClass
if (irClass.isExported(context)) {
irClass.removeConstructorForExport()
}
return listOf(declaration.convertToRegularConstructor(irClass))
}
private fun IrFunction.convertToRegularConstructor(irClass: IrClass): IrConstructor {
val original = this
val superClass = irClass.superClass
val classThisSymbol = irClass.thisReceiver!!.symbol
return factory.buildConstructor {
updateFrom(original)
isPrimary = true
returnType = original.returnType
origin = IrDeclarationOrigin.DEFINED
}.also { constructor ->
constructor.copyAnnotationsFrom(original)
constructor.copyParameterDeclarationsFrom(original)
constructor.parent = irClass
if (irClass.isExported(context)) {
constructor.annotations = original.annotations.withoutFirst { it.isAnnotation(JsAnnotations.jsExportIgnoreFqn) }
}
val boxParameter = constructor.boxParameter
val body = (original.body?.deepCopyWithSymbols(constructor) as IrBlockBody)
.also { constructor.body = it }
body.transformChildrenVoid(object : ValueRemapper(emptyMap()) {
override val map = original.valueParameters.zip(constructor.valueParameters)
.associate { it.first.symbol to it.second.symbol }
.toMutableMap<IrValueSymbol, IrValueSymbol>()
override fun visitReturn(expression: IrReturn): IrExpression {
return if (expression.returnTargetSymbol == original.symbol) {
return irEmpty(context)
} else {
super.visitReturn(expression)
}
}
override fun visitCall(expression: IrCall): IrExpression {
return if (expression.symbol == context.intrinsics.jsBoxApplySymbol) {
irEmpty(context)
} else {
super.visitCall(expression)
}
}
override fun visitVariable(declaration: IrVariable): IrStatement {
val initializer = declaration.initializer
if (initializer is IrCall) {
when {
initializer.isSyntheticDelegatingReplacement -> {
map[declaration.symbol] = classThisSymbol
return super.visitCall(initializer)
}
initializer.symbol == context.intrinsics.jsCreateThisSymbol -> {
map[declaration.symbol] = classThisSymbol
return if (boxParameter != null && superClass == null) {
super.visitCall(JsIrBuilder.buildCall(context.intrinsics.jsBoxApplySymbol).apply {
putValueArgument(0, JsIrBuilder.buildGetValue(irClass.thisReceiver!!.symbol))
putValueArgument(1, JsIrBuilder.buildGetValue(boxParameter.symbol))
})
} else {
irEmpty(context)
}
}
initializer.symbol == context.intrinsics.jsCreateExternalThisSymbol -> {
map[declaration.symbol] = classThisSymbol
val externalConstructor =
superClass?.primaryConstructor?.symbol ?: error("Expect to have external constructor here")
val parameters = initializer.getValueArgument(CREATE_EXTERNAL_THIS_CONSTRUCTOR_PARAMETERS) as? IrVararg
?: error("Wrong type of argument was provided")
return JsIrBuilder.buildDelegatingConstructorCall(externalConstructor).apply {
parameters.elements.forEachIndexed { i, it -> putValueArgument(i, it as IrExpression) }
}
}
}
}
return super.visitVariable(declaration)
}
})
}
}
private fun IrClass.removeConstructorForExport() {
declarations.removeIf { it is IrConstructor }
}
private inline fun <T> Iterable<T>.withoutFirst(predicate: (T) -> Boolean): List<T> {
val original = this
return buildList {
var isFirstMatch = true
for (element in original) {
if (!isFirstMatch || !predicate(element)) {
add(element)
} else {
isFirstMatch = false
}
}
}
}
}
class ES6PrimaryConstructorUsageOptimizationLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
val callee = expression.symbol.owner
return when {
!callee.shouldBeConvertedToPlainConstructor(context) -> super.visitCall(expression)
expression.isSyntheticDelegatingReplacement -> {
super.visitDelegatingConstructorCall(JsIrBuilder.buildDelegatingConstructorCall(callee.parentAsClass.primaryConstructor!!.symbol)
.apply { copyTypeAndValueArgumentsFrom(expression) })
}
else -> {
super.visitConstructorCall(JsIrBuilder.buildConstructorCall(callee.parentAsClass.primaryConstructor!!.symbol)
.apply { copyTypeAndValueArgumentsFrom(expression) })
}
}
}
})
}
}
/**
* When we can't optimize class hierarchy chain if there is a class inside which:
* 1. Has primary constructor which delegates to a secondary
* 2. Has secondary constructor which delegates to a primary
* 3. Has a constructor with a box parameter, and it has an external superclass
* 4. Is a subtype for Throwable, because we replace the super call inside constructors with `setPropertiesToThrowableInstance` call
* Otherwise, we can generate a simple ES-class constructor in each class of the hierarchy
*/
class ES6CollectPrimaryConstructorsWhichCouldBeOptimizedLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val esClassWhichNeedBoxParameters = context.mapping.esClassWhichNeedBoxParameters
private val esClassToPossibilityForOptimization = context.mapping.esClassToPossibilityForOptimization
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (
context.es6mode &&
declaration is IrClass &&
!declaration.isExternal &&
!context.inlineClassesUtils.isClassInlineLike(declaration) &&
!esClassToPossibilityForOptimization.contains(declaration)
) {
declaration.checkIfCanBeOptimized()
}
return null
}
private fun IrClass.checkIfCanBeOptimized() {
var currentClass: IrClass? = this
var nearestOptimizationDecision: MutableReference<Boolean>? = null
while (currentClass != null && !currentClass.isExternal) {
val currentClassOptimizationDecision = esClassToPossibilityForOptimization[currentClass]
if (currentClassOptimizationDecision != null) {
nearestOptimizationDecision = currentClassOptimizationDecision
break
}
currentClass = currentClass.superClass
}
if (nearestOptimizationDecision == null) {
nearestOptimizationDecision = mutableReferenceOf(true)
}
currentClass = this
while (currentClass != null && !currentClass.isExternal && !esClassToPossibilityForOptimization.contains(currentClass)) {
esClassToPossibilityForOptimization[currentClass] = nearestOptimizationDecision
if (nearestOptimizationDecision.value && !currentClass.canBeOptimized()) {
nearestOptimizationDecision.value = false
}
currentClass = currentClass.superClass
}
}
private fun IrClass.canBeOptimized(): Boolean {
return superClass?.symbol != context.throwableClass && !isSubclassOfExternalClassWithRequiredBoxParameter() && !hasPrimaryDelegatedToSecondaryOrSecondaryToPrimary()
}
private fun IrClass.hasPrimaryDelegatedToSecondaryOrSecondaryToPrimary(): Boolean {
declarations
.filterIsInstanceAnd<IrFunction> { it.isEs6ConstructorReplacement }
.forEach {
var meetUnoptimizedDelegation = false
it.body?.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCall(expression: IrCall): IrExpression {
if (expression.isSyntheticDelegatingReplacement && expression.symbol.owner.origin != it.origin) {
meetUnoptimizedDelegation = true
}
return super.visitCall(expression)
}
})
if (meetUnoptimizedDelegation) return true
}
return false
}
private fun IrClass.isSubclassOfExternalClassWithRequiredBoxParameter(): Boolean {
return superClass?.isExternal == true && esClassWhichNeedBoxParameters.contains(this)
}
}
private fun IrFunction.shouldBeConvertedToPlainConstructor(context: JsIrBackendContext): Boolean {
return isEs6PrimaryConstructorReplacement && context.mapping.esClassToPossibilityForOptimization[parentAsClass]?.value == true
}
@@ -6,11 +6,10 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
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.utils.isJsImplicitExport
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
@@ -86,7 +85,7 @@ class ImplicitlyExportedDeclarationsMarkingLowering(private val context: JsIrBac
private fun IrDeclaration.markWithJsImplicitExport() {
val jsImplicitExportCtor = context.intrinsics.jsImplicitExportAnnotationSymbol.constructors.single()
annotations += context.createIrBuilder(symbol).irCall(jsImplicitExportCtor)
annotations += JsIrBuilder.buildConstructorCall(jsImplicitExportCtor)
parentClassOrNull?.takeIf { it.shouldBeMarkedWithImplicitExport() }?.markWithJsImplicitExport()
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.jsConstructorReference
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
@@ -44,7 +45,7 @@ class JsClassUsageInReflectionLowering(val backendContext: JsIrBackendContext) :
private fun IrClassReference.generateDirectValueUsage(): IrExpression? {
return with(backendContext) {
when(val classSymbol = symbol as? IrClassSymbol ?: return null) {
when (val classSymbol = symbol as? IrClassSymbol ?: return null) {
irBuiltIns.nothingClass -> null
irBuiltIns.anyClass ->
JsIrBuilder.buildCall(intrinsics.jsCode).apply {
@@ -59,10 +60,7 @@ class JsClassUsageInReflectionLowering(val backendContext: JsIrBackendContext) :
)
}
else ->
JsIrBuilder.buildCall(intrinsics.jsClass, origin = JsStatementOrigins.CLASS_REFERENCE).apply {
putTypeArgument(0, classSymbol.owner.defaultType)
}
else -> classSymbol.owner.jsConstructorReference(backendContext)
}
}
}
@@ -17,13 +17,11 @@ import org.jetbrains.kotlin.ir.backend.js.utils.JsAnnotations
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.backend.js.utils.realOverrideTarget
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
@@ -72,18 +70,13 @@ class JsDefaultArgumentStubGenerator(override val context: JsIrBackendContext) :
?.also { new -> variables[param] = new } ?: param
}
val defaultResolutionStatements = valueParameters.mapNotNull {
irBuilder.createResolutionStatement(it, it.defaultValue?.expression)
}
if (variables.isNotEmpty()) {
body?.transformChildren(VariableRemapper(variables), null)
body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
statements += defaultResolutionStatements
statements += body?.statements ?: emptyList()
}
val blockBody = body as? IrBlockBody
if (blockBody != null && variables.isNotEmpty()) {
blockBody.transformChildren(VariableRemapper(variables), null)
blockBody.statements.addAll(0, valueParameters.mapNotNull {
irBuilder.createResolutionStatement(it, it.defaultValue?.expression)
})
}
return also {
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.backend.js.utils.jsConstructorReference
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.expressions.IrCall
@@ -91,19 +92,7 @@ class JsDefaultParameterInjector(override val context: JsIrBackendContext) :
0,
1
).apply {
putValueArgument(
0,
IrCallImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
context.dynamicType,
context.intrinsics.jsClass,
1,
0
).apply {
putTypeArgument(0, owner.defaultType)
}
)
putValueArgument(0, owner.jsConstructorReference(context))
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.common.lower.InnerClassesSupport
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.JsMapping
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder.SYNTHESIZED_DECLARATION
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.Namer
import org.jetbrains.kotlin.ir.builders.declarations.buildConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildField
@@ -41,7 +42,7 @@ class JsInnerClassesSupport(mapping: JsMapping, private val irFactory: IrFactory
irFactory.buildField {
origin = IrDeclarationOrigin.FIELD_FOR_OUTER_THIS
name = Name.identifier("\$this")
name = Name.identifier(Namer.SYNTHETIC_RECEIVER_NAME)
type = outerClass.defaultType
visibility = DescriptorVisibilities.PROTECTED
isFinal = true
@@ -12,15 +12,19 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
import org.jetbrains.kotlin.backend.common.lower.irIfThen
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.buildField
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
@@ -28,9 +32,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class ObjectDeclarationLowering(
val context: JsCommonBackendContext
) : DeclarationTransformer {
class ObjectDeclarationLowering(val context: JsCommonBackendContext) : DeclarationTransformer {
private var IrClass.instanceField by context.mapping.objectToInstanceField
private var IrClass.syntheticPrimaryConstructor by context.mapping.classToSyntheticPrimaryConstructor
@@ -39,7 +41,6 @@ class ObjectDeclarationLowering(
* If the object being lowered is nested inside an enum class, we want to also initialize the enum entries when initializing the object.
*/
private var IrClass.initEntryInstancesFun: IrSimpleFunction? by context.mapping.enumClassToInitEntryInstancesFun
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration !is IrClass || declaration.kind != ClassKind.OBJECT || declaration.isEffectivelyExternal())
return null
@@ -67,7 +68,7 @@ class ObjectDeclarationLowering(
if (initEntryInstancesFun != null)
+irCall(initEntryInstancesFun)
+irIfThen(
irEqualsNull(irGetField(null, instanceField)),
irNullabilityCheck(instanceField),
// Instance field initialized inside constructor
irCallConstructor(primaryConstructor.symbol, emptyList())
)
@@ -77,35 +78,30 @@ class ObjectDeclarationLowering(
return listOf(declaration, instanceField, getInstanceFun)
}
private fun IrBuilderWithScope.irNullabilityCheck(instanceField: IrField): IrExpression {
val context = this@ObjectDeclarationLowering.context
return if (context is JsIrBackendContext && context.es6mode) {
irEqeqeqWithoutBox(irGetField(null, instanceField), context.getVoid())
} else {
irEqualsNull(irGetField(null, instanceField))
}
}
}
class ObjectUsageLowering(
val context: JsCommonBackendContext
) : BodyLoweringPass {
class ObjectUsageLowering(val context: JsCommonBackendContext) : BodyLoweringPass {
private var IrClass.instanceField by context.mapping.objectToInstanceField
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (container is IrConstructor && container.isPrimary) {
val irClass = container.parentAsClass
irClass.instanceField?.let { instanceField ->
// Initialize instance field in the beginning of the constructor because it can be used inside the constructor later
val initInstanceField = context.createIrBuilder(container.symbol).buildStatement(UNDEFINED_OFFSET, UNDEFINED_OFFSET) {
irSetField(null, instanceField, irGet(irClass.thisReceiver!!))
}
if (context.es6mode) {
//find `superCall` and put after
(irBody as IrBlockBody).statements.transformFlat {
if (it is IrDelegatingConstructorCall) listOf(it, initInstanceField)
else if (it is IrVariable && it.origin === ES6_THIS_VARIABLE_ORIGIN) {
initInstanceField.value = JsIrBuilder.buildGetValue(it.symbol)
listOf(it, initInstanceField)
} else null
}
} else {
(irBody as IrBlockBody).statements.add(0, initInstanceField)
}
}
val functionContainer = container.takeIf { it is IrConstructor && it.isPrimary }
val irClass = functionContainer?.parentAsClass
irClass?.instanceField?.let {
if (context.es6mode && irClass.superClass == null) return@let
// Initialize instance field in the beginning of the constructor because it can be used inside the constructor later
val initInstanceField = generateInitInstanceField(it, irClass.getValueForInstanceFieldForTheFirstTime())
(irBody as IrBlockBody).statements.add(0, initInstanceField)
}
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
@@ -114,8 +110,35 @@ class ObjectUsageLowering(
if (obj.isEffectivelyExternal()) return expression
return JsIrBuilder.buildCall(context.getOrCreateGetInstanceFunction(obj).symbol)
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
val instanceField = irClass?.instanceField
return if (!context.es6mode || instanceField == null) {
super.visitDelegatingConstructorCall(expression)
} else {
JsIrBuilder.buildComposite(
context.irBuiltIns.unitType,
listOf(
super.visitDelegatingConstructorCall(expression),
generateInitInstanceField(instanceField, JsIrBuilder.buildGetValue(irClass.thisReceiver!!.symbol))
)
)
}
}
})
}
private fun IrClass.getValueForInstanceFieldForTheFirstTime(): IrExpression {
return if (context.es6mode && context is JsIrBackendContext) {
JsIrBuilder.buildNull(thisReceiver!!.type)
} else {
JsIrBuilder.buildGetValue(thisReceiver!!.symbol)
}
}
private fun generateInitInstanceField(instanceField: IrField, value: IrExpression): IrStatement {
return JsIrBuilder.buildSetField(instanceField.symbol, null, value, context.irBuiltIns.unitType)
}
}
private fun JsCommonBackendContext.getOrCreateGetInstanceFunction(obj: IrClass) =
@@ -7,6 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
@@ -17,18 +18,20 @@ import org.jetbrains.kotlin.ir.expressions.IrInstanceInitializerCall
import org.jetbrains.kotlin.ir.expressions.impl.IrDelegatingConstructorCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrInstanceInitializerCallImpl
import org.jetbrains.kotlin.ir.util.constructors
import org.jetbrains.kotlin.ir.util.isInterface
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
val IrDeclaration.isSyntheticPrimaryConstructor: Boolean
get() = origin == PrimaryConstructorLowering.SYNTHETIC_PRIMARY_CONSTRUCTOR
// Create primary constructor if it doesn't exist
class PrimaryConstructorLowering(val context: JsCommonBackendContext) : DeclarationTransformer {
private var IrClass.syntheticPrimaryConstructor by context.mapping.classToSyntheticPrimaryConstructor
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration is IrClass && !declaration.isInterface) {
if (declaration is IrClass && declaration.kind != ClassKind.INTERFACE) {
val constructors = declaration.constructors
if (constructors.any { it.isPrimary }) return null
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.backend.common.getOrPut
import org.jetbrains.kotlin.backend.common.ir.ValueRemapper
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
@@ -33,6 +32,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.runIf
class SecondaryConstructorLowering(val context: JsIrBackendContext) : DeclarationTransformer {
@@ -234,6 +234,7 @@ private fun JsIrBackendContext.buildConstructorFactory(constructor: IrConstructo
class SecondaryFactoryInjectorLowering(val context: JsIrBackendContext) : BodyLoweringPass {
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (context.es6mode) return
// TODO Simplify? Is this needed at all?
var parentFunction: IrFunction? = container as? IrFunction
var declaration = container
@@ -266,14 +267,7 @@ private class CallsiteRedirectionTransformer(private val context: JsIrBackendCon
val target = expression.symbol.owner
return if (target.isSecondaryConstructorCall) {
val factory = with(context) {
if (es6mode) mapping.secondaryConstructorToDelegate[target]
?: compilationException(
"Not found IrFunction for secondary ctor",
expression
)
else buildConstructorFactory(target, target.parentAsClass)
}
val factory = context.buildConstructorFactory(target, target.parentAsClass)
replaceSecondaryConstructorWithFactoryFunction(expression, factory.symbol)
} else expression
}
@@ -285,18 +279,8 @@ private class CallsiteRedirectionTransformer(private val context: JsIrBackendCon
return if (target.isSecondaryConstructorCall) {
val klass = target.parentAsClass
val delegate = with(context) {
if (es6mode) mapping.secondaryConstructorToDelegate[target]
?: compilationException(
"Not found IrFunction for secondary ctor",
expression
)
else buildConstructorDelegate(target, klass)
}
val delegate = context.buildConstructorDelegate(target, klass)
val newCall = replaceSecondaryConstructorWithFactoryFunction(expression, delegate.symbol)
if (context.es6mode) {
return newCall
}
val readThis = expression.run {
if (data is IrConstructor) {
@@ -315,16 +299,19 @@ private class CallsiteRedirectionTransformer(private val context: JsIrBackendCon
private fun replaceSecondaryConstructorWithFactoryFunction(
call: IrFunctionAccessExpression,
newTarget: IrSimpleFunctionSymbol
) = IrCallImpl(
call.startOffset, call.endOffset, call.type, newTarget,
typeArgumentsCount = call.typeArgumentsCount,
valueArgumentsCount = newTarget.owner.valueParameters.size
).apply {
): IrCall {
val irClass = call.symbol.owner.parentAsClass
return IrCallImpl(
call.startOffset, call.endOffset, call.type, newTarget,
typeArgumentsCount = call.typeArgumentsCount,
valueArgumentsCount = newTarget.owner.valueParameters.size,
superQualifierSymbol = irClass.symbol.takeIf { context.es6mode && call.isSyntheticDelegatingReplacement }
).apply {
copyTypeArgumentsFrom(call)
copyTypeArgumentsFrom(call)
for (i in 0 until call.valueArgumentsCount) {
putValueArgument(i, call.getValueArgument(i))
for (i in 0 until call.valueArgumentsCount) {
putValueArgument(i, call.getValueArgument(i))
}
}
}
}
@@ -6,8 +6,8 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.getVoid
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
@@ -21,12 +21,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformer
class ThrowableLowering(
val context: JsIrBackendContext,
val extendThrowableFunction: IrSimpleFunctionSymbol
) : BodyLoweringPass {
private val nothingNType = context.irBuiltIns.nothingNType
class ThrowableLowering(val context: JsIrBackendContext, val extendThrowableFunction: IrSimpleFunctionSymbol) : BodyLoweringPass {
private val throwableConstructors = context.throwableConstructors
private val newThrowableFunction = context.newThrowableSymbol
@@ -64,6 +59,8 @@ class ThrowableLowering(
}
inner class Transformer : IrElementTransformer<IrDeclarationParent> {
private val anyConstructor = context.irBuiltIns.anyClass.constructors.first()
override fun visitClass(declaration: IrClass, data: IrDeclarationParent) = super.visitClass(declaration, declaration)
override fun visitConstructorCall(expression: IrConstructorCall, data: IrDeclarationParent): IrExpression {
@@ -93,7 +90,7 @@ class ThrowableLowering(
val klass = data as IrClass
val thisReceiver = IrGetValueImpl(expression.startOffset, expression.endOffset, klass.thisReceiver!!.symbol)
return expression.run {
val expressionReplacement = expression.run {
IrCallImpl(
startOffset, endOffset, type, extendThrowableFunction,
valueArgumentsCount = 3,
@@ -104,6 +101,25 @@ class ThrowableLowering(
it.putValueArgument(2, causeArg)
}
}
return if (!context.es6mode) {
expressionReplacement
} else {
JsIrBuilder.buildComposite(
context.irBuiltIns.unitType,
listOf(
IrDelegatingConstructorCallImpl(
expression.startOffset,
expression.endOffset,
context.irBuiltIns.anyType,
anyConstructor,
0,
0
),
expressionReplacement
)
)
}
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2022 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
import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig
import org.jetbrains.kotlin.backend.common.phaser.invokeToplevel
import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
fun optimizeProgramByIr(
modules: Iterable<IrModuleFragment>,
context: JsIrBackendContext,
removeUnusedAssociatedObjects: Boolean
) {
eliminateDeadDeclarations(modules, context, removeUnusedAssociatedObjects)
jsOptimizationPhases.invokeToplevel(PhaseConfig(jsOptimizationPhases), context, modules)
}
@@ -166,7 +166,7 @@ class IrElementToJsExpressionTransformer : BaseIrElementToJsNodeTransformer<JsEx
}
return if (context.staticContext.backendContext.es6mode) {
JsInvocation(JsNameRef("super"), arguments)
JsInvocation(JsSuperRef(), arguments)
} else {
JsInvocation(callFuncRef, listOf(thisRef) + arguments)
}.withSource(expression, context)
@@ -65,7 +65,11 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
override fun visitComposite(expression: IrComposite, context: JsGenerationContext): JsStatement {
return JsBlock(expression.statements.map { it.accept(this, context) }).withSource(expression, context)
return if (expression.statements.isEmpty()) {
JsEmpty
} else {
JsBlock(expression.statements.map { it.accept(this, context) }).withSource(expression, context)
}
}
override fun visitExpression(expression: IrExpression, context: JsGenerationContext): JsStatement {
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.serialization.checkIsFunctionInterface
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.*
import org.jetbrains.kotlin.ir.backend.js.dce.eliminateDeadDeclarations
import org.jetbrains.kotlin.ir.backend.js.export.*
import org.jetbrains.kotlin.ir.backend.js.lower.StaticMembersLowering
import org.jetbrains.kotlin.ir.backend.js.lower.isBuiltInClass
@@ -46,29 +45,33 @@ val IrModuleFragment.safeName: String
get() = name.asString().safeModuleName
enum class TranslationMode(
val dce: Boolean,
val production: Boolean,
val perModule: Boolean,
val minimizedMemberNames: Boolean,
) {
FULL(dce = false, perModule = false, minimizedMemberNames = false),
FULL_DCE(dce = true, perModule = false, minimizedMemberNames = false),
FULL_DCE_MINIMIZED_NAMES(dce = true, perModule = false, minimizedMemberNames = true),
PER_MODULE(dce = false, perModule = true, minimizedMemberNames = false),
PER_MODULE_DCE(dce = true, perModule = true, minimizedMemberNames = false),
PER_MODULE_DCE_MINIMIZED_NAMES(dce = true, perModule = true, minimizedMemberNames = true);
FULL_DEV(production = false, perModule = false, minimizedMemberNames = false),
FULL_PROD(production = true, perModule = false, minimizedMemberNames = false),
FULL_PROD_MINIMIZED_NAMES(production = true, perModule = false, minimizedMemberNames = true),
PER_MODULE_DEV(production = false, perModule = true, minimizedMemberNames = false),
PER_MODULE_PROD(production = true, perModule = true, minimizedMemberNames = false),
PER_MODULE_PROD_MINIMIZED_NAMES(production = true, perModule = true, minimizedMemberNames = true);
companion object {
fun fromFlags(dce: Boolean, perModule: Boolean, minimizedMemberNames: Boolean): TranslationMode {
fun fromFlags(
production: Boolean,
perModule: Boolean,
minimizedMemberNames: Boolean
): TranslationMode {
return if (perModule) {
if (dce) {
if (minimizedMemberNames) PER_MODULE_DCE_MINIMIZED_NAMES
else PER_MODULE_DCE
} else PER_MODULE
if (production) {
if (minimizedMemberNames) PER_MODULE_PROD_MINIMIZED_NAMES
else PER_MODULE_PROD
} else PER_MODULE_DEV
} else {
if (dce) {
if (minimizedMemberNames) FULL_DCE_MINIMIZED_NAMES
else FULL_DCE
} else FULL
if (production) {
if (minimizedMemberNames) FULL_PROD_MINIMIZED_NAMES
else FULL_PROD
} else FULL_DEV
}
}
}
@@ -146,15 +149,15 @@ class IrModuleToJsTransformer(
val result = EnumMap<TranslationMode, CompilationOutputs>(TranslationMode::class.java)
modes.filter { !it.dce }.forEach {
modes.filter { !it.production }.forEach {
result[it] = makeJsCodeGeneratorFromIr(exportData, it).generateJsCode(relativeRequirePath, true)
}
if (modes.any { it.dce }) {
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
if (modes.any { it.production }) {
optimizeProgramByIr(modules, backendContext, removeUnusedAssociatedObjects)
}
modes.filter { it.dce }.forEach {
modes.filter { it.production }.forEach {
result[it] = makeJsCodeGeneratorFromIr(exportData, it).generateJsCode(relativeRequirePath, true)
}
@@ -165,8 +168,8 @@ class IrModuleToJsTransformer(
val exportData = associateIrAndExport(modules)
doStaticMembersLowering(modules)
if (mode.dce) {
eliminateDeadDeclarations(modules, backendContext, removeUnusedAssociatedObjects)
if (mode.production) {
optimizeProgramByIr(modules, backendContext, removeUnusedAssociatedObjects)
}
return makeJsCodeGeneratorFromIr(exportData, mode)
@@ -242,8 +245,12 @@ class IrModuleToJsTransformer(
val internalModuleName = ReservedJsNames.makeInternalModuleName().takeIf { !isEsModules }
val globalNames = NameTable<String>(globalNameScope)
val statements = result.declarations.statements
val fileStatements = fileExports.file.accept(IrFileToJsTransformer(useBareParameterNames = true), staticContext).statements
val exportStatements =
ExportModelToJsStatements(staticContext, { globalNames.declareFreshName(it, it) }).generateModuleExport(
ExportModelToJsStatements(staticContext, backendContext.es6mode, { globalNames.declareFreshName(it, it) }).generateModuleExport(
ExportedModule(mainModuleName, moduleKind, fileExports.exports),
internalModuleName,
isEsModules
@@ -252,9 +259,6 @@ class IrModuleToJsTransformer(
result.exports.statements += exportStatements
result.dts = fileExports.tsDeclarations
val statements = result.declarations.statements
val fileStatements = fileExports.file.accept(IrFileToJsTransformer(useBareParameterNames = true), staticContext).statements
if (fileStatements.isNotEmpty()) {
var startComment = ""
@@ -6,39 +6,32 @@
package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.export.isAllowedFakeOverriddenDeclaration
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.export.isOverriddenExported
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrVarargImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrClassifierSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.runIf
class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationContext) {
private val className = context.getNameForClass(irClass)
private val classNameRef = className.makeRef()
private val baseClass: IrType? = irClass.superTypes.firstOrNull { !it.classifierOrFail.isInterface }
private val classPrototypeRef by lazy { prototypeOf(classNameRef, context.staticContext) }
private val baseClassRef by lazy { // Lazy in case was not collected by namer during JsClassGenerator construction
if (baseClass != null && !baseClass.isAny()) baseClass.getClassRef(context) else null
}
private val classPrototypeRef = prototypeOf(classNameRef, context.staticContext)
private val classBlock = JsCompositeBlock()
private val classModel = JsIrClassModel(irClass)
@@ -67,14 +60,9 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
when (declaration) {
is IrConstructor -> {
if (es6mode) {
declaration.accept(IrFunctionToJsTransformer(), context).let {
//HACK: add superCall to Error
if ((baseClass?.classifierOrNull?.owner as? IrClass)?.symbol === context.staticContext.backendContext.throwableClass) {
it.body.statements.add(0, JsInvocation(JsNameRef("super")).makeStmt())
}
if (it.body.statements.any { it !is JsEmpty }) {
jsClass.constructor = it
declaration.accept(IrFunctionToJsTransformer(), context).let { fn ->
if (fn.body.statements.any { it !is JsEmpty && !it.isSimpleSuperCall(fn) }) {
jsClass.constructor = fn
}
}
} else {
@@ -85,13 +73,15 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
properties.addIfNotNull(declaration.correspondingPropertySymbol?.owner)
if (es6mode) {
val (memberRef, function) = generateMemberFunction(declaration)
function?.let { jsClass.members += it }
declaration.generateAssignmentIfMangled(memberRef)
if (declaration.isEs6ConstructorReplacement && irClass.isInterface) continue
val (memberName, function) = generateMemberFunction(declaration)
function?.let { jsClass.members += it.escapedIfNeed() }
declaration.generateAssignmentIfMangled(memberName)
} else {
val (memberRef, function) = generateMemberFunction(declaration)
val (memberName, function) = generateMemberFunction(declaration)
val memberRef = jsElementAccess(memberName, classPrototypeRef)
function?.let { classBlock.statements += jsAssignment(memberRef, it.apply { name = null }).makeStmt() }
declaration.generateAssignmentIfMangled(memberRef)
declaration.generateAssignmentIfMangled(memberName)
}
}
is IrClass -> {
@@ -164,6 +154,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
getterOverridesExternal ||
property.getJsName() != null
) {
val propertyName = context.getNameForProperty(property)
// Use "direct dispatch" for final properties, i. e. instead of this:
// Object.defineProperty(Foo.prototype, 'prop', {
@@ -180,7 +171,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
val getterForwarder = property.getter
.takeIf { it.shouldExportAccessor(context.staticContext.backendContext) }
.getOrGenerateIfFinal {
.getOrGenerateIfFinalOrEs6Mode {
propertyAccessorForwarder("getter forwarder") {
JsReturn(JsInvocation(it))
}
@@ -188,7 +179,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
val setterForwarder = property.setter
.takeIf { it.shouldExportAccessor(context.staticContext.backendContext) }
.getOrGenerateIfFinal {
.getOrGenerateIfFinalOrEs6Mode {
val setterArgName = JsName("value", false)
propertyAccessorForwarder("setter forwarder") {
JsInvocation(it, JsNameRef(setterArgName)).makeStmt()
@@ -197,29 +188,37 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
classBlock.statements += JsExpressionStatement(
defineProperty(
classPrototypeRef,
context.getNameForProperty(property).ident,
getter = getterForwarder,
setter = setterForwarder,
context.staticContext
if (es6mode) {
jsClass.members += listOfNotNull(
(getterForwarder as? JsFunction)?.apply {
name = propertyName
modifiers.add(JsFunction.Modifier.GET)
},
(setterForwarder as? JsFunction)?.apply {
name = propertyName
modifiers.add(JsFunction.Modifier.SET)
}
)
)
} else {
classBlock.statements += JsExpressionStatement(
defineProperty(classPrototypeRef, propertyName.ident, getterForwarder, setterForwarder, context.staticContext)
)
}
}
}
}
classModel.preDeclarationBlock.statements += generateSetMetadataCall()
val metadataPlace = if (es6mode) classModel.postDeclarationBlock else classModel.preDeclarationBlock
metadataPlace.statements += generateSetMetadataCall()
context.staticContext.classModels[irClass.symbol] = classModel
return classBlock
}
private inline fun IrSimpleFunction?.getOrGenerateIfFinal(generateFunc: IrSimpleFunction.() -> JsFunction?): JsExpression? {
private inline fun IrSimpleFunction?.getOrGenerateIfFinalOrEs6Mode(generateFunc: IrSimpleFunction.() -> JsFunction?): JsExpression? {
if (this == null) return null
return if (modality == Modality.FINAL) accessorRef() else generateFunc()
return if (!es6mode && modality == Modality.FINAL) accessorRef() else generateFunc()
}
private fun IrSimpleFunction.isDefinedInsideExportedInterface(): Boolean {
@@ -237,13 +236,13 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
)
}
private fun IrSimpleFunction.generateAssignmentIfMangled(memberRef: JsExpression) {
private fun IrSimpleFunction.generateAssignmentIfMangled(memberName: JsName) {
if (
irClass.isExported(context.staticContext.backendContext) &&
visibility.isPublicAPI && hasMangledName() &&
correspondingPropertySymbol == null
) {
classBlock.statements += jsAssignment(prototypeAccessRef(), memberRef).makeStmt()
classBlock.statements += jsAssignment(prototypeAccessRef(), jsElementAccess(memberName, classPrototypeRef)).makeStmt()
}
}
@@ -259,9 +258,8 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
return isInterface && !isEffectivelyExternal()
}
private fun generateMemberFunction(declaration: IrSimpleFunction): Pair<JsExpression, JsFunction?> {
private fun generateMemberFunction(declaration: IrSimpleFunction): Pair<JsName, JsFunction?> {
val memberName = context.getNameForMemberFunction(declaration.realOverrideTarget)
val memberRef = jsElementAccess(memberName.ident, classPrototypeRef)
if (declaration.isReal && declaration.body != null) {
val translatedFunction: JsFunction = declaration.accept(IrFunctionToJsTransformer(), context)
@@ -269,10 +267,10 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
if (irClass.isInterface) {
classModel.preDeclarationBlock.statements += translatedFunction.makeStmt()
return Pair(memberRef, null)
return Pair(memberName, null)
}
return Pair(memberRef, translatedFunction)
return Pair(memberName, translatedFunction)
}
// do not generate code like
@@ -294,7 +292,10 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
if (implClassDeclaration.shouldCopyFrom()) {
val reference = context.getNameForStaticDeclaration(it).makeRef()
classModel.postDeclarationBlock.statements += jsAssignment(memberRef, reference).makeStmt()
classModel.postDeclarationBlock.statements += jsAssignment(
jsElementAccess(memberName, classPrototypeRef),
reference
).makeStmt()
if (isFakeOverride) {
classModel.postDeclarationBlock.statements += missedOverrides
.map { missedOverride ->
@@ -307,7 +308,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
return Pair(memberRef, null)
return Pair(memberName, null)
}
private fun maybeGeneratePrimaryConstructor() {
@@ -322,7 +323,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
val setMetadataFor = context.staticContext.backendContext.intrinsics.setMetadataForSymbol.owner
val ctor = classNameRef
val parent = baseClassRef
val parent = baseClassRef?.takeIf { !es6mode }
val name = generateSimpleName()
val interfaces = generateInterfacesList()
val metadataConstructor = getMetadataConstructor()
@@ -352,8 +353,6 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
private fun IrType.isFunctionType() = isFunctionOrKFunction() || isSuspendFunctionOrKFunction()
private fun isCoroutineClass(): Boolean = irClass.superTypes.any { it.isSuspendFunctionTypeOrSubtype() }
private fun generateSimpleName(): JsStringLiteral? {
return irClass.name.takeIf { !it.isSpecial }?.let { JsStringLiteral(it.identifier) }
}
@@ -386,7 +385,7 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
.distinct()
.map { JsIntLiteral(it) }
return JsArrayLiteral(arity)
return JsArrayLiteral(arity).takeIf { arity.isNotEmpty() }
}
private fun generateAssociatedObjectKey(): JsIntLiteral? {
@@ -411,6 +410,31 @@ class JsClassGenerator(private val irClass: IrClass, val context: JsGenerationCo
}
}
fun JsFunction.escapedIfNeed(): JsFunction {
if (name?.ident?.isValidES5Identifier() == false) {
name = JsName("'${name.ident}'", name.isTemporary)
}
return this
}
fun JsStatement.isSimpleSuperCall(container: JsFunction): Boolean {
if (this !is JsExpressionStatement) return false
val invocation = expression as? JsInvocation ?: return false
if (invocation.qualifier !is JsSuperRef || container.parameters.size != invocation.arguments.size) return false
for (i in 0..container.parameters.lastIndex) {
val declaredParameter = container.parameters[i]
val providedParameter = (invocation.arguments[i] as? JsNameRef)?.takeIf { it.qualifier == null } ?: return false
if (declaredParameter.name != providedParameter.name) {
return false
}
}
return true
}
fun IrSimpleFunction?.shouldExportAccessor(context: JsIrBackendContext): Boolean {
if (this == null) return false
@@ -119,11 +119,6 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
JsObjectLiteral()
}
add(intrinsics.es6DefaultType) { call, context ->
val typeArgument = call.getTypeArgument(0)!!
typeArgument.getClassRef(context)
}
addIfNotNull(intrinsics.jsCode) { call, _ ->
compilationException(
"Should not be called",
@@ -272,6 +267,16 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
add(intrinsics.jsInvokeSuspendSuperTypeWithReceiverAndParam, suspendInvokeTransform)
add(intrinsics.jsArguments) { _, _ -> Namer.ARGUMENTS }
add(intrinsics.jsNewAnonymousClass) { call, context ->
val baseClass = translateCallArguments(call, context).single() as JsNameRef
JsClass(baseClass = baseClass)
}
add(intrinsics.void.owner.getter!!.symbol) { _, context ->
val backingField = context.getNameForField(intrinsics.void.owner.backingField!!)
JsNameRef(backingField)
}
}
}
@@ -39,8 +39,9 @@ class Merger(
importStatements.putIfAbsent(declaration, JsVars(JsVars.JsVar(importName, rename(importExpression))))
}
val classModels = mutableMapOf<JsName, JsIrIcClassModel>() + f.classes
f.classes.clear()
val classModels = (mutableMapOf<JsName, JsIrIcClassModel>() + f.classes)
.also { f.classes.clear() }
classModels.entries.forEach { (name, model) ->
f.classes[rename(name)] = JsIrIcClassModel(model.superClasses.map { rename(it) }).also {
it.preDeclarationBlock.statements += model.preDeclarationBlock.statements
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.sourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -32,7 +33,6 @@ import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
fun jsUndefined(context: IrNamer, backendContext: JsIrBackendContext): JsExpression {
return when (val void = backendContext.getVoid()) {
is IrGetField -> context.getNameForField(void.symbol.owner).makeRef()
@@ -114,6 +114,7 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
val function = JsFunction(emptyScope, body, "member function ${name ?: "annon"}")
.apply { if (declaration.isEs6ConstructorReplacement) modifiers.add(JsFunction.Modifier.STATIC) }
.withSource(declaration, context, useNameOf = declaration)
function.name = name
@@ -147,6 +148,7 @@ fun translateCall(
transformer: IrElementToJsExpressionTransformer
): JsExpression {
val function = expression.symbol.owner.realOverrideTarget
val currentDispatchReceiver = context.currentFunction?.parentClassOrNull
context.staticContext.intrinsics[function.symbol]?.let {
return it(expression, context)
@@ -194,6 +196,10 @@ fun translateCall(
Pair(function, superQualifier.owner)
}
if (expression.isSyntheticDelegatingReplacement || currentDispatchReceiver.canUseSuperRef(function, context, klass)) {
return JsInvocation(JsNameRef(context.getNameForMemberFunction(target), JsSuperRef()), arguments)
}
val callRef = if (klass.isInterface) {
val nameForStaticDeclaration = context.getNameForStaticDeclaration(target)
JsNameRef(Namer.CALL_FUNCTION, JsNameRef(nameForStaticDeclaration))
@@ -373,12 +379,15 @@ fun translateCallArguments(
): List<JsExpression> {
val size = expression.valueArgumentsCount
val varargParameterIndex = expression.symbol.owner.realOverrideTarget.varargParameterIndex()
val function = expression.symbol.owner
val varargParameterIndex = function.realOverrideTarget.varargParameterIndex()
val validWithNullArgs = expression.validWithNullArgs()
val arguments = (0 until size)
.mapTo(ArrayList(size)) { index ->
expression.getValueArgument(index).checkOnNullability(validWithNullArgs)
expression.getValueArgument(index).checkOnNullability(
validWithNullArgs || function.valueParameters[index].isBoxParameter
)
}
.dropLastWhile {
allowDropTailVoids &&
@@ -635,3 +644,11 @@ private val nameMappingOriginAllowList = setOf(
BOUND_VALUE_PARAMETER,
JsLoweredDeclarationOrigin.JS_SHADOWED_DEFAULT_PARAMETER,
)
private fun IrClass?.canUseSuperRef(function: IrFunction, context: JsGenerationContext, superClass: IrClass): Boolean {
return this != null &&
function.origin != IrDeclarationOrigin.LOWERED_SUSPEND_FUNCTION &&
context.staticContext.backendContext.es6mode &&
!superClass.isInterface && !isInner && !isLocal &&
context.currentFunction?.isEs6ConstructorReplacement != true
}
@@ -74,8 +74,20 @@ private fun JsNode.computeScopes(): Scope {
accept(object : RecursiveJsVisitor() {
var currentScope: Scope = rootScope
override fun visitFunction(x: JsFunction) {
override fun visitClass(x: JsClass) {
x.name?.let { currentScope.declaredNames += it }
// We need it to not rename methods and fields inside class body
// Because if they are in clash with something, it means overriding
x.constructor?.accept(this)
x.members.forEach { visitFunction(it, shouldReserveName = false) }
}
override fun visitFunction(x: JsFunction) {
visitFunction(x, shouldReserveName = true)
}
fun visitFunction(x: JsFunction, shouldReserveName: Boolean) {
x.name?.takeIf { shouldReserveName }?.let { currentScope.declaredNames += it }
val oldScope = currentScope
currentScope = Scope().apply {
currentScope.children += this
@@ -12,7 +12,6 @@ import org.jetbrains.kotlin.ir.declarations.IrOverridableDeclaration
import org.jetbrains.kotlin.ir.expressions.IrClassReference
import org.jetbrains.kotlin.ir.expressions.IrConst
import org.jetbrains.kotlin.ir.expressions.IrConstructorCall
import org.jetbrains.kotlin.ir.expressions.IrVararg
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
@@ -37,10 +36,6 @@ object JsAnnotations {
fun IrConstructorCall.getSingleConstStringArgument() =
(getValueArgument(0) as IrConst<String>).value
@Suppress("UNCHECKED_CAST")
fun IrConstructorCall.getClassReferencVarargArguments() =
(getValueArgument(0) as? IrVararg)?.elements as? List<IrClassReference>
fun IrAnnotationContainer.getJsModule(): String? =
getAnnotation(JsAnnotations.jsModuleFqn)?.getSingleConstStringArgument()
@@ -9,16 +9,28 @@ import org.jetbrains.kotlin.descriptors.isClass
import org.jetbrains.kotlin.descriptors.isInterface
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
import org.jetbrains.kotlin.ir.expressions.IrReturn
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl
import org.jetbrains.kotlin.ir.symbols.IrReturnableBlockSymbol
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.defaultType
import org.jetbrains.kotlin.ir.util.isAnnotationClass
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.parentClassOrNull
import org.jetbrains.kotlin.name.FqName
fun IrClass.jsConstructorReference(context: JsIrBackendContext): IrExpression {
return JsIrBuilder.buildCall(context.intrinsics.jsClass, origin = JsStatementOrigins.CLASS_REFERENCE)
.apply { putTypeArgument(0, defaultType) }
}
fun IrDeclaration.isExportedMember(context: JsIrBackendContext) =
(this is IrDeclarationWithVisibility && visibility.isPublicAPI) &&
parentClassOrNull?.isExported(context) == true
@@ -42,10 +54,23 @@ fun IrDeclarationWithName.getFqNameWithJsNameWhenAvailable(shouldIncludePackage:
}
}
fun IrConstructor.hasStrictSignature(context: JsIrBackendContext): Boolean {
val primitives = with(context.irBuiltIns) { primitiveTypesToPrimitiveArrays.values + stringClass }
return with(parentAsClass) {
isExternal || isExpect || isAnnotationClass || context.inlineClassesUtils.isClassInlineLike(this) || symbol in primitives
}
}
private fun getKotlinOrJsQualifier(parent: IrPackageFragment, shouldIncludePackage: Boolean): FqName? {
return (parent as? IrFile)?.getJsQualifier()?.let { FqName(it) } ?: parent.fqName.takeIf { shouldIncludePackage }
}
val IrFunctionAccessExpression.typeArguments: List<IrType?>
get() = List(typeArgumentsCount) { getTypeArgument(it) }
val IrFunctionAccessExpression.valueArguments: List<IrExpression?>
get() = List(valueArgumentsCount) { getValueArgument(it) }
// TODO: the code is written to pass Repl tests, so we should understand. why in Repl tests we don't have backingField
fun JsIrBackendContext.getVoid(): IrExpression =
intrinsics.void.owner.backingField?.let {
@@ -60,3 +85,8 @@ fun JsIrBackendContext.getVoid(): IrExpression =
UNDEFINED_OFFSET,
irBuiltIns.nothingNType
)
fun irEmpty(context: JsIrBackendContext): IrExpression {
return JsIrBuilder.buildComposite(context.dynamicType, emptyList())
}
@@ -15,10 +15,13 @@ object Namer {
val OUTER_NAME = "\$outer"
val UNREACHABLE_NAME = "\$unreachable"
val THROWABLE_CONSTRUCTOR = "\$throwableCtor"
val DELEGATE = "\$delegate"
val IMPLICIT_RECEIVER_NAME = "this"
val SYNTHETIC_RECEIVER_NAME = "\$this"
val ES6_BOX_PARAMETER_NAME = "\$box"
val ARGUMENTS = JsNameRef("arguments")
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2022 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.utils
class MutableReference<T>(var value: T)
fun <T> mutableReferenceOf(value: T): MutableReference<T> = MutableReference(value)
@@ -54,4 +54,6 @@ object ExpressionIds {
const val PROPERTY_REFERENCE = 19
const val INVOCATION = 20
const val NEW = 21
const val CLASS = 22
const val SUPER_REF = 23
}
@@ -240,6 +240,7 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
private val sideEffectKindValues = SideEffectKind.values()
private val jsBinaryOperatorValues = JsBinaryOperator.values()
private val jsUnaryOperatorValues = JsUnaryOperator.values()
private val jsFunctionModifiersValues = JsFunction.Modifier.values()
private fun readExpression(): JsExpression {
return withComments {
@@ -249,6 +250,9 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
THIS_REF -> {
JsThisRef()
}
SUPER_REF -> {
JsSuperRef()
}
NULL -> {
JsNullLiteral()
}
@@ -283,10 +287,15 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
)
}
FUNCTION -> {
JsFunction(scope, readBlock(), "").apply {
readRepeated { parameters += readParameter() }
ifTrue { name = nameTable[readInt()] }
isLocal = readBoolean()
readFunction()
}
CLASS -> {
JsClass(
ifTrue { nameTable[readInt()] },
ifTrue { nameTable[readInt()].makeRef() },
ifTrue { readFunction() },
).apply {
readRepeated { members += readFunction() }
}
}
DOC_COMMENT -> {
@@ -353,6 +362,15 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
}
}
private fun readFunction(): JsFunction {
return JsFunction(scope, readBlock(), "").apply {
readRepeated { parameters += readParameter() }
readRepeated { modifiers += jsFunctionModifiersValues[readInt()] }
ifTrue { name = nameTable[readInt()] }
isLocal = readBoolean()
}
}
private fun readJsImportedModule(): JsImportedModule {
return JsImportedModule(
stringTable[readInt()],
@@ -447,4 +465,4 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
ifTrue { this.commentsAfterNode = readArray { readComment() }.toList() }
}
}
}
}
@@ -336,6 +336,10 @@ private class JsIrAstSerializer {
writeByte(ExpressionIds.THIS_REF)
}
override fun visitSuper(x: JsSuperRef) {
writeByte(ExpressionIds.SUPER_REF)
}
override fun visitNull(x: JsNullLiteral) {
writeByte(ExpressionIds.NULL)
}
@@ -387,12 +391,24 @@ private class JsIrAstSerializer {
override fun visitFunction(x: JsFunction) {
writeByte(ExpressionIds.FUNCTION)
writeBlock(x.body)
writeCollection(x.parameters) { writeParameter(it) }
writeFunction(x)
}
override fun visitClass(x: JsClass) {
writeByte(ExpressionIds.CLASS)
ifNotNull(x.name) {
writeInt(internalizeName(it))
}
writeBoolean(x.isLocal)
// TODO: add more complex JsNameRef parsing in future when we will support `class` expressions inside a `js` call
ifNotNull(x.baseClass?.name) {
writeInt(internalizeName(it))
}
ifNotNull(x.constructor) {
writeFunction(it)
}
writeCollection(x.members) {
writeFunction(it)
}
}
override fun visitDocComment(comment: JsDocComment) {
@@ -494,6 +510,16 @@ private class JsIrAstSerializer {
ifNotNull(module.plainReference) { writeExpression(it) }
}
private fun DataWriter.writeFunction(function: JsFunction) {
writeBlock(function.body)
writeCollection(function.parameters) { writeParameter(it) }
writeCollection(function.modifiers) { writeInt(it.ordinal) }
ifNotNull(function.name) {
writeInt(internalizeName(it))
}
writeBoolean(function.isLocal)
}
private fun DataWriter.writeParameter(parameter: JsParameter) {
writeInt(internalizeName(parameter.name))
writeBoolean(parameter.hasDefaultValue)
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.ir.expressions
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOriginImpl
abstract class IrStatementOriginImpl(val debugName: String) : IrStatementOrigin {
override fun toString(): String = debugName
}
@@ -205,6 +205,12 @@ val IrProperty.isSimpleProperty: Boolean
val IrClass.functions: Sequence<IrSimpleFunction>
get() = declarations.asSequence().filterIsInstance<IrSimpleFunction>()
val IrClass.superClass: IrClass?
get() = superTypes
.firstOrNull { !it.isInterface() && !it.isAny() }
?.classOrNull
?.owner
val IrClassSymbol.functions: Sequence<IrSimpleFunctionSymbol>
get() = owner.functions.map { it.symbol }
@@ -823,8 +829,11 @@ fun IrFunction.copyValueParametersFrom(from: IrFunction, substitutionMap: Map<Ir
fun IrFunction.copyParameterDeclarationsFrom(from: IrFunction) {
assert(typeParameters.isEmpty())
copyTypeParametersFrom(from)
val substitutionMap = makeTypeParameterSubstitutionMap(from, this)
copyValueParametersFrom(from, substitutionMap)
copyValueParametersFrom(from)
}
fun IrFunction.copyValueParametersFrom(from: IrFunction) {
copyValueParametersFrom(from, makeTypeParameterSubstitutionMap(from, this))
}
fun IrTypeParametersContainer.copyTypeParameters(
+1
View File
@@ -45,6 +45,7 @@ where advanced options include:
-Xtyped-arrays Translate primitive arrays to JS typed arrays
-Xuse-deprecated-legacy-compiler
Use deprecated legacy compiler without error
-Xes-classes Generated JavaScript will use ES2015 classes.
-Xwasm Use experimental WebAssembly compiler backend
-Xwasm-debug-info Add debug info to WebAssembly compiled module
-Xwasm-enable-array-range-checks
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS, JS_IR, WASM
// IGNORE_BACKEND: JS, JS_IR, JS_IR_ES6, WASM
var result = ""
fun sideEffecting(): Int {
+1 -1
View File
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS, JS_IR
// IGNORE_BACKEND: JS, JS_IR, JS_IR_ES6
// The code in this test should be prohibited in the frontend, see KT-36188.
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS, JS_IR
// IGNORE_BACKEND: JS, JS_IR, JS_IR_ES6
// !JVM_DEFAULT_MODE: all
// JVM_TARGET: 1.8
@@ -7,7 +7,7 @@
// ^ wasm-function[1893]:0x1cf8a: RuntimeError: dereferencing a null pointer
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// ^ TypeError: tmp is not a function
// FILE: funInterfaceConstructedObjectsEquality.kt
@@ -7,7 +7,7 @@
// ^ Failed: ks1 != ks2 (same file, same SAM type)
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// ^ Failed: ks1 != ks2 (same file, same SAM type)
// FILE: funInterfaceConstructorEquality.kt
@@ -1,6 +1,6 @@
// !LANGUAGE: +DefinitelyNonNullableTypes
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// IGNORE_BACKEND: WASM
fun <T> test(t: T) = t as (T & Any)
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS, JS_IR, WASM
// IGNORE_BACKEND: JS, JS_IR, JS_IR_ES6, WASM
@Suppress("UNCHECKED_CAST")
fun <T> f() = 1L as T
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS, JS_IR, WASM
// IGNORE_BACKEND: JS, JS_IR, JS_IR_ES6, WASM
// boxed primitive comparisons
fun isBoolean(a: Any) = a::class == true::class
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND_K2: JS_IR
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
class Outer(val x: Any) {
inner class Inner(
@@ -1,5 +1,4 @@
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_BACKEND_K2: JS_IR
// IGNORE_BACKEND_K2: JS_IR, JS_IR_ES6
class Outer(val x: Any) {
inner class Inner(
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// WITH_COROUTINES
// WITH_STDLIB
// MODULE: lib(support)
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// WITH_COROUTINES
// WITH_STDLIB
// MODULE: lib(support)
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// KT-55464
// IGNORE_BACKEND_K2: NATIVE
// WITH_COROUTINES
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
// WITH_COROUTINES
// WITH_STDLIB
// MODULE: lib(support)
@@ -1,7 +1,6 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: NESTED_OBJECT_INIT
// IGNORE_BACKEND: NATIVE
// IGNORE_BACKEND: JS_IR_ES6
// WITH_STDLIB
// WITH_COROUTINES
import helpers.*
@@ -1,6 +1,6 @@
// WITH_STDLIB
// FULL_JDK
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
import kotlin.coroutines.*
@@ -1,4 +1,4 @@
// IGNORE_BACKEND: JVM, JS, JS_IR
// IGNORE_BACKEND: JVM, JS, JS_IR, JS_IR_ES6
// KT-30080
data class A(
@@ -1,7 +1,7 @@
// WITH_STDLIB
// CHECK_BYTECODE_LISTING
// FIR_IDENTICAL
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS_IR, JS_IR_ES6
import kotlin.reflect.KProperty
import kotlin.test.assertEquals
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// IGNORE_FIR_DIAGNOSTICS_DIFF
open class A {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun noTails() {
// nothing here
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
// IGNORE_FIR_DIAGNOSTICS_DIFF
@@ -1,7 +1,6 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// !DIAGNOSTICS: -UNUSED_PARAMETER
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
tailrec fun test(x : Int) : Int {
var z = if (x > 3) 3 else x
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,7 +1,6 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// !DIAGNOSTICS: -UNUSED_PARAMETER
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun foo() {
bar {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun foo() {
fun bar() {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun test(counter : Int) : Int {
if (counter == 0) return 0
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun test(counter : Int) : Int {
if (counter == 0) return 0
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun test(counter : Int) : Int {
if (counter == 0) return 0
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun test(counter : Int) : Int {
if (counter == 0) return 0
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
<!NO_TAIL_CALLS_FOUND!>tailrec<!> fun test(go: Boolean) : Unit {
if (!go) return
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// DONT_RUN_GENERATED_CODE: JS
// IGNORE_BACKEND: JVM
// IGNORE_FIR_DIAGNOSTICS_DIFF
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JS_IR_ES6
// DONT_RUN_GENERATED_CODE: JS
object O {
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
@@ -1,6 +1,5 @@
// IGNORE_BACKEND: WASM
// WASM_MUTE_REASON: IGNORED_IN_JS
// IGNORE_BACKEND: JS_IR_ES6
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS

Some files were not shown because too many files have changed in this diff Show More