JS: support unsigned primitive vararg's

This commit is contained in:
Anton Bannykh
2018-08-17 13:21:01 +03:00
committed by Ilya Gorbunov
parent 944c5b6044
commit 934e11aa60
9 changed files with 255 additions and 56 deletions
@@ -60,7 +60,7 @@ abstract class FunctionsFromAnyGenerator(protected val declaration: KtClassOrObj
generateEqualsMethod(function, properties)
}
private val primaryConstructorProperties: List<PropertyDescriptor>
protected val primaryConstructorProperties: List<PropertyDescriptor>
get() = primaryConstructorParameters
.filter { it.hasValOrVar() }
.map { bindingContext.get(BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, it)!! }
@@ -1,7 +1,6 @@
// WITH_UNSIGNED
// IGNORE_BACKEND: JVM_IR
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: JS
fun box(): String {
var sum = 0u
@@ -1,6 +1,6 @@
// IGNORE_BACKEND: JVM_IR
// WITH_UNSIGNED
// IGNORE_BACKEND: JS, JS_IR
// IGNORE_BACKEND: JS_IR
fun uint(vararg us: UInt): UIntArray = us
@@ -2495,6 +2495,11 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
runTest("js/js.translator/testData/box/expression/function/vararg.kt");
}
@TestMetadata("varargUInt.kt")
public void testVarargUInt() throws Exception {
runTest("js/js.translator/testData/box/expression/function/varargUInt.kt");
}
@TestMetadata("whenFunction.kt")
public void testWhenFunction() throws Exception {
runTest("js/js.translator/testData/box/expression/function/whenFunction.kt");
@@ -2495,6 +2495,11 @@ public class IrBoxJsTestGenerated extends AbstractIrBoxJsTest {
runTest("js/js.translator/testData/box/expression/function/vararg.kt");
}
@TestMetadata("varargUInt.kt")
public void testVarargUInt() throws Exception {
runTest("js/js.translator/testData/box/expression/function/varargUInt.kt");
}
@TestMetadata("whenFunction.kt")
public void testWhenFunction() throws Exception {
runTest("js/js.translator/testData/box/expression/function/whenFunction.kt");
@@ -18,13 +18,18 @@ package org.jetbrains.kotlin.js.translate.declaration;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.CodegenUtil;
import org.jetbrains.kotlin.backend.common.DataClassMethodGenerator;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.FunctionDescriptorImpl;
import org.jetbrains.kotlin.descriptors.impl.SimpleFunctionDescriptorImpl;
import org.jetbrains.kotlin.js.backend.ast.*;
import org.jetbrains.kotlin.js.translate.context.Namer;
import org.jetbrains.kotlin.js.translate.context.TranslationContext;
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils;
import org.jetbrains.kotlin.js.translate.utils.UtilsKt;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtClassOrObject;
import org.jetbrains.kotlin.psi.KtParameter;
import org.jetbrains.kotlin.resolve.BindingContext;
@@ -49,6 +54,15 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
this.isInline = isInline;
}
@Override
public void generate() {
if (isInline) {
generateUnboxFunction();
}
super.generate();
}
@Override
public void generateComponentFunction(@NotNull FunctionDescriptor function, @NotNull ValueParameterDescriptor parameter) {
if (isInline) return;
@@ -213,6 +227,18 @@ class JsDataClassGenerator extends DataClassMethodGenerator {
functionObj.getBody().getStatements().add(returnStatement);
}
private void generateUnboxFunction() {
PropertyDescriptor boxee = getPrimaryConstructorProperties().get(0);
JsFunction unboxFunction = context.createRootScopedFunction("unbox");
JsExpression prototypeRef = JsAstUtils.prototypeOf(context.getInnerReference(getClassDescriptor()));
JsExpression functionRef = new JsNameRef("unbox", prototypeRef);
unboxFunction.getBody().getStatements().add(new JsReturn(JsAstUtils.pureFqn(context.getNameForDescriptor(boxee), new JsThisRef())));
context.addDeclarationStatement(JsAstUtils.assignment(functionRef, unboxFunction).makeStmt());
}
private JsFunction generateJsMethod(@NotNull FunctionDescriptor functionDescriptor) {
JsFunction functionObject = context.createRootScopedFunction(functionDescriptor);
functionObject.setSource(getDeclaration());
@@ -40,6 +40,8 @@ import org.jetbrains.kotlin.js.translate.intrinsic.functions.basic.FunctionIntri
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.inline.InlineStrategy
import org.jetbrains.kotlin.resolve.isInlineClassType
import org.jetbrains.kotlin.types.KotlinType
import java.util.*
object ArrayFIF : CompositeFIF() {
@@ -64,14 +66,38 @@ object ArrayFIF : CompositeFIF() {
@JvmStatic
fun typedArraysEnabled(config: JsConfig) = config.configuration.get(JSConfigurationKeys.TYPED_ARRAYS_ENABLED, true)
fun castOrCreatePrimitiveArray(ctx: TranslationContext, type: PrimitiveType?, arg: JsArrayLiteral): JsExpression {
if (type == null || !typedArraysEnabled(ctx.config)) return arg
fun unsignedPrimitiveToSigned(type: KotlinType): PrimitiveType? {
// short-circuit
if (!type.isInlineClassType() || type.isMarkedNullable) return null
return if (type in TYPED_ARRAY_MAP) {
createTypedArray(type, arg)
return when {
KotlinBuiltIns.isUByte(type) -> BYTE
KotlinBuiltIns.isUShort(type) -> SHORT
KotlinBuiltIns.isUInt(type) -> INT
KotlinBuiltIns.isULong(type) -> LONG
else -> null
}
}
fun castOrCreatePrimitiveArray(ctx: TranslationContext, type: KotlinType, arg: JsArrayLiteral): JsExpression {
if (type.isMarkedNullable) return arg
val unsignedPrimitiveType = unsignedPrimitiveToSigned(type)
if (unsignedPrimitiveType != null) {
val conversionFunction = "to${unsignedPrimitiveType.typeName}"
arg.expressions.replaceAll { JsInvocation(JsNameRef(conversionFunction, it)) }
}
val primitiveType = unsignedPrimitiveType ?: KotlinBuiltIns.getPrimitiveType(type)?.takeUnless { type.isMarkedNullable}
if (primitiveType == null || !typedArraysEnabled(ctx.config)) return arg
return if (primitiveType in TYPED_ARRAY_MAP) {
createTypedArray(primitiveType, arg)
}
else {
JsAstUtils.invokeKotlinFunction(type.lowerCaseName + "ArrayOf", *arg.expressions.toTypedArray())
JsAstUtils.invokeKotlinFunction(primitiveType.lowerCaseName + "ArrayOf", *arg.expressions.toTypedArray())
}
}
@@ -5,12 +5,13 @@
package org.jetbrains.kotlin.js.translate.reference
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.builtins.getFunctionalClassKind
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
@@ -25,6 +26,8 @@ import org.jetbrains.kotlin.js.translate.utils.AnnotationsUtils
import org.jetbrains.kotlin.js.translate.utils.JsAstUtils
import org.jetbrains.kotlin.js.translate.utils.TranslationUtils
import org.jetbrains.kotlin.js.translate.utils.getReferenceToJsClass
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.components.isVararg
@@ -33,6 +36,7 @@ import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedValueArgument
import org.jetbrains.kotlin.resolve.calls.model.VarargValueArgument
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.isPrimitiveNumberType
import java.util.*
class CallArgumentTranslator private constructor(
@@ -77,7 +81,7 @@ class CallArgumentTranslator private constructor(
var argsBeforeVararg: List<JsExpression>? = null
var concatArguments: MutableList<JsExpression>? = null
val argsToJsExpr = translateUnresolvedArguments(context(), resolvedCall)
var varargPrimitiveType: PrimitiveType? = null
var varargElementType: KotlinType? = null
for (parameterDescriptor in valueParameters) {
val actualArgument = valueArgumentsByIndex[parameterDescriptor.index]
@@ -90,8 +94,7 @@ class CallArgumentTranslator private constructor(
hasSpreadOperator = arguments.any { it.getSpreadElement() != null }
}
val varargElementType = parameterDescriptor.original.varargElementType!!
varargPrimitiveType = KotlinBuiltIns.getPrimitiveType(varargElementType).takeUnless { varargElementType.isMarkedNullable }
varargElementType = parameterDescriptor.original.varargElementType!!
if (hasSpreadOperator) {
if (isNativeFunctionCall) {
@@ -102,10 +105,10 @@ class CallArgumentTranslator private constructor(
null)
}
else {
result.addAll(translateVarargArgument(actualArgument,
argsToJsExpr,
actualArgument.arguments.size > 1,
varargPrimitiveType))
translateVarargArgument(actualArgument,
argsToJsExpr,
actualArgument.arguments.size > 1,
varargElementType)?.let { result.add(it) }
}
}
else {
@@ -113,7 +116,7 @@ class CallArgumentTranslator private constructor(
result.addAll(translateResolvedArgument(actualArgument, argsToJsExpr))
}
else {
result.addAll(translateVarargArgument(actualArgument, argsToJsExpr, true, varargPrimitiveType))
translateVarargArgument(actualArgument, argsToJsExpr, true, varargElementType)?.let { result.add(it) }
}
}
}
@@ -134,7 +137,7 @@ class CallArgumentTranslator private constructor(
concatArguments!!.add(0, toArray(null, argsBeforeVararg))
}
result = mutableListOf(concatArgumentsIfNeeded(concatArguments!!, varargPrimitiveType, true))
result = mutableListOf(concatArgumentsIfNeeded(concatArguments!!, varargElementType, true))
if (receiver != null) {
cachedReceiver = context().getOrDeclareTemporaryConstVariable(receiver)
@@ -207,44 +210,64 @@ class CallArgumentTranslator private constructor(
return result
}
// Cache UTypeArray descriptor lookup
private val typeToUTypeArray = mutableMapOf<PrimitiveType, ClassDescriptor>()
private fun JsExpression.wrapInUArray(elementType: KotlinType): JsExpression {
return ArrayFIF.unsignedPrimitiveToSigned(elementType)?.let { primitiveType ->
val kotlinMemberScope = context.currentModule.getPackage(FqNameUnsafe("kotlin").toSafe()).memberScope
val classDescriptor = typeToUTypeArray.computeIfAbsent(primitiveType) {
val className = Name.identifier("U${primitiveType.typeName}Array")
kotlinMemberScope.getContributedClassifier(className, NoLookupLocation.FROM_BACKEND) as ClassDescriptor
}
JsNew(ReferenceTranslator.translateAsTypeReference(classDescriptor, context), listOf(this))
} ?: this
}
private fun translateVarargArgument(
resolvedArgument: ResolvedValueArgument,
translatedArgs: Map<ValueArgument, JsExpression>,
shouldWrapVarargInArray: Boolean,
varargPrimitiveType: PrimitiveType?
): List<JsExpression> {
resolvedArgument: ResolvedValueArgument,
translatedArgs: Map<ValueArgument, JsExpression>,
shouldWrapVarargInArray: Boolean,
varargElementType: KotlinType
): JsExpression? {
val arguments = resolvedArgument.arguments
if (arguments.isEmpty()) {
return if (shouldWrapVarargInArray) {
return listOf(toArray(varargPrimitiveType, listOf()))
}
else {
listOf()
return toArray(varargElementType, listOf()).wrapInUArray(varargElementType)
} else {
null
}
}
val list = translateResolvedArgument(resolvedArgument, translatedArgs)
return if (shouldWrapVarargInArray) {
val concatArguments = prepareConcatArguments(arguments, list, varargPrimitiveType)
val concatExpression = concatArgumentsIfNeeded(concatArguments, varargPrimitiveType, false)
listOf(concatExpression)
}
else {
listOf(JsAstUtils.invokeMethod(list[0], "slice"))
}
val concatArguments = prepareConcatArguments(arguments, list, varargElementType)
val concatExpression = concatArgumentsIfNeeded(concatArguments, varargElementType, false)
concatExpression
} else {
val arg = ArrayFIF.unsignedPrimitiveToSigned(varargElementType)?.let {type ->
JsInvocation(JsNameRef("unbox", list[0]))
} ?: list[0]
JsAstUtils.invokeMethod(arg, "slice")
}.wrapInUArray(varargElementType)
}
private fun toArray(varargPrimitiveType: PrimitiveType?, elements: List<JsExpression>): JsExpression {
return ArrayFIF.castOrCreatePrimitiveArray(context(),
varargPrimitiveType,
JsArrayLiteral(elements).apply { sideEffects = SideEffectKind.PURE })
private fun toArray(varargElementType: KotlinType?, elements: List<JsExpression>): JsExpression {
val argument = JsArrayLiteral(elements).apply { sideEffects = SideEffectKind.PURE }
if (varargElementType == null) return argument
return ArrayFIF.castOrCreatePrimitiveArray(
context(),
varargElementType,
argument)
}
private fun prepareConcatArguments(
arguments: List<ValueArgument>,
list: List<JsExpression>,
varargPrimitiveType: PrimitiveType?
arguments: List<ValueArgument>,
list: List<JsExpression>,
varargElementType: KotlinType?
): MutableList<JsExpression> {
assert(arguments.isNotEmpty()) { "arguments.size should not be 0" }
assert(arguments.size == list.size) { "arguments.size: " + arguments.size + " != list.size: " + list.size }
@@ -259,17 +282,19 @@ class CallArgumentTranslator private constructor(
if (valueArgument.getSpreadElement() != null) {
if (lastArrayContent.size > 0) {
concatArguments.add(toArray(varargPrimitiveType, lastArrayContent))
concatArguments.add(toArray(varargElementType, lastArrayContent))
lastArrayContent = mutableListOf()
}
concatArguments.add(expressionArgument)
}
else {
val e = if (varargElementType != null && ArrayFIF.unsignedPrimitiveToSigned(varargElementType) != null) {
JsInvocation(JsNameRef("unbox", expressionArgument))
} else expressionArgument
concatArguments.add(e)
} else {
lastArrayContent.add(expressionArgument)
}
}
if (lastArrayContent.size > 0) {
concatArguments.add(toArray(varargPrimitiveType, lastArrayContent))
concatArguments.add(toArray(varargElementType, lastArrayContent))
}
return concatArguments
@@ -306,23 +331,23 @@ class CallArgumentTranslator private constructor(
}
private fun concatArgumentsIfNeeded(
concatArguments: List<JsExpression>,
varargPrimitiveType: PrimitiveType?,
isMixed: Boolean
concatArguments: List<JsExpression>,
varargElementType: KotlinType?,
isMixed: Boolean
): JsExpression {
assert(concatArguments.isNotEmpty()) { "concatArguments.size should not be 0" }
return if (concatArguments.size > 1) {
if (varargPrimitiveType != null) {
if (varargElementType != null && (varargElementType.isPrimitiveNumberType() || ArrayFIF.unsignedPrimitiveToSigned(varargElementType) != null)) {
val method = if (isMixed) "arrayConcat" else "primitiveArrayConcat"
JsAstUtils.invokeKotlinFunction(method, concatArguments[0],
*concatArguments.subList(1, concatArguments.size).toTypedArray())
}
else {
JsAstUtils.invokeKotlinFunction(
method, concatArguments[0],
*concatArguments.subList(1, concatArguments.size).toTypedArray()
)
} else {
JsInvocation(JsNameRef("concat", concatArguments[0]), concatArguments.subList(1, concatArguments.size))
}
}
else {
} else {
concatArguments[0]
}
}
@@ -0,0 +1,113 @@
// LANGUAGE_VERSION: 1.3
// IGNORE_BACKEND: JS_IR
// EXPECTED_REACHABLE_NODES: 1566
package foo
fun testSize(expectedSize: Int, vararg i: UInt): Boolean {
return (i.size == expectedSize)
}
fun testSum(expectedSum: UInt, vararg i: UInt): Boolean {
var sum = 0u
for (j in i) {
sum += j
}
return (expectedSum == sum)
}
fun testSpreadOperator(vararg args: UInt): Boolean {
var sum = 0u
for (a in args) sum += a
return testSize(args.size, *args) && testSum(sum, *args)
}
class Bar(val size: Int, val sum: UInt) {
fun test(vararg args: UInt) = testSize(size, *args) && testSum(sum, *args)
}
object obj {
fun test(size: Int, sum: UInt, vararg args: UInt) = testSize(size, *args) && testSum(sum, *args)
}
fun spreadInMethodCall(size: Int, sum: UInt, vararg args: UInt) = Bar(size, sum).test(*args)
fun spreadInObjectMethodCall(size: Int, sum: UInt, vararg args: UInt) = obj.test(size, sum, *args)
fun testVarargWithFunLit(vararg args: UInt, f: (a: UIntArray) -> Boolean): Boolean = f(args)
fun <T> idVarArgs(vararg a: T) = a
fun <T> idArrayVarArg(vararg a: Array<T>) = a
fun sumFunValuesOnParameters(x: UInt, y: UInt, vararg a: UInt, f: (UInt) -> UInt): UInt {
var result = f(x) + f(y)
for(u in a) {
result += f(u)
}
return result
}
fun box(): String {
if (!testSize(0))
return "wrong vararg size when call function without args"
if (!testSum(0u))
return "wrong vararg sum (arguments) when call function without args"
if (!testSize(6, 1u, 1u, 1u, 2u, 3u, 4u))
return "wrong vararg size when call function with some args (1)"
if (!testSum(30, 10u, 20u, 0u))
return "wrong vararg sum (arguments) when call function with some args (1)"
if (!testSpreadOperator(30u, 10u, 20u, 0u))
return "failed when call function using spread operator"
if (!Bar(3, 30u).test(10u, 20u, 0u))
return "failed when call method"
if (!spreadInMethodCall(2, 3u, 1u, 2u))
return "failed when call method using spread operator"
if (!obj.test(5, 15u, 1u, 2u, 3u, 4u, 5u))
return "failed when call method of object"
if (!spreadInObjectMethodCall(2, 3u, 1u, 2u))
return "failed when call method of object using spread operator"
if (!testVarargWithFunLit(1u, 2u, 3u) { args -> args.size == 3 })
return "failed when call function with vararg and fun literal"
val a = arrayOf(1u, 2u, 3u)
val b = arrayOf(4u, 5u)
assertEquals(5, arrayOf(*a, *b).size)
assertEquals(8, arrayOf(10u, *a, 20u, *b, 30u).size)
assertEquals(5, idVarArgs(*a, *b).size)
assertEquals(8, idVarArgs(10u, *a, 20u, *b, 30u).size)
assertEquals(9, arrayOf(1u, *a, *a, 1u, 2u).size)
assertEquals(9, idVarArgs(1u, *a, *a, 1u, 2u).size)
assertEquals(9, arrayOf(1u, *a, *arrayOf(1u, 2u, 3u), 1u, 2u).size)
assertEquals(9, idVarArgs(1u, *a, *arrayOf(1u, 2u, 3u), 1u, 2u).size)
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 9u) { 2u*it })
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, *uintArrayOf(3u, 4u, 5u, 6u, 7u, 8u, 9u)) { 2u*it })
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, 3u, 4u, *uintArrayOf(5u, 6u, 7u, 8u, 9u)) { 2u*it })
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, *uintArrayOf(3u, 4u, 5u, 6u, 7u), 8u, 9u) { 2u*it })
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, *uintArrayOf(3u, 4u, 5u), *uintArrayOf(6u, 7u, 8u, 9u)) { 2u*it })
assertEquals(90u, sumFunValuesOnParameters(1u, 2u, *uintArrayOf(3u, 4u), 5u, 6u, *uintArrayOf(7u, 8u, 9u)) { 2u*it })
assertEquals(2, idArrayVarArg(arrayOf(1u), *arrayOf(arrayOf(2u, 3u, 4u))).size)
assertEquals(3, idArrayVarArg(arrayOf(1u, 2u), *arrayOf(arrayOf(3u, 4u), arrayOf(5u, 6u))).size)
assertEquals(6, idArrayVarArg(arrayOf(1u, 2u), *arrayOf(arrayOf(3u, 4u), arrayOf(5u, 6u)), arrayOf(7u), *arrayOf(arrayOf(8u, 9u), arrayOf(10u, 11u))).size)
val c = arrayOf(*a)
assertFalse(a === c, "Spread operator should copy its argument")
return "OK"
}