From af1b7c49b4aeb64c914e5a6a313dcbac2d02c51a Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 17 Jan 2017 17:23:47 +0300 Subject: [PATCH] JS: insert dummy initializers for property that initialized on `init` block or in secondary constructors. See KT-8211 --- .../js/test/semantics/BoxJsTestGenerated.java | 6 ++ .../initializer/InitializerVisitor.java | 38 ++++++++++- .../initialize/propertyInitializationOrder.kt | 66 +++++++++++++++++++ 3 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 js/js.translator/testData/box/initialize/propertyInitializationOrder.kt diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index c8d1f044558..d135100abb8 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3611,6 +3611,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { doTest(fileName); } + @TestMetadata("propertyInitializationOrder.kt") + public void testPropertyInitializationOrder() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/propertyInitializationOrder.kt"); + doTest(fileName); + } + @TestMetadata("rootPackageValInit.kt") public void testRootPackageValInit() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/rootPackageValInit.kt"); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java index 22db74a58e3..9bdd829212f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/initializer/InitializerVisitor.java @@ -16,16 +16,21 @@ package org.jetbrains.kotlin.js.translate.initializer; -import org.jetbrains.kotlin.js.backend.ast.JsExpression; -import org.jetbrains.kotlin.js.backend.ast.JsStatement; import org.jetbrains.annotations.NotNull; +import org.jetbrains.kotlin.builtins.KotlinBuiltIns; import org.jetbrains.kotlin.descriptors.PropertyDescriptor; +import org.jetbrains.kotlin.js.backend.ast.*; +import org.jetbrains.kotlin.js.translate.context.Namer; import org.jetbrains.kotlin.js.translate.context.TranslationContext; import org.jetbrains.kotlin.js.translate.declaration.PropertyTranslatorKt; import org.jetbrains.kotlin.js.translate.general.TranslatorVisitor; import org.jetbrains.kotlin.js.translate.utils.BindingUtils; import org.jetbrains.kotlin.js.translate.utils.JsAstUtils; +import org.jetbrains.kotlin.js.translate.utils.JsDescriptorUtils; +import org.jetbrains.kotlin.js.translate.utils.TranslationUtils; import org.jetbrains.kotlin.psi.*; +import org.jetbrains.kotlin.resolve.BindingContext; +import org.jetbrains.kotlin.types.KotlinType; import static org.jetbrains.kotlin.js.translate.general.Translation.translateAsStatementAndMergeInBlockIfNeeded; import static org.jetbrains.kotlin.js.translate.initializer.InitializerUtils.generateInitializerForDelegate; @@ -49,10 +54,19 @@ public final class InitializerVisitor extends TranslatorVisitor { assert value != null; statement = generateInitializerForProperty(context, descriptor, value); } - if (delegate != null) { + else if (delegate != null) { assert value != null; statement = generateInitializerForDelegate(descriptor, value); } + else if (Boolean.TRUE.equals(context.bindingContext().get(BindingContext.BACKING_FIELD_REQUIRED, descriptor))) { + JsExpression defaultValue = generateDefaultValue(descriptor, context); + statement = TranslationUtils.assignmentToBackingField(context, descriptor, defaultValue).makeStmt(); + } + else if (JsDescriptorUtils.isSimpleFinalProperty(descriptor)) { + JsExpression defaultValue = generateDefaultValue(descriptor, context); + JsName propertyName = context.getNameForDescriptor(descriptor); + statement = JsAstUtils.assignment(new JsNameRef(propertyName, JsLiteral.THIS), defaultValue).makeStmt(); + } if (statement != null && !JsAstUtils.isEmptyStatement(statement)) { context.addStatementsToCurrentBlock(JsAstUtils.flattenStatement(statement)); @@ -61,6 +75,24 @@ public final class InitializerVisitor extends TranslatorVisitor { return null; } + @NotNull + private static JsExpression generateDefaultValue(@NotNull PropertyDescriptor property, @NotNull TranslationContext context) { + if (property.isLateInit()) return Namer.getUndefinedExpression(); + + KotlinType type = property.getType(); + if (KotlinBuiltIns.isInt(type) || KotlinBuiltIns.isFloat(type) || KotlinBuiltIns.isDouble(type) || + KotlinBuiltIns.isByte(type) || KotlinBuiltIns.isShort(type) + ) { + return context.program().getNumberLiteral(0); + } + else if (KotlinBuiltIns.isBoolean(type)) { + return JsLiteral.FALSE; + } + else { + return JsLiteral.NULL; + } + } + @Override public Void visitAnonymousInitializer(@NotNull KtAnonymousInitializer initializer, @NotNull TranslationContext context) { KtExpression initializerBody = initializer.getBody(); diff --git a/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt b/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt new file mode 100644 index 00000000000..a6d5e5427b2 --- /dev/null +++ b/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt @@ -0,0 +1,66 @@ +open class A { + val a: Int + open val b: Int + val c = 99 + val d: Int = 555 + get() = field + + val e: Int + get() = field + + @get:JsName("getF") + val f: Int + + @get:JsName("getG") + val g: Int = 777 + + lateinit var h: String + + init { + foo() + a = 23 + b = 42 + e = 987 + f = 888 + } +} + +fun foo() {} + +fun box(): String { + val aBody = js("A").toString() + val expectedRegex = build { + property("a") + field("b") + property("c") + field("d") + field("e") + field("f") + field("g") + property("h") + } + if (expectedRegex.find(aBody) == null) return "fail" + + return "OK" +} + +fun build(f: RegexBuilder.() -> Unit): Regex { + val builder = RegexBuilder() + builder.f() + return Regex(builder.string + "foo()", RegexOption.MULTILINE) +} + +class RegexBuilder { + var string = "" + + fun property(name: String) { + string += "this.$name\\s+=$ANY_CHARS" + } + + fun field(name: String) { + string += "this.$name$FIELD_SUFFIX\\s+=$ANY_CHARS" + } +} + +val ANY_CHARS = "(.|\n)+" +val FIELD_SUFFIX = "_[a-zA-Z0-9\\\$_]+" \ No newline at end of file