Error message "None of the following functions can be called with the arguments supplied

As far as I can see, I am suppling properly the arguments.

The context is: I coded a proto file and I used gradle/io.grpc:protoc-gen-grpc-kotlin to generate the interfaces and its stubs. This very simple microservice exposes a gRPC endpoint and its reponse is nothing else than the response from another Rest Service.

The rest service response paylod is

[
  {
    "id_person": 1,
    "name": "Peter",
    "children": [
      {
        "id_person": "1",
        "name": "John",
        "age": 5
      },
      {
        "id_person": "2",
        "name": "Mary",
        "age": 10
      }
    ]
  }
]

This is the rest client

import com.mycomp.application.models.Person
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client


@Client("http://localhost:3000/people")
interface PersonClient {
    @Get("/{?id_person}")
    fun getPerson(id_person: String?): Person?
}

Here are the model Person serialized during rest call

data class Person (
        var id_person : String,
        var name : String,
        var children : List<Child>?
)

and

data class Child (
        var id_person : String,
        var name : String,
        var age : Int
)

The proto file

import "google/api/annotations.proto";

service PersonGrpcService {

  rpc Get (GetPersonRequest) returns (PersonResponse) {
  }
}

message GetPersonRequest{
    string id_person = 1;
}

message PersonResponse {
  string id_person = 1;
  string name = 2;
  repeated Child children = 3;
}

message Child{
    string id_person = 1;
    string name = 2;
    int32 age = 3;
}

And here is the service implementing the interface generated from previous proto file. Here my question is about

import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class PersonEndpoint :PersonGrpcServiceGrpcKt.PersonGrpcServiceCoroutineImplBase(){

    @Inject
    lateinit var personClient: PersonClient

    override suspend fun get(request: GetPersonRequest): PersonResponse {

        var personRest = personClient.getPerson("1")

        var personResponse = PersonResponse.newBuilder()

        if (personRest != null) {

            personResponse.idPerson = personRest.id_person
            personResponse.name = personRest.name

            //forEach Approach: working
            personRest.children?.forEach{
                personResponse.addChildren(
                        Child.newBuilder()
                                .setIdPerson(it.id_person)
                                .setName(it.name)
                                .setAge(it.age)
                )
            }

            //This also works but it still depends on .newBuilder()
            personRest.children?.map { x ->
                personResponse.addChildren(
                        Child.newBuilder()
                                .setIdPerson(x.id_person)
                                .setName(x.name)
                                .setAge(x.age)
                )
            }

            //Cleaner tentative but not working:
            // error message "None of the following functions can be called with the arguments supplied."
            // - addChildren(Child!) defined in com.mycomp.adapters.grpc.mypackage.PersonResponse.Builder
            // - addChildren(com.mycomp.adapters.grpc.mypackage.Child.Builder!) defined in com.mycomp.adapters.grpc.mypackage.PersonResponse.Builder
            // *** I don't understand why the code below doesn't fit the first option "addChildren(Child!) defined in com...PersonResponse.Builder
            personRest.children?.map { x ->
                personResponse.addChildren(x)
            }


        }

        return personResponse.build()
    }
}

So, my straight question is: why I am getting this error

error message "None of the following functions can be called with the arguments supplied.

  • addChildren(Child!) defined in com.mycomp.adapters.grpc.mypackage.PersonResponse.Builder

with

        personRest.children?.map { x ->
            personResponse.addChildren(x)
        }

since one of the two expected arguments is addChildren(Child!) and x on above lambda definitly is Child type?

In case it is relevant here is the main part of build.gradle

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.4.10"
    id "org.jetbrains.kotlin.kapt" version "1.4.10"
    id "org.jetbrains.kotlin.plugin.allopen" version "1.4.10"
    id "io.micronaut.application" version '1.0.3'
    id "com.google.protobuf" version "0.8.12"
}
...
dependencies {
    implementation("io.micronaut:micronaut-validation")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8:${kotlinVersion}")
    implementation("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}")
    implementation("io.micronaut.kotlin:micronaut-kotlin-runtime")
    implementation("io.micronaut:micronaut-runtime")
    runtimeOnly("com.fasterxml.jackson.module:jackson-module-kotlin")

}
...
compileKotlin {
    kotlinOptions {
        jvmTarget = '11'
        javaParameters = true
    }
}
...
protobuf {
    protoc { artifact = "com.google.protobuf:protoc:${protocVersion}" }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
        grpckt { artifact = "io.grpc:protoc-gen-grpc-kotlin:${grpcKotlinVersion}" }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {}
            grpckt {}
        }
    }
}

The method takes Child.Builder. You are passing in Child. They are not the same thing and Kotlin (very purposely to avoid confusing code) does not allow implicit conversions between types.

The code you present that works is creating a Child.Builder, which is why it works.

I get the impression from the grpc docs that there’s a toBuilder method that does this for you.

Try

personRest.children?.map { x ->
    personResponse.addChildren(x.toBuilder())
}

I haven’t used gRPC so not speaking from authority or experience there. The error is pretty straight forward though and it makes sense for the code generator to generate such a conversion method.

2 Likes

Thanks. Actually, there is no “.toBuilder()” method. BTW, you have answered my question. A last comment if you don’t mind. What are the main difference, advantages behind the scene, between I use forEach or map in this case? I mean, would you favour which one and why between

    personRest.children?.forEach{
        personResponse.addChildren(
                Child.newBuilder()
                        .setIdPerson(it.id_person)
                        .setName(it.name)
                        .setAge(it.age)
        )
    }

and

    personRest.children?.map { x ->
        personResponse.addChildren(
                Child.newBuilder()
                        .setIdPerson(x.id_person)
                        .setName(x.name)
                        .setAge(x.age)
        )
    }

?

map creates a new list, eg.

val lst = listOf(1, 2, 3, 4).map{ it * 2 } // == listOf(2, 4, 6, 8)

forEach just executes the lambda for each element in the original collection.

For your usecase it has no practical difference but I would still prefer forEach. You don’t map each element to a new element (use the result of the map function), instead you do something with each element.
map and forEach just convey a different meaning. map creates a new list of the old while forEach just uses the elements in the list to do something.

2 Likes