CL-USER
CL-USER

Reputation: 786

Good example of when to muffle warnings?

This question is somewhat related to an earlier one on programmatically generating symbol macros. I'm using that function in a convenience macro that throws undefined variable warnings. This macro and function:

(defmacro define-data (d body &optional doc)
  (if (and doc (not (stringp doc))) (error "Documentation is not a string"))
  `(let* ((d-str (string ',d))
          (old-package *package*)
          (*package* (if (find-package d-str)    ;exists?
                         (find-package d-str)    ;yes, return it
                         (make-package d-str)))) ;no, make it
     ;; Should we have an eval-when (:compile-toplevel) here?
     (defparameter ,d ,body ,doc)
     (export ',d old-package)
     (define-column-names ,d)))

(defun define-column-names (d)
  (maphash #'(lambda (key index)
           (eval `(cl:define-symbol-macro ,key (cl:aref (columns ,d) ,index))))
       (ordered-keys-table (slot-value d 'ordered-keys))))

are intended to be like defparameter, but additionally set up a few niceties for the user by defining:

  1. a package with the name of d
  2. a parameter in the current package with the data that will be sucked in by body
  3. symbol-macros in package d for access to the individual data vectors

If I use defparameter from the REPL, and then call define-column-names, all is well. However when using the macro I get:

; in: DEFINE-COLUMN-NAMES FOO
;     (DEFINE-COLUMN-NAMES CL-USER::FOO)
; 
; caught WARNING:
;   undefined variable: CL-USER::FOO

I suspect that this is because the compiler has no way of knowing that FOO will actually be defined when define-symbol-macro is called. Everything works fine, but I don't want the warning to frighten users, so am thinking of suppressing it. I hate suppressing warnings though, so thought I'd come here for a second opinion.

EDIT: I've marked an answer correct because it does correctly answer the question as asked. For an answer to the problem see my comments.

Upvotes: 1

Views: 148

Answers (2)

user5920214
user5920214

Reputation:

My answer to the 'when to muffle warnings' question in the title is: if it's your own code then never, under any circumstances. If it is someone else's code, then rewrite it not to warn unless you can't.

As to solving the problem I haven't thought about this hard enough, but the problem is that you definitely want the defparameter to be at top-level so the compiler can see it, and it can't really be if it's inside a let. But you can raise it to toplevel trivially since it depends on nothing inside the let.

I am then pretty certain that you want the rest of the macro to happen at compile time, because you definitely want the symbol-macros available at compile-time. So an attempt at the first macro would be (note I've fixed the handling of the docstring: (defparameter foo 1 nil) is bad):

(defmacro define-data (d body &optional doc)
  (when (and doc (not (stringp doc)))
    (error "Documentation is not a string"))
  `(progn
     (defparameter ,d ,body ,@(if doc (list doc) '()))
     (eval-when (:compile-toplevel :load-toplevel :execute)
       (let* ((d-str (string ',d))
              (old-package *package*)
              (*package* (if (find-package d-str)    ;exists?
                             (find-package d-str)    ;yes, return it
                           (make-package d-str)))) ;no, make it
         (export ',d old-package)
         (define-column-names ,d)))))

As a side note: although I think the fact that programmatically defining symbol macros is hard because CL left that out for some reason, I think I'd personally use some other approach rather than this, because eval is just so horrid. That's just me however: if you want to do this you do need eval I think (it is very rare that this is true!).

Upvotes: 1

coredump
coredump

Reputation: 38924

I am not sure exactly how define-columns-names works so I replaced it with a stub function that returns d.

Note also that you can use check-type and should try not injecting symbols in generated code, this introduces potential variable capture that can be avoided with gensym.

As far as I know you cannot use eval-when as suggested by your comment (see Issue EVAL-WHEN-NON-TOP-LEVEL Writeup for details).

But I have no warning if I declare the symbol as being special around the call.

(defmacro define-data (d body &optional doc)
  (check-type doc (or null string))
  (check-type d symbol)
  (let ((d-str (string d)))
    (alexandria:with-gensyms (old-package)
      `(let* ((,old-package *package*)
              (*package* (if (find-package ,d-str)    ;exists?
                             (find-package ,d-str)    ;yes, return it
                             (make-package ,d-str)))) ;no, make it
         (defparameter ,d ,body ,doc)
         (export ',d ,old-package)
         (locally (declare (special ,d))
           (define-column-names ,d))))))

It is also a bit strange that you expand into a call to define-column-names, which in turns evaluated a form built at runtime. I think it might be possible to do all you want during macroexpansion time, but as said earlier what you are trying to do is a bit unclear to me. What I have in mind is to replace define-column-names by:

,@(expand-column-names-macros d)

... where expand-column-names-macros builds a list of define-symbol-macro forms.

Upvotes: 0

Related Questions