Would it be possible to call a Kotlin function with named parameters from Groovy (including `copy`)?

523 Views Asked by At

Context - a Kotlin codebase with Spock/Groovy tests.

In my Groovy tests it would be convenient to call Kotlin functions with named parameters using the names. I use this particularly for test builders for entities.

I'd like to be able to call them with parameter names from Groovy.

So given this Kotlin:

data class User(
  val id: Id,
  val name: String,
  val emailAddress: String,
)

fun buildUser(
  id: Id = randomId(),
  name: String = randomName(),
  emailAddress: String = randomEmailAddress(),
): User

I'd like to be able to write this groovy:

User user = buildUser(
  name: 'My name'
)

It feels like this should be possible; either using a compiler plugin on the Kotlin side to overload the buildUser method with a version that takes a Map, or via an AST transform on the Groovy side. Has anyone done this?

This might also allow calling the copy method on a data class in the same way:

User user = buildUser()
User userWithDifferentEmailAddress = user.copy(emailAddress: '[email protected]')

Ideally IntelliJ IDEA would know about it enough to be able to respect renaming parameters and navigate from call site parameter name to receiver site parameter, but that might be asking too much...

EDIT - looking at the decompiled Kotlin it creates the following methods:


public static User buildUser$default(Id var0, String var1, String var2, int var3, Object var4) {
  // uses var3 as a flag to indicate which fields were provided by the caller
}

public static User copy$default(User var0, Id var1, String var2, String var3, int var4, Object var5) {
  // uses var4 as a flag to indicate which fields were provided by the caller
}

so it should be possible to wrangle Groovy to call them, I think...

1

There are 1 best solutions below

0
Leonard Brünings On

I can only offer a partial solution. With Groovy's @NamedVariant transformation you can declare your factory function like this in Groovy.

@NamedVariant
User buildUser(
  Id id = randomId(),
  String name = randomName(),
  String emailAddress = randomEmailAddress()) {
    new User (id, name, emailAddress )
}

and it will generate an overload that takes a map or parameters, so you can use them like you described.

As for the copy there is nothing to do in plain groovy.

You can try to write an AST Transform, but you'll be relying on the kotlin compiler implementation of how the method is generated and how to set the flags. I think it would be more easier to try to generate a custom groovy extension function that duplicates the copy behavior. See extension modules