Reputation: 272647
TL;DR Should functions with reified types take account of type-parameter nullability when generating code?
Test case
Consider the following Kotlin code; the only difference between the two methods is whether the type bound is nullable (Any?
) or not (Any
).
@Test
fun testNonNullableBound() {
val x: Int = nonNullableBound()
}
@Test
fun testNullableBound() {
val x: Int = nullableBound()
}
private inline fun <reified T : Any> nonNullableBound(): T {
return unsafeMethod()
}
private inline fun <reified T : Any?> nullableBound(): T {
return unsafeMethod()
}
where unsafeMethod
subverts the type system by being defined in Java:
public static <T> T unsafeMethod() { return null; }
This is Kotlin 1.1.4.
Expected behaviour
I'd expect these to behave equivalently - the type is reified, so the actual value of T
is known to be non-nullable, so a null check ought to be applied inside the function before the return
statement.
Observed behaviour
The two cases fail in different ways:
testNonNullableBound
behaves as expected (fails due to a null check on the value returned by unsafeMethod()
).testNullableBound
doesn't behave as expected - it fails with an NPE when doing the assignment to x
.So it appears the insertion of null checks is based on the type bound, rather than the actual type.
Analysis
For reference, the relevant bytecode is as follows. Note the null check added in testNonNullableBound
.
testNonNullableBound
public final testNonNullableBound()V
@Lorg/junit/Test;()
[...]
L1
LINENUMBER 28 L1
INVOKESTATIC JavaStuff.unsafeMethod ()Ljava/lang/Object;
DUP
LDC "unsafeMethod()"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
[...]
testNullableBound
public final testNullableBound()V
@Lorg/junit/Test;()
[...]
L1
LINENUMBER 27 L1
INVOKESTATIC JavaStuff.unsafeMethod ()Ljava/lang/Object;
[...]
Upvotes: 0
Views: 608
Reputation: 9273
The problem isn't if the function is inlined or not.
From Kotlin's documentation:
Any reference in Java may be null, which makes Kotlin's requirements of strict null-safety impractical for objects coming from Java. Types of Java declarations are treated specially in Kotlin and called platform types. Null-checks are relaxed for such types, so that safety guarantees for them are the same as in Java
Using a @Nullable
annotation, in java side, you can enforce kotlin to check if the type can either be null or not (using @NotNull
)
@Nullable
public static <T> T unsafeMethod() { return null; }
Upvotes: 1
Reputation: 33819
So it appears the insertion of null checks is based on the type bound, rather than the actual type.
Exactly right, this is how it is supposed to work!
A JVM function cannot have different byte code depending on the actual generic type, so the same implementation has to satisfy all possible outcomes.
Inlining doesn't affect this case because it follows the same rules as normal functions. It was designed in this way so that the developer is less surprised.
Upvotes: 1