[JS BE] implement es6-classes lowering

This commit is contained in:
Vitaly
2020-05-09 14:10:08 +03:00
committed by romanart
parent be85b36025
commit 8c412adfaa
7 changed files with 819 additions and 6 deletions
@@ -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",
@@ -21,6 +21,8 @@ class JsMapping : DefaultMapping() {
val classToSyntheticPrimaryConstructor = newMapping<IrClass, IrConstructor>()
val privateMemberToCorrespondingStatic = newMapping<IrFunction, IrSimpleFunction>()
val constructorToInitFunction = newMapping<IrConstructor, IrSimpleFunction>()
val enumEntryToGetInstanceFun = newMapping<IrEnumEntry, IrSimpleFunction>()
val enumEntryToInstanceField = newMapping<IrEnumEntry, IrField>()
val enumConstructorToNewConstructor = newMapping<IrConstructor, IrConstructor>()
@@ -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 }
}
}
@@ -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<IrVarargElement> {
val result = mutableListOf<IrVarargElement>()
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 (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<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 = 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
}
@@ -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
@@ -36,6 +36,7 @@ import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class SecondaryConstructorLowering(val context: JsIrBackendContext) : DeclarationTransformer {
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
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) {
@@ -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)