Search code examples
kotlinkotlin-contracts

Can a Kotlin contract imply non-nullability of all vararg arguments of a function?


I'd like to write a top-level function like this:

@ExperimentalContracts
fun containsNull(vararg objs: Any?): Boolean {
    contract {
        returns(false) implies (/* ???? */)
    }
        
    for (o in objs) if (o == null) return true
        
    return false
}

so that I can have a code like this compile with no errors

if (containsNull(value1, value2, value3)) return

doSomething(value1.myProperty) 
/* and similar calls multiple times, without the need to use the "?." operator */

but it seems there's no way to fill out the implies(*) to achieve this, because the information about the single arguments gets lost in their conversion to the iterable collection that is provided by the vararg keyword, and there is no way to let the compiler know that all those arguments are safely not null.

So the only way for me to achieve this would be to create multiple functions like:

containsNull(obj1: Any?, obj2: Any?)
containsNull(obj1: Any?, obj2: Any?, obj3: Any?)
etc...

and imply non-nullability for every single argument in every single contract.

OR, instead of writing functions, doing something like this

try {
   value1!!
   value2!!
} catch (e: NullPointerException) {
   return
}

doSomething(value1.myProperty)

which is slightly less cluttering than a cascade of x1==null && x2==null etc... and faster to write but looks kinda overkill.

Am I guessing correctly? Is there a more concise and clean way to get around this?


Solution

  • As of now, it seems it isn't possible to achieve a solution through a combination of vararg and contracts, so I ended up using the functions approach. In a single file I put the opt-in for contracts at the top

    @file:OptIn(ExperimentalContracts::class)
    

    then I declared all the functions. As an example here's the one that takes 5 parameters

    fun oneOrMoreIsNull(
        obj1: Any?,
        obj2: Any?,
        obj3: Any?,
        obj4: Any?,
        obj5: Any?
    ): Boolean {
        contract {
            returns(false) implies (
                obj1 != null &&
                obj2 != null &&
                obj3 != null &&
                obj4 != null &&
                obj5 != null
            )
        }
        
        return obj1 == null || obj2 == null || obj3 == null || 
               obj4 == null || obj5 == null
    }
    

    I'm then able to call the function and have the smart cast correctly detecting non-null values.

    if (oneOrMoreIsNull(arg1, arg2, arg3, arg4, arg5)) return
    
    // All these examples now don't require null checking.
    arg1.fancyComputation()
    if (arg2.isValid()) doSomething()
    doSomeMath(arg3.value, arg4.value)