JVM_IR: Support init blocks in inline classes

Put their content to constructor-impl, so they are called during
constructor call, but they are not called during boxing, because
box-impl calls <init> and not constructor-impl.

 #KT-28055 In progress
This commit is contained in:
Ilmir Usmanov
2020-10-28 01:46:38 +01:00
parent 5804f73ebd
commit 999604541e
15 changed files with 323 additions and 24 deletions
@@ -13417,6 +13417,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -13702,6 +13707,11 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
@@ -355,7 +355,6 @@ public interface Errors {
DiagnosticFactory0<PsiElement> NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtElement> INLINE_CLASS_CONSTRUCTOR_WRONG_PARAMETERS_SIZE = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtParameter> INLINE_CLASS_CONSTRUCTOR_NOT_FINAL_READ_ONLY_PARAMETER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> INLINE_CLASS_WITH_INITIALIZER = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<KtProperty> PROPERTY_WITH_BACKING_FIELD_INSIDE_INLINE_CLASS = DiagnosticFactory0.create(ERROR, DECLARATION_SIGNATURE);
DiagnosticFactory0<PsiElement> DELEGATED_PROPERTY_INSIDE_INLINE_CLASS = DiagnosticFactory0.create(ERROR);
DiagnosticFactory1<KtTypeReference, KotlinType> INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE = DiagnosticFactory1.create(ERROR);
@@ -709,7 +709,6 @@ public class DefaultErrorMessages {
MAP.put(NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS, "Primary constructor of inline class must be public");
MAP.put(INLINE_CLASS_CONSTRUCTOR_WRONG_PARAMETERS_SIZE, "Inline class must have exactly one primary constructor parameter");
MAP.put(INLINE_CLASS_CONSTRUCTOR_NOT_FINAL_READ_ONLY_PARAMETER, "Inline class primary constructor must have only final read-only (val) property parameter");
MAP.put(INLINE_CLASS_WITH_INITIALIZER, "Inline class cannot have an initializer block");
MAP.put(PROPERTY_WITH_BACKING_FIELD_INSIDE_INLINE_CLASS, "Inline class cannot have properties with backing fields");
MAP.put(DELEGATED_PROPERTY_INSIDE_INLINE_CLASS, "Inline class cannot have delegated properties");
MAP.put(INLINE_CLASS_HAS_INAPPLICABLE_PARAMETER_TYPE, "Inline class cannot have value parameter of type ''{0}''", RENDER_TYPE);
@@ -65,17 +65,6 @@ object InlineClassDeclarationChecker : DeclarationChecker {
return
}
val anonymousInitializers = declaration.getAnonymousInitializers()
if (anonymousInitializers.isNotEmpty()) {
for (anonymousInitializer in anonymousInitializers) {
if (anonymousInitializer is KtClassInitializer) {
trace.report(Errors.INLINE_CLASS_WITH_INITIALIZER.on(anonymousInitializer.initKeyword))
}
}
return
}
val baseParameterType = descriptor.safeAs<ClassDescriptor>()?.defaultType?.substitutedUnderlyingType()
val baseParameterTypeReference = baseParameter.typeReference
if (baseParameterType != null && baseParameterTypeReference != null) {
@@ -57,8 +57,8 @@ class IrFrameMap : FrameMapBase<IrSymbol>() {
return super.leave(key)
}
fun typeOf(symbol: IrSymbol): Type =
typeMap[symbol] ?: error("No mapping for symbol: ${symbol.owner.render()}")
fun typeOf(symbol: IrSymbol): Type = typeMap[symbol]
?: error("No mapping for symbol: ${symbol.owner.render()}")
}
internal val IrFunction.isStatic
@@ -379,9 +379,13 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F
when {
// Getting the underlying field of an inline class merely changes the IR type,
// since the underlying representations are the same.
expression.symbol.owner.isInlineClassFieldGetter -> {
val arg = expression.dispatchReceiver!!.transform(this, null)
coerceInlineClasses(arg, expression.symbol.owner.dispatchReceiverParameter!!.type, expression.type)
expression.symbol.owner.isInlineClassFieldGetter -> when (val ctor = findInitBlockOrConstructorImpl()) {
InitBlockOrConstructorImpl.InitBlock -> super.visitCall(expression)
is InitBlockOrConstructorImpl.ConstructorImpl -> getConstructorImplArgumentValue(ctor, expression.type)
else -> {
val arg = expression.dispatchReceiver!!.transform(this, null)
coerceInlineClasses(arg, expression.symbol.owner.dispatchReceiverParameter!!.type, expression.type)
}
}
// Specialize calls to equals when the left argument is a value of inline class type.
expression.isSpecializedInlineClassEqEq -> {
@@ -394,6 +398,25 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F
super.visitCall(expression)
}
private fun getConstructorImplArgumentValue(ctor: InitBlockOrConstructorImpl.ConstructorImpl, expectedType: IrType): IrExpression {
val arg: IrValueParameter = ctor.irElement.valueParameters.single()
return coerceInlineClasses(IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, arg.symbol), arg.type, expectedType)
}
@Suppress("UNCHECKED_CAST")
private fun findInitBlockOrConstructorImpl(): InitBlockOrConstructorImpl? {
for (scope in allScopes.reversed()) {
val irElement = scope.irElement
when {
irElement is IrAnonymousInitializer -> return InitBlockOrConstructorImpl.InitBlock
irElement is IrClass && irElement.isInline -> return InitBlockOrConstructorImpl.InitBlock
irElement is IrFunction && irElement.origin == JvmLoweredDeclarationOrigin.STATIC_INLINE_CLASS_CONSTRUCTOR ->
return InitBlockOrConstructorImpl.ConstructorImpl(irElement)
}
}
return null
}
private val IrCall.isSpecializedInlineClassEqEq: Boolean
get() {
// Note that reference equality (x === y) is not allowed on values of inline class type,
@@ -456,6 +479,13 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F
it.type, it.symbol, expression.origin
)
}
val owner = expression.symbol.owner
if (owner is IrValueParameter && (owner.parent as? IrClass)?.isInline == true && owner.origin == IrDeclarationOrigin.INSTANCE_RECEIVER) {
val ctor = findInitBlockOrConstructorImpl()
if (ctor is InitBlockOrConstructorImpl.ConstructorImpl) {
return getConstructorImplArgumentValue(ctor, expression.type)
}
}
return super.visitGetValue(expression)
}
@@ -496,13 +526,24 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F
// Add a static bridge method to the primary constructor.
// This is a placeholder for null-checks and default arguments.
val function = context.inlineClassReplacements.getReplacementFunction(irConstructor)!!
val initBlocks = irClass.declarations.filterIsInstance<IrAnonymousInitializer>()
function.valueParameters.forEach { it.transformChildrenVoid() }
with(context.createIrBuilder(function.symbol)) {
val argument = function.valueParameters[0]
function.body = irExprBody(
coerceInlineClasses(irGet(argument), argument.type, function.returnType)
)
val argument: IrValueParameter = function.valueParameters[0]
function.body = irBlockBody {
for (initBlock in initBlocks) {
for (stmt in initBlock.body.statements) {
+stmt
}
}
+irReturn(coerceInlineClasses(irGet(argument), argument.type, function.returnType))
}
}
function.accept(this, null)
irClass.declarations.removeAll(initBlocks)
irClass.declarations += function
}
@@ -549,3 +590,8 @@ private class JvmInlineClassLowering(private val context: JvmBackendContext) : F
irClass.declarations += function
}
}
private sealed class InitBlockOrConstructorImpl {
object InitBlock : InitBlockOrConstructorImpl()
class ConstructorImpl(val irElement: IrFunction) : InitBlockOrConstructorImpl()
}
@@ -0,0 +1,24 @@
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND: JS_IR
// IGNORE_LIGHT_ANALYSIS
inline class IC(val i: Int) {
init {
counter += i
}
}
var counter = 0
fun <T> id(t: T) = t
fun box(): String {
val ic = IC(42)
if (counter != 42) return "FAIL 1: $counter"
counter = 0
id(ic)
if (counter != 0) return "FAIL 2: $counter"
return "OK"
}
+172
View File
@@ -0,0 +1,172 @@
// IGNORE_BACKEND: JVM
// IGNORE_BACKEND: JS_IR
// IGNORE_LIGHT_ANALYSIS
inline class SingleInitBlock(val s: String) {
init {
res = s
}
}
inline class MultipleInitBlocks(val a: Any?) {
init {
res = "O"
}
init {
res += "K"
}
}
inline class Lambda(val s: String) {
init {
val lambda = { res = s }
lambda()
}
}
inline class FunLiteral(val s: String) {
init {
val funLiteral = fun() {
res = s
}
funLiteral()
}
}
inline class ObjectLiteral(val s: String) {
init {
val objectLiteral = object {
fun run() {
res = s
}
}
objectLiteral.run()
}
}
inline class LocalFunction(val s: String) {
init {
fun local() {
res = s
}
local()
}
}
inline class LocalClass(val s: String) {
init {
class Local {
fun run() {
res = s
}
}
Local().run()
}
}
inline class Getter(val s: String) {
init {
res = ok
}
val ok: String
get() = s
}
inline class GetterThis(val s: String) {
init {
res = this.ok
}
val ok: String
get() = s
}
inline class Method(val s: String) {
init {
res = ok(this)
}
fun ok(m: Method): String = m.s
}
inline class MethodThis(val s: String) {
init {
res = this.ok(this)
}
fun ok(m: MethodThis): String = m.s
}
inline class InlineFun(val s: String) {
init {
res = ok()
}
inline fun ok(): String = s
}
inline class InlineFunThis(val s: String) {
init {
res = this.ok()
}
inline fun ok(): String = s
}
var res: String = "FAIL"
fun box(): String {
SingleInitBlock("OK")
if (res != "OK") return "FAIL 1: $res"
res = "FAIL 2"
MultipleInitBlocks(null)
if (res != "OK") return "FAIL 21: $res"
res = "FAIL 3"
Lambda("OK")
if (res != "OK") return "FAIL 31: $res"
res = "FAIL 4"
FunLiteral("OK")
if (res != "OK") return "FAIL 41: $res"
res = "FAIL 5"
ObjectLiteral("OK")
if (res != "OK") return "FAIL 51: $res"
res = "FAIL 6"
LocalFunction("OK")
if (res != "OK") return "FAIL 61: $res"
res = "FAIL 7"
LocalClass("OK")
if (res != "OK") return "FAIL 71: $res"
res = "FAIL 8"
Getter("OK")
if (res != "OK") return "FAIL 81: $res"
res = "FAIL 9"
GetterThis("OK")
if (res != "OK") return "FAIL 91: $res"
res = "FAIL 10"
Method("OK")
if (res != "OK") return "FAIL 101: $res"
res = "FAIL 11"
MethodThis("OK")
if (res != "OK") return "FAIL 111: $res"
res = "FAIL 12"
InlineFun("OK")
if (res != "OK") return "FAIL 121: $res"
res = "FAIL 13"
InlineFunThis("OK")
if (res != "OK") return "FAIL 131: $res"
return "OK"
}
@@ -2,9 +2,9 @@
// !DIAGNOSTICS: -UNUSED_VARIABLE
inline class Foo(val x: Int) {
<!INLINE_CLASS_WITH_INITIALIZER!>init<!> {}
init {}
<!INLINE_CLASS_WITH_INITIALIZER!>init<!> {
init {
val f = 1
}
}
@@ -14817,6 +14817,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -15102,6 +15107,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
@@ -14799,6 +14799,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
runTest("compiler/testData/codegen/box/inlineClasses/anySuperCall.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void ignoreBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("initBlock.kt")
public void ignoreInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassWithCustomEquals.kt")
public void ignoreInlineClassWithCustomEquals() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassWithCustomEquals.kt");
@@ -13417,6 +13417,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -13702,6 +13707,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
@@ -11502,6 +11502,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -11772,6 +11777,11 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
@@ -11502,6 +11502,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -11772,6 +11777,11 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");
@@ -11567,6 +11567,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/boundCallableReferencePassedToInlineFunction.kt");
}
@TestMetadata("boxImplDoesNotExecuteInitBlock.kt")
public void testBoxImplDoesNotExecuteInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxImplDoesNotExecuteInitBlock.kt");
}
@TestMetadata("boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt")
public void testBoxNullableValueOfInlineClassWithNonNullUnderlyingType() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/boxNullableValueOfInlineClassWithNonNullUnderlyingType.kt");
@@ -11837,6 +11842,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
runTest("compiler/testData/codegen/box/inlineClasses/genericVararg2ndConstructor.kt");
}
@TestMetadata("initBlock.kt")
public void testInitBlock() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/initBlock.kt");
}
@TestMetadata("inlineClassAsLastExpressionInInLambda.kt")
public void testInlineClassAsLastExpressionInInLambda() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/inlineClassAsLastExpressionInInLambda.kt");