methodical icon indicating copy to clipboard operation
methodical copied to clipboard

defmulti should throw Exception if arg count doesn't match ^:arglists metadata from method definition

Open camsaul opened this issue 4 years ago • 1 comments

Add some macroexpansion-time error checking if the multifunction has ^:arglists metadata to check that the method matches a known argument count.

Example:

(m/defmulti mf
  {:arglists '([x y])}
  (fn [x & _]
    (keyword x)))

(m/defmethod mf :after ::bad-after
  [x]
  nil)
;; -> Wrong number of args, mf is only known to take [m]

camsaul avatar Jun 09 '21 17:06 camsaul

We could also have calls to next-method inside the defmethod body do the same thing.

camsaul avatar Jun 11 '21 18:06 camsaul

This is mostly obviated by #109 and #113.

camsaul avatar Sep 09 '22 07:09 camsaul

This becomes sort of an issue when you have "fancy" arglists like ([x y?]) (meaning ([x] [x y])). We wouldn't want to interpret that as meaning you HAVE to use two args. So this might be something we need to have people opt in to.

Another possibility is just supporting something like a :valid-arg-counts options, in this case it could be #{1 2} (support 1 or 2 args). For & maybe something like #{1 3 +}, #{1 3 &}, or #{1 (>= 3)}? Need to figure out how to handle something like ([x] [x y z & more]) (e.g. 2 args is not allowed). Maybe we should just allow some sort of expressions like (or 1 (>= 3)) (with fancy macro expansion) or maybe just take an arbitrary function like #(or (= % 1) (>= % 3)).

camsaul avatar Sep 09 '22 16:09 camsaul

What is the count of an arglist like [x y z & more]? We can't count all the forms -- it's not 5. It's basically (>= 3). So a simple function wouldn't work. We probably need some sort of special syntax for this.

... which brings us full circle. :arglists is basically already a special syntax for this. Maybe we just want to have an option for a different :arglists like :arglists-for-validation or something so you can have a "fancy" arglist like ([x y?]) for human consumption and another one for validation purposes.

camsaul avatar Sep 09 '22 16:09 camsaul

Since the & ... arity has to be greater than any other arity I think it's safe to just use a set like #{1 3 &} where & is just interpreted as (> 3) in this case

camsaul avatar Sep 09 '22 16:09 camsaul

Maybe :more is a more Clojurey way to do this.

(m/defmulti validate-arg-counts-mf
  ;; 2 ARGS ARE NOT ALLOWED.
  {:arglists '([x] [x y z & {:as options}]), :valid-arglist-counts #{1 3 :more}})

vs

(m/defmulti validate-arg-counts-mf
  ;; 2 ARGS ARE NOT ALLOWED.
  {:arglists '([x] [x y z & {:as options}]), :valid-arglist-counts #{1 3 '&}})

Or maybe #{1 [:>= 3]}? Or #{1 3 [:> 3]}? TBD

camsaul avatar Sep 09 '22 16:09 camsaul

Actually in the example above a defmethod basically needs to implement BOTH 1 and (>= 3), not just one or the other.

camsaul avatar Sep 09 '22 17:09 camsaul