Courtesy of ignis volens on SO: https://stackoverflow.com/questions/69675964/how-to-get-the-filename-where-a-function-is-defined-in-common-lisp/69677540#69677540
If what you seek is a portable solution – one that is written in portable CL – then the answer to that is to define wrappers for defining forms and then use the wrappers.
(defvar *flocs* (make-hash-table :test #'equal))
(defgeneric function-location (f/name)
(:method ((name t))
(values (gethash name *flocs* nil) t))
(:method ((f function))
(multiple-value-bind (le cp nm) (function-lambda-expression f)
(declare (ignore le cp))
(if nm
(function-location nm)
(values nil nil)))))
(defmacro define-function (f args &body doc/decls/forms)
(when (or *load-pathname* *compile-file-pathname*)
;; Prefer *load-pathname*
(setf (gethash f *flocs*) (or *load-pathname* *compile-file-pathname*)))
`(defun ,f ,args ,@doc/decls/forms))
In real life you'd call define-function defun of course, and similarly with define-variable etc, and then construct a conduit package for CL which exported all the CL symbols while replacing the defining forms with these ones.
If what you seek is a portable solution in the sense that it exports some standard interface but has varying implementation-dependent backends, then probably looking at what SLY or SWANK do is a good start. In the case of LW you would want the backend to use DSPECs which are how it deals with location information:
> (dspec:dspec-definition-locations '(defun foo))
(((defun foo) :listener))
> (dspec:dspec-definition-locations '(defun needs))
(((defmacro needs)
#P"..."))
> (defclass foo () ())
#<standard-class foo 402000B763>
> (dspec:name-definition-locations dspec:*dspec-classes* 'foo)
(((defclass foo) :listener) ((defun foo) :listener))