diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 01576fbd30a..ab249ed80eb 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -24,6 +24,7 @@ import com.intellij.psi.tree.IElementType; import com.intellij.util.ArrayUtil; import com.intellij.util.Function; import com.intellij.util.containers.Stack; +import kotlin.Pair; import kotlin.collections.CollectionsKt; import kotlin.Unit; import kotlin.jvm.functions.Function1; @@ -2450,8 +2451,8 @@ public class ExpressionCodegen extends KtVisitor impleme KotlinType type = TypeUtils.uncaptureTypeForInlineMapping(entry.getValue()); - TypeParameterDescriptor parameterDescriptor = TypeUtils.getTypeParameterDescriptorOrNull(type); - if (parameterDescriptor == null) { + Pair typeParameterAndReificationArgument = extractReificationArgument(type); + if (typeParameterAndReificationArgument == null) { // type is not generic BothSignatureWriter signatureWriter = new BothSignatureWriter(BothSignatureWriter.Mode.TYPE); Type asmType = typeMapper.mapTypeParameter(type, signatureWriter); @@ -2464,9 +2465,9 @@ public class ExpressionCodegen extends KtVisitor impleme key.isReified()); } else { - mappings.addParameterMappingToNewParameter( + mappings.addParameterMappingForFurtherReification( key.getName().getIdentifier(), type, - parameterDescriptor.getName().getIdentifier(), key.isReified()); + typeParameterAndReificationArgument.getSecond(), key.isReified()); } } return getOrCreateCallGenerator( @@ -2474,6 +2475,24 @@ public class ExpressionCodegen extends KtVisitor impleme ); } + + @Nullable + private static Pair extractReificationArgument(@NotNull KotlinType type) { + int arrayDepth = 0; + boolean isNullable = type.isMarkedNullable(); + while (KotlinBuiltIns.isArray(type)) { + arrayDepth++; + type = type.getArguments().get(0).getType(); + } + + TypeParameterDescriptor parameterDescriptor = TypeUtils.getTypeParameterDescriptorOrNull(type); + if (parameterDescriptor == null) return null; + + return new Pair( + parameterDescriptor, + new ReificationArgument(parameterDescriptor.getName().asString(), isNullable, arrayDepth)); + } + @NotNull public StackValue generateReceiverValue(@Nullable ReceiverValue receiverValue, boolean isSuper) { if (receiverValue instanceof ImplicitClassReceiver) { @@ -3746,15 +3765,15 @@ The "returned" value of try expression with no finally is either the last expres public void putReifiedOperationMarkerIfTypeIsReifiedParameter( @NotNull KotlinType type, @NotNull ReifiedTypeInliner.OperationKind operationKind ) { - TypeParameterDescriptor typeParameterDescriptor = TypeUtils.getTypeParameterDescriptorOrNull(type); - if (typeParameterDescriptor != null && typeParameterDescriptor.isReified()) { + Pair typeParameterAndReificationArgument = extractReificationArgument(type); + if (typeParameterAndReificationArgument != null && typeParameterAndReificationArgument.getFirst().isReified()) { + TypeParameterDescriptor typeParameterDescriptor = typeParameterAndReificationArgument.getFirst(); if (typeParameterDescriptor.getContainingDeclaration() != context.getContextDescriptor()) { parentCodegen.getReifiedTypeParametersUsages(). addUsedReifiedParameter(typeParameterDescriptor.getName().asString()); } v.iconst(operationKind.getId()); - boolean putNullableFlag = operationKind.isTypeNullabilityAware() && type.isMarkedNullable(); - v.visitLdcInsn(typeParameterDescriptor.getName().asString() + (putNullableFlag ? "?" : "")); + v.visitLdcInsn(typeParameterAndReificationArgument.getSecond().asString()); v.invokestatic( IntrinsicMethods.INTRINSICS_CLASS_NAME, ReifiedTypeInliner.REIFIED_OPERATION_MARKER_METHOD_NAME, Type.getMethodDescriptor(Type.VOID_TYPE, Type.INT_TYPE, Type.getType(String.class)), false diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt index 4fabc7701ca..6734cc0359a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/ReifiedTypeInliner.kt @@ -22,14 +22,39 @@ import org.jetbrains.kotlin.codegen.generateIsCheck import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods import org.jetbrains.kotlin.codegen.optimization.common.intConstant import org.jetbrains.kotlin.types.KotlinType -import org.jetbrains.kotlin.types.TypeUtils +import org.jetbrains.kotlin.types.Variance +import org.jetbrains.kotlin.types.typeUtil.builtIns +import org.jetbrains.kotlin.types.typeUtil.makeNullableIfNeeded import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.tree.* -private class ParameterNameAndNullability(val name: String, val nullable: Boolean) +class ReificationArgument( + val parameterName: String, val nullable: Boolean, val arrayDepth: Int +) { + fun asString() = "[".repeat(arrayDepth) + parameterName + (if (nullable) "?" else "") + fun combine(replacement: ReificationArgument) = + ReificationArgument( + replacement.parameterName, + this.nullable || (replacement.nullable && this.arrayDepth == 0), + this.arrayDepth + replacement.arrayDepth) + + fun reify(replacementAsmType: Type, kotlinType: KotlinType) = + Pair(Type.getType("[".repeat(arrayDepth) + replacementAsmType), kotlinType.arrayOf(arrayDepth).makeNullableIfNeeded(nullable)) + + private fun KotlinType.arrayOf(arrayDepth: Int): KotlinType { + val builtins = this.builtIns + var currentType = this + + repeat(arrayDepth) { + currentType = builtins.getArrayType(Variance.INVARIANT, currentType) + } + + return currentType + } +} class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) { @@ -37,7 +62,6 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) NEW_ARRAY, AS, SAFE_AS, IS, JAVA_CLASS; val id: Int get() = ordinal - val isTypeNullabilityAware: Boolean get() = this == AS || this == IS } companion object { @@ -98,20 +122,15 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) */ private fun processReifyMarker(insn: MethodInsnNode, instructions: InsnList): String? { val operationKind = insn.operationKind ?: return null - val parameter = insn.parameterNameAndNullability ?: return null - val mapping = parametersMapping?.get(parameter.name) ?: return null - val kotlinType = - if (operationKind.isTypeNullabilityAware && parameter.nullable) - TypeUtils.makeNullable(mapping.type) - else - mapping.type + val reificationArgument = insn.reificationArgument ?: return null + val mapping = parametersMapping?.get(reificationArgument.parameterName) ?: return null - - val asmType = mapping.asmType - if (asmType != null) { + if (mapping.asmType != null) { // process* methods return false if marker should be reified further // or it's invalid (may be emitted explicitly in code) // they return true if instruction is reified and marker can be deleted + val (asmType, kotlinType) = reificationArgument.reify(mapping.asmType, mapping.type) + if (when (operationKind) { OperationKind.NEW_ARRAY -> processNewArray(insn, asmType) OperationKind.AS -> processAs(insn, instructions, kotlinType, asmType, safe = false) @@ -126,9 +145,9 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) return null } else { - val nullableSuffix = if (operationKind.isTypeNullabilityAware && kotlinType.isMarkedNullable) "?" else "" - instructions.set(insn.previous!!, LdcInsnNode(mapping.newName + nullableSuffix)) - return mapping.newName + val newReificationArgument = reificationArgument.combine(mapping.reificationArgument!!) + instructions.set(insn.previous!!, LdcInsnNode(newReificationArgument.asString())) + return mapping.reificationArgument.parameterName } } @@ -198,17 +217,20 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?) } -private val MethodInsnNode.parameterNameAndNullability: ParameterNameAndNullability? +private val MethodInsnNode.reificationArgument: ReificationArgument? get() { val prev = previous!! - val parameterNameWithFlag = when (prev.opcode) { + val reificationArgumentRaw = when (prev.opcode) { Opcodes.LDC -> (prev as LdcInsnNode).cst as String else -> return null } - val parameterName = if (parameterNameWithFlag.endsWith("?")) parameterNameWithFlag.dropLast(1) else parameterNameWithFlag - return ParameterNameAndNullability(parameterName, parameterName !== parameterNameWithFlag) + val arrayDepth = reificationArgumentRaw.indexOfFirst { it != '[' } + val parameterName = reificationArgumentRaw.substring(arrayDepth).removeSuffix("?") + val nullable = reificationArgumentRaw.endsWith('?') + + return ReificationArgument(parameterName, nullable, arrayDepth) } private val MethodInsnNode.operationKind: ReifiedTypeInliner.OperationKind? get() = @@ -219,12 +241,12 @@ private val MethodInsnNode.operationKind: ReifiedTypeInliner.OperationKind? get( class TypeParameterMappings() { private val mappingsByName = hashMapOf() - public fun addParameterMappingToType(name: String, type: KotlinType, asmType: Type, signature: String, isReified: Boolean) { - mappingsByName[name] = TypeParameterMapping(name, type, asmType, newName = null, signature = signature, isReified = isReified) + fun addParameterMappingToType(name: String, type: KotlinType, asmType: Type, signature: String, isReified: Boolean) { + mappingsByName[name] = TypeParameterMapping(name, type, asmType, reificationArgument = null, signature = signature, isReified = isReified) } - public fun addParameterMappingToNewParameter(name: String, type: KotlinType, newName: String, isReified: Boolean) { - mappingsByName[name] = TypeParameterMapping(name, type, asmType = null, newName = newName, signature = null, isReified = isReified) + fun addParameterMappingForFurtherReification(name: String, type: KotlinType, reificationArgument: ReificationArgument, isReified: Boolean) { + mappingsByName[name] = TypeParameterMapping(name, type, asmType = null, reificationArgument = reificationArgument, signature = null, isReified = isReified) } operator fun get(name: String): TypeParameterMapping? { @@ -239,7 +261,11 @@ class TypeParameterMappings() { } class TypeParameterMapping( - val name: String, val type: KotlinType, val asmType: Type?, val newName: String?, val signature: String?, val isReified: Boolean + val name: String, val type: KotlinType, + val asmType: Type?, + val reificationArgument: ReificationArgument?, + val signature: String?, + val isReified: Boolean ) class ReifiedTypeParametersUsages { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/TypeRemapper.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/TypeRemapper.kt index fd4aa467ba5..35f4160b670 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/TypeRemapper.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/TypeRemapper.kt @@ -52,7 +52,7 @@ class TypeRemapper private constructor(private val typeMapping: MutableMap foo(x: Any?) = Pair(x is T, x is T?) + +fun box(): String { + val x1 = foo>(arrayOf("")) + if (x1.toString() != "(true, true)") return "fail 1" + + val x2 = foo?>(arrayOf("")) + if (x2.toString() != "(true, true)") return "fail 2" + + val x3 = foo>(null) + if (x3.toString() != "(false, true)") return "fail 3" + + val x4 = foo?>(null) + if (x4.toString() != "(true, true)") return "fail 4" + + val x5 = foo?>(arrayOf("")) + if (x5.toString() != "(false, false)") return "fail 5" + + val x6 = foo?>(null) + if (x6.toString() != "(true, true)") return "fail 6" + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/instanceOfArrays.kt b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/instanceOfArrays.kt new file mode 100644 index 00000000000..a9ce7dce4e0 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/instanceOfArrays.kt @@ -0,0 +1,35 @@ +inline fun foo(x: Any?) = Pair(x is T, x is T?) +inline fun bar(y: Any?) = foo>(y) +inline fun barNullable(y: Any?) = foo?>(y) + +fun box(): String { + val x1 = bar(arrayOf("")) + if (x1.toString() != "(true, true)") return "fail 1" + + val x3 = bar(null) + if (x3.toString() != "(false, true)") return "fail 3" + + val x4 = bar(null) + if (x4.toString() != "(false, true)") return "fail 4" + + val x5 = bar(arrayOf("")) + if (x5.toString() != "(false, false)") return "fail 5" + + val x6 = bar(null) + if (x6.toString() != "(false, true)") return "fail 6" + + // barNullable + + val x7 = barNullable(arrayOf("")) + if (x7.toString() != "(true, true)") return "fail 7" + + val x9 = barNullable(null) + if (x9.toString() != "(true, true)") return "fail 9" + + val x10 = barNullable(arrayOf("")) + if (x10.toString() != "(false, false)") return "fail 11" + + val x12 = barNullable(null) + if (x12.toString() != "(true, true)") return "fail 12" + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jClass.kt b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jClass.kt new file mode 100644 index 00000000000..a75a7b0bf17 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jClass.kt @@ -0,0 +1,13 @@ +inline fun jClass() = T::class.java +inline fun jClassArray() = jClass>() + +fun box(): String { + if (jClass>().simpleName != "String[]") return "fail 1" + if (jClass().simpleName != "int[]") return "fail 2" + + if (jClassArray().simpleName != "String[]") return "fail 3" + if (jClassArray>().simpleName != "String[][]") return "fail 4" + if (jClassArray().simpleName != "int[][]") return "fail 5" + + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArray.kt b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArray.kt new file mode 100644 index 00000000000..1a2bee24db9 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArray.kt @@ -0,0 +1,15 @@ +inline fun jaggedArray(x: (Int, Int) -> T): Array> = Array(1) { i -> + Array(1) { j -> x(i, j) } +} + +fun box(): String { + val x1: Array> = jaggedArray() { x, y -> "$x-$y" } + if (x1[0][0] != "0-0") return "fail 1" + + val x2: Array>> = jaggedArray() { x, y -> arrayOf("$x-$y") } + if (x2[0][0][0] != "0-0") return "fail 2" + + val x3: Array> = jaggedArray() { x, y -> intArrayOf(x + y + 1) } + if (x3[0][0][0] != 1) return "fail 3" + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArrayOfNulls.kt b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArrayOfNulls.kt new file mode 100644 index 00000000000..d15a19ec769 --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArrayOfNulls.kt @@ -0,0 +1,13 @@ +inline fun jaggedArrayOfNulls(): Array?> = arrayOfNulls>(1) + +fun box(): String { + val x1 = jaggedArrayOfNulls().javaClass.simpleName + if (x1 != "String[][]") return "fail1: $x1" + + val x2 = jaggedArrayOfNulls>().javaClass.simpleName + if (x2 != "String[][][]") return "fail2: $x2" + + val x3 = jaggedArrayOfNulls().javaClass.simpleName + if (x3 != "int[][][]") return "fail3: $x3" + return "OK" +} diff --git a/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedDeep.kt b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedDeep.kt new file mode 100644 index 00000000000..aa8f36fc23e --- /dev/null +++ b/compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedDeep.kt @@ -0,0 +1,17 @@ +inline fun jaggedArray(x: (Int, Int, Int) -> T): Array>> = Array(1) { i -> + Array(1) { + j -> Array(1) { k -> x(i, j, k) } + } +} + +fun box(): String { + val x1: Array>> = jaggedArray() { x, y, z -> "$x-$y-$z" } + if (x1[0][0][0] != "0-0-0") return "fail 1" + + val x2: Array>>> = jaggedArray() { x, y, z -> arrayOf("$x-$y-$z") } + if (x2[0][0][0][0] != "0-0-0") return "fail 2" + + val x3: Array>> = jaggedArray() { x, y, z -> intArrayOf(x + y + z + 1) } + if (x3[0][0][0][0] != 1) return "fail 3" + return "OK" +} diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java index 7ed15236c4a..201a877bb92 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/generated/BlackBoxWithStdlibCodegenTestGenerated.java @@ -4674,6 +4674,51 @@ public class BlackBoxWithStdlibCodegenTestGenerated extends AbstractBlackBoxCode String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/varargs.kt"); doTestWithStdlib(fileName); } + + @TestMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class ArraysReification extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInArraysReification() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxWithStdlib/reified/arraysReification"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("instanceOf.kt") + public void testInstanceOf() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/instanceOf.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("instanceOfArrays.kt") + public void testInstanceOfArrays() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/instanceOfArrays.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("jClass.kt") + public void testJClass() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jClass.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("jaggedArray.kt") + public void testJaggedArray() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArray.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("jaggedArrayOfNulls.kt") + public void testJaggedArrayOfNulls() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedArrayOfNulls.kt"); + doTestWithStdlib(fileName); + } + + @TestMetadata("jaggedDeep.kt") + public void testJaggedDeep() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxWithStdlib/reified/arraysReification/jaggedDeep.kt"); + doTestWithStdlib(fileName); + } + } } @TestMetadata("compiler/testData/codegen/boxWithStdlib/storeStackBeforeInline") diff --git a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt index 35a3740651c..9bedc061bfa 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/types/TypeUtils.kt @@ -42,6 +42,7 @@ val KotlinType.builtIns: KotlinBuiltIns get() = constructor.builtIns fun KotlinType.makeNullable() = TypeUtils.makeNullable(this) +fun KotlinType.makeNullableIfNeeded(nullable: Boolean) = TypeUtils.makeNullableIfNeeded(this, nullable) fun KotlinType.makeNotNullable() = TypeUtils.makeNotNullable(this) fun KotlinType.immediateSupertypes(): Collection = TypeUtils.getImmediateSupertypes(this)