master: doc: document sb-introspect

melisgl via Sbcl-commits <[email protected]> Sat, 30 May 2026 10:18:01 +0000
Newsgroups gmane.lisp.steel-bank.cvs
Message-ID <[email protected]>
The branch "master" has been updated in SBCL:
       via  48063e41707f0965f57b2c124312376be9b12096 (commit)
      from  480584f800ac07a1310f8c86dfc68024f34fa38e (commit)

- Log -----------------------------------------------------------------
commit 48063e41707f0965f57b2c124312376be9b12096
Author: Gabor Melis <[email protected]>
Date:   Mon May 25 20:40:37 2026 +0200

    doc: document sb-introspect
---
 NEWS                                        |   1 +
 contrib/sb-introspect/introspect.lisp       | 293 +++++++++++++++++++---------
 contrib/sb-introspect/sb-introspect.texinfo |  61 ++++++
 doc/manual/contrib-modules.texinfo          |   4 +
 doc/manual/docstrings.lisp                  |   3 +-
 5 files changed, 267 insertions(+), 95 deletions(-)

diff --git a/NEWS b/NEWS
index e8178a80e..b7dc4366b 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@
 changes relative to sbcl-2.6.5:
   * minor incompatible change: FDEFINITION now returns the outermost wrapper
     (added e.g. by TRACE, PROFILE) like SYMBOL-FUNCTION.  (lp#799533)
+  * documentation: SB-INTROSPECT is documented.
 
 changes in sbcl-2.6.5 relative to sbcl-2.6.4:
   * minor incompatible change: the condition signalled when an accessed slot
diff --git a/contrib/sb-introspect/introspect.lisp b/contrib/sb-introspect/introspect.lisp
index fb2c5c420..3d59acff1 100644
--- a/contrib/sb-introspect/introspect.lisp
+++ b/contrib/sb-introspect/introspect.lisp
@@ -17,7 +17,7 @@
 ;;; own project, there will be much wailing and gnashing of teeth.
 ;;; Your teeth.  If need be, we'll kick them for you.  This is a
 ;;; contrib, we're allowed to look in internals.  You're an
-;;; application programmer, and are not.
+;;; application programmer and are not.
 
 ;;; TODO
 ;;; 3) error handling.  Signal random errors, or handle and resignal 'our'
@@ -67,7 +67,7 @@
 ;;;
 (deftype debug-info ()
   "Structure containing all the debug information related to a function.
-Function objects reference debug-infos which in turn reference
+Function objects reference debug-infos, which in turn reference
 debug-sources and so on."
   'sb-c::compiled-debug-info)
 
@@ -92,8 +92,9 @@ include the pathname of the file and the position of the definition."
   (sb-c::debug-info-source debug-info))
 
 (defun valid-function-name-p (name)
-  "True if NAME denotes a valid function name, ie. one that can be passed to
-FBOUNDP."
+  "See if NAME is a valid function name. In addition to the ANSI
+definition of function name, which is symbols plus lists like (SETF
+SYMBOL), SBCL allows (CAS SYMBOL) and various internal constructs."
   (and (sb-int:valid-function-name-p name) t))
 
 ;;;; Utilities for code
@@ -108,9 +109,9 @@ FBOUNDP."
 (declaim (inline map-allocated-code-components))
 (defun map-allocated-code-components (spaces fn)
   "Call FN for each allocated code component in one of SPACES.  FN
-receives the object and its size as arguments.  SPACES should be a
-list of the symbols :dynamic, :static, :read-only, or :immobile on
-#+immobile-space"
+receives the object and its size as arguments. SPACES should be a list
+of the symbols :DYNAMIC, :STATIC, :READ-ONLY, or :IMMOBILE on
+#+IMMOBILE-SPACE. The shorthand (:ALL) is also accepted."
   (apply #'sb-vm:map-allocated-objects
      (lambda (obj header size)
        (when (= sb-vm:code-header-widetag header)
@@ -119,7 +120,7 @@ list of the symbols :dynamic, :static, :read-only, or :immobile on
 
 (declaim (inline map-caller-code-components))
 (defun map-caller-code-components (function spaces fn)
-  "Call FN for each code component with a fdefn for FUNCTION in its
+  "Call FN for each code component with a FDEFN for FUNCTION in its
 constant pool."
   (let ((function (coerce function 'function)))
     (map-allocated-code-components
@@ -146,32 +147,112 @@ constant pool."
 
 ;;;; Finding definitions
 
-(defstruct definition-source
-  ;; Pathname of the source file that the definition was compiled from.
-  ;; This is null if the definition was not compiled from a file.
-  (pathname nil :type (or null pathname))
-  ;; Source-path of the definition within the file.
-  ;; This may be incomplete depending on the debug level at which the
-  ;; source was compiled.
-  (form-path '() :type list)
-  ;; Depth first number of the form.
-  ;; FORM-PATH above usually contains just the top-level form number,
-  ;; ideally the proper form path could be dervied from the
-  ;; form-number and the tlf-number, but it's a bit complicated and
-  ;; Slime already knows how to deal with form numbers, so delegate
-  ;; that job to Slime.
-  (form-number nil :type (or null unsigned-byte))
-  ;; Character offset of the top-level-form containing the definition.
-  ;; This corresponds to the first element of form-path.
-  (character-offset nil :type (or null unsigned-byte))
-  ;; File-write-date of the source file when compiled.
-  ;; Null if not compiled from a file.
-  (file-write-date nil :type (or null unsigned-byte))
-  ;; plist from WITH-COMPILATION-UNIT
-  (plist nil)
+(defmacro defstruct* (name-and-options &rest slot-descriptions)
+  "Like DEFSTRUCT, but support :DOCUMENTATION among slot options.
+The documentation is attached to the slot's STRUCTURE-ACCESSOR.
+Example:
+
+    (defstruct* my-struct
+      (my-slot nil :documentation \"docstring\"))
+
+In addition to the normal DEFSTRUCT processing, the above also does
+the moral equivalent of
+
+    (setf (documentation 'my-struct-my-slot 'function) \"docstring\")"
+  (destructuring-bind (name &rest options)
+      (sb-c::ensure-list name-and-options)
+    (let* ((conc-name-option (find :conc-name options :key (lambda (x)
+                                                             (if (consp x)
+                                                                 (car x)
+                                                                 x))))
+           (conc-name (cond ((not conc-name-option)
+                             (format nil "~A-" name))
+                            ((or (atom conc-name-option)
+                                 (null (cdr conc-name-option))
+                                 (null (second conc-name-option)))
+                             "")
+                            (t
+                             (string (second conc-name-option)))))
+           (not-found (gensym))
+           (set-doc-forms ())
+           (new-sds
+             (loop
+               for sd in slot-descriptions
+               collect (let ((documentation
+                               (and (listp sd)
+                                    (getf (cddr sd) :documentation not-found))))
+                         (cond
+                           ((or (not (listp sd))
+                                (eq documentation not-found))
+                            sd)
+                           (t
+                            (let ((accessor-name (intern
+                                                  (format nil "~A~A" conc-name
+                                                          (first sd)))))
+                              (push `(setf (documentation ',accessor-name
+                                                          'function)
+                                           ,documentation)
+                                    set-doc-forms))
+                            (let ((sd (copy-seq sd)))
+                              (remf (cddr sd) :documentation)
+                              sd)))))))
+      `(progn
+         (defstruct ,name-and-options
+           ,@new-sds)
+         ,@set-doc-forms))))
+
+;;; FIXME: Rename this
+(defstruct* definition-source
+  "This structure identifies a sexp in a compiled file.
+Despite the name, the source location may not correspond to a
+definition but to e.g. a function call (see WHO-CALLS)."
+  (pathname
+   nil :type (or null pathname)
+   :documentation "Pathname of the source file.
+This is NIL if the source location is not in a compiled file.")
+  (form-path
+   '() :type list
+   :documentation "List of indices that identify the sexp in the
+file given by DEFINITION-SOURCE-PATHNAME. The first element in the
+list is the index of the top-level form that contains the sexp. If the
+file was compiled at a high enough debug level, then the rest of the
+elements recursively index into the list structure of the top-level
+form.
+
+Thus, the form path is somewhat stable regarding edits in the file,
+but it gets invalidated by, for example, inserting a new top-level
+form before the sexp in question.")
+  (form-number
+   nil :type (or null unsigned-byte)
+   :documentation "Depth-first index of the sexp within the top-level
+form identified by the first element of DEFINITION-SOURCE-FORM-PATH.
+That is, this is the index of the sexp in the list of subexpressions
+of the top-level form ordered according to depth-first traversal. 0
+corresponds to the top-level form itself.
+
+When combined with the index of the top-level form (given by the first
+element of DEFINITION-SOURCE-FORM-PATH), the form number allows
+reconstruction of the rest of the form path, which may be missing.
+This requires parsing the source file. Currently, this job is
+delegated to e.g. SLIME.")
+  (character-offset
+   nil :type (or null unsigned-byte)
+   :documentation "Character offset of the top-level form containing
+the sexp.")
+  (file-write-date
+   nil :type (or null unsigned-byte)
+   :documentation "FILE-WRITE-DATE of DEFINITION-SOURCE-PATHNAME at
+the time of compilation. NIL if not compiled from a file.")
+  (plist
+   nil
+   :documentation "The SOURCE-PLIST from WITH-COMPILATION-UNIT in effect
+when the file was compiled.")
   ;; Any extra metadata that the caller might be interested in. For
-  ;; example the specializers of the method whose definition-source this
-  ;; is.
+  ;; example, DEFINITION-SOURCE of a method contains the specializers
+  ;; of the method to help disambiguate it.
+  ;;
+  ;; FIXME: This is currently unexported, but it is also necessary to
+  ;; disambiguate methods. See e.g. WHO-SPECIALIZES-DIRECTLY.
   (description nil :type list))
 
 (defun vops-translating-fun (name)
@@ -204,9 +285,12 @@ constant pool."
 
 (defun find-definition-sources-by-name (name type)
   "Returns a list of DEFINITION-SOURCEs for definitions of NAME with
-the given definition TYPE. TYPE can currently be one of the following.
+the given definition TYPE. A DEFINITION-SOURCE object is always
+returned for definitions that exist, but the source location (e.g.
+DEFINITION-SOURCE-PATHNAME) may be missing. TYPE can currently be one
+of the following.
 
-Public definition TYPEs:
+Public definition types:
 
     :CLASS
     :COMPILER-MACRO
@@ -227,7 +311,7 @@ Public definition TYPEs:
     :VARIABLE
     :DECLARATION
 
-Internal definition TYPEs:
+Internal definition types:
 
     :OPTIMIZER
     :SOURCE-TRANSFORM
@@ -250,8 +334,8 @@ Valid NAMEs are generally SYMBOLs with the following exceptions:
 
 - For :PACKAGE, string designators are valid.
 
-If an unsupported TYPE is requested or NAME is invalid, the function
-will return NIL."
+If an unsupported TYPE is requested or NAME is invalid, this function
+returns NIL."
   (flet ((get-class (name)
            (and (symbolp name)
                 (find-class name nil)))
@@ -438,6 +522,18 @@ will return NIL."
         nil)))))
 
 (defun find-definition-source (object)
+  "Return the DEFINITION-SOURCE corresponding to the definition of OBJECT
+or NIL if there is no corresponding definition. OBJECT must be a
+PACKAGE, FUNCTION, METHOD, METHOD-COMBINATION, SB-MOP:SLOT-DEFINITION,
+STANDARD-OBJECT, STRUCTURE-OBJECT, CONDITION, CLASS, STRUCTURE-CLASS,
+or a subclass of CONDITION. An error is signalled for other types.
+
+A DEFINITION-SOURCE object is always returned for definitions that
+exist, but the source location (e.g. DEFINITION-SOURCE-PATHNAME) may
+be missing.
+
+For definitions that do not define an object (e.g. DEFVAR), use
+FIND-DEFINITION-SOURCES-BY-NAME."
   (typecase object
     ((or sb-pcl::condition-class sb-pcl::structure-class)
      (let ((classoid (sb-pcl::class-classoid object)))
@@ -533,13 +629,13 @@ will return NIL."
   (function-lambda-list function))
 
 (defun function-lambda-list (function)
-  "Return the lambda list for the extended function designator FUNCTION.
-Works for special-operators, macros, simple functions, interpreted functions,
-and generic functions. Signals an error if FUNCTION is not a valid extended
-function designator.
+  "Return the lambda list of FUNCTION.
+FUNCTION must be a function object or a function name in the sense of
+VALID-FUNCTION-NAME-P. Works for special operators, macros, simple
+functions, interpreted functions, and generic functions.
 
-If the function does not have a lambda list (compiled with debug 0),
-then two values are returned: (values nil t)"
+The second return value indicates whether the lambda list could not be
+determined (e.g. because the function was compiled with DEBUG 0)."
   (cond ((and (symbolp function) (special-operator-p function))
          (function-lambda-list (info :function :ir1-convert function)))
         ((valid-function-name-p function)
@@ -554,14 +650,19 @@ then two values are returned: (values nil t)"
                (values nil t)
                (values raw-result nil))))))
 
-(defun deftype-lambda-list (typespec-operator)
-  "Returns the lambda list of TYPESPEC-OPERATOR as first return
-value, and a flag whether the arglist could be found as second
-value."
-  (check-type typespec-operator symbol)
-  ;; Don't return a lambda-list for combinators AND,OR,NOT.
-  (let* ((f (and (info :type :kind typespec-operator)
-                 (info :type :expander typespec-operator)))
+(defun deftype-lambda-list (type-specifier-name)
+  "Returns the lambda list of TYPE-SPECIFIER-NAME as the first return
+value, and a flag whether the arglist could be found as the second
+value.
+
+TYPE-SPECIFIER-NAME must be a symbol. This function can find the
+lambda list of derived type specifiers (e.g. those defined with
+DEFTYPE) and classes with compound type specifier syntaxes (e.g. the
+class FLOAT). It returns NIL, NIL for other type specifiers (e.g. AND,
+OR, NOT) and types (e.g. LIST)."
+  (check-type type-specifier-name symbol)
+  (let* ((f (and (info :type :kind type-specifier-name)
+                 (info :type :expander type-specifier-name)))
          (f (if (listp f) (car f) f)))
     (if (functionp f)
         (let ((lambda-list (%fun-lambda-list f)))
@@ -571,7 +672,7 @@ value."
         (values nil nil))))
 
 (defun method-combination-lambda-list (method-combination)
-  "Return the lambda-list of METHOD-COMBINATION designator.
+  "Return the lambda list of the METHOD-COMBINATION designator.
 METHOD-COMBINATION can be a method combination object,
 or a method combination name."
   (let* ((name (etypecase method-combination
@@ -583,7 +684,7 @@ or a method combination name."
     (sb-pcl::method-combination-info-lambda-list info)))
 
 (defun function-type (function-designator)
-  "Returns the ftype of FUNCTION-DESIGNATOR, or NIL."
+  "Returns the ftype of FUNCTION-DESIGNATOR or NIL."
   (etypecase function-designator
     ((or symbol cons)
      ;; XXX: why require FBOUNDP? Would it be wrong to always report the proclaimed type?
@@ -651,7 +752,12 @@ or a method combination name."
         callees)))
 
 (defun find-function-callers (function &optional (spaces '(:all)))
-  "Return functions which call FUNCTION, by searching SPACES for code objects"
+  "List functions that call FUNCTION by searching SPACES for code objects.
+This can make previously garbage objects live.
+
+SPACES should be a list of the symbols :DYNAMIC, :STATIC, :READ-ONLY,
+or :IMMOBILE on #+IMMOBILE-SPACE. The shorthand (:ALL) is also
+accepted."
   (let ((referrers '()))
     (map-caller-code-components
      function
@@ -820,47 +926,46 @@ or a method combination name."
       result)))))
 
 (defun who-calls (function-name)
-  "Use the xref facility to search for source locations where the
-global function named FUNCTION-NAME is called. Returns a list of
-function name, definition-source pairs."
+  "Find the source locations where the global function FUNCTION-NAME is
+called, and return them as an alist of function or macro name,
+DEFINITION-SOURCE pairs."
   (collect-xref :calls function-name))
 
 (defun who-binds (symbol)
-  "Use the xref facility to search for source locations where the
-special variable SYMBOL is rebound. Returns a list of function name,
-definition-source pairs."
+  "Find the source locations where the special variable SYMBOL is bound,
+and return them as an alist of function or macro name,
+DEFINITION-SOURCE pairs."
   (collect-xref :binds symbol))
 
 (defun who-references (symbol)
-  "Use the xref facility to search for source locations where the
-special variable or constant SYMBOL is read. Returns a list of function
-name, definition-source pairs."
+  "Find the source locations where the special variable SYMBOL is read,
+and return them as an alist of function or macro name,
+DEFINITION-SOURCE pairs."
   (collect-xref :references symbol))
 
 (defun who-sets (symbol)
-  "Use the xref facility to search for source locations where the
-special variable SYMBOL is written to. Returns a list of function name,
-definition-source pairs."
+  "Find the source locations where the special variable SYMBOL is set,
+and return them as an alist of function or macro name,
+DEFINITION-SOURCE pairs."
   (collect-xref :sets symbol))
 
 (defun who-macroexpands (macro-name)
-  "Use the xref facility to search for source locations where the
-macro MACRO-NAME is expanded. Returns a list of function name,
-definition-source pairs."
+  "Find the source locations where the macro MACRO-NAME is expanded, and
+return them as an alist of function or macro name, DEFINITION-SOURCE
+pairs."
   (collect-xref :macroexpands macro-name))
 
 (defun who-specializes-directly (class-designator)
-  "Search for source locations of methods directly specializing on
-CLASS-DESIGNATOR. Returns an alist of method name, definition-source
-pairs.
+  "Find the source locations of methods directly specializing on
+CLASS-DESIGNATOR, and return them as an alist of generic function
+name, DEFINITION-SOURCE pairs.
 
 A method matches the criterion either if it specializes on the same
 class as CLASS-DESIGNATOR designates (this includes CLASS-EQ
 specializers), or if it eql-specializes on an instance of the
 designated class.
 
-Experimental.
-"
+Experimental."
   (let ((class (canonicalize-class-designator class-designator)))
     (unless class
       (return-from who-specializes-directly nil))
@@ -883,17 +988,17 @@ Experimental.
                 result))))
 
 (defun who-specializes-generally (class-designator)
-  "Search for source locations of methods specializing on
-CLASS-DESIGNATOR, or a subclass of it. Returns an alist of method
-name, definition-source pairs.
+  "Find the source locations of methods specializing on
+CLASS-DESIGNATOR or a subclass of it, and return them as an alist of
+generic function name, DEFINITION-SOURCE pairs.
+DEFINITION-SOURCE-DESCRIPTION identifies the method.
 
 A method matches the criterion either if it specializes on the
 designated class itself or a subclass of it (this includes CLASS-EQ
 specializers), or if it eql-specializes on an instance of the
 designated class or a subclass of it.
 
-Experimental.
-"
+Experimental."
   (let ((class (canonicalize-class-designator class-designator)))
     (unless class
       (return-from who-specializes-generally nil))
@@ -945,12 +1050,12 @@ Experimental.
           1)))
 
 (defun allocation-information (object)
-  "Returns information about the allocation of OBJECT. Primary return value
-indicates the general type of allocation: :IMMEDIATE, :HEAP, :STACK,
-or :FOREIGN.
+  "Returns information about the allocation of OBJECT. The primary return
+value indicates the general type of allocation: :IMMEDIATE, :HEAP,
+:STACK, or :FOREIGN.
 
-Possible secondary return value provides additional information about the
-allocation.
+Non-NIL secondary return values provide additional information about
+the allocation.
 
 For :HEAP objects the secondary value is a plist:
 
@@ -958,7 +1063,7 @@ For :HEAP objects the secondary value is a plist:
     Indicates the heap segment the object is allocated in.
 
   :GENERATION
-    Is the current generation of the object: 0 for nursery, 6 for pseudo-static
+    The current generation of the object: 0 for nursery, 6 for pseudo-static
     generation loaded from core. (GENCGC and :SPACE :DYNAMIC only.)
 
   :LARGE
@@ -967,13 +1072,13 @@ For :HEAP objects the secondary value is a plist:
 
   :BOXED
     Indicates that the object is allocated in a boxed region. Unboxed
-    allocation is used for eg. specialized arrays after they have survived one
+    allocation is used for e.g. specialized arrays after they have survived one
     collection. (GENCGC and :SPACE :DYNAMIC only.)
 
   :PINNED
     Indicates that the page(s) on which the object resides are kept live due
     to conservative references. Note that object may reside on a pinned page
-    even if :PINNED in NIL if the GC has not had the need to mark the the page
+    even if :PINNED is NIL if the GC has not had the need to mark the page
     as pinned. (GENCGC and :SPACE :DYNAMIC only.)
 
   :WRITE-PROTECTED
@@ -982,11 +1087,11 @@ For :HEAP objects the secondary value is a plist:
     the last GC of its generation. (GENCGC and :SPACE :DYNAMIC only.)
 
   :PAGE
-    The index of the page the object resides on. (GENGC and :SPACE :DYNAMIC
+    The index of the page the object resides on. (GENCGC and :SPACE :DYNAMIC
     only.)
 
-For :STACK objects secondary value is the thread on whose stack the object is
-allocated.
+For :STACK objects, the secondary value is the thread on whose stack
+the object is allocated.
 
 Expected use-cases include introspection to gain insight into allocation and
 GC behaviour and restricting memoization to heap-allocated arguments.
@@ -1044,12 +1149,12 @@ Experimental: interface subject to change."
 Returns OBJECT.
 
 If SIMPLE is true (default is NIL), elides those pointers that are not
-notionally part of certain built-in objects, but backpointers to a
-conceptual parent: eg. elides the pointer from a SYMBOL to the
+notionally part of certain built-in objects but backpointers to a
+conceptual parent: e.g. elides the pointer from a SYMBOL to the
 corresponding PACKAGE.
 
 If EXT is true (default is T), includes some pointers that are not
-actually contained in the object, but found in certain well-known
+actually contained in the object but found in certain well-known
 indirect containers: FDEFINITIONs, EQL specializers, classes, and
 thread-local symbol values in other threads fall into this category.
 
diff --git a/contrib/sb-introspect/sb-introspect.texinfo b/contrib/sb-introspect/sb-introspect.texinfo
new file mode 100644
index 000000000..b4b63a5af
--- /dev/null
+++ b/contrib/sb-introspect/sb-introspect.texinfo
@@ -0,0 +1,61 @@
+@node sb-introspect
+@section sb-introspect
+@cindex Introspection Library
+
+The @code{sb-introspect} module is about finding definitions, as well
+as querying their properties and relationships in the running image.
+
+@menu
+* Finding Definitions::
+* Special Variables in sb-introspect::
+* Functions::
+* Types and Classes::
+* Allocation::
+@end menu
+
+
+@node Finding Definitions
+@subsection Finding Definitions
+
+@include struct-sb-introspect-definition-source.texinfo
+@include fun-sb-introspect-definition-source-pathname.texinfo
+@include fun-sb-introspect-definition-source-form-path.texinfo
+@include fun-sb-introspect-definition-source-form-number.texinfo
+@include fun-sb-introspect-definition-source-character-offset.texinfo
+@include fun-sb-introspect-definition-source-file-write-date.texinfo
+@include fun-sb-introspect-definition-source-plist.texinfo
+
+@include fun-sb-introspect-find-definition-source.texinfo
+@include fun-sb-introspect-find-definition-sources-by-name.texinfo
+
+@node Special Variables in sb-introspect
+@subsection Special Variables
+
+@include fun-sb-introspect-who-binds.texinfo
+@include fun-sb-introspect-who-references.texinfo
+@include fun-sb-introspect-who-sets.texinfo
+
+@node Functions
+@subsection Functions
+
+@include fun-sb-introspect-function-lambda-list.texinfo
+@include fun-sb-introspect-function-type.texinfo
+@include fun-sb-introspect-method-combination-lambda-list.texinfo
+@include fun-sb-introspect-valid-function-name-p.texinfo
+@include fun-sb-introspect-find-function-callers.texinfo
+@include fun-sb-introspect-find-function-callees.texinfo
+@include fun-sb-introspect-who-calls.texinfo
+@include fun-sb-introspect-who-macroexpands.texinfo
+
+@node Types and Classes
+@subsection Types and Classes
+
+@include fun-sb-introspect-deftype-lambda-list.texinfo
+@include fun-sb-introspect-who-specializes-directly.texinfo
+@include fun-sb-introspect-who-specializes-generally.texinfo
+
+@node Allocation
+@subsection Allocation
+
+@include fun-sb-introspect-allocation-information.texinfo
+@include fun-sb-introspect-map-root.texinfo
diff --git a/doc/manual/contrib-modules.texinfo b/doc/manual/contrib-modules.texinfo
index 7862d7015..f5f06e085 100644
--- a/doc/manual/contrib-modules.texinfo
+++ b/doc/manual/contrib-modules.texinfo
@@ -13,6 +13,7 @@ contributed modules.
 * sb-concurrency::
 * sb-cover::
 * sb-grovel::
+* sb-introspect::
 * sb-md5::
 * sb-posix::
 * sb-queue::
@@ -32,6 +33,9 @@ contributed modules.
 @page
 @include sb-grovel/sb-grovel.texinfo
 
+@page
+@include sb-introspect/sb-introspect.texinfo
+
 @page
 @include sb-md5/sb-md5.texinfo
 
diff --git a/doc/manual/docstrings.lisp b/doc/manual/docstrings.lisp
index ede40cfa6..f6131ebba 100644
--- a/doc/manual/docstrings.lisp
+++ b/doc/manual/docstrings.lisp
@@ -86,7 +86,8 @@ you deserve to lose.")
 (defparameter *symbol-characters* "ABCDEFGHIJKLMNOPQRSTUVWXYZ*:-+&#'"
   "List of characters that make up symbols in a docstring.")
 
-(defparameter *symbol-delimiters* " ,.!?;()")
+;;; #\s is included to catch some plurals (e.g. FDEFINITIONs).
+(defparameter *symbol-delimiters* " ,.!?;()s")
 
 (defparameter *ordered-documentation-kinds*
   '(package type structure condition class macro))

-----------------------------------------------------------------------


hooks/post-receive
-- 
SBCL