From 91bd565972cce2bc83381a05bdf3307cb07e65d7 Mon Sep 17 00:00:00 2001 From: Igor Chevdar Date: Tue, 20 Feb 2018 13:30:33 +0300 Subject: [PATCH] Implemented isInitialized for lateinit properties + tests --- .../kotlin/backend/konan/KonanLower.kt | 7 +- .../kotlin/backend/konan/KonanPhases.kt | 6 +- .../konan/descriptors/DescriptorUtils.kt | 4 +- .../backend/konan/lower/LateinitLowering.kt | 69 +++++++++++++------ backend.native/tests/build.gradle | 10 +++ .../codegen/lateinit/globalIsInitialized.kt | 15 ++++ .../tests/codegen/lateinit/isInitialized.kt | 18 +++++ .../main/kotlin/konan/internal/Annotations.kt | 2 +- .../kotlin/kotlin/internal/Annotations.kt | 9 +++ .../src/main/kotlin/kotlin/util/Lateinit.kt | 19 +++++ 10 files changed, 129 insertions(+), 30 deletions(-) create mode 100644 backend.native/tests/codegen/lateinit/globalIsInitialized.kt create mode 100644 backend.native/tests/codegen/lateinit/isInitialized.kt create mode 100644 runtime/src/main/kotlin/kotlin/util/Lateinit.kt diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt index 9252da31f42..a7225c220f7 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanLower.kt @@ -73,6 +73,10 @@ internal class KonanLower(val context: Context) { irModule.files.forEach(InteropLoweringPart1(context)::lower) } + phaser.phase(KonanPhase.LOWER_LATEINIT) { + irModule.files.forEach(LateinitLowering(context)::lower) + } + @Suppress("DEPRECATION") irModule.replaceUnboundSymbols(context) validateIrModule(context, irModule) @@ -96,9 +100,6 @@ internal class KonanLower(val context: Context) { phaser.phase(KonanPhase.LOWER_INITIALIZERS) { InitializersLowering(context).runOnFilePostfix(irFile) } - phaser.phase(KonanPhase.LOWER_LATEINIT) { - LateinitLowering(context).lower(irFile) - } phaser.phase(KonanPhase.LOWER_SHARED_VARIABLES) { SharedVariablesLowering(context).runOnFilePostfix(irFile) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index 95af5795891..c283b4edd13 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -41,8 +41,8 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_ENUMS("Enum classes lowering"), /* ... ... */ LOWER_DELEGATION("Delegation lowering"), /* ... ... */ LOWER_INITIALIZERS("Initializers lowering", LOWER_ENUMS), - /* ... ... */ LOWER_LATEINIT("Lateinit properties lowering"), - /* ... ... */ LOWER_SHARED_VARIABLES("Shared Variable Lowering", LOWER_INITIALIZERS, LOWER_LATEINIT), + /* ... ... */ LOWER_LATEINIT("Lateinit properties lowering", LOWER_INLINE), + /* ... ... */ LOWER_SHARED_VARIABLES("Shared Variable Lowering", LOWER_INITIALIZERS), /* ... ... */ LOWER_CALLABLES("Callable references Lowering", LOWER_DELEGATION), /* ... ... */ LOWER_LOCAL_FUNCTIONS("Local Function Lowering", LOWER_SHARED_VARIABLES, LOWER_CALLABLES), /* ... ... */ LOWER_INTEROP_PART2("Interop lowering, part 2", LOWER_LOCAL_FUNCTIONS), @@ -51,7 +51,7 @@ enum class KonanPhase(val description: String, /* ... ... */ LOWER_FINALLY("Finally blocks lowering", LOWER_INITIALIZERS, LOWER_LOCAL_FUNCTIONS, LOWER_TAILREC), /* ... ... */ LOWER_DEFAULT_PARAMETER_EXTENT("Default Parameter Extent Lowering", LOWER_TAILREC, LOWER_ENUMS), /* ... ... */ LOWER_INNER_CLASSES("Inner classes lowering", LOWER_DEFAULT_PARAMETER_EXTENT), - /* ... ... */ LOWER_BUILTIN_OPERATORS("BuiltIn Operators Lowering", LOWER_DEFAULT_PARAMETER_EXTENT, LOWER_LATEINIT), + /* ... ... */ LOWER_BUILTIN_OPERATORS("BuiltIn Operators Lowering", LOWER_DEFAULT_PARAMETER_EXTENT), /* ... ... */ LOWER_COROUTINES("Coroutines lowering", LOWER_LOCAL_FUNCTIONS), /* ... ... */ LOWER_TYPE_OPERATORS("Type operators lowering", LOWER_COROUTINES), /* ... ... */ BRIDGES_BUILDING("Bridges building", LOWER_COROUTINES), diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt index ea04460c123..6a94f3ff600 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/DescriptorUtils.kt @@ -75,7 +75,7 @@ internal fun T.resolveFakeOverride(): T { private val intrinsicAnnotation = FqName("konan.internal.Intrinsic") // TODO: check it is external? -internal val FunctionDescriptor.isIntrinsic: Boolean +internal val CallableDescriptor.isIntrinsic: Boolean get() = this.annotations.findAnnotation(intrinsicAnnotation) != null internal fun FunctionDescriptor.externalOrIntrinsic() = isExternal || isIntrinsic || (this is IrBuiltinOperatorDescriptorBase) @@ -305,7 +305,7 @@ internal val FunctionDescriptor.needsInlining: Boolean get() { val inlineConstructor = annotations.hasAnnotation(inlineConstructor) if (inlineConstructor) return true - return (this.isInline && !this.isExternal) + return (this.isInline && !this.isExternal && !this.propertyIfAccessor.isIntrinsic) } internal val FunctionDescriptor.needsSerializedIr: Boolean diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt index c2038671e47..a7fcb7891db 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/LateinitLowering.kt @@ -18,9 +18,7 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.CommonBackendContext import org.jetbrains.kotlin.backend.common.FileLoweringPass -import org.jetbrains.kotlin.backend.common.lower.createIrBuilder -import org.jetbrains.kotlin.backend.common.lower.irBlock -import org.jetbrains.kotlin.backend.common.lower.irIfThen +import org.jetbrains.kotlin.backend.common.lower.* import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.VariableDescriptor import org.jetbrains.kotlin.ir.IrStatement @@ -29,34 +27,45 @@ import org.jetbrains.kotlin.ir.declarations.IrFile import org.jetbrains.kotlin.ir.declarations.IrFunction import org.jetbrains.kotlin.ir.declarations.IrProperty import org.jetbrains.kotlin.ir.declarations.IrVariable +import org.jetbrains.kotlin.ir.expressions.IrCall import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.IrPropertyReference import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl import org.jetbrains.kotlin.ir.symbols.IrFieldSymbol import org.jetbrains.kotlin.ir.symbols.IrSymbol -import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.ir.expressions.impl.IrGetFieldImpl +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.types.TypeUtils -class LateinitLowering( +internal class LateinitLowering( val context: CommonBackendContext, private val generateParameterNameInAssertion: Boolean = false ) : FileLoweringPass { + + private val KOTLIN_FQ_NAME = FqName("kotlin") + private val kotlinPackageScope = context.ir.irModule.descriptor.getPackage(KOTLIN_FQ_NAME).memberScope + private val isInitializedPropertyDescriptor = kotlinPackageScope + .getContributedVariables(Name.identifier("isInitialized"), NoLookupLocation.FROM_BACKEND).single { + it.extensionReceiverParameter.let { it != null && TypeUtils.getClassDescriptor(it.type) == context.reflectionTypes.kProperty0 } + } + private val isInitializedGetterDescriptor = isInitializedPropertyDescriptor.getter!! + override fun lower(irFile: IrFile) { - irFile.transformChildrenVoid(object : IrElementTransformerVoid() { + irFile.transformChildrenVoid(object : IrBuildingTransformer(context) { override fun visitVariable(declaration: IrVariable): IrStatement { declaration.transformChildrenVoid(this) - val symbol = declaration.symbol val descriptor = declaration.descriptor if (!descriptor.isLateInit) return declaration assert(declaration.initializer == null, { "'lateinit' modifier is not allowed for variables with initializer" }) assert(!KotlinBuiltIns.isPrimitiveType(descriptor.type), { "'lateinit' modifier is not allowed on primitive types" }) - val startOffset = declaration.startOffset - val endOffset = declaration.endOffset - val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset) - irBuilder.run { + builder.at(declaration).run { declaration.initializer = irNull() } return declaration @@ -68,10 +77,7 @@ class LateinitLowering( if (descriptor == null || !descriptor.isLateInit) return expression assert(!KotlinBuiltIns.isPrimitiveType(descriptor.type), { "'lateinit' modifier is not allowed on primitive types" }) - val startOffset = expression.startOffset - val endOffset = expression.endOffset - val irBuilder = context.createIrBuilder(symbol, startOffset, endOffset) - irBuilder.run { + builder.at(expression).run { return irBlock(expression) { // TODO: do data flow analysis to check if value is proved to be not-null. +irIfThen( @@ -83,6 +89,31 @@ class LateinitLowering( } } + override fun visitCall(expression: IrCall): IrExpression { + expression.transformChildrenVoid(this) + + val descriptor = expression.descriptor + if (descriptor != isInitializedGetterDescriptor) return expression + + val propertyReference = expression.extensionReceiver!! as IrPropertyReference + assert(propertyReference.extensionReceiver == null, { "'lateinit' modifier is not allowed on extension properties" }) + // TODO: Take propertyReference.fieldSymbol as soon as it will show up in IR. + val propertyDescriptor = propertyReference.descriptor + + val type = propertyDescriptor.type + assert(!KotlinBuiltIns.isPrimitiveType(type), { "'lateinit' modifier is not allowed on primitive types" }) + builder.at(expression).run { + @Suppress("DEPRECATION") + val fieldValue = IrGetFieldImpl( + expression.startOffset, + expression.endOffset, + propertyDescriptor, + propertyReference.dispatchReceiver + ) + return irNotEquals(fieldValue, irNull()) + } + } + override fun visitProperty(declaration: IrProperty): IrStatement { declaration.transformChildrenVoid(this) @@ -93,9 +124,7 @@ class LateinitLowering( transformGetter(backingField.symbol, declaration.getter!!) assert(backingField.initializer == null, { "'lateinit' modifier is not allowed for properties with initializer" }) - val startOffset = declaration.startOffset - val endOffset = declaration.endOffset - val irBuilder = context.createIrBuilder(backingField.symbol, startOffset, endOffset) + val irBuilder = context.createIrBuilder(backingField.symbol, declaration.startOffset, declaration.endOffset) irBuilder.run { backingField.initializer = irExprBody(irNull()) } @@ -106,9 +135,7 @@ class LateinitLowering( private fun transformGetter(backingFieldSymbol: IrFieldSymbol, getter: IrFunction) { val type = backingFieldSymbol.descriptor.type assert(!KotlinBuiltIns.isPrimitiveType(type), { "'lateinit' modifier is not allowed on primitive types" }) - val startOffset = getter.startOffset - val endOffset = getter.endOffset - val irBuilder = context.createIrBuilder(getter.symbol, startOffset, endOffset) + val irBuilder = context.createIrBuilder(getter.symbol, getter.startOffset, getter.endOffset) irBuilder.run { getter.body = irBlockBody { val resultVar = irTemporary( diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index cdd8d9b7104..d7fd0da6ba3 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -1058,6 +1058,16 @@ task lateinit_localCapturedNotInitialized(type: RunKonanTest) { source = "codegen/lateinit/localCapturedNotInitialized.kt" } +task lateinit_isInitialized(type: RunKonanTest) { + goldValue = "false\ntrue\n" + source = "codegen/lateinit/isInitialized.kt" +} + +task lateinit_globalIsInitialized(type: RunKonanTest) { + goldValue = "false\ntrue\n" + source = "codegen/lateinit/globalIsInitialized.kt" +} + task kclass0(type: RunKonanTest) { source = "codegen/kclass/kclass0.kt" } diff --git a/backend.native/tests/codegen/lateinit/globalIsInitialized.kt b/backend.native/tests/codegen/lateinit/globalIsInitialized.kt new file mode 100644 index 00000000000..5915817fde2 --- /dev/null +++ b/backend.native/tests/codegen/lateinit/globalIsInitialized.kt @@ -0,0 +1,15 @@ +package codegen.lateinit.globalIsInitialized + +import kotlin.test.* + +lateinit var s: String + +fun foo() { + println(::s.isInitialized) +} + +@Test fun runTest() { + foo() + s = "zzz" + foo() +} \ No newline at end of file diff --git a/backend.native/tests/codegen/lateinit/isInitialized.kt b/backend.native/tests/codegen/lateinit/isInitialized.kt new file mode 100644 index 00000000000..bb106be0150 --- /dev/null +++ b/backend.native/tests/codegen/lateinit/isInitialized.kt @@ -0,0 +1,18 @@ +package codegen.lateinit.isInitialized + +import kotlin.test.* + +class A { + lateinit var s: String + + fun foo() { + println(::s.isInitialized) + } +} + +@Test fun runTest() { + val a = A() + a.foo() + a.s = "zzz" + a.foo() +} \ No newline at end of file diff --git a/runtime/src/main/kotlin/konan/internal/Annotations.kt b/runtime/src/main/kotlin/konan/internal/Annotations.kt index 89fc804f285..f51d6f5d525 100644 --- a/runtime/src/main/kotlin/konan/internal/Annotations.kt +++ b/runtime/src/main/kotlin/konan/internal/Annotations.kt @@ -40,7 +40,7 @@ annotation class CName(val fullName: String = "", val shortName: String = "") /** * This annotation denotes that the element is intrinsic and its usages require special handling in compiler. */ -@Target(AnnotationTarget.FUNCTION) +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.BINARY) annotation class Intrinsic diff --git a/runtime/src/main/kotlin/kotlin/internal/Annotations.kt b/runtime/src/main/kotlin/kotlin/internal/Annotations.kt index 74686447dcd..696996b53b1 100644 --- a/runtime/src/main/kotlin/kotlin/internal/Annotations.kt +++ b/runtime/src/main/kotlin/kotlin/internal/Annotations.kt @@ -76,3 +76,12 @@ internal annotation class InlineExposed @Target(AnnotationTarget.TYPE_PARAMETER) @Retention(AnnotationRetention.BINARY) internal annotation class PureReifiable + +/** + * The value of this parameter should be a property reference expression (`this::foo`), referencing a `lateinit` property, + * the backing field of which is accessible at the point where the corresponding argument is passed. + */ +@Target(AnnotationTarget.VALUE_PARAMETER) +@Retention(AnnotationRetention.BINARY) +@SinceKotlin("1.2") +internal annotation class AccessibleLateinitPropertyLiteral diff --git a/runtime/src/main/kotlin/kotlin/util/Lateinit.kt b/runtime/src/main/kotlin/kotlin/util/Lateinit.kt new file mode 100644 index 00000000000..c58baaa7a6c --- /dev/null +++ b/runtime/src/main/kotlin/kotlin/util/Lateinit.kt @@ -0,0 +1,19 @@ +@file:Suppress("unused", "WRONG_ANNOTATION_TARGET_WITH_USE_SITE_TARGET_ON_TYPE") + +package kotlin + +import kotlin.internal.InlineOnly +import kotlin.internal.AccessibleLateinitPropertyLiteral +import kotlin.reflect.KProperty0 +import konan.internal.Intrinsic + +/** + * Returns `true` if this lateinit property has been assigned a value, and `false` otherwise. + * + * Cannot be used in an inline function, to avoid binary compatibility issues. + */ +@SinceKotlin("1.2") +@InlineOnly +@Intrinsic +inline val @receiver:AccessibleLateinitPropertyLiteral KProperty0<*>.isInitialized: Boolean + get() = throw NotImplementedError("Implementation is intrinsic")