First of all I just want to point out that I'm aware of the Force a null into non-nullable type and Kotlin Generics and nullable Class type but I don't think these questions are the same as mine (correct me if I'm wrong).
Background
I'm developing a library called Awaitility which, simply put, is designed to wait until a predicate evaluates to true. The Kotlin API provides a way to write expressions like this:
// Create a simple data class example
data class Data(var value: String)
// A fake repository that returns a possibly nullable instance of Data
interface DataRepository {
// Invoked from other thread
fun loadData() : Data?
}
val dataRepository = .. // Implementation of DataRepository
// Now Awaitility allows you to wait until the "value" in Data is equal to "Something"
val data : Data = await untilCallTo { dataRepository.loadData() } has {
value == "Something"
}
This works because has
returns false
if dataRepository.loadData()
returns null
and never calls the supplied receiver function ({ value == "Something" }
) if data
is null
. Awaitility will also throw an exception if the condition is not satisfied so we know that what's returned from the expression has type Data
(and not Data?
) as you can see in the example.
The has
function is implemented like this:
infix fun <T> AwaitilityKtUntilFunCondition<T?>.has(pred: T.() -> Boolean) = factory.until(fn) { t: T? ->
if (t == null) {
false
} else {
pred(t)
}
} as T
where AwaitilityKtUntilFunCondition
looks like this:
data class AwaitilityKtUntilFunCondition<T> internal constructor(internal val factory: ConditionFactory, internal val fn: () -> T?)
(you can also find the ConditionFactory here if needed)
While the example above works great when the lambda passed to untilCallTo
returns a nullable type (Data?
) it doesn't compile if we pass it a non-nullable type (i.e. Data
). For example if we simply modify the repository to look like this:
interface DataRepository {
// Invoked from other thread
fun loadData() : Data // Notice that loadData now returns a non-nullable type
}
and if we then try the same Awaitility expression as in the previous example:
val data : Data = await untilCallTo { dataRepository.loadData() } has {
value == "Something"
}
we'll get a compile-time error:
Error:(160, 20) Kotlin: Type mismatch: inferred type is AwaitilityKtUntilFunCondition<Data> but AwaitilityKtUntilFunCondition<Data?> was expected
Error:(160, 68) Kotlin: Type inference failed. Please try to specify type arguments explicitly.
Which is (of course) correct!
Question
What I want to do is to somehow modify the has
method to force the return type to always be the non-nullable equivalent of the type that's passed in as argument (which can be either nullable or non-nullable). I've tried to do something like this (which doesn't work):
infix fun <T, T2> AwaitilityKtUntilFunCondition<T>.has(pred: T2.() -> Boolean): T2
where T : Any?, // Any? is not required but added for clarity
T2 : T!! // This doesn't compile
= factory.until(fn) { t: T ->
if (t == null) {
false
} else {
pred(t as T2)
}
} as T2
This doesn't compile due to T2 : T!!
but I hope that it shows my intention. I.e. I want to somehow define T2
as:
- The non-nullable equivalent of type
T
ifT
is nullable - To be the same as
T
ifT
is a non-nullable type
Is this possible in Kotlin?
Update:
I've created a branch in the Awaitility project called has-with-non-nullable-type
where you get the compile-time error I'm talking about in the file KotlinTest. This is what I want to make compile. You can clone it using:
$ git clone https://github.com/awaitility/awaitility.git
Update 2:
I've added gist that I think demonstrates the problem without using any dependencies.