Disallowing unqualified return as last expression inside lambdas?

Hi, I’m wondering whether there’s any way to disallow or at least compiler warn when an unqualified return is used inside a lambda, especially if it is the last expression?

Considering the following case:

private val cache = WeakHashMap<SomeLibraryObject, Int>()

fun SomeLibraryObject.cachedExpensiveCalculation(): Int = cache.getOrPut(this) {
  val result = someExpensiveCalculation()

  return result
}

The return at the end returns the nearest function inside the lambda (cachedExpensiveCalculation in this case) and appears to work, but it never returns so the value is never put into the WeakHashMap.

Ideally I’d like a warning to force return@cachedExpensiveCalculation in this case

It’s your responsibility to write the correct program, not the compiler. If a programmer wants return - he got return.

Couldn’t you say that about all compiler warnings?

Maybe I’m missing something but here’s an example that shows it is already disallowed
Here’s a runnable example of the problem. The desired behavior is to print “ABC”. Instead, it prints “AB”:

inline fun getOrPut(block: () -> String): String {
    return block() + "C" // We'll just stub this to always run for the example
}

fun getValue1(): String = getOrPut {
    val result = "A" + "B"
    return result
}

fun main() {
    println(getValue1())
}

There are three ways to go about fixing this that I can think of off the top of my head:

  1. The getOrPost function says it shouldn’t ever be early-returned–which isn’t possible. But the getOrPost function is always able to add some defensive coding with a finally block to handle exits from returns and exceptions. So this option is off the table and handled already.
  2. The caller of the lambda somehow indicates they don’t want to return from a lambda (maybe with an annotation or some way that produces a warning).
  3. Some static code analysis to catch the usage.

Let’s look at the second option.

First, marking the call site with an annotation:
You could imagine some annotation that you’d mark your call of the lambda with to get an error. This has the added benefit of being do-able with code inspection tools instead of requiring it be added by JetBrains to Kotlin.

fun exampleFunction: Int = cache.getOrPut(key) @NoReturn {
        // ...
        return 5 // WARNING! Warning marked since the lambda has a @NoReturn annotation
    }
    // Implicit return of last line. Here is where we really want to return
}

This is nice, but there’s another solution that doesn’t require any new code. Since we’re changing our code to make it clear what we want to return and what we don’t, let’s just write our code in a way that’s more clear:

fun exampleFunction: Int {
    val result = cache.getOrPut(key) {
        // ...
        // When the lambda uses contracts--if someone adds a return here, we get a warning that the other lines are unreachable
        // Even without contracts, a return
    }
    return result // It's clear we want to return here
}
1 Like

Just to note that IntelliJ already has an inspection that catches this (but by default is “fix only” instead of warn); “Unlabeled return inside lambda”. If I change this to a warning, the IDE will highlight which is what I wanted in this instance;

image

2 Likes

If you change your getOrPut function to inline fun it’ll reproduce the behaviour.

1 Like

Thanks! I don’t know how I missed that–I even had the thought of “this should be inline, why isn’t it working?” and skipped past it :stuck_out_tongue:

I’ve edited my example above.

1 Like