I'm trying to invoke a function using Kotlin reflection, but I'm getting the error:
java.lang.IllegalArgumentException: Callable expects 4 arguments, but
3 were provided.
This is the code doing the reflective call:
annotation.listeners.forEach { listener: KClass<*> ->
listener.functions.forEach { function: KFunction<*> ->
if (function.name == "before") {
function.call(annotation.action, request, response)
}
}
}
I've added types for listener and function just to make the question more readable.
This is the method that's being called:
fun before(action: String, request: RestRequest, response: RestResponse)
To double check that my types are correct, I did this:
if (function.name == "before") {
println(annotation.action::class)
println(request::class)
println(response::class)
}
This prints (which is the correct types needed for the before function):
class kotlin.String
class com.mycompany.RestRequest
class com.mycompany.RestResponse
What should the fourth paramater be?
You are missing the "this" parameter which is the object that the method should be called with respect to.
it should be the first argument to the method
It's not obvious directly, but let's have a look at the doc of KCallable:
/**
* Calls this callable with the specified list of arguments and returns the result.
* Throws an exception if the number of specified arguments is not equal to the size of [parameters],
* or if their types do not match the types of the parameters
*/
public fun call(vararg args: Any?): R
"Throws an exception if the number of arguments is not equal to the size of [parameters] [...]". Parameters on the other hand is a List<KParameter> with following DOC:
/**
* Parameters required to make a call to this callable.
* If this callable requires a `this` instance or an extension receiver parameter,
* they come first in the list in that order.
*/
public val parameters: List<KParameter>
"If this callable requires a this instance [...] [it] come[s] first in the list in that order."
Like bennyl already answered correctly, a this instance is the first parameter, coming before the other three because the method needs an instance to be called on.
You can see it when you look at parameter's content:
class X{
fun before(action: String, request: String, response: String)= println("called")
}
fun main(args: Array<String>) {
X::class.functions.forEach { function: KFunction<*> ->
if (function.name == "before") {
function.parameters.forEach{ println(it)}
//function.call(X(), "a", "b", "c")
}
}
}
The printed parameter looks as follows:
instance of fun de.swirtz.jugcdemo.prepared.X.before(kotlin.String, kotlin.String, kotlin.String): kotlin.Unit
parameter #1 action of fun de.swirtz.jugcdemo.prepared.X.before(kotlin.String, kotlin.String, kotlin.String): kotlin.Unit
parameter #2 request of fun de.swirtz.jugcdemo.prepared.X.before(kotlin.String, kotlin.String, kotlin.String): kotlin.Unit
parameter #3 response of fun de.swirtz.jugcdemo.prepared.X.before(kotlin.String, kotlin.String, kotlin.String): kotlin.Unit
Related
I have code accepts a class as a parameter and prepares data to call either the constructor for that class of a companion object factory method if the factory method is present.
All works fine when calling the constructor, but I get the error
java.lang.IllegalArgumentException: No argument provided for a required parameter: instance of fun nz.salect.objjson.JVMTest.StudentWithFactory.Companion.fromJson(kotlin.String, kotlin.Int): nz.salect.objjson.JVMTest.StudentWithFactory
when calling the factory method. The factory method in question:
data class StudentWithFactory(val name: String, val years: Int=0) {
companion object {
fun fromJson(name: String="", age: Int = 0):StudentWithFactory {
return StudentWithFactory(name, age)
}
}
}
has no required parameters, unless there is some hidden parameter. Any ideas?
In fact, I reverted removing the parameters completely from fromJson and directly calling the companion method using ::fromJson.callby(emptyMap()). Same error.
It is clear that companion methods need at least one additional parameter. Perhaps the class? Or the companion object?
How can I specify the needed parameter(s)?
The function building up the callBy() is supplied a class (or finds the class from a supplied class) and json names and values.
var funk:KFunction<*>?=null
val companionFuncs=cls.companionObject?.declaredMemberFunctions
if(companionFuncs?.size ?:0 >0){
companionFuncs?.forEach {
if(it.name == "fromJson") funk=it
}
}
val cons:KFunction<T> = if(funk != null)
funk as KFunction<T>
else
cls.primaryConstructor ?: throw IllegalArgumentException("no primary constructor ${cls.simpleName}")
val valuesMap = cons.parameters.filter{it.name in vals}
.associateBy(
{it},
{generateValue(it)}
)
val data = cons.callBy(valuesMap) //as T
return data
In addition to my short answer, a more technical explanation:
Yes, there actually is a hidden parameter and you can see it (for example), if you take a look at the decompiled (to Java) bytecode:
public final class StudentWithFactory {
// ...
public static final class Companion {
// ...
#NotNull
public static StudentWithFactory fromJson$default(StudentWithFactory.Companion var0, String var1, int var2, int var3, Object var4) {
// ...
return var0.fromJson(var1, var2);
}
// ...
}
}
The first parameter (var0) is actually an instance of the companion object. var1 (name) and var2 (age) are the parameters you declared. var3 is a bitmask for determining if explicit values have been passed or if the default ones should be used*. I honestly don't know what var4 is for. It is unused in the Java code. But the imported part is that you only need to worry about var0, var1 and var2 if you want to invoke the function.
So, in the end the non-static version of fromJson* is actually invoked on the instance of the companion object:
var0.fromJson(var1, var2)
*left code out for simplicity
You can use the parameters property to determine how much parameters you have to pass to the function/constructor.
If you call
val paramsConstr = StudentWithFactory::class.primaryConstructor?.parameters
paramsConstr will be of size two as expected, but if you call
val paramsFunc = ::fromJson.parameters
paramsFunc will be of size three. The first element corresponds to the instance of the companion object. So, thats the list of parameters you need to provide.
You can invoke the fromJson like this:
// not using any default parameters
::fromJson.callBy(mapOf(
paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
paramsFunc[1] to "Hello",
paramsFunc[2] to 30
))
// using only the default parameter for "name"
::fromJson.callBy(mapOf(
paramsFunc[0] to StudentWithFactory::class.companionObjectInstance,
paramsFunc[2] to 30
))
I want to get a pointer to an object method, for instance for this class
class Foo {
has $thing = "baz";
method bar() { say $thing }
};
sub quux( Callable $flimflam ) {
$flimflam()
};
my $foo = Foo.new;
I want to grab of the $foo.bar method pointer to pas it to quux. However, this
quux(&($foo.bar))
fails with Type check failed in binding to parameter '$flimflam'; expected Callable but got Bool (Bool::True) in sub quux
This does not work either
quux($foo.bar)
maybe because it takes no parameters; however, if we change the definition of bar to:
method bar($some ) { say $some ~ $thing }
Then the error for the call above becomes Too few positionals passed; expected 2 arguments but got 1 in method bar, the error creeps up to bar itself, which means the object itself does not get in.
I have checked out this answer, but it's for a class method (new) and it involves using the meta-object protocol. Would there be a simpler way of doing that?
You can get the "static" method with .^lookup (the ^ indicates that it's a call on the meta object).
That method is not bound to the invocant $foo in any way, so you have to call it as
class Foo {
has $thing = "baz";
method bar() { say $thing }
};
sub quux( Callable $flimflam ) {
$flimflam()
};
my $foo = Foo.new;
my $method_bar = $foo.^lookup('bar');
$method_bar($foo);
You can use a closure to bind the method to the invocant:
my $bound_method = -> |c { $method_bar($foo, |c) }
Perl 6 also has a shortcut built in for that:
my $bound_method = $method_bar.assuming($foo);
But you can see that you could abbreviate the whole thing as
my $callback = { $foo.bar() }
or if the method potentially takes more arguments
my $callback = -> |c { $foo.bar(|c) }
I'm trying to generalize my hack from an answer to another question.
It should provide a way to reference a value which is not constructed yet inside its initializer (of course, not directly, but in lambdas and object expressions).
What I have at the moment:
class SelfReference<T>(val initializer: SelfReference<T>.() -> T) {
val self: T by lazy {
inner ?: throw IllegalStateException("Do not use `self` until initialized.")
}
private val inner = initializer()
}
fun <T> selfReference(initializer: SelfReference<T>.() -> T): T {
return SelfReference(initializer).self
}
It works, see this example:
class Holder(var x: Int = 0,
val action: () -> Unit)
val h: Holder = selfReference { Holder(0) { self.x++ } }
h.action()
h.action()
println(h.x) //2
But at this point the way in which initializer references the constructed value is self property.
And my question is: is there a way to rewrite SelfReference so that initializer is passed an argument (or a receiver) instead of using self property? This question can be reformulated to: is there a way to pass a lazily evaluated receiver/argument to a function or achieve this semantics some way?
What are the other ways to improve the code?
UPD: One possible way is to pass a function that returns self, thus it would be used as it() inside the initializer. Still looking for other ones.
The best I have managed to produce while still being completely generic is this:
class SelfReference<T>(val initializer: SelfReference<T>.() -> T) {
val self: T by lazy {
inner ?: throw IllegalStateException("Do not use `self` until initialized.")
}
private val inner = initializer()
operator fun invoke(): T = self
}
Adding the invoke operator lets you use it in the following way:
val h: Holder = selfReference { Holder(0) { this().x++ } }
This is the closest I got to make it look like something you would "normally" write.
Sadly I think it is not possible to get completely rid of a explicit access to the element. Since to do that you would need a lambda parameter of type T.() -> T but then you wouldn't be able to call that parameter without an instance of Tand being T a generic there is no clean and safe way to acquire this instance.
But maybe I'm wrong and this helps you think of a solution to the problem
is there a way to rewrite SelfReference so that initializer is passed an argument (or a receiver) instead of using self property? This question can be reformulated to: is there a way to pass a lazily evaluated receiver/argument to a function or achieve this semantics some way?
I'm not sure I completely understand your use case but this may be what you're looking for:
fun initHolder(x: Int = 0, holderAction: Holder.() -> Unit) : Holder {
var h: Holder? = null
h = Holder(x) { h!!.holderAction() }
return h
}
val h: Holder = initHolder(0) { x++ }
h.action()
h.action()
println(h.x) // 2
This works because holderAction is a lambda with a receiver (Holder.() -> Unit) giving the lambda access to the receiver's members.
This is a general solution since you may not be able to change the signature of the respective Holder constructor. It may be worth noting this solution does not require the class to be open, otherwise a similar approach could be done with a subclass using a secondary constructor.
I prefer this solution to creating a SelfReference class when there are only a few number of classes that need the change.
You may want to check for null instead of using !! in order to throw a helpful error. If Holder calls action in it's constructor or init block, you'll get a null pointer exception.
I'm pretty sure you can achieve the same results in a more readable and clear way using something like this:
fun <T> selfReferenced(initializer: () -> T) = initializer.invoke()
operator fun<T> T.getValue(any: Any?, property: KProperty<*>) = this
and later use
val valueName: ValueType by selfReferenced{
//here you can create and use the valueName object
}
Using as example your quoted question https://stackoverflow.com/a/35050722/2196460 you can do this:
val textToSpeech:TextToSpeech by selfReferenced {
TextToSpeech(
App.instance,
TextToSpeech.OnInitListener { status ->
if (status == TextToSpeech.SUCCESS) {
textToSpeech.setLanguage(Locale.UK)
}
})
}
Inside the selfReferenced block you can use the outer object with no restrictions. The only thing you should take care of, is declaring the type explicitly to avoid recursive type checking issues.
I'm trying to use macro annotations in scala, where my macro annotation would take an argument of another type. It would then use scala reflection to look at the passed in type, and add some methods as appropriate.Eg.
trait MyTrait {
def x: Int
def y: Float
}
#MyAnnotation class MyClass //<-- somehow, this annotation should reference MyTrait
class MyAnnotation(val target: Any) extends StaticAnnotation {
def macroTransform(annottees: Any*) = macro MyAnnotationImpl.impl
}
object MyAnnotationImpl {
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
// if I can get a handle on the type MyTrait in here
// then I can call .members on it, etc.
...
}
}
Basically, the same thing as Using Scala reflection in Scala macros, except using macro annotations. However, when I try to template my macro annotation with a TypeTag
class MyAnnotation[T](val target: Any) extends StaticAnnotation {
def macroTransform[T](annottees: Any*) = macro MyAnnotationImpl.impl[T]
}
object MyAnnotationImpl {
def impl[T: c.WeakTypeTag](c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
...
}
}
I get
[error] /Users/imran/other_projs/learn_macros/macros/src/main/scala/com/imranrashid/oleander/macros/MacrosWithReflection.scala:7: macro annotation has wrong shape:
[error] required: def macroTransform(annottees: Any*) = macro ...
[error] found : def macroTransform[T](annottees: Any*) = macro ...
[error] class MyAnnotation[T](val target: Any) extends StaticAnnotation {
[error] ^
I've also tried to make the type an argument to my annotation, so I would use it like #MyAnnotation(MyTrait) class Foo .... I can extract the name as a String with something like
val targetTrait = c.prefix.tree match {
case Apply(Select(New(Ident(_)), nme.CONSTRUCTOR), List(Ident(termName))) => termName
}
but, I'm not sure what I can do w/ that String to get back the full type. I've also tried variants like #MyAnnotation(typeOf[MyTrait]) class Foo ..., and then use c.eval on the typeOf inside my macro, but that doesn't compile either.
In macro paradise 2.0.0-SNAPSHOT we have quite a tricky way of accessing type parameters for macro annotations (the situation will improve later on when we have dedicated APIs for that, but right now it's very difficult to introduce new functionality to scala-reflect.jar in macro paradise, so the current API is a bit rough).
For now it's necessary to specify the type parameter on the annotation class and not to declare any type parameters on the macroTransform method. Then, in macro expansion, access c.macroApplication and extract the untyped tree corresponding to the passed type parameter. Afterwards, do c.typeCheck as described in Can't access Parent's Members while dealing with Macro Annotations.
As Eugene points out in his answer it is possible to match on the tree of the whole macro application. Like every Scala method, annotation macro applications can take multiple type argument lists as well as multiple value argument lists.
Consider the macro application of an annotation macro called test:
#test[A, B][C, D](a, b)(c, d) trait Foo
In the implementation of test we can inspect the macro application by
println(show(c.macroApplication))
which will result in:
new test[A, B][C, D](a, b)(c, d).macroTransform(abstract trait Foo extends scala.AnyRef)
To extract the (type/value) parameters from the tree you have to pattern match on the tree. A parser for an arbitrary amount of parameter lists can be found in this project
Using this parser retrieving the first value argument of the macro application is as easy as
val List(List(arg)) = MacroApp(c.macroApplication).termArgs
I have a "format" method that works in a similar manner to the C# String.Format method, with the following signature:
In a class named StringTools:
/**
* Formats a string, based on C# String.Format method.
* #param raw A string with numbered tokens, such as "{0}, {1}"
* #param rest Values that replace the numbered tokens in raw.
*/
public static function format(raw:String, ...rest:*):String;
StringTools.format("{0}, {1}", "Hello", "World") returns the string "Hello, World" as expected. Now, I'm trying to get my logging class to use this method, but I'm having trouble passing the optional variables through. The signature of the method in the logging class is:
public static function infof(raw:String, ...rest:*):String;
If I pass "rest" directly into StringTools.format(raw, rest), it's passed in as an array, and not as a series of parameters, so if I call it liks this: infof("{0}, {1}", "Hello", "World"), I get the string "Hello,World, {1}", since it replaces the first token with the entire array of values.
I also tried constructing an arguments array, and calling the method like this:
var collectArgs:Array = [raw];
for (var i:Number = 0; i < rest.length; i++)
{
collectArgs.push(rest[i]);
}
var callFunction:Function = StringTools.format.call;
trace(callFunction.apply(null, collectArgs));
However, this traces "World,6". So, it looks like the parameters are shifted. So, I tried initializing collectArgs as [null, raw], and I get "Hello World,6. The number is {1}" again.
Am I doing something wrong? What is the correct way to pass optional parameters from one method that expects optional parameters to another method that expects optional parameters?
Thanks!
I think you are on the right lines using apply. This seems to do illustrate the behaviour you want:
static function f1(raw:String, ...rest:*):void
{
trace("f1: "+raw+" "+rest);
rest.unshift(raw);
f2.apply(null, rest);
}
static function f2(raw:String, ...rest:*):void
{
trace("f2: "+raw+" "+rest);
}
function passSomeArguments():void
{
f1("A",1,2,3);
}
EDIT: You need to pass 'null' as the 1st parameter to apply because the first parameter is what is considered to be 'this' when the function is called. Since the functions are static (and in any case have no dependency on 'this') you can pass null, but you must pass something.
You could also do something like this (of course this is not best implementation for the string formatting):
public static function format(raw:String, ...rest:*):String {
if (rest[0] is Array && rest.length == 1) {
rest = rest[0];
}
var r:RegExp = /(\{\d+\})/g;
var matches:Array = raw.match(r);
for (var i:Number = 0; i < rest.length; i++) {
raw = raw.replace(matches[i], rest[i]);
}
return raw;
}
Then your infof function would just look like this:
public static function infof(raw:String, ...rest:*):void {
var formatted = StringTools.format(raw, rest);
}
As mentioned in my comment, if you remove the call method from the end of you callFunction setter, then you do not need to supply null as the first argument. See http://livedocs.adobe.com/ to understand what the call method actually does, and what the first parameter is for.
As #stephen mentioned, it is a lot simpler to unshift your raw var onto the rest array, rather than building up a new one.
Actually, just found that it's my problem. It should work fine using the argument collection method described, as long as the first element in the arguments array is null. I'm not sure why null is necessary, but it works fine this way.