Non-null type in nullable references - kotlin

I have a class for binding a viewholder
class ViewHolderBinder(val onBind: (ViewHolder) -> Unit, val onClick: () -> Unit)
There is a list of these items in presenter
val items: MutableList<ViewHolderBinder> = mutableListOf()
ViewHolderBinder contains 2 non-null functions, but this call causes compile-time error
items.getOrNull(position)?.onClick()
However this call compiles as expected
items.getOrNull(position)?.let { it.onClick() }
Maybe i have missed something, but 2 these constructions are fully equivalent and i prefer to use first one, but it is not compiling.
I am using kotlin 1.3.10

Here's the actual error message you're getting:
Reference has a nullable type '(() -> Unit)?', use explicit '?.invoke()' to make a function-like call instead
Suppose you had this code:
val binder: ViewHolderBinder? = getBinder()
binder?.onClick()
onClick() is not a function you can invoke on the ViewHolderBinder instance. It's a property that holds a callback object. The full syntax to invoke that callback's function is
binder?.onClick?.invoke()
Kotlin also offers a special shorthand syntax that would work on a non-nullable binder:
binder.onClick()
If you apply it to a nullable binder,
binder?.onClick()
it expands to
binder?.onClick.invoke()
The type of the expression binder?.onClick is (() -> Unit)?, just like the error says. You aren't allowed to apply the . operator to a nullable type.

Related

Reified inline function in Kotlin still leading to compiler error on 'is' check

I have the following function:
inline fun <reified T> create(preference: Preference<T>, title: String = ""): DebugOption{
val type = when (preference) {
is Preference<String> -> Type.STRING
is Preference<Boolean> -> Type.BOOLEAN
else -> Type.STRING
}
return DebugOption(type, preference, displayTitle = StringModel(title))
}
I was expecting to be able to easily perform this 'is' check, since the generic type is reified, but I am still getting a compiler error:
Cannot check for instance of erased type: Preference<String>
Cannot check for instance of erased type: Preference<Boolean>
So I am confused how am I misusing 'reified' / what am I missing here.
Is there a problem with using reified generic types as the type parameter of another class?
The problem is that is checks the runtime type of preference, and the runtime type of preference isn't available until all the generic type information has been erased. Reified types are not magic, after all.
What you can do instead, is check T instead, since as you said, T is indeed reified.
val type = when (T::class) {
String::class -> Type.STRING
Boolean::class -> Type.BOOLEAN
else -> Type.STRING
}
But note that if Preference is covariant (like List) or contravariant, this might not work as you expect in some cases. For example:
// suppose Preference is covariant
// (i.e. the type parameter is declared <out T>)
val pref: Preference<Any> = Preference<Boolean>()
create(pref, "foo")
In this case, T is inferred to be Any, so type will be assigned Type.STRING. If that is unexpected to you, and you want Type.BOOLEAN instead, you might want to use another way to check a preference's type, rather than reified types, as this cannot be determined at compile time.

What is exactly null in kotlin and why i can't cause NPE with it?

Look at the following code running in Kotlin REPL:
>>> null.takeIf({1==2})
res4: kotlin.Nothing? = null
Why this don't cause NullPointerException?
You code doesn't cause NullPointerException because takeIf is an extension function.
Extension functions are translated into method receiver, first argument being the object you invoked your function on.
So instead of null.takeIf { false } (simplifying your example), you could wonder why the following function doesn't throw a NullPointerException:
fun <T> takeIf(me: T, predicate: (T) -> Boolean): T? {
...
}
Well, for that we need to look into takeIf() implementation (removing annotations and contract):
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
return if (predicate(this)) this else null
}
So, in case your predicate is false (as in your example, 1==2), it just short-circuits and returns null.
What if it's true, then? Will we finally get a NullPointer?
Nope:
val nothing: Nothing? = null.takeIf {
true
}
Kotlin design approach for types aims to eliminate NPEs on purpose. Conditions that might trigger one are limited to, as seen on the docs:
An explicit call to throw NullPointerException();
Usage of the !! operator that is described below;
Some data inconsistency with regard to initialization, such as when:
An uninitialized this available in a constructor is passed and used somewhere ("leaking this");
A superclass constructor calls an open member whose implementation in the derived class uses uninitialized state;
Java interoperation:
Attempts to access a member on a null reference of a platform type;
Generic types used for Java interoperation with incorrect nullability, e.g. a piece of Java code might add null into a Kotlin MutableList, meaning that MutableList should be used for working with it;
Other issues caused by external Java code.
The third option is for NPE-lovers: the not-null assertion operator
(!!) converts any value to a non-null type and throws an exception if
the value is null. We can write b!!, and this will return a non-null
value of b (e.g., a String in our example) or throw an NPE if b is
null:
val l = b!!.length
Thus, if you want an NPE, you can have it, but you
have to ask for it explicitly, and it does not appear out of the blue.
So your code is behaving as expected. See the link for more info
https://kotlinlang.org/docs/reference/null-safety.html

::property.isInitialized cannot differentiate between method and property with same name

I'm creating a builder (for Java compat), where context is both a private property and public method.
private lateinit var context: Context
fun context(appContext: Context) = apply {
context = appContext
}
fun build(): MySdk {
// this::context fails to compile because it cannot differentiate between the
// method `context()` vs property `context`
require(this::context.isInitialized) {
"context == null"
}
But I get a compilation issue for ::context.isInitialized, because it cannot differentiate between the method context() vs property context
Does Kotlin have a workaround for this? or am I forced to use unique property/method names?
This is a case of overload resolution ambiguity and the kotlin compiler is unable to identify whether you are using the property or the method.
This is because of callable references (::) . Internally when you are using the callable references it calls a method.
Callable references : References to functions, properties, and
constructors, apart from introspecting the program structure, can also
be called or used as instances of function types.
The common supertype for all callable references is KCallable, where R is the return value type, which is the property type for properties, and the constructed type for constructors.
KCallable<out R> // supertype for all callable references
So, for function the type is KFunction and for properties the type is KProperty
interface KFunction<out R> : KCallable<R>, Function<R> (source)
interface KProperty<out R> : KCallable<R> (source)
When you use a function like :
fun context(appContext: Context) = apply {
context = appContext
}
It can be used as a Function reference
::context // This is a Function reference i.e. KFunction
When you use a property reference, like
private lateinit var context: Context
fun something(){
::context // this is a property reference, KProperty
}
A property reference can be used where a function with one parameter is expected:
val strs = listOf("a", "bc", "def")
println(strs.map(String::length))
So, its not that Kotlin forces you to use different property and function names("although it is not recommended"). Its just that its unable to differentiate in this case as
Both are KCallable and have the same name
A property reference can be used where a function with one parameter is expected
You can resolve the ambiguity between the property and the method by giving the expected type:
val prop: kotlin.reflect.KProperty0<*> = this::context
Alas, prop.isInitialized then gives a compilation error:
This declaration can only be called on a property literal (e.g. 'Foo::bar')
So this doesn't appear to be possible currently. OTOH, since the error shows isInitialized is already handled specially by the compiler, it's likely possible to fix; I suggest reporting it on http://youtrack.jetbrains.com/ (after searching for duplicates).

Why do unsafe .run() call works fine on a null value in Kotlin?

I have the following code fragment:
val foo: String? = null
foo.run { println("foo") }
I have here a nullable variable foo that is actually set to null followed by a nonsafe .run() call.
When I run the code snippet, I get foo printed out despite the fact that the run method is called on a null. Why is that? Why no NullPointerException? Why does compiler allow a nonsafe call on an optional value?
If I pass println(foo), I get a nice juicy null in the console so I think it's safe to assume that foo is actually null.
I believe, there are two things that both might be of some surprise: the language semantics that allow such a call, and what happens at runtime when this code executes.
From the language side, Kotlin allows nullable receiver, but only for extensions. To write an extension function that accepts a nullable receiver, one should either write the nullable type explicitly, or use a nullable upper bound for a type parameter (actually, when you specify no upper bound, the default one is nullable Any?):
fun List<*>?.isEmptyOrNull() = this == null || size == 0 // explicit nullable type
fun <T : CharSequence?> T.nullWhenEmpty() = if ("$this" == "") null else this // nullable T
fun <T> T.identity() = this // default upper bound Any? is nullable
This feature is used in kotlin-stdlib in several places: see CharSequence?.isNullOrEmpty(), CharSequence?.isNullOrBlank(), ?.orEmpty() for containers and String?.orEmpty(), and even Any?.toString(). Some functions like T.let, T.run that you asked about and some others just don't provide an upper bound for the type parameter, and that defaults to nullable Any?. And T.use provides a nullable upper bound Closeable?.
Under the hood, that is, from the runtime perspective, the extension calls are not compiled into the JVM member call instructions INVOKEVIRTUAL, INVOKEINTERFACE or INVOKESPECIAL (the JVM checks the first argument of such calls, the implicit this, for being null and throws an NPE if it is, and this is how Java & Kotlin member functions are called). Instead, the Kotlin extension functions are compiled down to static methods, and the receiver is just passed as the first argument. Such a method is called with the INVOKESTATIC instruction that does not check the arguments for being null.
Note that when a receiver of an extension can be nullable, Kotlin does not allow you to use it where a not-null value is required without checking it for null first:
fun Int?.foo() = this + 1 // error, + is not defined for nullable Int?
To add to what #holi-java said, there is nothing unsafe about your code at all. println("foo") is perfectly valid whether foo is null or not. If you tried something like
foo.run { subString(1) }
it would be unsafe, and you will find it won't even compile without some sort of null check:
foo.run { this?.subString(1) }
// or
foo?.run { subString(1) }
This is because the top-level function run accept anything Any & Any?. so an extension function with Null Receiver doesn't checked by Kotlin in runtime.
// v--- accept anything
public inline fun <T, R> T.run(block: T.() -> R): R = block()
Indeed, the inline function run is generated by Kotlin without any assertions if the receiver can be nullable, so it is more like a noinline function generated to Java code as below:
public static Object run(Object receiver, Function1<Object, Object> block){
//v--- the parameters checking is taken away if the reciever can be nullable
//Intrinsics.checkParameterIsNotNull(receiver, "receiver");
Intrinsics.checkParameterIsNotNull(block, "block");
// ^--- checking the `block` parameter since it can't be null
}
IF you want to call it in a safety way, you can use safe-call operator ?. instead, for example:
val foo: String? = null
// v--- short-circuited if the foo is null
foo?.run { println("foo") }

Bound Callable Reference Not Working With Reactor Subscribe

Since 1.1, Kotlin has had bound callable references. As I am on 1.1.3, I think I should be able to use the following to access the add method :
val elements = mutableListOf<Int>()
Flux.just(1, 2, 3, 4)
.log()
.subscribe(elements::add)
However, this throws an error:
I'm not sure what that error means in this specific instance. I can use .subscribe({ elements.add(it) }) with no issue, but shouldn't I be able to use the elements::add version?
Kotlin Function Reference Expression is not like as java Method Reference Expression. the return type Any is not compatible with the return type Unit.
the error comes from the return type (Boolean) of the MutableList#add(Int) method is not compatible with the parameter parameter type (Int)->Unit of the subscribe method.
so you can only using lambda expression this way.
you can using list::add in somewhere when both the parameter types and return type are compatible with the function. for example:
val add1:(Int) -> Boolean = list::add; // MutableList.add(Int);
val add2:(Int,Int) -> Unit = list::add; // MutableList.add(Int,Int);
To rephrase and elaborate on the other answer: the problem is that a Consumer is expected, which takes an element and returns nothing in its single method. The corresponding function type for this in Kotlin would be (T) -> Unit.
The add method described in the MutableList interface however has the type (T) -> Boolean: it returns true if the element was successfully added (this is to support implementations of the interface that can't contain duplicates).
A possible solution to this is to add an extension method that adds an element to a MutableList without returning anything:
fun <T> MutableList<T>.addItem(element: T): Unit {
this.add(element)
}
You can then use a bound callable reference to this extension, just like other MutableList methods:
Flux.just(1, 2, 3, 4)
.log()
.subscribe(elements::addItem)