clj-fakes icon indicating copy to clipboard operation
clj-fakes copied to clipboard

It's impossible to patch a variadic function with function calling an original implementation (ClojureScript only)

Open metametadata opened this issue 8 years ago • 0 comments

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.

metametadata avatar Jun 22 '16 17:06 metametadata