In ClojureJVM, if I compile
(ns test.meta)
(defn ^double d [^double x] x)
and decompile the result, I find
public final class meta$d extends AFunction implements {
public static double invokeStatic(double x) {
return x;
}
public Object invoke(Object paramObject) {
return Double.valueOf(invokeStatic(RT.doubleCast(paramObject)));
}
public final double invokePrim(double paramDouble) {
return invokeStatic(paramDouble);
}
}
I think pretty clearly invokeStatic , invokePrim , and invoke are in agreement that this function is double in, double out.
Questions:
1. Shouldn't this really be Implementing IFn.DD instead of ?
2. The JVM really lets you get by with this? I.e., it allows signature double invokePrim(double) to satisfy with method Object invokePrim(double) ? (The ClojureCLR compiler mimics this and the CLR is really not happy. As in an error saying that is missing an implementation for the invokePrim in DO.)
If this is the desired behavior, I cannot make it happen on the CLR.
(Fortunately, the fix to make the function implement DD instead of DO is relatively simple.)var metadata is evaluated, signature metadata is not (this was a fork in the road long past) with some consequences. here, it means that primitive type hint is invalid on the var and thus ignored (treated as object)
my sincere goal is to start reporting the invalid type hint as such, at least as a warning (this is fairly common) and I think I have a ticket for that... (there are several old tickets about it for sure)
I do understand the metadata evaluation problem. Specifically, If found the discussion https://ask.clojure.org/index.php/8993/where-to-place-function-return-type-hints . And I just now found an comment in that discussion that I had missed before: https://ask.clojure.org/index.php/8993/where-to-place-function-return-type-hints?show=14657#c14657 In that example, he is getting an exception similar to the one I'm getting on ClojureCLR:
Receiver class user$pythag does not define or inherit an
implementation of the resolved method 'abstract java.lang.Object
invokePrim(double, double)' of interface clojure.lang.IFn$DDO.
(A comment is made that this only a problem from Clojure 1.8.0 on)
That comment also refers to a https://old.reddit.com/r/Clojure/comments/1mfh5no/feature_or_bug_primitive_type_hints/ on the topic where this comment is made:
What's happening under the hood is that pythag is compiled down to a IFn.DDO implementation, but double invokePrim(double, double) is implemented as opposed to Object invokePrim(double, double), as if the intent was to emit to a IFn.DDD implementation. Clearly, the part choosing the class to implement misses the tag on the fn symbol while the part that generates the methods looks at it (this by itself is surprising, why would there be two ways to look that information up?).
Which accords with the decompilation I showed.
Somehow the double has to be being passed along, unevaluated, to be seen in FnMethod.Parse -- and it is being seen, else the return type of staticInvoke and invokePrim would be Object and not double. Assuming the symbol double is being passed, I can explain exactly why the code is being generated with a mismatch between the invokes and the interface. (It is due to FnMethod.PrimInterface.)
I thought the situation was this: defn is a macro, and gets access to the metadata before evaluation. It grabs the :tag value from the symbol metadata and passes it along as the value of for :rettag. That value for :rettag would not be the value of double , i.e., the double function, because the metadata is being attached to the cons during expansion and is not seen explicitly as part of the expanded code.
(list 'def (with-meta name m)
;;todo - restore propagation of fn name
;;must figure out how to convey primitive hints to self calls first
;;(cons `fn fdecl)
(with-meta (cons `fn fdecl) {:rettag (:tag m)})))))
(And I believe the code added to the definition of defn that uses :rettag was added in Clojure 1.8.0 -- relevant to the comment above.)
If I am incorrect, please educate me. But even if I am incorrect in this analysis, this leaves unexplained how meta$d.staticInvoke and meta$d.primInvoke have double for the return type if the double has been evaluated and hence ignored. Not trying to be contentious; I just really don't understand how that double gets there otherwise.One thing I didn't try was actually evaluating the meta$d function:
user=> (d 12.0)
Execution error (AbstractMethodError) at user/eval84 (REPL:1).
Receiver class user$d does not define or inherit an implementation of the resolved method 'abstract java.lang.Object invokePrim(double)' of interface clojure.lang.IFn$DO.
So though JVM doesn't complain about the type being defined, it does complain about the type being used.
CLR complains about the type being defined,.
what happens if you put the return tag on the vector?
What if you do ^Double/TYPE instead?
If one puts the return tag on the vector, which admiittedly is the recommended way, then the function class implement IFn.DD , as one would hope and expect.
if one puts ^Double/TYPE on the var name, as is being discussed above, then it is implements . However, in this situation, that is correct: the return type for each of invokePrim and invokeStatic is Object . This is because of the way the tag value is dealt with. It is not evaluated, and ends up being ignored, which leads to the default return of Object. If you put ^Double/TYPE on the parameter vector, the ClojureJVM compiler will throw an IllegalArgumentException (Unable to resolve classname: Double/TYPE).
A small change in FnMethod.Parse would result in IFn.DD being the named interface instead of . However, I am not the judge of what is correct behavior. (Which is a good thing for all concerned.)