JVM IR: prevent behavior change with operator dot calls on literals

#KT-42321 Fixed
This commit is contained in:
Alexander Udalov
2021-02-25 20:20:03 +01:00
parent c7498dd74d
commit bf844aa8e4
22 changed files with 920 additions and 50 deletions
@@ -5,7 +5,6 @@
package org.jetbrains.kotlin.ir.backend.js.utils
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.util.OperatorNameConventions
object OperatorNames {
@@ -21,12 +20,12 @@ object OperatorNames {
val AND = OperatorNameConventions.AND
val OR = OperatorNameConventions.OR
val XOR = Name.identifier("xor")
val INV = Name.identifier("inv")
val XOR = OperatorNameConventions.XOR
val INV = OperatorNameConventions.INV
val SHL = Name.identifier("shl")
val SHR = Name.identifier("shr")
val SHRU = Name.identifier("ushr")
val SHL = OperatorNameConventions.SHL
val SHR = OperatorNameConventions.SHR
val SHRU = OperatorNameConventions.USHR
val NOT = OperatorNameConventions.NOT
@@ -37,4 +36,4 @@ object OperatorNames {
val BINARY = setOf(ADD, SUB, MUL, DIV, MOD, REM, AND, OR, XOR, SHL, SHR, SHRU)
val UNARY = setOf(UNARY_PLUS, UNARY_MINUS, INV, NOT, INC, DEC)
val ALL = BINARY + UNARY
}
}
@@ -160,6 +160,9 @@ class JvmGeneratorExtensionsImpl(private val generateFacades: Boolean = true) :
)
}
override val shouldPreventDeprecatedIntegerValueTypeLiteralConversion: Boolean
get() = true
private val flexibleNullabilityAnnotationClass =
createSpecialAnnotationClass(JvmSymbols.FLEXIBLE_NULLABILITY_ANNOTATION_FQ_NAME, kotlinIrInternalPackage)
@@ -20,7 +20,6 @@ open class GeneratorExtensions : StubGeneratorExtensions() {
get() = SamConversion
open class SamConversion {
open fun isPlatformSamType(type: KotlinType): Boolean = false
open fun getSamTypeForValueParameter(valueParameter: ValueParameterDescriptor): KotlinType? = null
@@ -37,4 +36,7 @@ open class GeneratorExtensions : StubGeneratorExtensions() {
descriptor: ClassDescriptor,
context: GeneratorContext,
): IrDelegatingConstructorCall? = null
open val shouldPreventDeprecatedIntegerValueTypeLiteralConversion: Boolean
get() = false
}
@@ -24,10 +24,8 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrVariable
import org.jetbrains.kotlin.ir.PsiIrFileEntry
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.descriptors.IrBasedDeclarationDescriptor
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.*
@@ -35,33 +33,40 @@ import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.impl.originalKotlinType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.types.toKotlinType
import org.jetbrains.kotlin.ir.util.SymbolTable
import org.jetbrains.kotlin.ir.util.TypeTranslator
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtBinaryExpression
import org.jetbrains.kotlin.psi2ir.containsNull
import org.jetbrains.kotlin.psi2ir.findSingleFunction
import org.jetbrains.kotlin.psi2ir.generators.GeneratorContext
import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions
import org.jetbrains.kotlin.psi2ir.generators.OPERATORS_DESUGARED_TO_CALLS
import org.jetbrains.kotlin.psi2ir.generators.getSubstitutedFunctionTypeForSamType
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.typeUtil.*
import org.jetbrains.kotlin.util.OperatorNameConventions
fun insertImplicitCasts(element: IrElement, context: GeneratorContext) {
fun insertImplicitCasts(file: IrFile, context: GeneratorContext) {
InsertImplicitCasts(
context.builtIns,
context.irBuiltIns,
context.typeTranslator,
context.callToSubstitutedDescriptorMap,
context.extensions,
context.symbolTable
).run(element)
context.symbolTable,
file,
).run(file)
}
internal class InsertImplicitCasts(
@@ -70,7 +75,8 @@ internal class InsertImplicitCasts(
private val typeTranslator: TypeTranslator,
private val callToSubstitutedDescriptorMap: Map<IrDeclarationReference, CallableDescriptor>,
private val generatorExtensions: GeneratorExtensions,
private val symbolTable: SymbolTable
private val symbolTable: SymbolTable,
private val file: IrFile,
) : IrElementTransformerVoid() {
private val expectedFunctionExpressionReturnType = hashMapOf<FunctionDescriptor, IrType>()
@@ -402,6 +408,11 @@ internal class InsertImplicitCasts(
private fun IrExpression.coerceIntToAnotherIntegerType(targetType: KotlinType): IrExpression {
if (!type.originalKotlinType!!.isInt()) throw AssertionError("Expression of type 'kotlin.Int' expected: $this")
if (targetType.isInt()) return this
if (generatorExtensions.shouldPreventDeprecatedIntegerValueTypeLiteralConversion &&
this is IrCall && preventDeprecatedIntegerValueTypeLiteralConversion()
) return this
return if (this is IrConst<*>) {
val value = this.value as Int
val irType = targetType.toIrType()
@@ -429,6 +440,29 @@ internal class InsertImplicitCasts(
}
}
// In JVM, we don't convert values resulted from calling built-in operators on integer literals to another integer type.
// The reason is that doing so would change behavior, which we want to avoid, see KT-42321.
// At the same time, such structure seems possible to achieve only via the magical integer value type, but inferring the result of
// the operator call based on an expected type is deprecated behavior which is going to be removed in the future, see KT-38895.
private fun IrCall.preventDeprecatedIntegerValueTypeLiteralConversion(): Boolean {
val descriptor = symbol.descriptor
if (descriptor.name !in operatorsWithDeprecatedIntegerValueTypeLiteralConversion) return false
// This bug is only reproducible for non-operator calls, for example "1.plus(2)", NOT "1 + 2".
if (origin in OPERATORS_DESUGARED_TO_CALLS) return false
// For infix methods, this bug is only reproducible for non-infix calls, for example "1.shl(2)", NOT "1 shl 2".
if (descriptor.isInfix) {
if ((file.fileEntry as? PsiIrFileEntry)?.findPsiElement(this) is KtBinaryExpression) return false
}
return descriptor.dispatchReceiverParameter?.type?.let { KotlinBuiltIns.isPrimitiveType(it) } == true
}
private val operatorsWithDeprecatedIntegerValueTypeLiteralConversion = with(OperatorNameConventions) {
setOf(PLUS, MINUS, TIMES, DIV, REM, UNARY_PLUS, UNARY_MINUS, SHL, SHR, USHR, AND, OR, XOR, INV)
}
private fun IrExpression.invokeIntegerCoercionFunction(targetType: KotlinType, coercionFunName: String): IrExpression {
val coercionFunction = builtIns.int.unsubstitutedMemberScope.findSingleFunction(Name.identifier(coercionFunName))
return IrCallImpl(