ClojureScript

Incorrect behaviour of apply-to for (>= argc 6)

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Major Major
  • Resolution: Completed
  • Affects Version/s: None
  • Fix Version/s: None
  • Component/s: None
  • Labels:
    None

Description

The cljs.core/apply-to function exhibits incorrect behaviour when called with an "argument count" argc (the second argument to apply-to) greater than or equal to 6.

This is because the cljs.core/gen-apply-to macro produces (essentially) to following function:

(defn apply-to
  [f argc args]
  (let [args (seq args)]
    (if (== argc 0)
      (f)
      (let [[a & args] args]
        (if (== argc 1)
          (f a)
          (...
            (if (== argc 6)
              (let [[f % args] args] ;; f is being overshadowed
                (f a b c d e f)
                (if (== argc 7)
                  (let [[g & args] args]
                    (f a b c d e f g)
                    (...)))))))))))

For (>= argc 6) the f in

(defn apply-to [f argc args] ...)

is being overshadowed by the one in

(let [[f % args] args] ...)

The obvious solution would be to output something like

(defn apply-to
  [func argc args]
  (let [args (seq args)]
    (if (== argc 0)
      (func)
      (let [[a & args] args]
        (if (== argc 1)
          (func a)
          (...
            (if (== argc 6)
              (let [[f % args] args]
                (func a b c d e f)
                (if (== argc 7)
                  (let [[g & args] args]
                    (func a b c d e f g)
                    (...)))))))))))

The following changes to gen-apply-to-helper and gen-apply-to should do the trick:

(defn gen-apply-to-helper
  ([] (gen-apply-to-helper 1))
  ([n]
     (let [prop (symbol (core/str "-cljs$core$IFn$_invoke$arity$" n))
           func (symbol (core/str "cljs$core$IFn$_invoke$arity$" n))]
       (if (core/<= n 20)
         `(let [~(cs (core/dec n)) (-first ~'args)
                ~'args (-rest ~'args)]
            (if (core/== ~'argc ~n)
              (if (. ~'func ~prop)
                (. ~'func (~func ~@(take n cs)))
                (~'func ~@(take n cs)))
              ~(gen-apply-to-helper (core/inc n))))
         `(throw (js/Error. "Only up to 20 arguments supported on ifntions"))))))
         
(defmacro gen-apply-to []
  `(do
     (set! ~'*unchecked-if* true)
     (defn ~'apply-to [~'func ~'argc ~'args]
       (let [~'args (seq ~'args)]
         (if (zero? ~'argc)
           (~'func)
           ~(gen-apply-to-helper))))
     (set! ~'*unchecked-if* false)))

Activity

Hide
David Nolen added a comment -

apply-to is an internal helper do you have an example where this is a problem in practice? Thanks.

Show
David Nolen added a comment - apply-to is an internal helper do you have an example where this is a problem in practice? Thanks.
Hide
Kevin Marolt added a comment -

Sure, try this with :optimizations :none (I haven't tested it with other optimization settings):

(def a (atom {:foo (with-meta [] {:bar '(1 2 3)})}))
(swap! a update-in [:foo] vary-meta update-in [:bar] vector)
(prn (= @a {:foo (with-meta [] {:bar [1 2 3]})})) ;; prints "false"
(prn (= @a [{:foo (with-meta [] {:bar '(1 2 3)})}
            [:foo]
            vary-meta
            update-in
            [:bar
            vector])) ;; prints "true"

The goal was to vectorize the list '(1 2 3), but swap! is applying update-in to the arguments

{:foo (with-meta [] {:bar '(1 2 3)})} ;; a
[:foo]                                ;; b
vary-meta                             ;; c
update-in                             ;; d
[:bar]                                ;; e
vector                                ;; f

and thus, because vector is in sixth position (and therefore the f-argument), what's actually happening is that vector is incorrectly called with those same arguments instead.

Show
Kevin Marolt added a comment - Sure, try this with :optimizations :none (I haven't tested it with other optimization settings):
(def a (atom {:foo (with-meta [] {:bar '(1 2 3)})}))
(swap! a update-in [:foo] vary-meta update-in [:bar] vector)
(prn (= @a {:foo (with-meta [] {:bar [1 2 3]})})) ;; prints "false"
(prn (= @a [{:foo (with-meta [] {:bar '(1 2 3)})}
            [:foo]
            vary-meta
            update-in
            [:bar
            vector])) ;; prints "true"
The goal was to vectorize the list '(1 2 3), but swap! is applying update-in to the arguments
{:foo (with-meta [] {:bar '(1 2 3)})} ;; a
[:foo]                                ;; b
vary-meta                             ;; c
update-in                             ;; d
[:bar]                                ;; e
vector                                ;; f
and thus, because vector is in sixth position (and therefore the f-argument), what's actually happening is that vector is incorrectly called with those same arguments instead.

People

Vote (1)
Watch (1)

Dates

  • Created:
    Updated:
    Resolved: