clj-fakes
clj-fakes copied to clipboard
It's impossible to patch a variadic function with function calling an original implementation (ClojureScript only)
Steps
See unit.patch tests:
; TODO: fails in CLJS with:
; "TypeError: undefined is not a constructor (evaluating 'unit.fixtures.functions.variadic.cljs$core$IFn$_invoke$arity$1(arguments[0])')"
#?(:clj
(u/-deftest
"variadic function can be patched with non-variadic function which calls original function"
(f/with-fakes
(f/patch! #'funcs/variadic (fn my-sum
[x]
((f/original-val #'funcs/variadic) x)))
(is (= "[a]" (funcs/variadic 100))))))
; TODO: fails in CLJS with "RangeError: Maximum call stack size exceeded."
#?(:clj
(u/-deftest
"variadic function can be patched with variadic function which calls original function"
(f/with-fakes
(let [original-variadic funcs/variadic]
(f/patch! #'funcs/variadic (fn my-variadic
([] (original-variadic))
([a] ((f/original-val #'funcs/variadic) a))
([_ _] 2)
([_ _ _] 3)
([_ _ _ & _] :etc)))
(is (= "[]" (funcs/variadic)))
(is (= "[a]" (funcs/variadic 1)))
(is (= 2 (funcs/variadic 1 2)))
(is (= 3 (funcs/variadic 1 2 3)))
(is (= :etc (funcs/variadic 1 2 3 4 5 6 7)))))))
Cause
Bug appears because of the way variadic functions are compiled to JS code. Example:
(defn variadic
([] "[]")
([_] "[a]"))
(let [original-variadic variadic
new-variadic (fn my-variadic
[]
(original-variadic))]
(set! variadic new-variadic)
(variadic))
compiles to this JS code:
cljs.user.variadic = (function cljs$user$variadic(var_args){
....
var G__24 = args.length;
switch (G__24) {
case (0):
return cljs.user.variadic.cljs$core$IFn$_invoke$arity$0();
break;
case (1):
return cljs.user.variadic.cljs$core$IFn$_invoke$arity$1((arguments[(0)]));
break;
default:
throw (new Error([cljs.core.str("Invalid arity: "),cljs.core.str(args.length)].join('')));
}
});
cljs.user.variadic.cljs$core$IFn$_invoke$arity$0 = (function (){...});
cljs.user.variadic.cljs$core$IFn$_invoke$arity$1 = (function (_){...});
cljs.user.variadic.cljs$lang$maxFixedArity = (1);
var original_variadic_29 = cljs.user.variadic;
var new_variadic_30 = ((function (original_variadic_29){
return (function cljs$user$my_variadic(){
return original_variadic_29.call(null);
});
})(original_variadic_29));
cljs.user.variadic = new_variadic_30;
cljs.user.variadic.call(null);
As you can see, cljs.user.variadic = new_variadic_30; will erase cljs.user.variadic.cljs$core$IFn$_invoke$arity$0 leading to a bug.