I faintly remember coming across a way to trigger a compiler error/warning when calling a function expecting an argument of a nullable type (e.g., Any?) with an argument of a non-nullable type (e.g., Any), but I’m unable to google it now…
For example:
fun rejectIfNull(value: Any?) { // how can I statically disallow Any here?
if (value == null) {
// do something
}
}
rejectIfNull("I'm not null") // should generate a compiler error/warning
This is generally not doable, because technically speaking, a non-nullable type is a subtype of nullable type and we assume the upcasting is always safe. In other words, Any is always at the same time an Any?.
Of course, we can create some code rules on top of the language logic, but we should be aware this is a non-standard behavior and could be confusing to others. One way is using some kind of a linter or compiler plugin which enforces such additional rules.
Also, one potential trick I see is by utilizing the fact that if there are overloaded functions, the compiler chooses the one with the most specific arguments - in that case Any. This way we can easily target cases where we provided a compile-time subtype. We can return Nothing, we can use @Deprecated or @RequiresOptIn:
fun main() {
rejectIfNull("hello") // compile error
rejectIfNull("hello" as String?) // ok
}
fun rejectIfNull(value: Any?) {}
@InvalidArgumentTypes
fun rejectIfNull(value: Any) {}
@RequiresOptIn
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.FUNCTION)
annotation class InvalidArgumentTypes
Still, this may be a little confusing and it won’t work in the case like:
fun main() {
foo("hello") // ok
}
fun foo(s: String?) = rejectIfNull(s) // ok
@madmax1028 My use case was avoiding copy/paste errors in the context of implementing validators for various classes.
For instance:
fun rejectIfNull(value: Any?) {
if (value == null) { TODO("reject") }
}
data class Foo(val x: String?)
class FooValidator {
fun validate(target: Foo) {
rejectIfNull(target.x) // ok
if (target.x == null) { TODO("reject") } // logic inlined, ok
}
}
data class Bar(val x: String)
class BarValidator {
fun validate(target: Bar) {
// copy/paste error potentially going unnoticed (meant to be rejectIfBlank(target.x) or similar)
rejectIfNull(target.x)
// copy/paste error, but a warning is displayed by static analysis tools
if (target.x == null) { TODO("reject") }
}
}