Reputation: 81929
I'm trying to write an assert function that checks if a given object is of a type T
:
@UseExperimental(ExperimentalContracts::class)
inline fun <reified T> assertIsInstance(value: Any?) {
contract {
returns() implies (value is T)
}
Assertions.assertThat(value).isInstanceOf(T::class.java)
}
The function uses AssertJ to do the concrete assertion but I'm willing to let the compiler know that after its execution, the value
is of type T
so that a smartcast is possible. It seems like this does not work because:
Error in contract description: references to type parameters are forbidden in contracts
Is there another way to achieve this behavior? What's the issue here? Will this eventually be possible?
(Using Kotlin v1.3)
Upvotes: 8
Views: 2619
Reputation: 577
I have a solution that passes the my unit tests on Kotlin 1.8.20:
@OptIn(ExperimentalContracts::class)
@Suppress("UnusedPrivateMember")
public inline fun <K : KClass<V>, reified V> Any.checkIsInstance(kClass: K, lazyMessage: () -> Any): V {
contract {
returns() implies (this@checkIsInstance is V)
}
if (this !is V) {
val message = lazyMessage()
throw IllegalStateException(message.toString())
} else {
return this
}
}
Edit: This can simplify to a version very similar to that in the original post, suggesting that the problem is now fixed in Kotlin:
@OptIn(ExperimentalContracts::class)
public inline infix fun <reified V> Any?.checkIsInstance(lazyMessage: () -> Any): V {
contract {
returns() implies (this@checkIsInstance is V)
}
if (this !is V) {
val message = lazyMessage()
throw IllegalStateException(message.toString())
} else {
return this
}
}
Upvotes: 1
Reputation: 2659
Doesn't the as operator do this?
fun main() {
val x: Any = "string"
x as String
val len = x.length
println(len)
}
Upvotes: -1
Reputation: 28238
This has been bugging me for a couple hours, especially since this is possible:
val x: Any = "string"
require(x is String)
val len = x.length
The compiler is clearly able to understand these, so this is likely a limitation of the contracts themselves.
I've spent a while now trying to come up with some workarounds. For reference:
@UseExperimental(ExperimentalContracts::class)
inline fun <reified T> assertIsInstance(value: Any?) {
contract {
returns() implies T::class.isInstance(value))
}
if(value !is T){
throw java.lang.IllegalArgumentException("Incorrect type");
}
}
"Unsupported construct"
@UseExperimental(ExperimentalContracts::class)
inline fun <reified T> assertIsInstance(value: Any?, condition: Boolean = value is T) {
contract {
returns() implies condition
}
if(!condition)
throw IllegalArgumentException("Incorrect type");
}
Compiles, but doesn't enable smart cast. The original motivation behind that one was placing a boolean in front of the contract, but contracts need to be the first part of a function, which made that impossible. You might as well remove the contract; it's useless in this case.
This was my last try:
@UseExperimental(ExperimentalContracts::class)
inline fun assertIsInstance(value: Any?, cls: KClass<out Any>) {
contract {
returns() implies (cls.isInstance(value))
}
if(!cls.isInstance(value))
throw IllegalArgumentException("");
}
Another "unsupported construct".
Somehow I ended up with this:
@UseExperimental(ExperimentalContracts::class)
inline fun assertIsInstance(value: Any?) {
contract {
returns() implies (value.hashCode() == 0)
}
if(value.hashCode() != 0)
throw java.lang.IllegalArgumentException();
}
But this gives a new error: only references to parameters are allowed in contract description
.
It doesn't look like you can. Sneaking it in like I did in the second example doesn't trigger smart cast, and the rest don't work due to various compiler errors.
At least for now, there doesn't appear to be a way. You could of course open an issue in the Kotlin repo and ask for something like this, but for now, it doesn't appear to be possible.
Upvotes: 4
Reputation: 76
At some point there were some (deeply technical) concerns regarding support of such constructions in an IDE, but it's possible that this limitation will be relaxed in the future.
Upvotes: 3