Kotlin合约:对具体化类型参数Assert示例

zqdjd7g9  于 2023-04-21  发布在  Kotlin
关注(0)|答案(4)|浏览(144)

我尝试编写一个assert函数,检查给定对象是否为T类型:

@UseExperimental(ExperimentalContracts::class)
inline fun <reified T> assertIsInstance(value: Any?) {
    contract {
        returns() implies (value is T)
    }

    Assertions.assertThat(value).isInstanceOf(T::class.java)
}

该函数使用AssertJ来执行具体的Assert,但我愿意让编译器知道在执行后,value的类型是T,因此可以进行smartcast。看起来这不起作用,因为:
Error in contract description: references to type parameters are forbidden in contracts
有没有其他方法可以实现这种行为?这里的问题是什么?这最终可能吗?
(使用Kotlinv1.3)

ewm0tg9j

ewm0tg9j1#

这已经困扰了我几个小时,特别是因为这是可能的:

val x: Any = "string"
require(x is String)
val len = x.length

编译器显然能够理解这些,所以这可能是契约本身的限制。
我已经花了一段时间来尝试想出一些变通方法。供参考:

@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");
    }
}

“无支撑结构”

@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");
}

编译,但不启用智能转换。其背后的原始动机是在合约前面放置一个布尔值,但合约需要成为函数的第一部分,这使得这是不可能的。您还不如删除合约;在这种情况下是没用的。
这是我最后一次尝试:

@UseExperimental(ExperimentalContracts::class)
inline fun assertIsInstance(value: Any?, cls: KClass<out Any>) {
    contract {
        returns() implies (cls.isInstance(value))
    }
    if(!cls.isInstance(value))
        throw IllegalArgumentException("");
}

另一个“无支撑结构”。
不知怎的,我最终得到了这个:

@UseExperimental(ExperimentalContracts::class)
inline fun assertIsInstance(value: Any?) {
    contract {
        returns() implies (value.hashCode() == 0)
    }
    if(value.hashCode() != 0)
        throw java.lang.IllegalArgumentException();
}

但这会产生一个新的错误:only references to parameters are allowed in contract description .

TL;DR:

看起来你做不到。像我在第二个例子中所做的那样偷偷地把它放进去并不会触发智能强制转换,其余的由于各种编译器错误而无法工作。
至少现在,似乎没有办法。你当然可以在Kotlin仓库中打开一个问题,并要求这样的东西,但现在,这似乎是不可能的。

u91tlkcl

u91tlkcl2#

在某些时候,IDE中对此类构造的支持存在一些(技术上的)问题,但将来可能会放宽这一限制。

8gsdolmq

8gsdolmq3#

  • as* 操作符不是这样做的吗?
fun main() {
    val x: Any = "string"
    x as String
    val len = x.length
    println(len)
}

https://pl.kotl.in/uFCsGWEZm

ep6jt1vc

ep6jt1vc4#

我有一个解决方案,通过了我在Kotlin1.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
    }
}

相关问题