JS IR: support kotlin.test
This commit is contained in:
@@ -22,6 +22,7 @@ import org.jetbrains.kotlin.ir.IrElement
|
||||
import org.jetbrains.kotlin.ir.SourceManager
|
||||
import org.jetbrains.kotlin.ir.SourceRangeInfo
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.CallableReferenceKey
|
||||
import org.jetbrains.kotlin.ir.backend.js.lower.ConstructorPair
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.OperatorNames
|
||||
@@ -81,6 +82,17 @@ class JsIrBackendContext(
|
||||
}
|
||||
}
|
||||
|
||||
private var testContainerField: IrSimpleFunction? = null
|
||||
|
||||
val hasTests get() = testContainerField != null
|
||||
|
||||
val testContainer
|
||||
get() = testContainerField ?: JsIrBuilder.buildFunction("test fun", irBuiltIns.unitType, implicitDeclarationFile).apply {
|
||||
body = JsIrBuilder.buildBlockBody(emptyList())
|
||||
testContainerField = this
|
||||
implicitDeclarationFile.declarations += this
|
||||
}
|
||||
|
||||
override val sharedVariablesManager = JsSharedVariablesManager(irBuiltIns, implicitDeclarationFile)
|
||||
override val declarationFactory = JsDeclarationFactory()
|
||||
|
||||
@@ -224,6 +236,9 @@ class JsIrBackendContext(
|
||||
|
||||
val throwISEymbol = symbolTable.referenceSimpleFunction(getFunctions(kotlinPackageFqn.child(Name.identifier("THROW_ISE"))).single())
|
||||
|
||||
val suiteFun = getFunctions(FqName("kotlin.test.suite")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
val testFun = getFunctions(FqName("kotlin.test.test")).singleOrNull()?.let { symbolTable.referenceSimpleFunction(it) }
|
||||
|
||||
val coroutineImplLabelPropertyGetter by lazy { ir.symbols.coroutineImpl.getPropertyGetter("state")!!.owner }
|
||||
val coroutineImplLabelPropertySetter by lazy { ir.symbols.coroutineImpl.getPropertySetter("state")!!.owner }
|
||||
val coroutineImplResultSymbolGetter by lazy { ir.symbols.coroutineImpl.getPropertyGetter("result")!!.owner }
|
||||
|
||||
@@ -333,10 +333,17 @@ private val callsLoweringPhase = makeJsModulePhase(
|
||||
description = "Handle intrinsics"
|
||||
)
|
||||
|
||||
private val testGenerationPhase = makeJsModulePhase(
|
||||
::TestGenerator,
|
||||
name = "TestGenerationLowering",
|
||||
description = "Generate invocations to kotlin.test suite and test functions"
|
||||
)
|
||||
|
||||
val jsPhases = namedIrModulePhase(
|
||||
name = "IrModuleLowering",
|
||||
description = "IR module lowering",
|
||||
lower = expectDeclarationsRemovingPhase then
|
||||
lower = testGenerationPhase then
|
||||
expectDeclarationsRemovingPhase then
|
||||
functionInliningPhase then
|
||||
lateinitLoweringPhase then
|
||||
tailrecLoweringPhase then
|
||||
|
||||
@@ -5,27 +5,18 @@
|
||||
|
||||
package org.jetbrains.kotlin.ir.backend.js.ir
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedTypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedVariableDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.Visibility
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrTypeParameterImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrVariableImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.*
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrTypeParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrVariableSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.*
|
||||
import org.jetbrains.kotlin.ir.types.IrType
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -149,6 +140,9 @@ object JsIrBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildAnonymousInitializer() =
|
||||
IrAnonymousInitializerImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, SYNTHESIZED_DECLARATION, IrAnonymousInitializerSymbolImpl(WrappedClassDescriptor()))
|
||||
|
||||
fun buildGetObjectValue(type: IrType, classSymbol: IrClassSymbol) =
|
||||
IrGetObjectValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, classSymbol)
|
||||
|
||||
@@ -257,6 +251,7 @@ object JsIrBuilder {
|
||||
fun buildBoolean(type: IrType, v: Boolean) = IrConstImpl.boolean(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
|
||||
fun buildInt(type: IrType, v: Int) = IrConstImpl.int(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, v)
|
||||
fun buildString(type: IrType, s: String) = IrConstImpl.string(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type, s)
|
||||
fun buildTry(type: IrType) = IrTryImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, type)
|
||||
fun buildCatch(ex: IrVariable, block: IrBlockImpl) = IrCatchImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, ex, block)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. 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.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.ir.isExpect
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrBlockBody
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.fqNameSafe
|
||||
import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
|
||||
class TestGenerator(val context: JsIrBackendContext) : FileLoweringPass {
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.declarations.forEach {
|
||||
if (it is IrClass) {
|
||||
generateTestCalls(it) { suiteForPackage(irFile.fqName).body }
|
||||
}
|
||||
|
||||
// TODO top-level functions
|
||||
}
|
||||
}
|
||||
|
||||
private val packageSuites = mutableMapOf<FqName, FunctionWithBody>()
|
||||
|
||||
private fun suiteForPackage(fqName: FqName) = packageSuites.getOrPut(fqName) {
|
||||
context.suiteFun!!.createInvocation(fqName.asString(), context.testContainer.body as IrBlockBody)
|
||||
}
|
||||
|
||||
private data class FunctionWithBody(val function: IrSimpleFunction, val body: IrBlockBody)
|
||||
|
||||
private fun IrSimpleFunctionSymbol.createInvocation(
|
||||
name: String,
|
||||
parentBody: IrBlockBody,
|
||||
ignored: Boolean = false
|
||||
): FunctionWithBody {
|
||||
val body = JsIrBuilder.buildBlockBody(emptyList())
|
||||
|
||||
val function = JsIrBuilder.buildFunction(
|
||||
"$name test fun",
|
||||
context.irBuiltIns.unitType,
|
||||
context.implicitDeclarationFile
|
||||
).also {
|
||||
it.body = body
|
||||
context.implicitDeclarationFile.declarations += it
|
||||
}
|
||||
|
||||
parentBody.statements += JsIrBuilder.buildCall(this).apply {
|
||||
putValueArgument(0, JsIrBuilder.buildString(context.irBuiltIns.stringType, name))
|
||||
putValueArgument(1, JsIrBuilder.buildBoolean(context.irBuiltIns.booleanType, ignored))
|
||||
|
||||
val refType = IrSimpleTypeImpl(context.functionN(0), false, emptyList(), emptyList())
|
||||
putValueArgument(2, JsIrBuilder.buildFunctionReference(refType, function.symbol))
|
||||
}
|
||||
|
||||
return FunctionWithBody(function, body)
|
||||
}
|
||||
|
||||
private fun generateTestCalls(irClass: IrClass, parentBody: () -> IrBlockBody) {
|
||||
if (irClass.modality == Modality.ABSTRACT || irClass.isEffectivelyExternal() || irClass.isExpect) return
|
||||
|
||||
val suiteFunBody by lazy { context.suiteFun!!.createInvocation(irClass.name.asString(), parentBody(), irClass.isIgnored) }
|
||||
|
||||
val beforeFunctions = irClass.declarations.filterIsInstance<IrSimpleFunction>().filter { it.isBefore }
|
||||
val afterFunctions = irClass.declarations.filterIsInstance<IrSimpleFunction>().filter { it.isAfter }
|
||||
|
||||
irClass.declarations.forEach {
|
||||
when {
|
||||
it is IrClass ->
|
||||
generateTestCalls(it) { suiteFunBody.body }
|
||||
|
||||
it is IrSimpleFunction && it.isTest ->
|
||||
generateCodeForTestMethod(it, beforeFunctions, afterFunctions, irClass, suiteFunBody.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateCodeForTestMethod(
|
||||
testFun: IrSimpleFunction,
|
||||
beforeFuns: List<IrSimpleFunction>,
|
||||
afterFuns: List<IrSimpleFunction>,
|
||||
irClass: IrClass,
|
||||
parentBody: IrBlockBody
|
||||
) {
|
||||
val (fn, body) = context.testFun!!.createInvocation(testFun.name.asString(), parentBody, testFun.isIgnored)
|
||||
|
||||
val classVal = JsIrBuilder.buildVar(irClass.defaultType, fn, initializer = irClass.instance())
|
||||
|
||||
body.statements += classVal
|
||||
|
||||
body.statements += beforeFuns.map {
|
||||
JsIrBuilder.buildCall(it.symbol).apply {
|
||||
dispatchReceiver = JsIrBuilder.buildGetValue(classVal.symbol)
|
||||
}
|
||||
}
|
||||
|
||||
val returnStatement = JsIrBuilder.buildReturn(
|
||||
fn.symbol,
|
||||
JsIrBuilder.buildCall(testFun.symbol).apply {
|
||||
dispatchReceiver = JsIrBuilder.buildGetValue(classVal.symbol)
|
||||
},
|
||||
context.irBuiltIns.unitType
|
||||
)
|
||||
|
||||
if (afterFuns.isEmpty()) {
|
||||
body.statements += returnStatement
|
||||
} else {
|
||||
body.statements += JsIrBuilder.buildTry(context.irBuiltIns.unitType).apply {
|
||||
tryResult = returnStatement
|
||||
finallyExpression = JsIrBuilder.buildComposite(context.irBuiltIns.unitType).apply {
|
||||
statements += afterFuns.map {
|
||||
JsIrBuilder.buildCall(it.symbol).apply {
|
||||
dispatchReceiver = JsIrBuilder.buildGetValue(classVal.symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrClass.instance(): IrExpression {
|
||||
return if (kind == ClassKind.OBJECT) {
|
||||
JsIrBuilder.buildGetObjectValue(defaultType, symbol)
|
||||
} else {
|
||||
declarations.asSequence().filterIsInstance<IrConstructor>().single { it.isPrimary }.let { constructor ->
|
||||
JsIrBuilder.buildCall(constructor.symbol).also {
|
||||
if (isInner) {
|
||||
it.dispatchReceiver = (parent as IrClass).instance()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val IrAnnotationContainer.isTest
|
||||
get() = hasAnnotation("kotlin.test.Test")
|
||||
|
||||
private val IrAnnotationContainer.isIgnored
|
||||
get() = hasAnnotation("kotlin.test.Ignore")
|
||||
|
||||
private val IrAnnotationContainer.isBefore
|
||||
get() = hasAnnotation("kotlin.test.BeforeTest")
|
||||
|
||||
private val IrAnnotationContainer.isAfter
|
||||
get() = hasAnnotation("kotlin.test.AfterTest")
|
||||
|
||||
private fun IrAnnotationContainer.hasAnnotation(fqName: String) =
|
||||
annotations.any { it.symbol.owner.parent.fqNameSafe == FqName(fqName) }
|
||||
}
|
||||
+1
-4
@@ -7,10 +7,7 @@ package org.jetbrains.kotlin.ir.backend.js.transformers.irToJs
|
||||
|
||||
import org.jetbrains.kotlin.ir.backend.js.utils.JsGenerationContext
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsDeclarationScope
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsEmpty
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsStatement
|
||||
import org.jetbrains.kotlin.js.backend.ast.JsVars
|
||||
import org.jetbrains.kotlin.js.backend.ast.*
|
||||
|
||||
class IrDeclarationToJsTransformer : BaseIrElementToJsNodeTransformer<JsStatement, JsGenerationContext> {
|
||||
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ class IrModuleToJsTransformer(private val backendContext: JsIrBackendContext) :
|
||||
statements += postDeclarationBlock
|
||||
statements += context.staticContext.initializerBlock
|
||||
|
||||
if (backendContext.hasTests) {
|
||||
statements += JsInvocation(context.getNameForSymbol(backendContext.testContainer.symbol).makeRef()).makeStmt()
|
||||
}
|
||||
|
||||
return statements
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user