diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index c5812997620..b2794736a76 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -228,7 +228,14 @@ private val removeInlineFunctionsWithReifiedTypeParametersLoweringPhase = makeDe ) private val throwableSuccessorsLoweringPhase = makeBodyLoweringPhase( - ::ThrowableLowering, + { context -> + context.run { + val extendThrowableSymbol = + if (es6mode) setPropertiesToThrowableInstanceSymbol else extendThrowableSymbol + + ThrowableLowering(this, extendThrowableSymbol) + } + }, name = "ThrowableLowering", description = "Link kotlin.Throwable and JavaScript Error together to provide proper interop between language and platform exceptions" ) @@ -517,6 +524,19 @@ 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", diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt index f6beb2cd61d..4d63b8ddbb9 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsMapping.kt @@ -21,6 +21,8 @@ class JsMapping : DefaultMapping() { val classToSyntheticPrimaryConstructor = newMapping() val privateMemberToCorrespondingStatic = newMapping() + val constructorToInitFunction = newMapping() + val enumEntryToGetInstanceFun = newMapping() val enumEntryToInstanceField = newMapping() val enumConstructorToNewConstructor = newMapping() diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6AddInternalParametersToConstructorPhase.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6AddInternalParametersToConstructorPhase.kt new file mode 100644 index 00000000000..7058a9018bd --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6AddInternalParametersToConstructorPhase.kt @@ -0,0 +1,213 @@ +/* + * 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.Modality +import org.jetbrains.kotlin.descriptors.Visibilities +import org.jetbrains.kotlin.ir.IrStatement +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.* +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.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 + +/** + * 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) { + assert(context.es6mode) + + 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: IrDeclaration): 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 JsIrBuilder.buildFunction( + functionName, + context.irBuiltIns.unitType, + irClass, + Visibilities.PROTECTED, + Modality.FINAL, + constructor.isInline, + constructor.isExternal, + origin = ES6_SYNTHETIC_PRIMARY_INIT_FUNCTION + ).apply { + addValueParameter("\$this\$", context.dynamicType) + + body = JsIrBuilder.buildBlockBody(constructor.body?.statements ?: emptyList()) + + 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) { primitiveArrays + stringClass } + return with(parentAsClass) { isExternal || isInline || symbol in primitives } + } +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt new file mode 100644 index 00000000000..1d8660f2f9e --- /dev/null +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ES6ConstructorLowering.kt @@ -0,0 +1,565 @@ +/* + * 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.ir.IrElement +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.ES6AddInternalParametersToConstructorPhase.* +import org.jetbrains.kotlin.ir.backend.js.lower.PrimaryConstructorLowering.* +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.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.* +import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid +import org.jetbrains.kotlin.ir.visitors.IrElementVisitor + +object ES6_THIS_VARIABLE_ORIGIN : IrDeclarationOriginImpl("ES6_THIS_VARIABLE_ORIGIN") + +class ES6ConstructorLowering(val context: JsIrBackendContext) : BodyLoweringPass { + override fun lower(irBody: IrBody, container: IrDeclaration) { + assert(context.es6mode) + + if (container !is IrConstructor) return + + if (container.hasStrictSignature()) return + + hackEnums(container) + hackExceptions(context, container) + + val superCall = getSuperCall(container) ?: return + val superCtor = superCall.symbol.owner + val helper = LowerCtorHelper(context, container, superCtor) + + if (container.isPrimary) { + if (superCtor.isPrimary) primaryToPrimary(helper) + else primaryToSecondary(helper) + } else { + if (superCtor.isPrimary) secondaryToPrimary(helper) + else secondaryToSecondary(helper) + + replaceCallToDefaultPrimary(context, container) + changeIrConstructorToIrFunction(context, container) + } + } + + /** + * 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) + } + } + + 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 + + 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) + } + } + } + + //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) + ) + + 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 { + val result = mutableListOf() + + val superCall = constructor.body!!.statements.filterIsInstance().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 (arg.type.getInlinedClass() != null) { + val any = context.irBuiltIns.anyNType + result += JsIrBuilder.buildTypeOperator(any, IrTypeOperator.REINTERPRET_CAST, arg, any) + } 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) + } + } + + 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)) } + } + }, null) + } + + //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)) + } + } + + //util + private fun IrConstructor.hasStrictSignature(): Boolean { + val primitives = with(context.irBuiltIns) { primitiveArrays + stringClass } + return with(parentAsClass) { isExternal || isInline || symbol in primitives } + } +} + +//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.transform { + 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.transform { + 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 { + 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 = JsIrBuilder.buildFunction( + "${container.parentAsClass.name}_constructor", + container.returnType, + container.parent + ).apply { + 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.transform { + if (it === container) newConstructor else it + } + + context.mapping.secondaryConstructorToDelegate[container] = newConstructor +} diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrimaryConstructorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrimaryConstructorLowering.kt index 88a9704c79b..3c83304315e 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrimaryConstructorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/PrimaryConstructorLowering.kt @@ -38,7 +38,7 @@ class PrimaryConstructorLowering(context: JsCommonBackendContext) : DeclarationT return null } - private object SYNTHETIC_PRIMARY_CONSTRUCTOR : IrDeclarationOriginImpl("SYNTHETIC_PRIMARY_CONSTRUCTOR") + object SYNTHETIC_PRIMARY_CONSTRUCTOR : IrDeclarationOriginImpl("SYNTHETIC_PRIMARY_CONSTRUCTOR") private val unitType = context.irBuiltIns.unitType diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt index b7eebc326d9..9e825bba0a8 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/SecondaryCtorLowering.kt @@ -36,6 +36,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid class SecondaryConstructorLowering(val context: JsIrBackendContext) : DeclarationTransformer { override fun transformFlat(declaration: IrDeclaration): List? { + assert(!context.es6mode) if (declaration is IrConstructor && !declaration.isPrimary) { val irClass = declaration.parentAsClass @@ -228,7 +229,10 @@ private class CallsiteRedirectionTransformer(private val context: JsIrBackendCon val target = expression.symbol.owner return if (target.isSecondaryConstructorCall) { - val factory = context.buildConstructorFactory(target, target.parentAsClass) + val factory = with(context) { + if (es6mode) mapping.secondaryConstructorToDelegate[target] ?: error("Not found IrFunction for secondary ctor") + else buildConstructorFactory(target, target.parentAsClass) + } replaceSecondaryConstructorWithFactoryFunction(expression, factory.symbol) } else expression } @@ -240,8 +244,14 @@ private class CallsiteRedirectionTransformer(private val context: JsIrBackendCon return if (target.isSecondaryConstructorCall) { val klass = target.parentAsClass - val delegate = context.buildConstructorDelegate(target, klass) + val delegate = with(context) { + if (es6mode) mapping.secondaryConstructorToDelegate[target] ?: error("Not found IrFunction for secondary ctor") + else buildConstructorDelegate(target, klass) + } val newCall = replaceSecondaryConstructorWithFactoryFunction(expression, delegate.symbol) + if (context.es6mode) { + return newCall + } val readThis = expression.run { if (data!! is IrConstructor) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ThrowableLowering.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ThrowableLowering.kt index 7032be95797..775eac097b7 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ThrowableLowering.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/lower/ThrowableLowering.kt @@ -13,18 +13,21 @@ import org.jetbrains.kotlin.ir.declarations.IrDeclaration import org.jetbrains.kotlin.ir.declarations.IrDeclarationParent import org.jetbrains.kotlin.ir.expressions.* import org.jetbrains.kotlin.ir.expressions.impl.* +import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol import org.jetbrains.kotlin.ir.types.isNullableString import org.jetbrains.kotlin.ir.types.makeNotNull import org.jetbrains.kotlin.ir.util.* import org.jetbrains.kotlin.ir.visitors.IrElementTransformer -class ThrowableLowering(val context: JsIrBackendContext) : BodyLoweringPass { +class ThrowableLowering( + val context: JsIrBackendContext, + val extendThrowableFunction: IrSimpleFunctionSymbol +) : BodyLoweringPass { private val nothingNType get() = context.irBuiltIns.nothingNType private val throwableConstructors = context.throwableConstructors private val newThrowableFunction = context.newThrowableSymbol - private val extendThrowableFunction = context.extendThrowableSymbol fun nullValue(): IrExpression = IrConstImpl.constNull(UNDEFINED_OFFSET, UNDEFINED_OFFSET, nothingNType)