What is the meaning of the "explicit-check" declaration identifier in SBCL?

Viewed 167

In the source code of SBCL's y-or-n-p, I see (declare (explicit-check)):

(defun y-or-n-p (&optional format-string &rest arguments)
  "..."
  (declare (explicit-check))
  (flet ((print-query ()
           (apply #'maybe-print-query "(y or n)" format-string arguments)))
    (loop (print-query)
          (case (query-read-char)
            ((#\y #\Y) (return t))
            ((#\n #\N) (return nil))
            (t (clarify-legal-query-input "y" "n"))))))

What does explicit-check do? It is not listed as a standard declaration identifier in the HyperSpec, so it is probably implementation-defined. However, I don't see any mention of explicit-check in the SBCL manual.

1 Answers

Based on the following sources in the code, it appears that explicit-check indicates that the types associated with function parameters, as declared by an FTYPE declaration, are not automatically checked but checked explicitly (manually).

This avoids having redundant checks when some function f dispatches on type of one argument a to a specialized function f_a whose signature is declared by FTYPE. Without the explicit-check in f_a, the type of a would be checked twice, once during type dispatch, and once when entering the function.

  • src/compiler/ir1-translators.lisp

    ;;; Check a new global function definition for consistency with
    ;;; previous declaration or definition, and assert argument/result
    ;;; types if appropriate. This assertion is suppressed by the
    ;;; EXPLICIT-CHECK attribute, which is specified on functions that
    ;;; check their argument types as a consequence of type dispatching.
    ;;; This avoids redundant checks such as NUMBERP on the args to +, etc.
    ;;; FIXME: this seems to have nothing at all to do with adding "new"
    ;;; definitions, as it is only called from IR1-CONVERT-INLINE-EXPANSION.
    
  • src/compiler/ctype.lisp

    (warn "Explicit-check without known FTYPE is meaningless")
    
  • package-data-list.lisp-expr

    ;; Advice to the compiler that it doesn't need to assert types.
    "EXPLICIT-CHECK"
    
  • src/compiler/ir1tran.lisp

    ;; EXPLICIT-CHECK by itself specifies that all argument and
    ;; result types are checked by the function body.
    

In the context of y-or-n-p, the intent is to have types checked only once. It could be done early, e.g. at the beginning of y-or-n-p, which would then call "unsafe" functions which do not check their type, but this is not the case here.

Instead, the function is defined with defknown, which executes the following code:

(setf (info :function :type name) type-to-store)

(see src/compiler/knownfun.lisp)

This is the same effect, if I am not mistaken, as having an FTYPE declared for the function (both ftype and defknown set this info slot).

But, here y-or-n-p does not need to check the type itself, since it mostly delegates to another function, namely maybe-print-query. That function calls format, which is also declared explicit-check.

The first thing it does is to etypecheck the destination argument, in order to dispatch the call to format to different invocations of %format, which in turns also branches out to different outcomes based on the type of the next argument (a control string or a formatter function).

So in practice, checking the type of the arguments is interleaved with dispatching, because there are a lot of corner cases to consider.

A top-down approach where the input types are checked first then passed down to unsafe functions would need to perform some complex type checking step too, and then it would still require to dispatch based on the argument types. That's probably why type checking is delayed until the code reaches the different special cases.

Related