Can anyone confirm that it not possible to `reflec...
# reflect
v
Can anyone confirm that it not possible to
reflect()
on a lambda specified using the
::methodName
syntax, and that it will return null? I'm experimenting with a library (https://github.com/moia-oss/lambda-kotlin-request-router) and this code passes:
Copy code
POST("/something") { req: Request<Unit> -> ResponseEntity.ok(Unit) }
But this code fails, and indeed the library seems to suggest that it will always fail because of a limitation in kotlin reflection on lambdas:
Copy code
val dummy = object {
                fun handler(r: Request<Unit>) = ResponseEntity.ok(Unit)
            }
GET("/some", dummy::handler)
The library uses kotlin reflection while processing the GET() function:
Copy code
al requestType = handler.reflect()?.parameters?.first()?.type?.arguments?.first()?.type
            ?: throw IllegalArgumentException("reflection failed, try using a real lambda instead of function references (Kotlin 1.6 bug?)")
Clearly the library has chosen not to support the ::method syntax, despite using it in its documentation and samples. Any suggestions? Using Kotlin 1.7.22
More details... if I do
Copy code
println("Handler: $handler")
Just before the call to reflect(), for the working `{ req: Request -> Response() }`syntax I get:
Handler: (io.moia.router.Request<kotlin.String>) -> io.moia.router.ResponseEntity<kotlin.String>
And for the non-working
class::method
I get:
Handler: fun io.moia.router.RequestHandlerTest.DummyController.handler(io.moia.router.Request<kotlin.Unit>): io.moia.router.ResponseEntity<kotlin.String>
(Ignore the different generic types, String and Unit, they aren't relevant, I've tried all combinations!)
This does what I need. Is it a bad idea?
Copy code
val requestType: KType? = try {
            val fk = handler as KFunction<*>
            fk.parameters.first().type.arguments.first().type
        } catch (cce: ClassCastException) {
            log.warn("Unable to to cast handler to KFunction; assuming it's a lambda and reflecting on that")
            handler.reflect()?.parameters?.first()?.type?.arguments?.first()?.type
        }
u
You can also do it this way:
Copy code
val kFunction = when (handler) {
        is KFunction<*> -> handler
        is kotlin.jvm.internal.Lambda<*> -> handler.reflect() ?: error(...)
        else -> error(...)
    }
    val requestType = kFunction.parameters.first().type.arguments.first().type
It uses internal API though,
kotlin.jvm.internal.Lambda
is the class which we use for lambdas with metadata
Note though that after KT-45375 is fixed, Kotlin lambdas by default won’t have metadata, so
reflect()
won’t work on them (it’s an experimental API anyway). You’ll need to mark lambdas that you need to reflect on with
@JvmSerializableLambda
v
I've tried this out and it is working in unit tests, but I'll need to raise a pull request with the library - and so far, doesn't seem to be active. Struggling to deploy the library to mavenLocal so can't test it in my app.
Copy code
val requestType: KType? = try {
            val fk = handler as KFunction<*>
            fk.parameters.first().type.arguments.first().type
        } catch (cce: ClassCastException) {
            log.warn("Unable to to cast handler to KFunction; assuming it's a lambda and reflecting on that")
            handler.reflect()?.parameters?.first()?.type?.arguments?.first()?.type
        }
But I guess doing the type check is better than cast-and-catch.
Is there a better way, without using reflection perhaps, to get the KType for a lambda or ::method parameter?