Sidestepping errors by defining vars with SETF

135 Views Asked by At

Crew,

I'm one of those types that insists on defining my variables with SETF. I've upgraded to a new machine (and a new version of SBCL) and it's not letting me get away with doing that (naturally, I get the appropriate "==> undefined variable..." error)

My problem here is, I've already written 20,000 lines of code (incorrectly) defining my variables with SETF and i don't like the prospect of re-writing all of my code to get the interpreter to digest it all.

Is there a way to shut down that error such that interpretation can continue?

Any help is appreciated.

Sincerely,

-Todd

2

There are 2 best solutions below

0
Xach On

One option is to set up your package environment so that a bare symbol setf refers to my-gross-hack::setf instead of cl:setf. For example, you could set things up like:

(defpackage #:my-gross-hack
  (:use #:cl)
  (:shadow #:setf))

;;; define your own setf here

(defpackage #:my-project
  (use #:cl)
  (shadowing-import-from #:my-gross-hack #:setf))

;;; proceed to use setf willy-nilly
0
coredump On

You can handle warnings, so that compilation terminates; while doing so, you can collect enough information to fix your code.

Tested with SBCL 1.3.13.

Handle warnings

I have warnings when using setf with undefined variables. The following invokes the debugger, from which I can invoke muffle-warning:

(handler-bind ((warning #'invoke-debugger)) 
  (compile nil '(lambda () (setf *shame* :on-you))))

The warning is of type SIMPLE-WARNING, which has the following accessors: SIMPLE-CONDITION-FORMAT-CONTROL and SIMPLE-CONDITION-FORMAT-ARGUMENTS.

(defparameter *setf-declarations* nil)

(defun handle-undefined-variables (condition)
  (when (and (typep condition  'simple-warning)
             (string= (simple-condition-format-control condition)
                      "undefined ~(~A~): ~S"))
    (let* ((arguments (simple-condition-format-arguments condition))
           (variable (and (eq (first arguments) :variable)
                          (second arguments))))
      (when variable
        (proclaim `(special ,variable))
        (push variable *setf-declarations*)
        (invoke-restart 'muffle-warning)))))

Use that as a handler:

(handler-bind ((warning #'handle-undefined-variables))
  ;; compilation, quickload, asdf ...
  )

The above handler is not robust: the error message might change in future versions, the code assumes the arguments follow a given pattern, ... But this needs only work once, since from now on you are going to declare all your variables.

Fix your code

Now that your code compiles, get rid of the ugly. Or at least, add proper declarations.

(with-open-file (out #P"declarations.lisp" :direction :output)
  (let ((*package* (find-package :cl-user)))
    (format out
            "(in-package :cl-user)~%~%~{(defvar ~(~S~))~%~}"
            *setf-declarations*)))

This iterates over all the symbols you collected and write declarations inside a single file. In my example, it would contain:

(in-package :cl-user)

(defvar *shame*)

Try to cleanly recompile without handling errors, by loading this file early in your compilation process, but after packages are defined. Eventually, you may want to find the time to move those declarations in place of the setf expressions that triggered a warning.