JS: throw exception when accessing uninitialized lateinit property (KT-14964)
This commit is contained in:
@@ -1,6 +1,3 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
public class A {
|
||||
|
||||
fun getMyStr(): String {
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
class A {
|
||||
private lateinit var str: String
|
||||
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
class A {
|
||||
public lateinit var str: String
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS
|
||||
|
||||
interface Intf {
|
||||
val str: String
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
// FILE: lateinit.kt
|
||||
private lateinit var s: String
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
|
||||
import kotlin.UninitializedPropertyAccessException
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// LANGUAGE_VERSION: 1.2
|
||||
// WITH_RUNTIME
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
// IGNORE_BACKEND: NATIVE
|
||||
|
||||
import kotlin.UninitializedPropertyAccessException
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
// TODO: muted automatically, investigate should it be ran for JS or not
|
||||
// IGNORE_BACKEND: JS, NATIVE
|
||||
|
||||
// TARGET_BACKEND: JVM
|
||||
// WITH_RUNTIME
|
||||
// FULL_JDK
|
||||
|
||||
|
||||
@@ -30,3 +30,8 @@ private fun throwCCE() {
|
||||
private fun throwISE(message: String) {
|
||||
throw IllegalStateException(message)
|
||||
}
|
||||
|
||||
@JsName("throwUPAE")
|
||||
private fun throwUPAE(propertyName: String) {
|
||||
throw UninitializedPropertyAccessException("lateinit property ${propertyName} has not been initialized")
|
||||
}
|
||||
|
||||
@@ -43,3 +43,5 @@ public open class AssertionError(message: String? = null) : Error(message)
|
||||
public open class NoSuchElementException(message: String? = null) : Exception(message)
|
||||
|
||||
public open class NoWhenBranchMatchedException(message: String? = null) : RuntimeException(message)
|
||||
|
||||
public open class UninitializedPropertyAccessException(message: String? = null) : RuntimeException(message)
|
||||
|
||||
@@ -3916,6 +3916,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/rootValInit.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("uninitializedLateinit.kt")
|
||||
public void testUninitializedLateinit() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/uninitializedLateinit.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("js/js.translator/testData/box/inline")
|
||||
|
||||
+7
-61
@@ -15003,13 +15003,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("accessorException.kt")
|
||||
public void testAccessorException() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/accessorException.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInLateinit() throws Exception {
|
||||
@@ -15019,25 +15013,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("exceptionField.kt")
|
||||
public void testExceptionField() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/exceptionField.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("exceptionGetter.kt")
|
||||
public void testExceptionGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("override.kt")
|
||||
@@ -15049,13 +15031,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("overrideException.kt")
|
||||
public void testOverrideException() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/overrideException.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("privateSetter.kt")
|
||||
@@ -15076,18 +15052,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("visibility.kt")
|
||||
public void testVisibility() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/visibility.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/codegen/box/properties/lateinit/local")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
@@ -15164,13 +15128,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("accessorException.kt")
|
||||
public void testAccessorException() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("accessorForTopLevelLateinit.kt")
|
||||
@@ -15192,25 +15150,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
||||
@TestMetadata("uninitializedMemberAccess.kt")
|
||||
public void testUninitializedMemberAccess() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("uninitializedRead.kt")
|
||||
public void testUninitializedRead() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt");
|
||||
try {
|
||||
doTest(fileName);
|
||||
}
|
||||
catch (Throwable ignore) {
|
||||
return;
|
||||
}
|
||||
throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that.");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +90,7 @@ public final class Namer {
|
||||
private static final String THROW_NPE_FUN_NAME = "throwNPE";
|
||||
private static final String THROW_CLASS_CAST_EXCEPTION_FUN_NAME = "throwCCE";
|
||||
private static final String THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME = "throwISE";
|
||||
private static final String THROW_UNINITIALIZED_PROPERTY_ACCESS_EXCEPTION = "throwUPAE";
|
||||
private static final String PROTOTYPE_NAME = "prototype";
|
||||
private static final String CAPTURED_VAR_FIELD = "v";
|
||||
|
||||
@@ -235,6 +236,11 @@ public final class Namer {
|
||||
return new JsNameRef(THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME, kotlinObject());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsExpression throwUninitializedPropertyAccessExceptionFunRef() {
|
||||
return new JsNameRef(THROW_UNINITIALIZED_PROPERTY_ACCESS_EXCEPTION, kotlinObject());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static JsNameRef kotlin(@NotNull JsName name) {
|
||||
return pureFqn(name, kotlinObject());
|
||||
|
||||
+5
@@ -94,6 +94,11 @@ class DefaultPropertyTranslator(
|
||||
assert(!descriptor.isExtension) { "Unexpected extension property $descriptor}" }
|
||||
assert(descriptor is PropertyDescriptor) { "Property descriptor expected: $descriptor" }
|
||||
val result = backingFieldReference(context(), descriptor as PropertyDescriptor)
|
||||
if (getterDescriptor is PropertyAccessorDescriptor && getterDescriptor.correspondingProperty.isLateInit) {
|
||||
function.body.statements += JsIf(JsBinaryOperation(JsBinaryOperator.EQ, result, JsNullLiteral()),
|
||||
JsReturn(JsInvocation(Namer.throwUninitializedPropertyAccessExceptionFunRef(),
|
||||
JsStringLiteral(getterDescriptor.correspondingProperty.name.asString()))))
|
||||
}
|
||||
function.body.statements += JsReturn(result).apply { source = descriptor.source.getPsi() }
|
||||
}
|
||||
|
||||
|
||||
@@ -125,7 +125,8 @@ public final class JsDescriptorUtils {
|
||||
}
|
||||
|
||||
private static boolean isDefaultAccessor(@Nullable PropertyAccessorDescriptor accessorDescriptor) {
|
||||
return accessorDescriptor == null || accessorDescriptor.isDefault();
|
||||
return accessorDescriptor == null || accessorDescriptor.isDefault() &&
|
||||
!(accessorDescriptor instanceof PropertySetterDescriptor && accessorDescriptor.getCorrespondingProperty().isLateInit());
|
||||
}
|
||||
|
||||
public static boolean sideEffectsPossibleOnRead(@NotNull PropertyDescriptor property) {
|
||||
|
||||
@@ -39,7 +39,7 @@ fun box(): String {
|
||||
field("e")
|
||||
field("f")
|
||||
field("g")
|
||||
property("h")
|
||||
field("h")
|
||||
}
|
||||
if (expectedRegex.find(aBody) == null) return "fail"
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
// EXPECTED_REACHABLE_NODES: 1059
|
||||
// MODULE: lib
|
||||
// FILE: lib.kt
|
||||
open class A {
|
||||
private lateinit var a: Any
|
||||
open lateinit var c: Any
|
||||
|
||||
fun useA() = a
|
||||
fun useC() = c
|
||||
|
||||
companion object {
|
||||
lateinit var b: Any
|
||||
|
||||
fun useB() = b
|
||||
}
|
||||
}
|
||||
|
||||
object O {
|
||||
lateinit var o: Any
|
||||
|
||||
fun useO() = o
|
||||
}
|
||||
|
||||
fun <T> checkException(propertyName: String, f: () -> T) {
|
||||
try {
|
||||
f()
|
||||
}
|
||||
catch (e: kotlin.UninitializedPropertyAccessException) {
|
||||
assertEquals(e.message, "lateinit property ${propertyName} has not been initialized")
|
||||
}
|
||||
}
|
||||
|
||||
fun runChecks() {
|
||||
checkException("a") { A().useA() }
|
||||
checkException("b") { A.b }
|
||||
checkException("b") { A.useB() }
|
||||
checkException("o") { O.o }
|
||||
checkException("o") { O.useO() }
|
||||
}
|
||||
|
||||
// MODULE: main(lib)
|
||||
// FILE: main.kt
|
||||
|
||||
class C: A() {
|
||||
override lateinit var c: Any
|
||||
}
|
||||
|
||||
fun box(): String {
|
||||
runChecks()
|
||||
|
||||
checkException("a") { A().useA() }
|
||||
checkException("b") { A.b }
|
||||
checkException("b") { A.useB() }
|
||||
checkException("o") { O.o }
|
||||
checkException("o") { O.useO() }
|
||||
|
||||
checkException("a") { C().useA() }
|
||||
checkException("c") { C().c }
|
||||
checkException("c") { C().useC() }
|
||||
|
||||
return "OK"
|
||||
}
|
||||
Reference in New Issue
Block a user