2009/5/11 Martin Simmons martin@lispworks.com:
It's a minefield, because the &allow-other-keys makes all keywords legal for all methods...
Right. My example was bad due to the &allow-other-keys. Here's a bit of code that does what I want in SBCL -- portable version needs %SPLIT-ARGLIST and FUNCTION-KEYWORD-PARAMETERS (which can be replaced with FUNCTION-KEYWORDS.)
No comments on how tricky it would be too hook up to slime:
Examples:
(defgeneric foo (object &rest initargs &key))
(generic-function-lambda-list-using-keys #'foo nil) ; => (OBJECT &REST INITARGS &KEY)
(defmethod foo ((cons cons) &key car cdr) (cons car cdr))
(defmethod foo ((complex complex) &key realpart imagpart) (complex realpart imagpart))
(generic-function-lambda-list-using-keys #'foo nil) ; => (OBJECT &REST INITARGS &KEY CDR CAR IMAGPART REALPART) (generic-function-lambda-list-using-keys #'foo '(:realpart)) ; => (OBJECT &REST INITARGS &KEY REALPART IMAGPART) (generic-function-lambda-list-using-keys #'foo '(:cdr)) ; => (OBJECT &REST INITARGS &KEY CAR CDR) (generic-function-lambda-list-using-keys #'foo '(:zot)) ; => (OBJECT &REST INITARGS)
Code:
(in-package :sb-pcl)
;; Adapted from SB-PCL::GENERIC-FUNCTION-PRETTY-ARGLIST (defmethod generic-function-lambda-list-using-keys (generic-function req-keys) (let ((gf-lambda-list (generic-function-lambda-list generic-function)) (methods (generic-function-methods generic-function))) (if (null methods) gf-lambda-list (multiple-value-bind (gf.required gf.optional gf.rest gf.keys gf.allowp) (%split-arglist gf-lambda-list) ;; Possibly extend the keyword parameters of the gf by additional ;; key parameters of those methods that include requested keys not ;; in the generic function lambda-list. (let ((methods.keys nil) (methods.allowp nil)) (dolist (m methods) (multiple-value-bind (m.keywords m.allow-other-keys) (function-keywords m) (when (or gf.allowp m.allow-other-keys (every (lambda (key) (member key m.keywords :key #'maybe-car)) req-keys)) (multiple-value-bind (keyparams allowp) (function-keyword-parameters m) (setq methods.keys (union methods.keys keyparams :key #'maybe-car)) (setq methods.allowp (or methods.allowp allowp)))))) (let ((arglist '())) (when (or gf.allowp methods.allowp) (push '&allow-other-keys arglist)) (when (or gf.keys methods.keys) ;; We make sure that the keys of the gf appear before ;; those of its methods, since they're probably more ;; generally appliable. (setq arglist (nconc (list '&key) gf.keys (nset-difference methods.keys gf.keys) arglist))) (when gf.rest (setq arglist (nconc (list '&rest gf.rest) arglist))) (when gf.optional (setq arglist (nconc (list '&optional) gf.optional arglist))) (nconc gf.required arglist)))))))
Perhaps Slime could:
1. Use GENERIC-FUNCTION-LAMBDA-LIST for DEFMETHOD lambda-list hinting. Collecting keywords from all methods is not helpful there as far as I can see.
2. Use something akin to above for GF call lambda-list hinting -- passing in the list of keys already typed as the second argument.
Cheers,
-- Nikodemus