Search code examples
arrayschartypeerrorcommon-lispsbcl

SBCL Compile Error & Warning about a Conflict with Type base-char


I'm trying to optimize a function by changing an argument from type string to simple-base-string (among other things). But this produces a compiler error:

(declaim (ftype (function (simple-base-string) (or nil t)) dictionary-compatible))
(defun dictionary-compatible ($new-cross-str)  ;eg, "A?R??" of length 5
  "Tests if a string (with uppercase alphabetic and ? characters)
   is compatible with the dictionary words (symbols) in a hash-table."
  (declare (optimize (speed 3)))
  (iter (with len = (length $new-cross-str))
        (declare (fixnum len))
        (for chr in-vector $new-cross-str)
        (declare (base-char chr))
        (for index from 0)
        (declare (fixnum index))
        (when (char/= chr #\?)
          (collect (gethash (format nil "~D~C~D" len chr index)
                            *lci-dictionary*) into dict-words))
        (finally (return (reduce #'intersection dict-words)))))


; in: DEFUN DICTIONARY-COMPATIBLE
;     (ITERATE:ITER
;       (ITERATE:WITH WOULDWORK-PKG::LEN = (LENGTH WOULDWORK-PKG::$NEW-CROSS-STR))
;       (DECLARE (FIXNUM WOULDWORK-PKG::LEN))
;       (ITERATE:FOR WOULDWORK-PKG::CHR WOULDWORK-PKG::IN-VECTOR
;        WOULDWORK-PKG::$NEW-CROSS-STR)
;       (DECLARE (BASE-CHAR WOULDWORK-PKG::CHR))
;       (ITERATE:FOR WOULDWORK-PKG::INDEX WOULDWORK-PKG::FROM 0)
;       (DECLARE (FIXNUM WOULDWORK-PKG::INDEX))
;       (WHEN (CHAR/= WOULDWORK-PKG::CHR #\?)
;         (ITERATE:COLLECT
;          (GETHASH
;           (FORMAT NIL "~D~C~D" WOULDWORK-PKG::LEN WOULDWORK-PKG::CHR
;                   WOULDWORK-PKG::INDEX)
;           WOULDWORK-PKG::*LCI-DICTIONARY*)
;          WOULDWORK-PKG::INTO WOULDWORK-PKG::DICT-WORDS))
;       (ITERATE:FINALLY
;        (RETURN (REDUCE #'INTERSECTION WOULDWORK-PKG::DICT-WORDS))))
; --> BLOCK BLOCK TAGBODY PROGN IF PROGN
; ==>
;   WOULDWORK-PKG::DICT-WORDS
;
; note: deleting unreachable code
;
; caught WARNING:
;   Constant NIL conflicts with its asserted type BASE-CHAR.
;   See also:
;     The SBCL Manual, Node "Handling of Types"

Does the macroexpansion of the iter statement offer any clues to the problem(s)?

* (macroexpand-1 '(iter (with len = (length $new-cross-str))
        (declare (fixnum len))
        (for chr in-vector $new-cross-str)
        (declare (base-char chr))
        (for index from 0)
        (declare (fixnum index))
        (when (char/= chr #\?)
          (collect (gethash (format nil "~D~C~D" len chr index)
                            *lci-dictionary*) into dict-words))
        (finally (return (reduce #'intersection dict-words)))))
(LET* ((LEN (THE FIXNUM (LENGTH $NEW-CROSS-STR)))
       (#:SEQUENCE414 NIL)
       (#:LIMIT415 NIL)
       (CHR NIL)
       (#:INDEX413 NIL)
       (INDEX 0)
       (DICT-WORDS NIL)
       (#:END-POINTER416 NIL)
       (#:TEMP417 NIL))
  (DECLARE (FIXNUM LEN))
  (DECLARE (BASE-CHAR CHR))
  (DECLARE (FIXNUM INDEX))
  (BLOCK NIL
    (BLOCK #:ITERATE142
      (TAGBODY
        (PROGN (SETQ #:SEQUENCE414 $NEW-CROSS-STR) (SETQ #:LIMIT415 (LENGTH #:SEQUENCE414)) (SETQ #:INDEX413 -1) (SETQ INDEX -1))
       LOOP-TOP-NIL
        (PROGN
         (SETQ #:INDEX413 (+ #:INDEX413 1))
         (IF (>= #:INDEX413 #:LIMIT415)
             (GO LOOP-END-NIL))
         (SETQ CHR (AREF #:SEQUENCE414 #:INDEX413))
         (SETQ INDEX (+ INDEX 1))
         (IF (CHAR/= CHR #\?)
             (PROGN
              (SETQ #:TEMP417 (LIST (GETHASH (FORMAT NIL "~D~C~D" LEN CHR INDEX) *LCI-DICTIONARY*)))
              (SETQ #:END-POINTER416
                      (IF DICT-WORDS
                          (SETF (CDR #:END-POINTER416) #:TEMP417)
                          (SETQ DICT-WORDS #:TEMP417)))
              DICT-WORDS)))
        (PROGN)
        (GO LOOP-TOP-NIL)
       LOOP-END-NIL
        (PROGN (RETURN (REDUCE #'INTERSECTION DICT-WORDS))))
      NIL)))
T

Solution

  • This is a bug in iterate which is fairly common (series had it, long ago, as well). The problem is that it binds your chr variable too early, and in particular before it has worked out that it can bind it to the first element of the vector (because the vector might not have any elements). So it needs to find some default value ... and picks nil, which is not correct.

    This is sometimes hard to avoid: in the series case I think there were clearly cases where it was difficult to deal with. This is not one of those.

    A horrid workaround is to declare types as (or null <type-you-want>). loop also gets this right in SBCL (by, if it has to, turning the type declaration in something like (loop ... for x of-type fosh across ... ...) into (or null fosh)). This is better than not working at all, but it does mean that the type declaration for the variable is not what you expect it to be by looking at the code, so in particular something like (loop for x of-type fosh across ... do (setq x nil) ...) will not be picked up as an error.


    As a note: this is why I avoid complicated iteration constructs: they almost always have obscure bugs like this. loop probably has had most of them sorted out by now if you can bear it (I can't).