4

Is there anything built into elisp for reflection on function argument signatures?

I have used (length (cadr (symbol-function #'my/function))), but this notably fails on built-in C functions.

I'm sure there are probably limitations because of function representation and there being argument keywords and destructuring in different contexts, but the goal here is strictly exploratory.

Possibilities:

  • signature length (arity)
  • has optional args
  • min signature length
  • ...
ebpa
  • 7,319
  • 26
  • 53
  • Looks like`C-h f` is using `help-function-arglist` to build function argument signature, maybe taking a look at its implementation can give you some clues. – xuchunyang Dec 12 '16 at 09:13

3 Answers3

2

Stable Emacs releases offer subr-arity which returns a cons describing the argument length. This function has been extended to work in a more general fashion on the master branch, check out func-arity and the thread leading to it.

I'd generally advise against using this for more than tools helping you with elisp development (such as better error messages indicating minimum/maximum args). If you can, find a way to not reach into that bag of tricks.

wasamasa
  • 21,803
  • 1
  • 65
  • 97
  • 1
    Indeed, buyer beware: e.g. for any function that has an *advice* placed on it, `func-arity` will say `(0 . many)`. – Stefan Dec 12 '16 at 14:23
2

Here's the code I have in my dot files. This code evolved over the years while maintaining backward compatibility; it should work with any Emacs or XEmacs version since the late 19.x series. If you only care about recent versions, the code can undoubtedly be simplified.

(cond
 ;; XEmacs
 ((fboundp 'compiled-function-arglist)
  (defalias 'emacsen-compiled-function-arglist 'compiled-function-arglist))
 ;; GNU Emacs
 (t
  (defun emacsen-make-up-number-arglist (start end tail)
    (while (< start end)
      (setq end (1- end))
      (setq tail (cons (intern (format "a%d" end)) tail)))
    tail)
  (defun emacsen-compiled-function-arglist (func)
    (let ((a (aref func 0)))
      (if (integerp a)
          ;; An integer encoding the arity. Encountered in Emacs 24.3.
          ;; http://emacs.stackexchange.com/questions/971/argspec-or-arity-of-a-bytecode-function-in-emacs-24/973#973
          (let ((arglist (if (zerop (logand a 128))
                             nil
                           '(&rest rest)))
                (mandatory (logand a 127))
                (nonrest (lsh a -8)))
            (if (> nonrest mandatory)
                (setq arglist (cons '&optional (emacsen-make-up-number-arglist mandatory nonrest arglist))))
            (emacsen-make-up-number-arglist 0 mandatory arglist))
        ;; Otherwise: this is the arglist. The only format I've seen up to GNU 23.
        a)))))
(defun interactive-spec (function &optional safe)
  "Return the interactive calling specs of FUNCTION.
Signal an error if FUNCTION does not have interactive calling specs.
However, in this case, if optional second argument SAFE is non-nil,
return nil."
  (want-type 'functionp function)
  (while (symbolp function)
    (setq function (symbol-function function)))
  (condition-case e
      (progn
        (if (eq (car-safe function) 'autoload)
            (progn
              (if (null (fifth function))
                  (signal 'wrong-type-argument `(interactivep ,function)))
              (load (third function))))
        (cond
         ((byte-code-function-p function)
          (or (if (fboundp 'compiled-function-interactive)
                  (compiled-function-interactive function)
                (aref function 5))
              (signal 'wrong-type-argument `(interactivep ,function))))
         ((and (consp function)
               (eq 'lambda (car function)))
          (or (cdr (assq 'interactive (cdr (cdr function))))
              (signal 'wrong-type-argument `(interactivep ,function))))
         (t
          (signal 'failure `(interactive-spec ,function ,@(and safe (list safe)))))))
    (wrong-type-argument (if (and (eq (car-safe (cdr e)) 'interactivep) safe)
                             nil
                           (signal 'wrong-type-argument (cdr e))))))

(defun function-argspec (func)
  "Return a function's argument list.
For byte-compiled functions in Emacs >=24, some information may be lost as the
byte compiler sometimes erases argument names. In this case, fake argument names
are reconstructed."
  (if (symbolp func) (setq func (indirect-function func)))
  (cond
   ((or (subrp func)
        (and (consp func)
             (eq (car func) 'autoload)
             (consp (cdr func))
             (consp (cdr (cdr func)))
             (stringp (car (cdr (cdr func))))))
    (let ((docstring (documentation func)))
      (save-match-data
        (if (string-match "\n.*\\'" docstring)
            (let ((form (read (match-string 0 docstring))))
              (cdr form))
          nil))))
   ((byte-code-function-p func)
    (emacsen-compiled-function-arglist func))
   ((and (consp func)
         (eq (car func) 'lambda)
         (consp (cdr func)))
    (car (cdr func)))
   ((and (consp func)
         (eq (car func) 'closure)
         (consp (cdr func))
         (consp (cdr (cdr func))))
    (car (cdr (cdr func))))
   (t (signal 'wrong-type-argument
              (list 'functionp func)))))

(defun function-arity (func)
  "Return a function's arity as (MIN . MAX).
Return minimum and maximum number of args allowed for SUBR.
The returned value is a pair (MIN . MAX).  MIN is the minimum number
of args.  MAX is the maximum number or the symbol `many', for a
function with `&rest' args, or `unevalled' for a special form.

This function is like `subr-arity', but also works with user-defined
and byte-code functions. Symbols are dereferenced through
`indirect-function'."
  ;; TODO: keyword support
  (if (symbolp func) (setq func (indirect-function func)))
  (cond
   ((and (subrp func) (fboundp 'subr-arity))
    (subr-arity func))
   (t
    (let ((mandatory 0) (optional 0) (rest nil)
          (where 'mandatory))
      (when (and (consp func) (eq 'macro (car func)))
        (setq func (cdr func))
        (setq rest 'unevalled))
      (let ((argspec (function-argspec func)))
        (dolist (arg argspec)
          (cond
           ((eq arg '&optional) (setq where 'optional))
           ((eq arg '&rest) (unless rest (setq rest 'many)))
           (t (set where (+ (symbol-value where) 1)))))
        (cons mandatory (or rest (+ mandatory optional))))))))

For built-in functions, there was no way to obtain parameter information directly before Emacs 25, but the information can be parsed from the docstring.

1

Do people still answer old questions? Anyway, I think the built-in help-function-arglist could be useful here.

It skips any advice in effect, but other than that it provides just what you'd see in the help buffer.

purple_arrows
  • 2,373
  • 10
  • 19