5

I have a function that looks something like:

fun MyInput?.toOutput() : Output? {
  if (this == null) return null
  return Output(this.someValue)
}

In places where I know that my MyInput is non-null (for example, inside a method that takes a input: MyInput as an arg), I'd like to be able to use input.toOutput as Output instead of Output?

I've tried using

contract {
  returnsNotNull() implies (this@toOutput != null)
}

But that has the implication backwards. That tells me that if toOutput returns a non-null type, that my input was non-null. I want to tell the analyzer things about the return value based on the arguments. In Java, I could use org.jetbrains.annotations.@Contract("null -> null ; !null -> !null") to accomplish this.

Is there a way to do this in Kotlin?

karl
  • 3,544
  • 3
  • 30
  • 46
  • 2
    Note that for this, you might as well not offer `MyInput?.toOutput()` at all. Just provide `MyInput.toOutput()`, and then let callers write `myInput?.toOutput()` with the question mark. That'll make the nullability behavior much more obvious to users as well as letting you avoid this entire issue. – Louis Wasserman Apr 25 '19 at 17:27
  • @LouisWasserman That's a great idea, but would it work to call `toOutput` from Java? – karl Apr 26 '19 at 19:42
  • It would work in the sense that you'd have to check for null on the Java end. – Louis Wasserman Apr 26 '19 at 23:37

1 Answers1

8

You don't need contracts for this. You just need to make a non-nullable overload. Like this:

fun MyInput?.toOutput(): Output? {
  if (this == null) return null
  return Output(this.someValue)
}

fun MyInput.toOutput(): Output = Output(this.someValue)

However, this will not work out of the box on the JVM, because the function signatures will clash. To make it work, you have to give one of the functions a new name with the @JvmName annotation. For example:

@JvmName("toOutputNonNull")
fun MyInput.toOutput(): Output = Output(this.someValue)

You will still be able to call it like input.toOutput() from Kotlin, but it will become something like FileNameKt.toOutputNonNull(input) if you call it from Java.

marstran
  • 26,413
  • 5
  • 61
  • 67