[JS IR] Explicitly convert Long values to String in string concatenation
#KT-39891 Fixed
This commit is contained in:
@@ -161,6 +161,12 @@ private val expectDeclarationsRemovingPhase = makeDeclarationTransformerPhase(
|
||||
description = "Remove expect declaration from module fragment"
|
||||
)
|
||||
|
||||
private val stringConcatenationLoweringPhase = makeJsModulePhase(
|
||||
::JsStringConcatenationLowering,
|
||||
name = "JsStringConcatenationLowering",
|
||||
description = "Call toString() for values of some types when concatenating strings"
|
||||
).toModuleLowering()
|
||||
|
||||
private val lateinitNullableFieldsPhase = makeDeclarationTransformerPhase(
|
||||
::NullableFieldsForLateinitCreationLowering,
|
||||
name = "LateinitNullableFields",
|
||||
@@ -820,6 +826,7 @@ private val loweringList = listOf<Lowering>(
|
||||
copyInlineFunctionBodyLoweringPhase,
|
||||
removeInlineDeclarationsWithReifiedTypeParametersLoweringPhase,
|
||||
createScriptFunctionsPhase,
|
||||
stringConcatenationLoweringPhase,
|
||||
callableReferenceLowering,
|
||||
singleAbstractMethodPhase,
|
||||
tailrecLoweringPhase,
|
||||
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2010-2021 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.CommonBackendContext
|
||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||
|
||||
class JsStringConcatenationLowering(val context: CommonBackendContext) : FileLoweringPass {
|
||||
override fun lower(irFile: IrFile) {
|
||||
irFile.transformChildrenVoid(JsStringConcatenationTransformer(context))
|
||||
}
|
||||
}
|
||||
|
||||
private class JsStringConcatenationTransformer(val context: CommonBackendContext) : IrElementTransformerVoid() {
|
||||
|
||||
private val IrType.shouldExplicitlyConvertToString: Boolean
|
||||
get() {
|
||||
// If the type is Long or a supertype of Long, we want to call toString() on values of that type.
|
||||
// See KT-39891
|
||||
if (this !is IrSimpleType) return false
|
||||
return when (classifier.signature) {
|
||||
IdSignatureValues.any, IdSignatureValues.comparable, IdSignatureValues.number, IdSignatureValues._long -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrExpression.explicitlyConvertedToString(): IrExpression {
|
||||
assert(type.shouldExplicitlyConvertToString)
|
||||
|
||||
return if (type.isNullable()) {
|
||||
JsIrBuilder.buildCall(context.ir.symbols.extensionToString).apply {
|
||||
extensionReceiver = this@explicitlyConvertedToString
|
||||
}
|
||||
} else {
|
||||
JsIrBuilder.buildCall(context.ir.symbols.memberToString).apply {
|
||||
dispatchReceiver = this@explicitlyConvertedToString
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val IrFunctionSymbol.isStringPlus: Boolean
|
||||
get() = context.ir.symbols.isStringPlus(this)
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
fun explicitlyConvertToStringIfNeeded(): IrExpression {
|
||||
val lastArgIndex = expression.valueArgumentsCount - 1
|
||||
val plusArg = expression.getValueArgument(lastArgIndex) ?: return super.visitCall(expression)
|
||||
if (!plusArg.type.shouldExplicitlyConvertToString)
|
||||
return super.visitCall(expression)
|
||||
|
||||
expression.putValueArgument(lastArgIndex, plusArg.explicitlyConvertedToString())
|
||||
return expression
|
||||
}
|
||||
|
||||
if (expression.valueArgumentsCount == 0)
|
||||
return super.visitCall(expression)
|
||||
|
||||
if (expression.symbol.isStringPlus)
|
||||
return explicitlyConvertToStringIfNeeded()
|
||||
|
||||
if (expression.dispatchReceiver.safeAs<IrFunctionReference>()?.symbol?.isStringPlus == true)
|
||||
return explicitlyConvertToStringIfNeeded()
|
||||
|
||||
return super.visitCall(expression)
|
||||
}
|
||||
|
||||
override fun visitStringConcatenation(expression: IrStringConcatenation): IrExpression {
|
||||
expression
|
||||
.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||
override fun visitExpression(expression: IrExpression): IrExpression {
|
||||
if (expression.type.shouldExplicitlyConvertToString)
|
||||
return expression.explicitlyConvertedToString()
|
||||
return expression
|
||||
}
|
||||
})
|
||||
return super.visitStringConcatenation(expression)
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -1,20 +1,26 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS_IR_ES6
|
||||
val MAX_LONG = "9223372036854775807"
|
||||
val PREFIX = "max = "
|
||||
|
||||
fun box(): String {
|
||||
if (MAX_LONG != "${Long.MAX_VALUE}") return "fail template"
|
||||
if (MAX_LONG != "" + Long.MAX_VALUE) return "fail \"\" +"
|
||||
if (MAX_LONG != ("" as String?) + Long.MAX_VALUE) return "fail \"\"? +"
|
||||
if (MAX_LONG != "".plus(Long.MAX_VALUE)) return "fail \"\".plus"
|
||||
if (MAX_LONG != ("" as String?).plus(Long.MAX_VALUE)) return "fail \"\"?.plus"
|
||||
if (MAX_LONG != (String::plus)("", Long.MAX_VALUE)) return "fail String::plus"
|
||||
if (MAX_LONG != (String?::plus)("", Long.MAX_VALUE)) return "fail String?::plus"
|
||||
if (MAX_LONG != (""::plus)(Long.MAX_VALUE)) return "fail \"\"::plus"
|
||||
if (MAX_LONG != (("" as String?)::plus)(Long.MAX_VALUE)) return "fail \"\"?::plus"
|
||||
|
||||
if (PREFIX + MAX_LONG != "max = ${Long.MAX_VALUE}") return "fail template with prefix"
|
||||
if (PREFIX + MAX_LONG != PREFIX + Long.MAX_VALUE) return "fail \"$PREFIX\" +"
|
||||
if (PREFIX + MAX_LONG != (PREFIX as String?) + Long.MAX_VALUE) return "fail \"$PREFIX\"? +"
|
||||
if (PREFIX + MAX_LONG != PREFIX.plus(Long.MAX_VALUE)) return "fail \"$PREFIX\".plus"
|
||||
if (PREFIX + MAX_LONG != (PREFIX as String?).plus(Long.MAX_VALUE)) return "fail \"$PREFIX\"?.plus"
|
||||
if (PREFIX + MAX_LONG != (String::plus)(PREFIX, Long.MAX_VALUE)) return "fail String::plus($PREFIX, ...)"
|
||||
if (PREFIX + MAX_LONG != (String?::plus)(PREFIX, Long.MAX_VALUE)) return "fail String?::plus($PREFIX, ...)"
|
||||
if (PREFIX + MAX_LONG != (PREFIX::plus)(Long.MAX_VALUE)) return "fail \"$PREFIX\"::plus"
|
||||
if (PREFIX + MAX_LONG != ((PREFIX as String?)::plus)(Long.MAX_VALUE)) return "fail \"$PREFIX\"?::plus"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
|
||||
+7
-5
@@ -1,5 +1,3 @@
|
||||
// IGNORE_BACKEND: JS_IR
|
||||
// IGNORE_BACKEND: JS_IR_ES6
|
||||
// KJS_WITH_FULL_RUNTIME
|
||||
// EXPECTED_REACHABLE_NODES: 1378
|
||||
package foo
|
||||
@@ -11,9 +9,13 @@ fun box(): String {
|
||||
if (" ${x.toLong() as Number}" != " $x") return "FAIL 3"
|
||||
if (" ${x.toLong() as Any}" != " $x") return "FAIL 4"
|
||||
if (" ${x.toLong() as Long?}" != " $x") return "FAIL 5"
|
||||
if (" ${x.toLong() as Comparable<Long>?}" != " $x") return "FAIL 6"
|
||||
if (" ${x.toLong() as Number?}" != " $x") return "FAIL 7"
|
||||
if (" ${x.toLong() as Any?}" != " $x") return "FAIL 8"
|
||||
if (" ${null as Long?}" != " null") return "FAIL 6"
|
||||
if (" ${x.toLong() as Comparable<Long>?}" != " $x") return "FAIL 7"
|
||||
if (" ${null as Comparable<Long>?}" != " null") return "FAIL 8"
|
||||
if (" ${x.toLong() as Number?}" != " $x") return "FAIL 9"
|
||||
if (" ${null as Number?}" != " null") return "FAIL 10"
|
||||
if (" ${x.toLong() as Any?}" != " $x") return "FAIL 11"
|
||||
if (" ${null as Any?}" != " null") return "FAIL 12"
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user