Search code examples
gradlekotlingradle-kotlin-dsl

Kotlin not able to convert gradle's Action class to a lambda


So, while this is quite a kotlin-dsl for gradle specific issue, I think it overall applies to the kotlin language itself, so I am not going to use that tag.

In the gradle API, the class Action<T> is defined as:

@HasImplicitReceiver
public interface Action<T> {
    /**
     * Performs this action against the given object.
     *
     * @param t The object to perform the action on.
     */
    void execute(T t);
 }

So ideally, this should work in kotlin (because it is a class with a SAM):

val x : Action<String> = {
    println(">> ${it.trim(0)}")
    Unit
}

But I get the following two errors:

Unresolved reference it
Expected Action<String> but found () -> Unit

Fwiw, even Action<String> = { input: String -> ... } doesn't work.

Now here's the really intriguing part. If I do the following in IntelliJ (which btw, works):

object : Action<String> {
    override fun execute(t: String?) {
        ...
    }
}

IntelliJ pops the suggestion Convert to lambda, which when I do, I get:

val x = Action<String> {
}

which is better, but it is still unresolved. Specifying it now:

val x = Action<String> { input -> ... }

gives the following errors Could not infer type for input and Expected no parameters. Can someone help me with what is going on?


Solution

  • This is because the Action class in gradle is annotated with HasImplicitReceiver. From the documentation:

    Marks a SAM interface as a target for lambda expressions / closures where the single parameter is passed as the implicit receiver of the invocation (this in Kotlin, delegate in Groovy) as if the lambda expression was an extension method of the parameter type.

    (emphasis mine)

    So, the following compiles just fine:

    val x = Action<String> {
        println(">> ${this.trim()}")
    }
    

    You could even just write ${trim()} and omit the this in front of it.