[JVM IR] Add support for exposing boxed inline classes for use from Java
Added a new annotation @JvmExposeBoxed to expose boxed forms of inline
classes for effective use from Java. For functions and constructors
that take or return inline classes, a wrapper declaration for Java is
created where inline classes are boxed. Fixed IR transformation
to properly handle this annotation. Added the required checker to
handle misuse.
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
index 223427a..ddb1ebc 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDataClassConverters.kt
@@ -4857,6 +4857,12 @@
token,
)
}
+ add(FirJvmErrors.JVM_EXPOSE_BOXED_WITHOUT_INLINE) { firDiagnostic ->
+ JvmExposeBoxedWithoutInlineImpl(
+ firDiagnostic as KtPsiDiagnostic,
+ token,
+ )
+ }
add(FirJvmErrors.JAVA_TYPE_MISMATCH) { firDiagnostic ->
JavaTypeMismatchImpl(
firSymbolBuilder.typeBuilder.buildKtType(firDiagnostic.a),
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
index 64682fe..c20b756 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnostics.kt
@@ -3386,6 +3386,10 @@
override val diagnosticClass get() = JvmInlineWithoutValueClass::class
}
+ interface JvmExposeBoxedWithoutInline : KtFirDiagnostic<KtAnnotationEntry> {
+ override val diagnosticClass get() = JvmExposeBoxedWithoutInline::class
+ }
+
interface JavaTypeMismatch : KtFirDiagnostic<KtExpression> {
override val diagnosticClass get() = JavaTypeMismatch::class
val expectedType: KtType
diff --git a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
index 9816fac..4efef2e 100644
--- a/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
+++ b/analysis/analysis-api-fir/src/org/jetbrains/kotlin/analysis/api/fir/diagnostics/KtFirDiagnosticsImpl.kt
@@ -4088,6 +4088,11 @@
token: KtLifetimeToken,
) : KtAbstractFirDiagnostic<PsiElement>(firDiagnostic, token), KtFirDiagnostic.JvmInlineWithoutValueClass
+internal class JvmExposeBoxedWithoutInlineImpl(
+ firDiagnostic: KtPsiDiagnostic,
+ token: KtLifetimeToken,
+) : KtAbstractFirDiagnostic<KtAnnotationEntry>(firDiagnostic, token), KtFirDiagnostic.JvmExposeBoxedWithoutInline
+
internal class JavaTypeMismatchImpl(
override val expectedType: KtType,
override val actualType: KtType,
diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt
index 8da1c92..8eccaef 100644
--- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt
+++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt
@@ -559,7 +559,7 @@
fun generateResumePathUnboxing(v: InstructionAdapter, inlineClass: KotlinTypeMarker, typeMapper: KotlinTypeMapperBase) {
addBeforeUnboxInlineClassMarker(v)
StackValue.unboxInlineClass(AsmTypes.OBJECT_TYPE, inlineClass, v, typeMapper)
- // Suspend functions always returns Any?, but the unboxing disrupts type analysis of the bytecode.
+ // Suspend functions always returns Any?, but the unboxing disrupts type analysis of the bytecode.
// For example, if the underlying type is String, CHECKCAST String is removed.
// However, the unboxing is moved to the resume path, the direct path still has Any?, but now, without the CHECKCAST.
// Thus, we add CHECKCAST Object, which we remove, after we copy the unboxing to the resume path.
diff --git a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt
index fb73a56..41c6d7f 100644
--- a/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt
+++ b/compiler/fir/checkers/checkers-component-generator/src/org/jetbrains/kotlin/fir/checkers/generator/diagnostics/FirJvmDiagnosticsList.kt
@@ -40,6 +40,7 @@
val VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION by error<PsiElement>()
val JVM_INLINE_WITHOUT_VALUE_CLASS by error<PsiElement>()
+ val JVM_EXPOSE_BOXED_WITHOUT_INLINE by error<KtAnnotationEntry>()
}
val TYPES by object : DiagnosticGroup("Types") {
diff --git a/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt b/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt
index 4654f69..072a8be 100644
--- a/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt
+++ b/compiler/fir/checkers/checkers.jvm/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrors.kt
@@ -44,6 +44,7 @@
val FUNCTION_DELEGATE_MEMBER_NAME_CLASH by error0<PsiElement>(SourceElementPositioningStrategies.DECLARATION_NAME)
val VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION by error0<PsiElement>()
val JVM_INLINE_WITHOUT_VALUE_CLASS by error0<PsiElement>()
+ val JVM_EXPOSE_BOXED_WITHOUT_INLINE by error0<KtAnnotationEntry>()
// Types
val JAVA_TYPE_MISMATCH by error2<KtExpression, ConeKotlinType, ConeKotlinType>()
diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt
index bab9352..cd14c28 100644
--- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt
+++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/diagnostics/jvm/FirJvmErrorsDefaultMessages.kt
@@ -34,6 +34,7 @@
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_DEFAULT_IN_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_DEFAULT_WITH_COMPATIBILITY_IN_DECLARATION
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_DEFAULT_WITH_COMPATIBILITY_NOT_ON_INTERFACE
+import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_EXPOSE_BOXED_WITHOUT_INLINE
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_INLINE_WITHOUT_VALUE_CLASS
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_PACKAGE_NAME_CANNOT_BE_EMPTY
import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors.JVM_PACKAGE_NAME_MUST_BE_VALID_NAME
@@ -113,42 +114,42 @@
)
map.put(
SUPER_CALL_WITH_DEFAULT_PARAMETERS,
- "Super-calls with default arguments are prohibited. Please specify all arguments of ''super.{0}'' explicitly.",
+ "Super-calls with default arguments are not allowed. Please specify all arguments of ''super.{0}'' explicitly",
TO_STRING
)
- map.put(LOCAL_JVM_RECORD, "Local '@JvmRecord' classes are prohibited.")
- map.put(NON_FINAL_JVM_RECORD, "'@JvmRecord' class should be final.")
- map.put(ENUM_JVM_RECORD, "'@JvmRecord' class should not be an enum.")
+ map.put(LOCAL_JVM_RECORD, "Local @JvmRecord classes are not allowed")
+ map.put(NON_FINAL_JVM_RECORD, "@JvmRecord class should be final")
+ map.put(ENUM_JVM_RECORD, "@JvmRecord class should not be an enum")
map.put(
JVM_RECORD_WITHOUT_PRIMARY_CONSTRUCTOR_PARAMETERS,
- "Primary constructor with parameters is required for '@JvmRecord' class."
+ "Primary constructor with parameters is required for @JvmRecord class"
)
- map.put(JVM_RECORD_NOT_VAL_PARAMETER, "Constructor parameter of '@JvmRecord' class should be a 'val'.")
- map.put(JVM_RECORD_NOT_LAST_VARARG_PARAMETER, "Only the last constructor parameter of '@JvmRecord' can be a vararg.")
- map.put(JVM_RECORD_EXTENDS_CLASS, "Record cannot extend a class.", RENDER_TYPE)
- map.put(INNER_JVM_RECORD, "'@JvmRecord' class should not be inner.")
- map.put(FIELD_IN_JVM_RECORD, "Non-constructor properties with backing field in '@JvmRecord' class are prohibited.")
- map.put(DELEGATION_BY_IN_JVM_RECORD, "Delegation is prohibited for '@JvmRecord' classes.")
- map.put(NON_DATA_CLASS_JVM_RECORD, "Only data classes are allowed to be marked as '@JvmRecord'.")
- map.put(ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE, "Classes cannot have explicit 'java.lang.Record' supertype.")
+ map.put(JVM_RECORD_NOT_VAL_PARAMETER, "Constructor parameter of @JvmRecord class should be a val")
+ map.put(JVM_RECORD_NOT_LAST_VARARG_PARAMETER, "Only the last constructor parameter of @JvmRecord may be a vararg")
+ map.put(JVM_RECORD_EXTENDS_CLASS, "Record cannot inherit a class", RENDER_TYPE)
+ map.put(INNER_JVM_RECORD, "@JvmRecord class should not be inner")
+ map.put(FIELD_IN_JVM_RECORD, "It's not allowed to have non-constructor properties with backing field in @JvmRecord class")
+ map.put(DELEGATION_BY_IN_JVM_RECORD, "Delegation is not allowed for @JvmRecord classes")
+ map.put(NON_DATA_CLASS_JVM_RECORD, "Only data classes are allowed to be marked as @JvmRecord")
+ map.put(ILLEGAL_JAVA_LANG_RECORD_SUPERTYPE, "Classes cannot have explicit 'java.lang.Record' supertype")
- map.put(OVERRIDE_CANNOT_BE_STATIC, "Override member cannot be '@JvmStatic' in an object.")
+ map.put(OVERRIDE_CANNOT_BE_STATIC, "Override member cannot be '@JvmStatic' in object")
map.put(
JVM_STATIC_NOT_IN_OBJECT_OR_CLASS_COMPANION,
- "Only members in named objects and companion objects of classes can be annotated with '@JvmStatic'."
+ "Only members in named objects and companion objects of classes can be annotated with '@JvmStatic'"
)
map.put(
JVM_STATIC_NOT_IN_OBJECT_OR_COMPANION,
- "Only members in named objects and companion objects can be annotated with '@JvmStatic'."
+ "Only members in named objects and companion objects can be annotated with '@JvmStatic'"
)
map.put(
JVM_STATIC_ON_NON_PUBLIC_MEMBER,
- "Only public members in interface companion objects can be annotated with '@JvmStatic'."
+ "Only public members in interface companion objects can be annotated with '@JvmStatic'"
)
map.put(
JVM_STATIC_ON_CONST_OR_JVM_FIELD,
- "'@JvmStatic' annotation is useless for const or '@JvmField' properties.",
+ "'@JvmStatic' annotation is useless for const or '@JvmField' properties",
)
map.put(
JVM_STATIC_ON_EXTERNAL_IN_INTERFACE,
@@ -162,6 +163,7 @@
map.put(VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION, "Value classes without '@JvmInline' annotation are not yet supported.")
map.put(JVM_INLINE_WITHOUT_VALUE_CLASS, "'@JvmInline' annotation is applicable only to value classes.")
+ map.put(JVM_EXPOSE_BOXED_WITHOUT_INLINE, "'@JvmExposeBoxed' annotation is only applicable to '@JvmInline' value classes.")
map.put(JVM_DEFAULT_IN_DECLARATION, "Usage of ''@{0}'' is only allowed with ''-Xjvm-default'' option.", STRING)
map.put(
diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmDeclarationCheckers.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmDeclarationCheckers.kt
index 2477f22..6c88b56 100644
--- a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmDeclarationCheckers.kt
+++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/JvmDeclarationCheckers.kt
@@ -30,6 +30,7 @@
get() = setOf(
FirJvmRecordChecker,
FirJvmInlineApplicabilityChecker,
+ FirJvmExposeBoxedChecker,
FirJvmConflictsChecker,
FirInlineBodyRegularClassChecker,
)
diff --git a/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmExposeBoxedChecker.kt b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmExposeBoxedChecker.kt
new file mode 100644
index 0000000..e5331f2
--- /dev/null
+++ b/compiler/fir/checkers/checkers.jvm/src/org/jetbrains/kotlin/fir/analysis/jvm/checkers/declaration/FirJvmExposeBoxedChecker.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.fir.analysis.jvm.checkers.declaration
+
+import org.jetbrains.kotlin.diagnostics.DiagnosticReporter
+import org.jetbrains.kotlin.diagnostics.reportOn
+import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
+import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirRegularClassChecker
+import org.jetbrains.kotlin.fir.analysis.diagnostics.jvm.FirJvmErrors
+import org.jetbrains.kotlin.fir.declarations.*
+import org.jetbrains.kotlin.fir.declarations.utils.*
+import org.jetbrains.kotlin.resolve.JVM_EXPOSE_BOXED_ANNOTATION_CLASS_ID
+import org.jetbrains.kotlin.resolve.JVM_INLINE_ANNOTATION_CLASS_ID
+
+object FirJvmExposeBoxedChecker : FirRegularClassChecker() {
+ override fun check(declaration: FirRegularClass, context: CheckerContext, reporter: DiagnosticReporter) {
+ val jvmInlineAnnotation = declaration.getAnnotationByClassId(JVM_INLINE_ANNOTATION_CLASS_ID, context.session)
+ val jvmExposeBoxedAnnotation = declaration.getAnnotationByClassId(JVM_EXPOSE_BOXED_ANNOTATION_CLASS_ID, context.session)
+
+ if ((jvmInlineAnnotation == null || !declaration.isInline) && jvmExposeBoxedAnnotation != null) {
+ reporter.reportOn(jvmExposeBoxedAnnotation.source, FirJvmErrors.JVM_EXPOSE_BOXED_WITHOUT_INLINE, context)
+ }
+ }
+}
diff --git a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt
index 7c781a6..7118bed3 100644
--- a/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt
+++ b/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirNonSuppressibleErrorNames.kt
@@ -573,6 +573,7 @@
"FUNCTION_DELEGATE_MEMBER_NAME_CLASH",
"VALUE_CLASS_WITHOUT_JVM_INLINE_ANNOTATION",
"JVM_INLINE_WITHOUT_VALUE_CLASS",
+ "JVM_EXPOSE_BOXED_WITHOUT_INLINE",
"JAVA_TYPE_MISMATCH",
"UPPER_BOUND_CANNOT_BE_ARRAY",
"STRICTFP_ON_CLASS",
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
index 933d303..7087617 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirLightTreeBlackBoxCodegenTestGenerated.java
@@ -53188,6 +53188,12 @@
}
@Test
+ @TestMetadata("exposedInlineClass.kt")
+ public void testExposedInlineClass() throws Exception {
+ runTest("compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt");
+ }
+
+ @Test
@TestMetadata("fakeOverrideCall.kt")
public void testFakeOverrideCall() throws Exception {
runTest("compiler/testData/codegen/box/valueClasses/fakeOverrideCall.kt");
diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
index 037082a..2819c6e 100644
--- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
+++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirPsiBlackBoxCodegenTestGenerated.java
@@ -53188,6 +53188,12 @@
}
@Test
+ @TestMetadata("exposedInlineClass.kt")
+ public void testExposedInlineClass() throws Exception {
+ runTest("compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt");
+ }
+
+ @Test
@TestMetadata("fakeOverrideCall.kt")
public void testFakeOverrideCall() throws Exception {
runTest("compiler/testData/codegen/box/valueClasses/fakeOverrideCall.kt");
diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt
index 7629160..ebfef16 100644
--- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt
+++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt
@@ -251,14 +251,14 @@
}
val contextReceivers = valueParameters.subList(0, contextReceiverParametersCount)
for (contextReceiver in contextReceivers) {
- frameMap.enter(contextReceiver, classCodegen.typeMapper.mapType(contextReceiver.type))
+ frameMap.enter(contextReceiver, classCodegen.typeMapper.mapType(contextReceiver))
}
extensionReceiverParameter?.let {
frameMap.enter(it, classCodegen.typeMapper.mapType(it))
}
val regularParameters = valueParameters.subList(contextReceiverParametersCount, valueParameters.size)
for (parameter in regularParameters) {
- frameMap.enter(parameter, classCodegen.typeMapper.mapType(parameter.type))
+ frameMap.enter(parameter, classCodegen.typeMapper.mapType(parameter))
}
return frameMap
}
diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt
index 7aeac31..a873c19 100644
--- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt
+++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/intrinsics/CompareTo.kt
@@ -21,6 +21,7 @@
import org.jetbrains.kotlin.backend.jvm.codegen.*
import org.jetbrains.kotlin.backend.jvm.ir.isSmartcastFromHigherThanNullable
import org.jetbrains.kotlin.backend.jvm.ir.receiverAndArgs
+import org.jetbrains.kotlin.backend.jvm.mapping.mapType
import org.jetbrains.kotlin.builtins.PrimitiveType
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.AsmUtil.comparisonOperandType
@@ -58,7 +59,7 @@
val callee = expression.symbol.owner
val calleeParameter = callee.dispatchReceiverParameter ?: callee.extensionReceiverParameter!!
val parameterType = comparisonOperandType(
- classCodegen.typeMapper.mapType(calleeParameter.type),
+ classCodegen.typeMapper.mapType(calleeParameter),
signature.valueParameters.single().asmType,
)
return IntrinsicFunction.create(expression, signature, classCodegen, listOf(parameterType, parameterType)) {
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt
index 798359c..60053f8 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/BridgeLowering.kt
@@ -15,6 +15,7 @@
import org.jetbrains.kotlin.backend.jvm.MemoizedMultiFieldValueClassReplacements
import org.jetbrains.kotlin.backend.jvm.SpecialBridge
import org.jetbrains.kotlin.backend.jvm.ir.*
+import org.jetbrains.kotlin.backend.jvm.mapping.mapType
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -171,6 +172,9 @@
if (isMethodOfAny())
return false
+ if (origin == JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS)
+ return false
+
// We don't produce bridges for abstract functions in interfaces.
if (isJvmAbstract(context.config.jvmDefaultMode)) {
if (parentAsClass.isJvmInterface) {
@@ -552,7 +556,7 @@
// If the signature of this method will be changed in the output to take a boxed argument instead of a primitive,
// rewrite the argument so that code will be generated for a boxed argument and not a primitive.
valueParameters.forEachIndexed { i, p ->
- if (AsmUtil.isPrimitive(context.defaultTypeMapper.mapType(p.type)) && ourSignature.argumentTypes[i].sort == Type.OBJECT) {
+ if (AsmUtil.isPrimitive(context.defaultTypeMapper.mapType(p)) && ourSignature.argumentTypes[i].sort == Type.OBJECT) {
p.type = p.type.makeNullable()
}
}
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
index 226f86f..a047aa2 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmInlineClassLowering.kt
@@ -8,16 +8,21 @@
import org.jetbrains.kotlin.backend.common.ScopeWithIr
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irBlockBody
+import org.jetbrains.kotlin.backend.common.pop
+import org.jetbrains.kotlin.backend.common.push
import org.jetbrains.kotlin.backend.jvm.*
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
+import org.jetbrains.kotlin.backend.jvm.ir.isExposedSingleFieldValueClass
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
+import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.IrStatement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.addConstructor
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
+import org.jetbrains.kotlin.ir.builders.declarations.buildValueParameter
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
@@ -26,6 +31,7 @@
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
+import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.JVM_INLINE_ANNOTATION_FQ_NAME
@@ -71,6 +77,7 @@
override val specificMangle: SpecificMangle
get() = SpecificMangle.Inline
+
override fun visitClassNewDeclarationsWhenParallel(declaration: IrDeclaration) = Unit
override fun visitClassNew(declaration: IrClass): IrClass {
@@ -99,12 +106,13 @@
}
override fun handleSpecificNewClass(declaration: IrClass) {
- val irConstructor = declaration.primaryConstructor!!
+ val primaryConstructor = declaration.primaryConstructor!!
// The field getter is used by reflection and cannot be removed here unless it is internal.
- declaration.declarations.removeIf {
- it == irConstructor || (it is IrFunction && it.isInlineClassFieldGetter && !it.visibility.isPublicAPI)
+ declaration.declarations.removeAll {
+ it == primaryConstructor || (it is IrFunction && it.isInlineClassFieldGetter && !it.visibility.isPublicAPI)
}
- buildPrimaryInlineClassConstructor(declaration, irConstructor)
+
+ buildInlineClassConstructors(declaration, primaryConstructor)
buildBoxFunction(declaration)
buildUnboxFunction(declaration)
buildSpecializedEqualsMethodIfNeeded(declaration)
@@ -131,11 +139,96 @@
}
}
+ override fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List<IrDeclaration> {
+ replacement.valueParameters.forEach {
+ visitParameter(it)
+ it.defaultValue?.patchDeclarationParents(replacement)
+ }
+
+ allScopes.push(createScope(replacement))
+ replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement)
+ allScopes.pop()
+
+ val result = ArrayList<IrDeclaration>(3)
+ result += replacement
+
+ val declaredOverrideWithDispatchReceiver = function.overriddenSymbols.isNotEmpty() && replacement.dispatchReceiverParameter == null
+
+ val takesOrReturnsExposeBoxedClass = function.explicitParameters
+ .any { it.type.classOrNull?.owner?.isExposedSingleFieldValueClass == true }
+ || function.returnType.classOrNull?.owner?.isExposedSingleFieldValueClass == true
+
+ if (declaredOverrideWithDispatchReceiver) {
+ result += createBridgeFunction(function, replacement)
+ }
+
+ // Replaces the original with function taking/returning boxed inline class, intended for use from Java.
+ // The function body just calls the replacement.
+ if (takesOrReturnsExposeBoxedClass && !function.isEquals() && !function.isToString() && !function.isHashCode()) {
+ val functionForJava = function.deepCopyWithSymbols(initialParent = function.parent)
+
+ functionForJava.origin = JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS
+
+ function.correspondingPropertySymbol?.let { property ->
+ val propertyName = property.owner.name.asString()
+ functionForJava.name = Name.identifier(
+ if (function.isGetter) {
+ JvmAbi.getterName(propertyName)
+ } else {
+ JvmAbi.setterName(propertyName)
+ }
+ )
+ }
+
+ // Don't create a default argument stub
+ for (param in functionForJava.valueParameters) {
+ param.defaultValue = null
+ }
+
+ if (replacement.modality != Modality.ABSTRACT) {
+ functionForJava.body = context.createIrBuilder(functionForJava.symbol).irBlockBody(functionForJava) {
+ +irReturn(irCall(replacement).also { access ->
+ var paramOffset = 0
+
+ if (functionForJava.parentClassOrNull?.isExposedSingleFieldValueClass == true) {
+ functionForJava.dispatchReceiverParameter?.let {
+ access.putValueArgument(paramOffset, irGet(it))
+ paramOffset++
+ }
+
+ functionForJava.extensionReceiverParameter?.let {
+ access.putValueArgument(paramOffset, irGet(it))
+ paramOffset++
+ }
+ } else {
+ functionForJava.dispatchReceiverParameter?.let {
+ access.dispatchReceiver = irGet(it)
+ }
+
+ functionForJava.extensionReceiverParameter?.let {
+ access.extensionReceiver = irGet(it)
+ }
+ }
+
+ for ((idx, parameter) in functionForJava.valueParameters.withIndex()) {
+ access.putValueArgument(paramOffset + idx, irGet(parameter))
+ }
+ })
+ }
+ }
+
+ result += functionForJava
+ }
+
+ return result
+ }
+
// Secondary constructors for boxed types get translated to static functions returning
// unboxed arguments. We remove the original constructor.
// Primary constructors' case is handled at the start of transformFunctionFlat
override fun transformSecondaryConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List<IrDeclaration> {
replacement.valueParameters.forEach { it.transformChildrenVoid() }
+
replacement.body = context.createIrBuilder(replacement.symbol, replacement.startOffset, replacement.endOffset).irBlockBody(
replacement
) {
@@ -146,9 +239,7 @@
+statement
.transformStatement(object : IrElementTransformerVoid() {
// Don't recurse under nested class declarations
- override fun visitClass(declaration: IrClass): IrStatement {
- return declaration
- }
+ override fun visitClass(declaration: IrClass): IrStatement = declaration
// Capture the result of a delegating constructor call in a temporary variable "thisVar".
//
@@ -189,13 +280,49 @@
+irReturn(irGet(thisVar))
}
+ if (constructor.parentAsClass.isExposedSingleFieldValueClass) {
+ val valueClass = constructor.parentAsClass
+
+ constructor.valueParameters = constructor.valueParameters.map {
+ it.deepCopyWithSymbols(initialParent = constructor)
+ }
+
+ constructor.body = context.createIrBuilder(constructor.symbol).irBlockBody(constructor) {
+ +irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+ val firstArgument = constructor.valueParameters[0]
+ val field = getInlineClassBackingField(valueClass)
+
+ +irSetField(
+ irGet(valueClass.thisReceiver!!),
+ field,
+ coerceInlineClasses(
+ irCall(replacement.symbol).also { access ->
+ access.putValueArgument(0, irGet(firstArgument))
+ val valueParameterMap = constructor.explicitParameters.zip(replacement.explicitParameters).toMap()
+
+ for ((parameter, argument) in typedArgumentList(constructor, access)) {
+ if (argument == null) continue
+ val newParameter = valueParameterMap.getValue(parameter)
+ access.putArgument(replacement, newParameter, argument.transform(this@JvmInlineClassLowering, null))
+ }
+ },
+ replacement.returnType,
+ field.type,
+ true,
+ )
+ )
+ }
+
+ return listOf(constructor, replacement)
+ }
+
return listOf(replacement)
}
private fun IrMemberAccessExpression<*>.buildReplacement(
originalFunction: IrFunction,
original: IrMemberAccessExpression<*>,
- replacement: IrSimpleFunction
+ replacement: IrSimpleFunction,
) {
copyTypeArgumentsFrom(original)
val valueParameterMap = originalFunction.explicitParameters.zip(replacement.explicitParameters).toMap()
@@ -347,8 +474,7 @@
.specializeEqualsCall(leftOp, rightOp)
?: expression
}
- else ->
- super.visitCall(expression)
+ else -> super.visitCall(expression)
}
private val IrCall.isEqualsMethodCallOnInlineClass: Boolean
@@ -413,59 +539,110 @@
return super.visitSetValue(expression)
}
- private fun buildPrimaryInlineClassConstructor(valueClass: IrClass, irConstructor: IrConstructor) {
- // Add the default primary constructor
+ private fun buildInlineClassConstructors(valueClass: IrClass, constructor: IrConstructor) {
+ // Add the primary synthetic constructor for boxing
valueClass.addConstructor {
- updateFrom(irConstructor)
+ updateFrom(constructor)
visibility = DescriptorVisibilities.PRIVATE
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_INLINE_CLASS_MEMBER
- returnType = irConstructor.returnType
+ returnType = constructor.returnType
}.apply {
- // Don't create a default argument stub for the primary constructor
- irConstructor.valueParameters.forEach { it.defaultValue = null }
- copyParameterDeclarationsFrom(irConstructor)
- annotations = irConstructor.annotations
- body = context.createIrBuilder(this.symbol).irBlockBody(this) {
+ copyParameterDeclarationsFrom(constructor)
+
+ // Don't create a default argument stub for the primary constructor for boxing
+ for (param in valueParameters) {
+ param.defaultValue = null
+ }
+
+ if (valueClass.isExposedSingleFieldValueClass) {
+ valueParameters += buildValueParameter(this) {
+ type = context.irBuiltIns.nothingNType
+ name = Name.identifier("boxingConstructorMarker")
+ index = 1
+ }
+ }
+ annotations = constructor.annotations
+ body = context.createIrBuilder(symbol).irBlockBody(this) {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+irSetField(
irGet(valueClass.thisReceiver!!),
getInlineClassBackingField(valueClass),
- irGet(this@apply.valueParameters[0])
+ irGet(this@apply.valueParameters[0]),
)
}
}
// Add a static bridge method to the primary constructor. This contains
// null-checks, default arguments, and anonymous initializers.
- val function = context.inlineClassReplacements.getReplacementFunction(irConstructor)!!
+ val constructorImpl = checkNotNull(context.inlineClassReplacements.getReplacementFunction(constructor)) {
+ "No replacement function present for ${constructor.dump()}"
+ }
val initBlocks = valueClass.declarations.filterIsInstance<IrAnonymousInitializer>()
.filterNot { it.isStatic }
- function.valueParameters.forEach { it.transformChildrenVoid() }
- function.body = context.createIrBuilder(function.symbol).irBlockBody {
- val argument = function.valueParameters[0]
- val thisValue = irTemporary(coerceInlineClasses(irGet(argument), argument.type, function.returnType, skipCast = true))
+ constructorImpl.valueParameters.forEach { it.transformChildrenVoid() }
+ constructorImpl.body = context.createIrBuilder(constructorImpl.symbol).irBlockBody {
+ val argument = constructorImpl.valueParameters[0]
+ val thisValue = irTemporary(coerceInlineClasses(irGet(argument), argument.type, constructorImpl.returnType, skipCast = true))
valueMap[valueClass.thisReceiver!!.symbol] = thisValue
for (initBlock in initBlocks) {
for (stmt in initBlock.body.statements) {
- +stmt.transformStatement(this@JvmInlineClassLowering).patchDeclarationParents(function)
+ +stmt.transformStatement(this@JvmInlineClassLowering).patchDeclarationParents(constructorImpl)
}
}
+irReturn(irGet(thisValue))
}
+ if (valueClass.isExposedSingleFieldValueClass) {
+ // Build primary constructor as a public secondary constructor for Java
+ valueClass.addConstructor {
+ updateFrom(constructor)
+ returnType = constructor.returnType
+ isPrimary = false
+ }.apply constructor@{
+ copyParameterDeclarationsFrom(constructor)
+ copyAnnotationsFrom(constructor)
+
+ // Don't create a default argument stub
+ for (param in valueParameters) {
+ param.defaultValue = null
+ }
+
+ body = context.createIrBuilder(symbol).irBlockBody(this) {
+ +irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
+ +irSetField(
+ irGet(valueClass.thisReceiver!!),
+ getInlineClassBackingField(valueClass),
+ irGet(valueParameters[0])
+ )
+ val argument = valueParameters[0]
+ val thisValue =
+ irTemporary(coerceInlineClasses(irGet(argument), argument.type, constructorImpl.returnType, skipCast = true))
+ valueMap[valueClass.thisReceiver!!.symbol] = thisValue
+ +irCall(constructorImpl.symbol).apply {
+ putValueArgument(0, irGet(valueParameters[0]))
+ }
+ }
+ }
+ }
+
valueClass.declarations.removeAll(initBlocks)
- valueClass.declarations += function
+ valueClass.declarations += constructorImpl
}
private fun buildBoxFunction(valueClass: IrClass) {
val function = context.inlineClassReplacements.getBoxFunction(valueClass)
with(context.createIrBuilder(function.symbol)) {
+ val constructor = checkNotNull(valueClass.primaryConstructor) {
+ "Building box function in value class without a primary constructor"
+ }
function.body = irExprBody(
- irCall(valueClass.primaryConstructor!!.symbol).apply {
+ irCall(constructor.symbol).apply {
passTypeArgumentsFrom(function)
putValueArgument(0, irGet(function.valueParameters[0]))
+ if (valueClass.isExposedSingleFieldValueClass)
+ putValueArgument(1, irNull())
}
)
}
@@ -527,4 +704,33 @@
valueClass.declarations += function
}
+ override fun handleRegularClassConstructor(constructor: IrConstructor) {
+ if (constructor.valueParameters.none { it.type.classOrNull?.owner?.isExposedSingleFieldValueClass == true }) return
+ if (constructor.origin == JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS) return
+ if (DescriptorVisibilities.isPrivate(constructor.visibility)) return
+ val valueClass = constructor.parentAsClass
+ // Adding secondary constructor for Java with boxed exposed value class parameters
+ valueClass.addConstructor {
+ updateFrom(constructor)
+ isPrimary = false
+ origin = JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS
+ returnType = constructor.returnType
+ }.apply {
+ copyParameterDeclarationsFrom(constructor)
+
+ // Don't create a default argument stub for the constructor for Java
+ for (param in valueParameters) {
+ param.defaultValue = null
+ }
+
+ copyAnnotationsFrom(constructor)
+ body = context.createIrBuilder(symbol).irBlockBody(this) {
+ +irDelegatingConstructorCall(constructor).also {
+ for ((idx, param) in this@apply.valueParameters.withIndex()) {
+ it.putValueArgument(idx, irGet(param))
+ }
+ }
+ }
+ }
+ }
}
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt
index b2d4574..24317c0 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmMultiFieldValueClassLowering.kt
@@ -443,6 +443,26 @@
}
}
+ override fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List<IrDeclaration> {
+ replacement.valueParameters.forEach {
+ visitParameter(it)
+ it.defaultValue?.patchDeclarationParents(replacement)
+ }
+
+ allScopes.push(createScope(replacement))
+ replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement)
+ allScopes.pop()
+ replacement.copyAttributes(function)
+
+ // Don't create a wrapper for functions which are only used in an unboxed context
+ if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null)
+ return listOf(replacement)
+
+ val bridgeFunction = createBridgeFunction(function, replacement)
+ return listOf(replacement, bridgeFunction)
+ }
+
+
override fun transformSecondaryConstructorFlat(constructor: IrConstructor, replacement: IrSimpleFunction): List<IrDeclaration> {
for (param in replacement.valueParameters) {
param.defaultValue?.patchDeclarationParents(replacement)
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt
index 23e7474..1a6c11a 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmValueClassAbstractLowering.kt
@@ -40,6 +40,7 @@
if (replacement == null) {
if (function is IrConstructor) {
+ handleRegularClassConstructor(function)
val constructorReplacement = replacements.getReplacementForRegularClassConstructor(function)
if (constructorReplacement != null) {
addBindingsFor(function, constructorReplacement)
@@ -77,6 +78,9 @@
}
}
+ protected open fun handleRegularClassConstructor(constructor: IrConstructor) {
+ }
+
private fun transformFlattenedConstructor(function: IrConstructor, replacement: IrConstructor): List<IrDeclaration> {
replacement.valueParameters.forEach {
it.defaultValue?.patchDeclarationParents(replacement)
@@ -105,24 +109,7 @@
return declaration
}
- private fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List<IrDeclaration> {
- replacement.valueParameters.forEach {
- it.defaultValue?.patchDeclarationParents(replacement)
- visitParameter(it)
- }
- allScopes.push(createScope(replacement))
- replacement.body = function.body?.transform(this, null)?.patchDeclarationParents(replacement)
- allScopes.pop()
- replacement.copyAttributes(function)
-
- // Don't create a wrapper for functions which are only used in an unboxed context
- if (function.overriddenSymbols.isEmpty() || replacement.dispatchReceiverParameter != null)
- return listOf(replacement)
-
- val bridgeFunction = createBridgeFunction(function, replacement)
-
- return listOf(replacement, bridgeFunction)
- }
+ protected abstract fun transformSimpleFunctionFlat(function: IrSimpleFunction, replacement: IrSimpleFunction): List<IrDeclaration>
final override fun visitReturn(expression: IrReturn): IrExpression {
(expression.returnTargetSymbol.owner as? IrFunction)?.let { target ->
@@ -177,7 +164,7 @@
protected enum class SpecificMangle { Inline, MultiField }
protected abstract val specificMangle: SpecificMangle
- private fun createBridgeFunction(
+ protected fun createBridgeFunction(
function: IrSimpleFunction,
replacement: IrSimpleFunction
): IrSimpleFunction {
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt
index d5ebb37..6baf9ee 100644
--- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt
+++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SuspendLambdaLowering.kt
@@ -16,6 +16,7 @@
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.ir.hasChild
import org.jetbrains.kotlin.backend.jvm.ir.isReadOfCrossinline
+import org.jetbrains.kotlin.backend.jvm.mapping.mapType
import org.jetbrains.kotlin.codegen.coroutines.COROUTINE_LABEL_FIELD_NAME
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
@@ -177,7 +178,7 @@
val parametersFields = function.explicitParameters.map {
val field = if (it in usedParams) addField {
- val normalizedType = context.defaultTypeMapper.mapType(it.type).normalize()
+ val normalizedType = context.defaultTypeMapper.mapType(it).normalize()
val index = varsCountByType[normalizedType]?.plus(1) ?: 0
varsCountByType[normalizedType] = index
// Rename `$this` to avoid being caught by inlineCodegenUtils.isCapturedFieldName()
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt
index 6ffbfe7..d586d66 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/InlineClassAbi.kt
@@ -7,6 +7,7 @@
import org.jetbrains.kotlin.backend.jvm.MemoizedMultiFieldValueClassReplacements.RemappedParameter.MultiFieldValueClassMapping
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
+import org.jetbrains.kotlin.backend.jvm.ir.isExposedSingleFieldValueClass
import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType
import org.jetbrains.kotlin.backend.jvm.ir.isValueClassType
import org.jetbrains.kotlin.builtins.StandardNames
@@ -20,6 +21,7 @@
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.name.Name
+import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
/**
* Replace inline classes by their underlying types.
@@ -65,7 +67,7 @@
assert(irFunction.constructedClass.isValue) {
"Should not mangle names of non-inline class constructors: ${irFunction.render()}"
}
- return Name.identifier("constructor-impl")
+ return Name.identifier("constructor${JvmAbi.IMPL_SUFFIX_FOR_INLINE_CLASS_MEMBERS}")
}
if (irFunction.isAlreadyMangledMfvcFunction(context)) {
return irFunction.name
@@ -87,7 +89,7 @@
irFunction.name.asString()
}
- return Name.identifier("$base-${suffix ?: "impl"}")
+ return Name.identifier("$base${if (suffix == null) JvmAbi.IMPL_SUFFIX_FOR_INLINE_CLASS_MEMBERS else "-$suffix"}")
}
private fun IrFunction.isAlreadyMangledMfvcFunction(context: JvmBackendContext) =
@@ -144,16 +146,21 @@
val IrFunction.fullValueParameterList: List<IrValueParameter>
get() = listOfNotNull(extensionReceiverParameter) + valueParameters
-fun IrFunction.hasMangledParameters(includeInline: Boolean = true, includeMFVC: Boolean = true): Boolean =
- (dispatchReceiverParameter != null && when {
- parentAsClass.isSingleFieldValueClass -> includeInline
+fun IrFunction.hasMangledParameters(includeInline: Boolean = true, includeMFVC: Boolean = true): Boolean {
+ val includesInlineAndNotExposed = includeInline && origin != JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS
+
+ return (dispatchReceiverParameter != null && when {
+ parentAsClass.isSingleFieldValueClass -> includesInlineAndNotExposed
parentAsClass.isMultiFieldValueClass -> includeMFVC
else -> false
- }) || fullValueParameterList.any { it.type.getRequiresMangling(includeInline, includeMFVC) } || (this is IrConstructor && when {
- constructedClass.isSingleFieldValueClass -> includeInline
+ }) || fullValueParameterList.any {
+ it.type.getRequiresMangling(includesInlineAndNotExposed, includeMFVC)
+ } || (this is IrConstructor && when {
+ constructedClass.isSingleFieldValueClass -> includesInlineAndNotExposed
constructedClass.isMultiFieldValueClass -> includeMFVC
else -> false
})
+}
val IrFunction.hasMangledReturnType: Boolean
get() = returnType.isInlineClassType() && parentClassOrNull?.isFileClass != true
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt
index c4529ac..a4d983a 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLoweredDeclarationOrigin.kt
@@ -29,9 +29,10 @@
object ENUM_MAPPINGS_FOR_WHEN : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_WHEN", isSynthetic = true)
object ENUM_MAPPINGS_FOR_ENTRIES : IrDeclarationOriginImpl("ENUM_MAPPINGS_FOR_ENTRIES", isSynthetic = true)
object SYNTHETIC_INLINE_CLASS_MEMBER : IrDeclarationOriginImpl("SYNTHETIC_INLINE_CLASS_MEMBER", isSynthetic = true)
- object SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER :
+ object SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER :
IrDeclarationOriginImpl("SYNTHETIC_MULTI_FIELD_VALUE_CLASS_MEMBER", isSynthetic = true)
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
+ object FUNCTION_WITH_EXPOSED_INLINE_CLASS : IrDeclarationOriginImpl("FUNCTION_WITH_EXPOSED_INLINE_CLASS")
object MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("MULTI_FIELD_VALUE_CLASS_GENERATED_IMPL_METHOD")
object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT")
object STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_MULTI_FIELD_VALUE_CLASS_REPLACEMENT")
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt
index 18d540e..6145911 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/MemoizedInlineClassReplacements.kt
@@ -135,7 +135,7 @@
private val specializedEqualsCache = storageManager.createCacheWithNotNullValues<IrClass, IrSimpleFunction>()
fun getSpecializedEqualsMethod(irClass: IrClass, irBuiltIns: IrBuiltIns): IrSimpleFunction {
- require(irClass.isSingleFieldValueClass)
+ require(irClass.isSingleFieldValueClass) { "Not a single-field inline class" }
return specializedEqualsCache.computeIfAbsent(irClass) {
irFactory.buildFun {
name = InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrCoroutineUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrCoroutineUtils.kt
index 48d0592..23ec74a 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrCoroutineUtils.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrCoroutineUtils.kt
@@ -95,6 +95,7 @@
isSuspend && shouldContainSuspendMarkers() &&
// These are templates for the inliner; the continuation is borrowed from the caller method.
!isEffectivelyInlineOnly() &&
+ origin != JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS &&
origin != JvmLoweredDeclarationOrigin.INLINE_LAMBDA &&
origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE &&
origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt
index 1fb747c..42e663c 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/ir/JvmIrUtils.kt
@@ -55,6 +55,7 @@
import org.jetbrains.kotlin.load.kotlin.JvmPackagePartSource
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
import org.jetbrains.kotlin.name.ClassId
+import org.jetbrains.kotlin.name.JvmNames
import org.jetbrains.kotlin.name.JvmNames.JVM_DEFAULT_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.JVM_DEFAULT_NO_COMPATIBILITY_FQ_NAME
import org.jetbrains.kotlin.name.JvmNames.JVM_DEFAULT_WITH_COMPATIBILITY_FQ_NAME
@@ -69,6 +70,9 @@
import org.jetbrains.org.objectweb.asm.commons.Method
import java.io.File
+val IrClass.isExposedSingleFieldValueClass: Boolean
+ get() = isSingleFieldValueClass && hasAnnotation(JvmNames.JVM_EXPOSE_BOXED)
+
fun IrDeclaration.getJvmNameFromAnnotation(): String? {
// TODO lower @JvmName?
val const = getAnnotation(DescriptorUtils.JVM_NAME)?.getValueArgument(0) as? IrConst<*> ?: return null
@@ -316,6 +320,7 @@
fun IrSimpleFunction.suspendFunctionOriginal(): IrSimpleFunction =
if (isSuspend &&
!isStaticValueClassReplacement &&
+ origin != JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS &&
!isOrOverridesDefaultParameterStub() &&
parentClassOrNull?.origin != JvmLoweredDeclarationOrigin.DEFAULT_IMPLS
)
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/IrTypeMapping.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/IrTypeMapping.kt
index 4dd3258..b19fffe 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/IrTypeMapping.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/IrTypeMapping.kt
@@ -5,13 +5,11 @@
package org.jetbrains.kotlin.backend.jvm.mapping
+import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
import org.jetbrains.kotlin.codegen.signature.JvmSignatureWriter
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
-import org.jetbrains.kotlin.ir.declarations.IrClass
-import org.jetbrains.kotlin.ir.declarations.IrField
-import org.jetbrains.kotlin.ir.declarations.IrValueParameter
-import org.jetbrains.kotlin.ir.declarations.IrVariable
+import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeArgument
@@ -27,8 +25,14 @@
fun IrTypeMapper.mapType(irVariable: IrVariable): Type =
mapType(irVariable.type)
-fun IrTypeMapper.mapType(irValueParameter: IrValueParameter): Type =
- mapType(irValueParameter.type)
+fun IrTypeMapper.mapType(irValueParameter: IrValueParameter): Type {
+ val mode = if ((irValueParameter.parent as? IrFunction)?.origin == JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS)
+ TypeMappingMode.DEFAULT.wrapInlineClassesMode()
+ else
+ TypeMappingMode.DEFAULT
+
+ return mapType(irValueParameter.type, mode)
+}
fun IrTypeMapper.mapType(irField: IrField): Type =
mapType(irField.type)
diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt
index 82df40c..53df3f7 100644
--- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt
+++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/mapping/MethodSignatureMapper.kt
@@ -36,6 +36,7 @@
import org.jetbrains.kotlin.metadata.deserialization.getExtensionOrNull
import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
+import org.jetbrains.kotlin.name.JvmNames
import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.resolve.jvm.JAVA_LANG_RECORD_FQ_NAME
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
@@ -195,6 +196,7 @@
// See also: KotlinTypeMapper.forceBoxedReturnType
private fun forceBoxedReturnType(function: IrFunction): Boolean =
isBoxMethodForInlineClass(function) ||
+ function.origin == JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS ||
forceFoxedReturnTypeOnOverride(function) ||
forceBoxedReturnTypeOnDefaultImplFun(function) ||
function.isFromJava() && function.returnType.isInlineClassType()
@@ -345,24 +347,31 @@
}
private fun writeParameterType(sw: JvmSignatureWriter, type: IrType, declaration: IrDeclaration, materialized: Boolean = true) {
- if (sw.skipGenericSignature()) {
+ var mode = if (sw.skipGenericSignature()) {
if (type.isInlineClassType() && declaration.isFromJava()) {
- typeMapper.mapType(type, TypeMappingMode.GENERIC_ARGUMENT, sw, materialized)
+ TypeMappingMode.GENERIC_ARGUMENT
} else {
- typeMapper.mapType(type, TypeMappingMode.DEFAULT, sw, materialized)
+ TypeMappingMode.DEFAULT
}
- return
+ } else {
+ with(typeSystem) {
+ val extractTypeMappingModeFromAnnotation = extractTypeMappingModeFromAnnotation(
+ declaration.suppressWildcardsMode(), type, isForAnnotationParameter = false, mapTypeAliases = false
+ )
+ when {
+ extractTypeMappingModeFromAnnotation != null -> extractTypeMappingModeFromAnnotation
+ declaration.isMethodWithDeclarationSiteWildcards && !declaration.isStaticInlineClassReplacement && type.argumentsCount() != 0 ->
+ TypeMappingMode.GENERIC_ARGUMENT // Render all wildcards
+ else -> typeSystem.getOptimalModeForValueParameter(type)
+ }
+ }
}
- val mode = with(typeSystem) {
- extractTypeMappingModeFromAnnotation(
- declaration.suppressWildcardsMode(), type, isForAnnotationParameter = false, mapTypeAliases = false
- )
- ?: if (declaration.isMethodWithDeclarationSiteWildcards && !declaration.isStaticInlineClassReplacement && type.argumentsCount() != 0) {
- TypeMappingMode.GENERIC_ARGUMENT // Render all wildcards
- } else {
- typeSystem.getOptimalModeForValueParameter(type)
- }
+
+ if (type.classOrNull?.owner?.isExposedSingleFieldValueClass == true &&
+ declaration.origin == JvmLoweredDeclarationOrigin.FUNCTION_WITH_EXPOSED_INLINE_CLASS
+ ) {
+ mode = mode.wrapInlineClassesMode()
}
typeMapper.mapType(type, mode, sw, materialized)
diff --git a/compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt b/compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt
new file mode 100644
index 0000000..6977f08
--- /dev/null
+++ b/compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt
@@ -0,0 +1,101 @@
+// WITH_STDLIB
+// !LANGUAGE: +ContextReceivers
+// TARGET_BACKEND: JVM_IR
+// CHECK_BYTECODE_LISTING
+// FILE: Example.kt
+@file:OptIn(ExperimentalStdlibApi::class)
+
+package example
+
+import interop.*
+
+@Target(AnnotationTarget.CONSTRUCTOR)
+annotation class A
+
+interface I<T> {
+ fun virtualFunction(another: T): T
+}
+
+@JvmInline
+@JvmExposeBoxed
+value class StringWrapper(val s: String) : I<StringWrapper> {
+ @A
+ constructor(i: Int) : this(i.toString())
+
+ init {
+ require(s != "")
+ }
+
+ fun plainFunction(): Unit = println(s)
+ override fun virtualFunction(another: StringWrapper): StringWrapper = StringWrapper(s + another.s)
+}
+
+fun topLevelFunction(e: StringWrapper): StringWrapper = e.virtualFunction(e)
+
+suspend fun suspendFunction(e: StringWrapper): StringWrapper = e.virtualFunction(e)
+
+@JvmInline
+@JvmExposeBoxed
+value class IntWrapper(val i: Int) : I<IntWrapper> {
+ constructor(l: Long) : this(l.toInt())
+
+ init {
+ require(i != 0)
+ }
+
+ fun plainFunction(another: IntWrapper): IntWrapper = IntWrapper(i + another.i)
+
+ fun IntWrapper.extensionFunction(another: IntWrapper): IntWrapper = IntWrapper(this.i + this@IntWrapper.i + another.i)
+
+ fun StringWrapper.extensionFunction(another: IntWrapper): IntWrapper = IntWrapper(another.i)
+
+ override fun virtualFunction(another: IntWrapper): IntWrapper = IntWrapper(i + another.i)
+}
+
+fun topLevelFunction(e: IntWrapper): IntWrapper = e.plainFunction(e)
+
+fun IntWrapper.topLevelFunction(e: IntWrapper): IntWrapper = when {
+ e.i > 100 -> IntWrapper(42)
+ else -> e.plainFunction(e)
+}
+
+fun assertEquals(value1: Any?, value2: Any?) {
+ if (value1 != value2)
+ throw AssertionError("Expected $value1, got $value2")
+}
+
+data class HasWrappers(val i: IntWrapper = IntWrapper(42), val j: StringWrapper = StringWrapper(84))
+
+fun box(): String {
+ val intWrapper = IntWrapper(42L)
+ assertEquals(IntWrapper(84), intWrapper.plainFunction(intWrapper))
+ val stringWrapper = StringWrapper(42)
+ assertEquals(StringWrapper("4242"), stringWrapper.virtualFunction(stringWrapper))
+ assertEquals(IntWrapper(84), intWrapper.virtualFunction(intWrapper))
+ assertEquals(IntWrapper(84), topLevelFunction(intWrapper))
+ assertEquals(StringWrapper("4242"), topLevelFunction(stringWrapper))
+ val h = HasWrappers(intWrapper)
+ assertEquals(intWrapper, h.i)
+ Interop.main()
+ return "OK"
+}
+
+// FILE: interop/Interop.java
+package interop;
+
+import example.*;
+
+public class Interop {
+ public static void main() {
+ IntWrapper intWrapper = new IntWrapper(42L);
+ ExampleKt.assertEquals(new IntWrapper(84), intWrapper.plainFunction(intWrapper));
+ StringWrapper stringWrapper = new StringWrapper(42);
+ ExampleKt.assertEquals(new StringWrapper("4242"), stringWrapper.virtualFunction(stringWrapper));
+ ExampleKt.assertEquals(new IntWrapper(84), intWrapper.virtualFunction(intWrapper));
+ ExampleKt.assertEquals(new IntWrapper(84L), ExampleKt.topLevelFunction(intWrapper));
+ ExampleKt.assertEquals(new StringWrapper("4242"), ExampleKt.topLevelFunction(stringWrapper));
+ HasWrappers h = new HasWrappers(intWrapper, stringWrapper);
+ ExampleKt.assertEquals(intWrapper, h.getI());
+ ExampleKt.assertEquals(stringWrapper, h.getJ());
+ }
+}
diff --git a/compiler/testData/codegen/box/valueClasses/exposedInlineClass.txt b/compiler/testData/codegen/box/valueClasses/exposedInlineClass.txt
new file mode 100644
index 0000000..afacfe9
--- /dev/null
+++ b/compiler/testData/codegen/box/valueClasses/exposedInlineClass.txt
@@ -0,0 +1,115 @@
+@kotlin.annotation.Target(allowedTargets=[CONSTRUCTOR])
+@java.lang.annotation.Retention(value=RUNTIME)
+@java.lang.annotation.Target(value=[CONSTRUCTOR])
+@kotlin.Metadata
+public annotation class example/A {
+ // source: 'Example.kt'
+}
+
+@kotlin.Metadata
+public final class example/ExampleKt {
+ // source: 'Example.kt'
+ public final static method assertEquals(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Object): void
+ public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
+ public final static @org.jetbrains.annotations.Nullable method suspendFunction(@org.jetbrains.annotations.NotNull p0: example.StringWrapper, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
+ public final static @org.jetbrains.annotations.Nullable method suspendFunction-gComZGY(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
+ public final static @org.jetbrains.annotations.NotNull method topLevelFunction(@org.jetbrains.annotations.NotNull p0: example.IntWrapper): example.IntWrapper
+ public final static @org.jetbrains.annotations.NotNull method topLevelFunction(@org.jetbrains.annotations.NotNull p0: example.IntWrapper, @org.jetbrains.annotations.NotNull p1: example.IntWrapper): example.IntWrapper
+ public final static @org.jetbrains.annotations.NotNull method topLevelFunction(@org.jetbrains.annotations.NotNull p0: example.StringWrapper): example.StringWrapper
+ public final static method topLevelFunction-JIAth_k(p0: int, p1: int): int
+ public final static method topLevelFunction-OeTM1uU(p0: int): int
+ public final static @org.jetbrains.annotations.NotNull method topLevelFunction-bzFBYxs(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
+}
+
+@kotlin.Metadata
+public final class example/HasWrappers {
+ // source: 'Example.kt'
+ private final field i: int
+ private final @org.jetbrains.annotations.NotNull field j: java.lang.String
+ public method <init>(@org.jetbrains.annotations.NotNull p0: example.IntWrapper, @org.jetbrains.annotations.NotNull p1: example.StringWrapper): void
+ private method <init>(p0: int, p1: java.lang.String): void
+ public synthetic method <init>(p0: int, p1: java.lang.String, p2: int, p3: kotlin.jvm.internal.DefaultConstructorMarker): void
+ public synthetic method <init>(p0: int, p1: java.lang.String, p2: kotlin.jvm.internal.DefaultConstructorMarker): void
+ public final @org.jetbrains.annotations.NotNull method component1(): example.IntWrapper
+ public final method component1-P7HTv5Y(): int
+ public final @org.jetbrains.annotations.NotNull method component2(): example.StringWrapper
+ public final @org.jetbrains.annotations.NotNull method component2-yiaqiYc(): java.lang.String
+ public final @org.jetbrains.annotations.NotNull method copy(@org.jetbrains.annotations.NotNull p0: example.IntWrapper, @org.jetbrains.annotations.NotNull p1: example.StringWrapper): example.HasWrappers
+ public synthetic static method copy-026PlWA$default(p0: example.HasWrappers, p1: int, p2: java.lang.String, p3: int, p4: java.lang.Object): example.HasWrappers
+ public final @org.jetbrains.annotations.NotNull method copy-026PlWA(p0: int, @org.jetbrains.annotations.NotNull p1: java.lang.String): example.HasWrappers
+ public method equals(@org.jetbrains.annotations.Nullable p0: java.lang.Object): boolean
+ public final @org.jetbrains.annotations.NotNull method getI(): example.IntWrapper
+ public final method getI-P7HTv5Y(): int
+ public final @org.jetbrains.annotations.NotNull method getJ(): example.StringWrapper
+ public final @org.jetbrains.annotations.NotNull method getJ-yiaqiYc(): java.lang.String
+ public method hashCode(): int
+ public @org.jetbrains.annotations.NotNull method toString(): java.lang.String
+}
+
+@kotlin.Metadata
+public interface example/I {
+ // source: 'Example.kt'
+ public abstract method virtualFunction(p0: java.lang.Object): java.lang.Object
+}
+
+@kotlin.jvm.JvmInline
+@kotlin.Metadata
+@kotlin.jvm.JvmExposeBoxed
+public final class example/IntWrapper {
+ // source: 'Example.kt'
+ private final field i: int
+ public method <init>(p0: int): void
+ private synthetic method <init>(p0: int, p1: java.lang.Void): void
+ public method <init>(p0: long): void
+ public synthetic final static method box-impl(p0: int): example.IntWrapper
+ public static method constructor-impl(p0: int): int
+ public static method constructor-impl(p0: long): int
+ public method equals(p0: java.lang.Object): boolean
+ public static method equals-impl(p0: int, p1: java.lang.Object): boolean
+ public final static method equals-impl0(p0: int, p1: int): boolean
+ public final @org.jetbrains.annotations.NotNull method extensionFunction(@org.jetbrains.annotations.NotNull p0: example.IntWrapper, @org.jetbrains.annotations.NotNull p1: example.IntWrapper): example.IntWrapper
+ public final @org.jetbrains.annotations.NotNull method extensionFunction(@org.jetbrains.annotations.NotNull p0: example.StringWrapper, @org.jetbrains.annotations.NotNull p1: example.IntWrapper): example.IntWrapper
+ public final static method extensionFunction-7sm2r4Y(p0: int, @org.jetbrains.annotations.NotNull p1: java.lang.String, p2: int): int
+ public final static method extensionFunction-ke8FMIE(p0: int, p1: int, p2: int): int
+ public final method getI(): int
+ public method hashCode(): int
+ public static method hashCode-impl(p0: int): int
+ public final @org.jetbrains.annotations.NotNull method plainFunction(@org.jetbrains.annotations.NotNull p0: example.IntWrapper): example.IntWrapper
+ public final static method plainFunction-IkJdR0s(p0: int, p1: int): int
+ public method toString(): java.lang.String
+ public static method toString-impl(p0: int): java.lang.String
+ public synthetic final method unbox-impl(): int
+ public @org.jetbrains.annotations.NotNull method virtualFunction(@org.jetbrains.annotations.NotNull p0: example.IntWrapper): example.IntWrapper
+ public synthetic bridge method virtualFunction(p0: java.lang.Object): java.lang.Object
+ public method virtualFunction-IkJdR0s(p0: int): int
+ public static method virtualFunction-IkJdR0s(p0: int, p1: int): int
+}
+
+@kotlin.jvm.JvmInline
+@kotlin.Metadata
+@kotlin.jvm.JvmExposeBoxed
+public final class example/StringWrapper {
+ // source: 'Example.kt'
+ private final @org.jetbrains.annotations.NotNull field s: java.lang.String
+ public method <init>(@org.jetbrains.annotations.NotNull p0: java.lang.String): void
+ public @example.A method <init>(p0: int): void
+ private synthetic method <init>(p0: java.lang.String, p1: java.lang.Void): void
+ public synthetic final static method box-impl(p0: java.lang.String): example.StringWrapper
+ public static @org.jetbrains.annotations.NotNull method constructor-impl(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
+ public static @example.A @org.jetbrains.annotations.NotNull method constructor-impl(p0: int): java.lang.String
+ public method equals(p0: java.lang.Object): boolean
+ public static method equals-impl(p0: java.lang.String, p1: java.lang.Object): boolean
+ public final static method equals-impl0(p0: java.lang.String, p1: java.lang.String): boolean
+ public final @org.jetbrains.annotations.NotNull method getS(): java.lang.String
+ public method hashCode(): int
+ public static method hashCode-impl(p0: java.lang.String): int
+ public final method plainFunction(): void
+ public final static method plainFunction-impl(p0: java.lang.String): void
+ public method toString(): java.lang.String
+ public static method toString-impl(p0: java.lang.String): java.lang.String
+ public synthetic final method unbox-impl(): java.lang.String
+ public @org.jetbrains.annotations.NotNull method virtualFunction(@org.jetbrains.annotations.NotNull p0: example.StringWrapper): example.StringWrapper
+ public synthetic bridge method virtualFunction(p0: java.lang.Object): java.lang.Object
+ public @org.jetbrains.annotations.NotNull method virtualFunction-cnKg8a0(@org.jetbrains.annotations.NotNull p0: java.lang.String): java.lang.String
+ public static @org.jetbrains.annotations.NotNull method virtualFunction-cnKg8a0(p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: java.lang.String): java.lang.String
+}
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
index 292915f..d6a81e0 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java
@@ -53188,6 +53188,12 @@
}
@Test
+ @TestMetadata("exposedInlineClass.kt")
+ public void testExposedInlineClass() throws Exception {
+ runTest("compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt");
+ }
+
+ @Test
@TestMetadata("fakeOverrideCall.kt")
public void testFakeOverrideCall() throws Exception {
runTest("compiler/testData/codegen/box/valueClasses/fakeOverrideCall.kt");
diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java
index bd3fd4d..bc01bbe 100644
--- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java
+++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenWithIrInlinerTestGenerated.java
@@ -53188,6 +53188,12 @@
}
@Test
+ @TestMetadata("exposedInlineClass.kt")
+ public void testExposedInlineClass() throws Exception {
+ runTest("compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt");
+ }
+
+ @Test
@TestMetadata("fakeOverrideCall.kt")
public void testFakeOverrideCall() throws Exception {
runTest("compiler/testData/codegen/box/valueClasses/fakeOverrideCall.kt");
diff --git a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
index a8278e9..8c094b4 100644
--- a/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
+++ b/compiler/tests-gen/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java
@@ -42977,6 +42977,11 @@
runTest("compiler/testData/codegen/box/valueClasses/equality.kt");
}
+ @TestMetadata("exposedInlineClass.kt")
+ public void testExposedInlineClass() throws Exception {
+ runTest("compiler/testData/codegen/box/valueClasses/exposedInlineClass.kt");
+ }
+
@TestMetadata("fakeOverrideCall.kt")
public void testFakeOverrideCall() throws Exception {
runTest("compiler/testData/codegen/box/valueClasses/fakeOverrideCall.kt");
diff --git a/core/compiler.common.jvm/src/org/jetbrains/kotlin/name/JvmNames.kt b/core/compiler.common.jvm/src/org/jetbrains/kotlin/name/JvmNames.kt
index f9309b1..1e22d5a 100644
--- a/core/compiler.common.jvm/src/org/jetbrains/kotlin/name/JvmNames.kt
+++ b/core/compiler.common.jvm/src/org/jetbrains/kotlin/name/JvmNames.kt
@@ -14,6 +14,15 @@
val JVM_NAME_SHORT: String = JVM_NAME.shortName().asString()
+ @JvmField
+ val JVM_EXPOSE_BOXED: FqName = FqName("kotlin.jvm.JvmExposeBoxed")
+
+ @JvmField
+ val JVM_EXPOSE_BOXED_CLASS_ID = ClassId.topLevel(JVM_EXPOSE_BOXED)
+
+ @JvmField
+ val JVM_EXPOSE_BOXED_SHORT: String = JVM_EXPOSE_BOXED.shortName().asString()
+
val JVM_MULTIFILE_CLASS: FqName = FqName("kotlin.jvm.JvmMultifileClass")
val JVM_MULTIFILE_CLASS_ID: ClassId = ClassId(FqName("kotlin.jvm"), Name.identifier("JvmMultifileClass"))
val JVM_MULTIFILE_CLASS_SHORT = JVM_MULTIFILE_CLASS.shortName().asString()
diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt
index ef0baa2..062d2ed 100644
--- a/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt
+++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/inlineClassesUtils.kt
@@ -19,6 +19,9 @@
val JVM_INLINE_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmInline")
val JVM_INLINE_ANNOTATION_CLASS_ID = ClassId.topLevel(JVM_INLINE_ANNOTATION_FQ_NAME)
+val JVM_EXPOSE_BOXED_ANNOTATION_FQ_NAME = FqName("kotlin.jvm.JvmExposeBoxed")
+val JVM_EXPOSE_BOXED_ANNOTATION_CLASS_ID = ClassId.topLevel(JVM_EXPOSE_BOXED_ANNOTATION_FQ_NAME)
+
// FIXME: DeserializedClassDescriptor in reflection do not have @JvmInline annotation, that we
// FIXME: would like to check as well.
fun DeclarationDescriptor.isInlineClass(): Boolean = this is ClassDescriptor && this.valueClassRepresentation is InlineClassRepresentation
diff --git a/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt b/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt
index 6536780..279ccdd 100644
--- a/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt
+++ b/libraries/stdlib/common/src/kotlin/JvmAnnotationsH.kt
@@ -216,3 +216,24 @@
@SinceKotlin("1.8")
@OptionalExpectation
public expect annotation class JvmSerializableLambda()
+
+/**
+ * Instructs the compiler
+ * to expose the API related to the annotated [kotlin.jvm.JvmInline] value classes as its boxed variant for effective usage from Java.
+ *
+ * Particularly, it performs the following transformations:
+ *
+ * - For functions and constructors that take or return inline classes,
+ * a wrapper declaration for Java is created where inline classes are boxed.
+ * - Functions declared in the marked class can be called normally from Java.
+ * - Constructor available from Java is added.
+ *
+ * These additions preserve backwards compatibility (both binary and source), so existing inline classes can be marked safely.
+ */
+@Target(CLASS)
+@Retention(AnnotationRetention.BINARY)
+@MustBeDocumented
+@SinceKotlin("1.9")
+@OptionalExpectation
+@ExperimentalStdlibApi
+public expect annotation class JvmExposeBoxed()
diff --git a/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt b/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt
index 93ce1a2..6e95eae 100644
--- a/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt
+++ b/libraries/stdlib/jvm/runtime/kotlin/jvm/annotations/JvmPlatformAnnotations.kt
@@ -177,3 +177,23 @@
@MustBeDocumented
@SinceKotlin("1.5")
public actual annotation class JvmRecord
+
+/**
+ * Instructs the compiler
+ * to expose the API related to the annotated [kotlin.jvm.JvmInline] value classes as its boxed variant for effective usage from Java.
+ *
+ * Particularly, it performs the following transformations:
+ *
+ * - For functions and constructors that take or return inline classes,
+ * a wrapper declaration for Java is created where inline classes are boxed.
+ * - Functions declared in the marked class can be called normally from Java.
+ * - Constructor available from Java is added.
+ *
+ * These additions preserve backwards compatibility (both binary and source), so existing inline classes can be marked safely.
+ */
+@Target(AnnotationTarget.CLASS)
+@Retention(AnnotationRetention.BINARY)
+@MustBeDocumented
+@SinceKotlin("1.9")
+@ExperimentalStdlibApi
+public actual annotation class JvmExposeBoxed
diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt
index abd1d00..914b200 100644
--- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt
+++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt
@@ -3418,6 +3418,9 @@
public abstract interface annotation class kotlin/jvm/JvmDefaultWithoutCompatibility : java/lang/annotation/Annotation {
}
+public abstract interface annotation class kotlin/jvm/JvmExposeBoxed : java/lang/annotation/Annotation {
+}
+
public abstract interface annotation class kotlin/jvm/JvmField : java/lang/annotation/Annotation {
}