Next: Implementation Limitations, Previous: Precise Type Checking, Up: Handling of Types
Since SBCL's compiler does much more comprehensive type checking than most Lisp compilers, SBCL may detect type errors in programs that have been debugged using other compilers. These errors are mostly incorrect declarations, although compile-time type errors can find actual bugs if parts of the program have never been tested.
Some incorrect declarations can only be detected by run-time type
checking. It is very important to initially compile a program with
full type checks (high safety
optimization) and then test this
safe version. After the checking version has been tested, then you can
consider weakening or eliminating type checks. This applies
even to previously debugged programs, because the SBCL compiler does
much more type inference than other Common Lisp compilers, so an
incorrect declaration can do more damage.
The most common problem is with variables whose constant initial value doesn't match the type declaration. Incorrect constant initial values will always be flagged by a compile-time type error, and they are simple to fix once located. Consider this code fragment:
(prog (foo) (declare (fixnum foo)) (setq foo ...) ...)
Here foo
is given an initial value of nil
, but is
declared to be a fixnum
. Even if it is never read, the initial
value of a variable must match the declared type. There are two ways
to fix this problem. Change the declaration
(prog (foo) (declare (type (or fixnum null) foo)) (setq foo ...) ...)
or change the initial value
(prog ((foo 0)) (declare (fixnum foo)) (setq foo ...) ...)
It is generally preferable to change to a legal initial value rather than to weaken the declaration, but sometimes it is simpler to weaken the declaration than to try to make an initial value of the appropriate type.
Another declaration problem occasionally encountered is incorrect
declarations on defmacro
arguments. This can happen when a
function is converted into a macro. Consider this macro:
(defmacro my-1+ (x) (declare (fixnum x)) `(the fixnum (1+ ,x)))
Although legal and well-defined Common Lisp code, this meaning of this definition is almost certainly not what the writer intended. For example, this call is illegal:
(my-1+ (+ 4 5))
This call is illegal because the argument to the macro is (+ 4
5)
, which is a list
, not a fixnum
. Because of macro
semantics, it is hardly ever useful to declare the types of macro
arguments. If you really want to assert something about the type of
the result of evaluating a macro argument, then put a the
in
the expansion:
(defmacro my-1+ (x) `(the fixnum (1+ (the fixnum ,x))))
In this case, it would be stylistically preferable to change this macro back to a function and declare it inline.
Some more subtle problems are caused by incorrect declarations that can't be detected at compile time. Consider this code:
(do ((pos 0 (position #\a string :start (1+ pos)))) ((null pos)) (declare (fixnum pos)) ...)
Although pos
is almost always a fixnum
, it is nil
at the end of the loop. If this example is compiled with full type
checks (the default), then running it will signal a type error at the
end of the loop. If compiled without type checks, the program will go
into an infinite loop (or perhaps position
will complain
because (1+ nil)
isn't a sensible start.) Why? Because if you
compile without type checks, the compiler just quietly believes the
type declaration. Since the compiler believes that pos
is
always a fixnum
, it believes that pos
is never
nil
, so (null pos)
is never true, and the loop exit test
is optimized away. Such errors are sometimes flagged by unreachable
code notes, but it is still important to initially compile and test
any system with full type checks, even if the system works fine when
compiled using other compilers.
In this case, the fix is to weaken the type declaration to (or
fixnum null)
1.
Note that there is usually little performance penalty for weakening a
declaration in this way. Any numeric operations in the body can still
assume that the variable is a fixnum
, since nil
is not a
legal numeric argument. Another possible fix would be to say:
(do ((pos 0 (position #\a string :start (1+ pos)))) ((null pos)) (let ((pos pos)) (declare (fixnum pos)) ...))
This would be preferable in some circumstances, since it would allow a
non-standard representation to be used for the local pos
variable in the loop body.
[1] Actually, this declaration is unnecessary in
SBCL, since it already knows that position
returns a
non-negative fixnum
or nil
.