From 1c7b8099839f62ddfaa5a0f87c29bcd905095dee Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Fri, 23 Feb 2024 10:17:27 +0100 Subject: [PATCH 01/11] * Add function type declarations for Lisp functions * lisp/emacs-lisp/byte-run.el (byte-run--set-declared-type): Add alias. (defun-declarations-alist): Use it for 'type' declaration. --- lisp/emacs-lisp/byte-run.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/byte-run.el b/lisp/emacs-lisp/byte-run.el index cc176821026..88571593c31 100644 --- a/lisp/emacs-lisp/byte-run.el +++ b/lisp/emacs-lisp/byte-run.el @@ -217,6 +217,11 @@ So far, FUNCTION can only be a symbol, not a lambda expression." (cadr elem))) val))))) +(defalias 'byte-run--set-declared-type + #'(lambda (f _args &rest val) + (list 'function-put (list 'quote f) + ''declared-type (list 'quote val)))) + ;; Add any new entries to info node `(elisp)Declare Form'. (defvar defun-declarations-alist (list @@ -239,7 +244,8 @@ If `error-free', drop calls even if `byte-compile-delete-errors' is nil.") (list 'speed #'byte-run--set-speed) (list 'completion #'byte-run--set-completion) (list 'modes #'byte-run--set-modes) - (list 'interactive-args #'byte-run--set-interactive-args)) + (list 'interactive-args #'byte-run--set-interactive-args) + (list 'type #'byte-run--set-declared-type)) "List associating function properties to their macro expansion. Each element of the list takes the form (PROP FUN) where FUN is a function. For each (PROP . VALUES) in a function's declaration, From d8c941df7d8167fdec8cad562c095e27203f7818 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Fri, 23 Feb 2024 15:56:47 +0100 Subject: [PATCH 02/11] Make use of Lisp function declarations * lisp/emacs-lisp/comp.el (comp-primitive-func-cstr-h): Rename. (comp--get-function-cstr): Define new function. (comp--add-call-cstr, comp--fwprop-call): Update. * lisp/emacs-lisp/comp-common.el (comp-function-type-spec): Update. * lisp/help-fns.el (help-fns--signature): Mention when a type is declared. * lisp/emacs-lisp/comp.el (comp-primitive-func-cstr-h): Rename. --- lisp/emacs-lisp/comp-common.el | 29 +++++++++++++++++------------ lisp/emacs-lisp/comp.el | 18 +++++++++++++----- lisp/help-fns.el | 2 +- 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/lisp/emacs-lisp/comp-common.el b/lisp/emacs-lisp/comp-common.el index 62fd28f772e..cfaf843a3fd 100644 --- a/lisp/emacs-lisp/comp-common.el +++ b/lisp/emacs-lisp/comp-common.el @@ -532,22 +532,27 @@ Account for `native-comp-eln-load-path' and `comp-native-version-dir'." (defun comp-function-type-spec (function) "Return the type specifier of FUNCTION. -This function returns a cons cell whose car is the function -specifier, and cdr is a symbol, either `inferred' or `know'. -If the symbol is `inferred', the type specifier is automatically -inferred from the code itself by the native compiler; if it is -`know', the type specifier comes from `comp-known-type-specifiers'." - (let ((kind 'know) - type-spec ) +This function returns a cons cell whose car is the function specifier, +and cdr is a symbol, either `inferred' or `declared'. If the symbol is +`inferred', the type specifier is automatically inferred from the code +itself by the native compiler; if it is `declared', the type specifier +comes from `comp-known-type-specifiers' or the function type declaration +itself." + (let ((kind 'declared) + type-spec) (when-let ((res (assoc function comp-known-type-specifiers))) + ;; Declared primitive (setf type-spec (cadr res))) (let ((f (and (symbolp function) (symbol-function function)))) - (when (and f - (null type-spec) - (subr-native-elisp-p f)) - (setf kind 'inferred - type-spec (subr-type f)))) + (when (and f (null type-spec)) + (if-let ((delc-type (function-get function 'declared-type))) + ;; Declared Lisp function + (setf type-spec (car delc-type)) + (when (subr-native-elisp-p f) + ;; Native compiled inferred + (setf kind 'inferred + type-spec (subr-type f)))))) (when type-spec (cons type-spec kind)))) diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el index 2ec55ed98ee..a7d4c71dc26 100644 --- a/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el @@ -179,16 +179,24 @@ For internal use by the test suite only.") Each function in FUNCTIONS is run after PASS. Useful to hook into pass checkers.") -(defconst comp-known-func-cstr-h +(defconst comp-primitive-func-cstr-h (cl-loop with comp-ctxt = (make-comp-cstr-ctxt) with h = (make-hash-table :test #'eq) - for (f type-spec) in comp-known-type-specifiers + for (f type-spec) in comp-primitive-type-specifiers for cstr = (comp-type-spec-to-cstr type-spec) do (puthash f cstr h) finally return h) "Hash table function -> `comp-constraint'.") +(defun comp--get-function-cstr (function) + "Given FUNCTION return the corresponding `comp-constraint'." + (when (symbolp function) + (let ((f (symbol-function function))) + (or (gethash f comp-primitive-func-cstr-h) + (when-let ((res (function-get function 'declared-type))) + (comp-type-spec-to-cstr (car res))))))) + ;; Keep it in sync with the `cl-deftype-satisfies' property set in ;; cl-macs.el. We can't use `cl-deftype-satisfies' directly as the ;; relation type <-> predicate is not bijective (bug#45576). @@ -2102,10 +2110,10 @@ TARGET-BB-SYM is the symbol name of the target block." (when-let ((match (pcase insn (`(set ,lhs (,(pred comp--call-op-p) ,f . ,args)) - (when-let ((cstr-f (gethash f comp-known-func-cstr-h))) + (when-let ((cstr-f (comp--get-function-cstr f))) (cl-values f cstr-f lhs args))) (`(,(pred comp--call-op-p) ,f . ,args) - (when-let ((cstr-f (gethash f comp-known-func-cstr-h))) + (when-let ((cstr-f (comp--get-function-cstr f))) (cl-values f cstr-f nil args)))))) (cl-multiple-value-bind (f cstr-f lhs args) match (cl-loop @@ -2642,7 +2650,7 @@ Fold the call in case." (comp-cstr-imm-vld-p (car args))) (setf f (comp-cstr-imm (car args)) args (cdr args))) - (when-let ((cstr-f (gethash f comp-known-func-cstr-h))) + (when-let ((cstr-f (comp--get-function-cstr f))) (let ((cstr (comp-cstr-f-ret cstr-f))) (when (comp-cstr-empty-p cstr) ;; Store it to be rewritten as non local exit. diff --git a/lisp/help-fns.el b/lisp/help-fns.el index cfe27077055..26fe614ffb5 100644 --- a/lisp/help-fns.el +++ b/lisp/help-fns.el @@ -734,7 +734,7 @@ the C sources, too." (insert (format (if (eq kind 'inferred) "\nInferred type: %s\n" - "\nType: %s\n") + "\nDeclared type: %s\n") type-spec)))) (fill-region fill-begin (point)) high-doc))))) From fa0bf96837ad267e5259e531e3d592dd40fdc445 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Wed, 10 Apr 2024 22:07:16 +0200 Subject: [PATCH 03/11] Move lisp function arg type declarations to the functions itself * lisp/emacs-lisp/comp-common.el (comp-primitive-type-specifiers): Remove type declaration of lisp functions. * lisp/window.el (get-lru-window, get-largest-window) (one-window-p): Declare type. * lisp/subr.el (ignore, error, zerop, fixnump, bignump, lsh) (last, eventp, mouse-movement-p, log10, memory-limit) (interactive-p): Likewise. * lisp/simple.el (count-lines, mark, lax-plist-get): Likewise. * lisp/files.el (parse-colon-path): Likewise. * lisp/env.el (getenv): Likewise. * lisp/emacs-lisp/regexp-opt.el (regexp-opt): Likewise. * lisp/emacs-lisp/lisp.el (buffer-end): Likewise. * lisp/emacs-lisp/comp.el (comp-hint-fixnum, comp-hint-cons): Likewise. * lisp/custom.el (custom-variable-p): Likewise. --- lisp/custom.el | 3 ++- lisp/emacs-lisp/comp-common.el | 37 +++------------------------------- lisp/emacs-lisp/comp.el | 6 ++++-- lisp/emacs-lisp/lisp.el | 3 ++- lisp/emacs-lisp/regexp-opt.el | 3 ++- lisp/env.el | 3 ++- lisp/files.el | 1 + lisp/simple.el | 10 ++++++--- lisp/subr.el | 36 ++++++++++++++++++++++----------- lisp/window.el | 9 ++++++--- 10 files changed, 53 insertions(+), 58 deletions(-) diff --git a/lisp/custom.el b/lisp/custom.el index a19b14aaf8a..6f2aa18ba1d 100644 --- a/lisp/custom.el +++ b/lisp/custom.el @@ -667,7 +667,8 @@ If NOSET is non-nil, don't bother autoloading LOAD when setting the variable." A customizable variable is either (i) a variable whose property list contains a non-nil `standard-value' or `custom-autoload' property, or (ii) an alias for another customizable variable." - (declare (side-effect-free t)) + (declare (type (function (symbol) t)) + (side-effect-free t)) (when (symbolp variable) (setq variable (indirect-variable variable)) (or (get variable 'standard-value) diff --git a/lisp/emacs-lisp/comp-common.el b/lisp/emacs-lisp/comp-common.el index cfaf843a3fd..dea7af66a0c 100644 --- a/lisp/emacs-lisp/comp-common.el +++ b/lisp/emacs-lisp/comp-common.el @@ -68,7 +68,7 @@ Used to modify the compiler environment." :risky t :version "28.1") -(defconst comp-known-type-specifiers +(defconst comp-primitive-type-specifiers `( ;; Functions we can trust not to be redefined, or, if redefined, ;; to expose the same type. The vast majority of these are @@ -97,7 +97,6 @@ Used to modify the compiler environment." (assq (function (t list) list)) (atan (function (number &optional number) float)) (atom (function (t) boolean)) - (bignump (function (t) boolean)) (bobp (function () boolean)) (bolp (function () boolean)) (bool-vector-count-consecutive @@ -107,7 +106,6 @@ Used to modify the compiler environment." (bool-vector-p (function (t) boolean)) (bool-vector-subsetp (function (bool-vector bool-vector) boolean)) (boundp (function (symbol) boolean)) - (buffer-end (function ((or number marker)) integer)) (buffer-file-name (function (&optional buffer) (or string null))) (buffer-list (function (&optional frame) list)) (buffer-local-variables (function (&optional buffer) list)) @@ -157,8 +155,6 @@ Used to modify the compiler environment." (copy-sequence (function (sequence) sequence)) (copysign (function (float float) float)) (cos (function (number) float)) - (count-lines - (function ((or integer marker) (or integer marker) &optional t) integer)) (current-buffer (function () buffer)) (current-global-map (function () cons)) (current-indentation (function () integer)) @@ -171,7 +167,6 @@ Used to modify the compiler environment." (current-time-zone (function (&optional (or number list) (or symbol string cons integer)) cons)) - (custom-variable-p (function (symbol) t)) (decode-char (function (cons t) (or fixnum null))) (decode-time (function (&optional (or number list) (or symbol string cons integer) @@ -179,7 +174,6 @@ Used to modify the compiler environment." cons)) (default-boundp (function (symbol) boolean)) (default-value (function (symbol) t)) - (degrees-to-radians (function (number) float)) (documentation (function ((or function symbol subr) &optional t) (or null string))) (downcase (function ((or fixnum string)) (or fixnum string))) @@ -192,7 +186,6 @@ Used to modify the compiler environment." (eql (function (t t) boolean)) (equal (function (t t) boolean)) (error-message-string (function (list) string)) - (eventp (function (t) boolean)) (exp (function (number) float)) (expt (function (number number) number)) (fboundp (function (symbol) boolean)) @@ -207,7 +200,6 @@ Used to modify the compiler environment." (file-readable-p (function (string) boolean)) (file-symlink-p (function (string) (or boolean string))) (file-writable-p (function (string) boolean)) - (fixnump (function (t) boolean)) (float (function (number) float)) (float-time (function (&optional (or number list)) float)) (floatp (function (t) boolean)) @@ -230,18 +222,12 @@ Used to modify the compiler environment." (function (&optional (or buffer string) (or symbol (integer 0 0))) (or null window))) (get-file-buffer (function (string) (or null buffer))) - (get-largest-window (function (&optional t t t) (or window null))) - (get-lru-window (function (&optional t t t) (or window null))) - (getenv (function (string &optional frame) (or null string))) (gethash (function (t hash-table &optional t) t)) (hash-table-count (function (hash-table) integer)) (hash-table-p (function (t) boolean)) (identity (function (t) t)) - (ignore (function (&rest t) null)) - (int-to-string (function (number) string)) (integer-or-marker-p (function (t) boolean)) (integerp (function (t) boolean)) - (interactive-p (function () boolean)) (intern-soft (function ((or string symbol) &optional (or obarray vector)) symbol)) (invocation-directory (function () string)) @@ -250,8 +236,6 @@ Used to modify the compiler environment." (keymap-parent (function (cons) (or cons null))) (keymapp (function (t) boolean)) (keywordp (function (t) boolean)) - (last (function (list &optional integer) list)) - (lax-plist-get (function (list t) t)) (ldexp (function (number integer) float)) (length (function (t) (integer 0 *))) (length< (function (sequence fixnum) boolean)) @@ -265,7 +249,6 @@ Used to modify the compiler environment." (local-variable-p (function (symbol &optional buffer) boolean)) (locale-info (function ((member codeset days months paper)) (or null string))) (log (function (number number) float)) - (log10 (function (number) float)) (logand (function (&rest (or integer marker)) integer)) (logb (function (number) integer)) (logcount (function (integer) integer)) @@ -273,7 +256,6 @@ Used to modify the compiler environment." (lognot (function (integer) integer)) (logxor (function (&rest (or integer marker)) integer)) ;; (lsh (function ((integer ,most-negative-fixnum *) integer) integer)) ? - (lsh (function (integer integer) integer)) (make-byte-code (function ((or fixnum list) string vector integer &optional string t &rest t) @@ -282,14 +264,12 @@ Used to modify the compiler environment." (make-marker (function () marker)) (make-string (function (integer fixnum &optional t) string)) (make-symbol (function (string) symbol)) - (mark (function (&optional t) (or integer null))) (mark-marker (function () marker)) (marker-buffer (function (marker) (or buffer null))) (markerp (function (t) boolean)) (max (function ((or number marker) &rest (or number marker)) number)) (max-char (function (&optional t) fixnum)) (member (function (t list) list)) - (memory-limit (function () integer)) (memq (function (t list) list)) (memql (function (t list) list)) (min (function ((or number marker) &rest (or number marker)) number)) @@ -298,7 +278,6 @@ Used to modify the compiler environment." (mod (function ((or number marker) (or number marker)) (or (integer 0 *) (float 0 *)))) - (mouse-movement-p (function (t) boolean)) (multibyte-char-to-unibyte (function (fixnum) fixnum)) (natnump (function (t) boolean)) (next-window (function (&optional window t t) window)) @@ -310,9 +289,7 @@ Used to modify the compiler environment." (number-or-marker-p (function (t) boolean)) (number-to-string (function (number) string)) (numberp (function (t) boolean)) - (one-window-p (function (&optional t t) boolean)) (overlayp (function (t) boolean)) - (parse-colon-path (function (string) list)) (plist-get (function (list t &optional t) t)) (plist-member (function (list t &optional t) list)) (point (function () integer)) @@ -325,13 +302,11 @@ Used to modify the compiler environment." (processp (function (t) boolean)) (proper-list-p (function (t) (or fixnum null))) (propertize (function (string &rest t) string)) - (radians-to-degrees (function (number) float)) (rassoc (function (t list) list)) (rassq (function (t list) list)) (read-from-string (function (string &optional integer integer) cons)) (recent-keys (function (&optional (or cons null)) vector)) (recursion-depth (function () integer)) - (regexp-opt (function (list) string)) (regexp-quote (function (string) string)) (region-beginning (function () integer)) (region-end (function () integer)) @@ -387,7 +362,6 @@ Used to modify the compiler environment." (upcase (function ((or fixnum string)) (or fixnum string))) (user-full-name (function (&optional integer) (or string null))) (user-login-name (function (&optional integer) (or string null))) - (user-original-login-name (function (&optional integer) (or string null))) (user-real-login-name (function () string)) (user-real-uid (function () integer)) (user-uid (function () integer)) @@ -400,13 +374,8 @@ Used to modify the compiler environment." (window-live-p (function (t) boolean)) (window-valid-p (function (t) boolean)) (windowp (function (t) boolean)) - (zerop (function (number) boolean)) - ;; Type hints - (comp-hint-fixnum (function (t) fixnum)) - (comp-hint-cons (function (t) cons)) ;; Non returning functions (throw (function (t t) nil)) - (error (function (string &rest t) nil)) (signal (function (symbol t) nil))) "Alist used for type propagation.") @@ -536,11 +505,11 @@ This function returns a cons cell whose car is the function specifier, and cdr is a symbol, either `inferred' or `declared'. If the symbol is `inferred', the type specifier is automatically inferred from the code itself by the native compiler; if it is `declared', the type specifier -comes from `comp-known-type-specifiers' or the function type declaration +comes from `comp-primitive-type-specifiers' or the function type declaration itself." (let ((kind 'declared) type-spec) - (when-let ((res (assoc function comp-known-type-specifiers))) + (when-let ((res (assoc function comp-primitive-type-specifiers))) ;; Declared primitive (setf type-spec (cadr res))) (let ((f (and (symbolp function) diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el index a7d4c71dc26..b37af4c8dc2 100644 --- a/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el @@ -3309,11 +3309,13 @@ Prepare every function for final compilation and drive the C back-end." ;; are assumed just to be true. Use with extreme caution... (defun comp-hint-fixnum (x) - (declare (gv-setter (lambda (val) `(setf ,x ,val)))) + (declare (type (function (t) fixnum)) + (gv-setter (lambda (val) `(setf ,x ,val)))) x) (defun comp-hint-cons (x) - (declare (gv-setter (lambda (val) `(setf ,x ,val)))) + (declare (type (function (t) cons)) + (gv-setter (lambda (val) `(setf ,x ,val)))) x) diff --git a/lisp/emacs-lisp/lisp.el b/lisp/emacs-lisp/lisp.el index 7e6db51b1d5..9edc11ad132 100644 --- a/lisp/emacs-lisp/lisp.el +++ b/lisp/emacs-lisp/lisp.el @@ -534,7 +534,8 @@ major mode's decisions about context.") "Return the \"far end\" position of the buffer, in direction ARG. If ARG is positive, that's the end of the buffer. Otherwise, that's the beginning of the buffer." - (declare (side-effect-free error-free)) + (declare (type (function ((or number marker)) integer)) + (side-effect-free error-free)) (if (> arg 0) (point-max) (point-min))) (defun end-of-defun (&optional arg interactive) diff --git a/lisp/emacs-lisp/regexp-opt.el b/lisp/emacs-lisp/regexp-opt.el index 59c1b7d8e10..076232bc613 100644 --- a/lisp/emacs-lisp/regexp-opt.el +++ b/lisp/emacs-lisp/regexp-opt.el @@ -130,7 +130,8 @@ usually more efficient than that of a simplified version: (concat (car parens) (mapconcat \\='regexp-quote strings \"\\\\|\") (cdr parens))))" - (declare (pure t) (side-effect-free t)) + (declare (type (function (list) string)) + (pure t) (side-effect-free t)) (save-match-data ;; Recurse on the sorted list. (let* ((max-lisp-eval-depth 10000) diff --git a/lisp/env.el b/lisp/env.el index e0a8df8476c..7d0c7dd0126 100644 --- a/lisp/env.el +++ b/lisp/env.el @@ -207,7 +207,8 @@ parameter. Otherwise, this function searches `process-environment' for VARIABLE. If it is not found there, then it continues the search in the environment list of the selected frame." - (declare (side-effect-free t)) + (declare (type (function (string &optional frame) (or null string))) + (side-effect-free t)) (interactive (list (read-envvar-name "Get environment variable: " t))) (let ((value (getenv-internal (if (multibyte-string-p variable) (encode-coding-string diff --git a/lisp/files.el b/lisp/files.el index 7dec67c5cf0..57f3042e4da 100644 --- a/lisp/files.el +++ b/lisp/files.el @@ -862,6 +862,7 @@ GNU and Unix systems). Substitute environment variables into the resulting list of directory names. For an empty path element (i.e., a leading or trailing separator, or two adjacent separators), return nil (meaning `default-directory') as the associated list element." + (declare (type (function (string) list))) (when (stringp search-path) (let ((spath (substitute-env-vars search-path)) (double-slash-special-p diff --git a/lisp/simple.el b/lisp/simple.el index be64f3574e0..a459f6ecfd2 100644 --- a/lisp/simple.el +++ b/lisp/simple.el @@ -1762,7 +1762,9 @@ not at the start of a line. When IGNORE-INVISIBLE-LINES is non-nil, invisible lines are not included in the count." - (declare (side-effect-free t)) + (declare (type (function ((or integer marker) (or integer marker) &optional t) + integer)) + (side-effect-free t)) (save-excursion (save-restriction (narrow-to-region start end) @@ -6882,7 +6884,8 @@ is active, and returns an integer or nil in the usual way. If you are using this in an editing command, you are most likely making a mistake; see the documentation of `set-mark'." - (declare (side-effect-free t)) + (declare (type (function (&optional t) (or integer null))) + (side-effect-free t)) (if (or force (not transient-mark-mode) mark-active mark-even-if-inactive) (marker-position (mark-marker)) (signal 'mark-inactive nil))) @@ -11163,7 +11166,8 @@ killed." (defun lax-plist-get (plist prop) "Extract a value from a property list, comparing with `equal'." - (declare (pure t) (side-effect-free t) (obsolete plist-get "29.1")) + (declare (type (function (list t) t)) + (pure t) (side-effect-free t) (obsolete plist-get "29.1")) (plist-get plist prop #'equal)) (defun lax-plist-put (plist prop val) diff --git a/lisp/subr.el b/lisp/subr.el index 352ecc315ef..92d1e50ab2c 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -451,7 +451,8 @@ This function accepts any number of arguments in ARGUMENTS. Also see `always'." ;; Not declared `side-effect-free' because we don't want calls to it ;; elided; see `byte-compile-ignore'. - (declare (pure t) (completion ignore)) + (declare (type (function (&rest t) null)) + (pure t) (completion ignore)) (interactive) nil) @@ -480,7 +481,8 @@ for the sake of consistency. To alter the look of the displayed error messages, you can use the `command-error-function' variable." - (declare (advertised-calling-convention (string &rest args) "23.1")) + (declare (type (function (string &rest t) nil)) + (advertised-calling-convention (string &rest args) "23.1")) (signal 'error (list (apply #'format-message args)))) (defun user-error (format &rest args) @@ -545,19 +547,22 @@ was called." "Return t if NUMBER is zero." ;; Used to be in C, but it's pointless since (= 0 n) is faster anyway because ;; = has a byte-code. - (declare (pure t) (side-effect-free t) + (declare (type (function (number) boolean)) + (pure t) (side-effect-free t) (compiler-macro (lambda (_) `(= 0 ,number)))) (= 0 number)) (defun fixnump (object) "Return t if OBJECT is a fixnum." - (declare (side-effect-free error-free)) + (declare (type (function (t) boolean)) + (side-effect-free error-free)) (and (integerp object) (<= most-negative-fixnum object most-positive-fixnum))) (defun bignump (object) "Return t if OBJECT is a bignum." - (declare (side-effect-free error-free)) + (declare (type (function (t) boolean)) + (side-effect-free error-free)) (and (integerp object) (not (fixnump object)))) (defun lsh (value count) @@ -570,7 +575,8 @@ Most uses of this function turn out to be mistakes. We recommend to use `ash' instead, unless COUNT could ever be negative, and if, when COUNT is negative, your program really needs the special treatment of negative COUNT provided by this function." - (declare (compiler-macro + (declare (type (function (integer integer) integer)) + (compiler-macro (lambda (form) (macroexp-warn-and-return (format-message "avoid `lsh'; use `ash' instead") @@ -748,7 +754,8 @@ treatment of negative COUNT provided by this function." If LIST is nil, return nil. If N is non-nil, return the Nth-to-last link of LIST. If N is bigger than the length of LIST, return LIST." - (declare (pure t) (side-effect-free t)) ; pure up to mutation + (declare (type (function (list &optional integer) list)) + (pure t) (side-effect-free t)) ; pure up to mutation (if n (and (>= n 0) (let ((m (safe-length list))) @@ -1585,7 +1592,8 @@ See also `current-global-map'.") (defun eventp (object) "Return non-nil if OBJECT is an input event or event object." - (declare (pure t) (side-effect-free error-free)) + (declare (type (function (t) boolean)) + (pure t) (side-effect-free error-free)) (or (integerp object) (and (if (consp object) (setq object (car object)) @@ -1652,7 +1660,8 @@ in the current Emacs session, then this function may return nil." (defsubst mouse-movement-p (object) "Return non-nil if OBJECT is a mouse movement event." - (declare (side-effect-free error-free)) + (declare (type (function (t) boolean)) + (side-effect-free error-free)) (eq (car-safe object) 'mouse-movement)) (defun mouse-event-p (object) @@ -1961,7 +1970,8 @@ be a list of the form returned by `event-start' and `event-end'." (defun log10 (x) "Return (log X 10), the log base 10 of X." - (declare (side-effect-free t) (obsolete log "24.4")) + (declare (type (function (number) float)) + (side-effect-free t) (obsolete log "24.4")) (log x 10)) (set-advertised-calling-convention @@ -3245,7 +3255,8 @@ It can be retrieved with `(process-get PROCESS PROPNAME)'." (defun memory-limit () "Return an estimate of Emacs virtual memory usage, divided by 1024." - (declare (side-effect-free error-free)) + (declare (type (function () integer)) + (side-effect-free error-free)) (let ((default-directory temporary-file-directory)) (or (cdr (assq 'vsize (process-attributes (emacs-pid)))) 0))) @@ -6467,7 +6478,8 @@ To test whether a function can be called interactively, use `commandp'." ;; Kept around for now. See discussion at: ;; https://lists.gnu.org/r/emacs-devel/2020-08/msg00564.html - (declare (obsolete called-interactively-p "23.2") + (declare (type (function () boolean)) + (obsolete called-interactively-p "23.2") (side-effect-free error-free)) (called-interactively-p 'interactive)) diff --git a/lisp/window.el b/lisp/window.el index cdc6f690bab..639090752be 100644 --- a/lisp/window.el +++ b/lisp/window.el @@ -2515,7 +2515,8 @@ have special meanings: Any other value of ALL-FRAMES means consider all windows on the selected frame and no others." - (declare (side-effect-free error-free)) + (declare (type (function (&optional t t t) (or window null))) + (side-effect-free error-free)) (let ((windows (window-list-1 nil 'nomini all-frames)) best-window best-time second-best-window second-best-time time) (dolist (window windows) @@ -2594,7 +2595,8 @@ have special meanings: Any other value of ALL-FRAMES means consider all windows on the selected frame and no others." - (declare (side-effect-free error-free)) + (declare (type (function (&optional t t t) (or window null))) + (side-effect-free error-free)) (let ((best-size 0) best-window size) (dolist (window (window-list-1 nil 'nomini all-frames)) @@ -4089,7 +4091,8 @@ with a special meaning are: Anything else means consider all windows on the selected frame and no others." - (declare (side-effect-free error-free)) + (declare (type (function (&optional t t) boolean)) + (side-effect-free error-free)) (let ((base-window (selected-window))) (if (and nomini (eq base-window (minibuffer-window))) (setq base-window (next-window base-window))) From 15016288ecaefbfb2822c1fcef7146a5d8663650 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Mon, 29 Apr 2024 18:08:57 +0200 Subject: [PATCH 04/11] * Document function type declarations * doc/lispref/functions.texi (Declare Form): Document function type declaration. --- doc/lispref/functions.texi | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index c57de08460f..b5e234fa068 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -2709,6 +2709,13 @@ native code emitted for the function. In particular, if @var{n} is @minus{}1, native compilation of the function will emit bytecode instead of native code for the function. +@item (type @var{type}) +Declare @var{type} to be the type of this function. This is used for +documentation by @code{describe-function}. Also it can be used by the +native compiler (@pxref{Native-Compilation}) for improving code +generation and for deriving more precisely the type of other functions +without type declaration. + @item no-font-lock-keyword This is valid for macros only. Macros with this declaration are highlighted by font-lock (@pxref{Font Lock Mode}) as normal functions, From 0757ea98654bef58d19a46ce2f7ce1a715ec65ca Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Mon, 29 Apr 2024 20:31:05 +0200 Subject: [PATCH 05/11] Rename property 'declared-type' to 'function-type' * lisp/emacs-lisp/byte-run.el (byte-run--set-function-type): Rename. (defun-declarations-alist): Update. * lisp/emacs-lisp/comp.el (comp--get-function-cstr): Likewise. --- lisp/emacs-lisp/byte-run.el | 6 +++--- lisp/emacs-lisp/comp-common.el | 2 +- lisp/emacs-lisp/comp.el | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lisp/emacs-lisp/byte-run.el b/lisp/emacs-lisp/byte-run.el index 88571593c31..84cc83f2270 100644 --- a/lisp/emacs-lisp/byte-run.el +++ b/lisp/emacs-lisp/byte-run.el @@ -217,10 +217,10 @@ So far, FUNCTION can only be a symbol, not a lambda expression." (cadr elem))) val))))) -(defalias 'byte-run--set-declared-type +(defalias 'byte-run--set-function-type #'(lambda (f _args &rest val) (list 'function-put (list 'quote f) - ''declared-type (list 'quote val)))) + ''function-type (list 'quote val)))) ;; Add any new entries to info node `(elisp)Declare Form'. (defvar defun-declarations-alist @@ -245,7 +245,7 @@ If `error-free', drop calls even if `byte-compile-delete-errors' is nil.") (list 'completion #'byte-run--set-completion) (list 'modes #'byte-run--set-modes) (list 'interactive-args #'byte-run--set-interactive-args) - (list 'type #'byte-run--set-declared-type)) + (list 'type #'byte-run--set-function-type)) "List associating function properties to their macro expansion. Each element of the list takes the form (PROP FUN) where FUN is a function. For each (PROP . VALUES) in a function's declaration, diff --git a/lisp/emacs-lisp/comp-common.el b/lisp/emacs-lisp/comp-common.el index dea7af66a0c..ef40882a98a 100644 --- a/lisp/emacs-lisp/comp-common.el +++ b/lisp/emacs-lisp/comp-common.el @@ -515,7 +515,7 @@ itself." (let ((f (and (symbolp function) (symbol-function function)))) (when (and f (null type-spec)) - (if-let ((delc-type (function-get function 'declared-type))) + (if-let ((delc-type (function-get function 'function-type))) ;; Declared Lisp function (setf type-spec (car delc-type)) (when (subr-native-elisp-p f) diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el index b37af4c8dc2..d7cd6b79c86 100644 --- a/lisp/emacs-lisp/comp.el +++ b/lisp/emacs-lisp/comp.el @@ -194,7 +194,7 @@ Useful to hook into pass checkers.") (when (symbolp function) (let ((f (symbol-function function))) (or (gethash f comp-primitive-func-cstr-h) - (when-let ((res (function-get function 'declared-type))) + (when-let ((res (function-get function 'function-type))) (comp-type-spec-to-cstr (car res))))))) ;; Keep it in sync with the `cl-deftype-satisfies' property set in From 1087d55d2710f610edc5195175e2260aebaa4589 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Tue, 30 Apr 2024 09:19:31 +0200 Subject: [PATCH 06/11] * lisp/emacs-lisp/regexp-opt.el (regexp-opt): Fix type declaration. --- lisp/emacs-lisp/regexp-opt.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lisp/emacs-lisp/regexp-opt.el b/lisp/emacs-lisp/regexp-opt.el index 076232bc613..f23343a34c6 100644 --- a/lisp/emacs-lisp/regexp-opt.el +++ b/lisp/emacs-lisp/regexp-opt.el @@ -130,7 +130,7 @@ usually more efficient than that of a simplified version: (concat (car parens) (mapconcat \\='regexp-quote strings \"\\\\|\") (cdr parens))))" - (declare (type (function (list) string)) + (declare (type (function (list &optional t) string)) (pure t) (side-effect-free t)) (save-match-data ;; Recurse on the sorted list. From fccd35f2c89a50675ed8c14d4814b603fd4fa166 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Wed, 1 May 2024 20:03:02 +0200 Subject: [PATCH 07/11] * doc/lispref/functions.texi (Declare Form): Improve declare type. --- doc/lispref/functions.texi | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index b5e234fa068..1816ea93e3d 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -2716,6 +2716,45 @@ native compiler (@pxref{Native-Compilation}) for improving code generation and for deriving more precisely the type of other functions without type declaration. +@var{type} is a type specifier in the form @code{(function (ARG-1-TYPE +... ARG-N-TYPE) RETURN-TYPE)}. Argument types can be interleaved with +symbols @code{&optional} and @code{&rest} to match the @pxref{Argument +List} of the function. + +Here's an example of using @code{type} inside @code{declare} to declare +a function @code{positive-p} that takes an argument of type @var{number} +and return a @var{boolean}: + +@lisp +(defun positive-p (x) + (declare (type (function (number) boolean))) + (when (> x 0) + t)) +@end lisp + +Similarly this declares a function @code{cons-or-number} that: expects a +first argument being a @var{cons} or a @var{number}, a second optional +argument of type @var{string} and return one of the symbols +@code{is-cons} or @code{is-number}: + +@lisp +(defun cons-or-number (x &optional err-msg) + (declare (type (function ((or cons number) &optional string) + (member is-cons is-number)))) + (if (consp x) + 'is-cons + (if (numberp x) + 'is-number + (error (or err-msg "Unexpected input"))))) +@end lisp + +More types are described in @pxref{Lisp Data Types}. + +Declaring a function with an incorrect type produces undefined behavior. +Note also that when redefining (or advising) a type declared function +the replacement should respect the original signature to avoid undefined +behavior. + @item no-font-lock-keyword This is valid for macros only. Macros with this declaration are highlighted by font-lock (@pxref{Font Lock Mode}) as normal functions, From 8e1e8099aad0fbb2bc375b45379913b8ca55e926 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Wed, 1 May 2024 22:49:01 +0200 Subject: [PATCH 08/11] * etc/NEWS (Function type declaration): Add entry. --- etc/NEWS | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etc/NEWS b/etc/NEWS index 4b0106fcb07..73dbe3b2b83 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1935,6 +1935,10 @@ unibyte string. * Lisp Changes in Emacs 30.1 +** Function type declaration +It is now possible, using the 'declare' macro, to declare expected types +of function arguments and return type. + ** New types 'closure' and 'interpreted-function'. 'interpreted-function' is the new type used for interpreted functions, and 'closure' is the common parent type of 'interpreted-function' From 390b606ffcedd7a14e94631c8ab1155db623e723 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Thu, 2 May 2024 12:15:30 +0200 Subject: [PATCH 09/11] * doc/lispref/functions.texi (Declare Form): Better warn about UB. --- doc/lispref/functions.texi | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 1816ea93e3d..3aa4fc9a457 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -2750,9 +2750,10 @@ argument of type @var{string} and return one of the symbols More types are described in @pxref{Lisp Data Types}. -Declaring a function with an incorrect type produces undefined behavior. -Note also that when redefining (or advising) a type declared function -the replacement should respect the original signature to avoid undefined +Declaring a function with an incorrect type produces undefined behavior +and could lead to unexpected results or might even crash Emacs. Note +also that when redefining (or advising) a type declared function the +replacement should respect the original signature to avoid undefined behavior. @item no-font-lock-keyword From 02690d95f9e47163ecca9b26a01270215727cd69 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Thu, 2 May 2024 16:38:14 +0200 Subject: [PATCH 10/11] * doc/lispref/functions.texi (Declare Form): Improve again declare type. --- doc/lispref/functions.texi | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi index 3aa4fc9a457..d88f5d05339 100644 --- a/doc/lispref/functions.texi +++ b/doc/lispref/functions.texi @@ -2712,31 +2712,34 @@ instead of native code for the function. @item (type @var{type}) Declare @var{type} to be the type of this function. This is used for documentation by @code{describe-function}. Also it can be used by the -native compiler (@pxref{Native-Compilation}) for improving code +native compiler (@pxref{Native Compilation}) for improving code generation and for deriving more precisely the type of other functions without type declaration. -@var{type} is a type specifier in the form @code{(function (ARG-1-TYPE -... ARG-N-TYPE) RETURN-TYPE)}. Argument types can be interleaved with -symbols @code{&optional} and @code{&rest} to match the @pxref{Argument -List} of the function. +@var{type} is a type specifier in the form @w{@code{(function +(ARG-1-TYPE ... ARG-N-TYPE) RETURN-TYPE)}}. Argument types can be +interleaved with symbols @code{&optional} and @code{&rest} to match the +function's arguments (@pxref{Argument List}). Here's an example of using @code{type} inside @code{declare} to declare a function @code{positive-p} that takes an argument of type @var{number} and return a @var{boolean}: +@group @lisp (defun positive-p (x) (declare (type (function (number) boolean))) (when (> x 0) t)) @end lisp +@end group Similarly this declares a function @code{cons-or-number} that: expects a first argument being a @var{cons} or a @var{number}, a second optional argument of type @var{string} and return one of the symbols @code{is-cons} or @code{is-number}: +@group @lisp (defun cons-or-number (x &optional err-msg) (declare (type (function ((or cons number) &optional string) @@ -2747,14 +2750,16 @@ argument of type @var{string} and return one of the symbols 'is-number (error (or err-msg "Unexpected input"))))) @end lisp +@end group -More types are described in @pxref{Lisp Data Types}. +More types are described in the Lisp Data Types chapter (@ref{Lisp Data +Types}). Declaring a function with an incorrect type produces undefined behavior -and could lead to unexpected results or might even crash Emacs. Note -also that when redefining (or advising) a type declared function the -replacement should respect the original signature to avoid undefined -behavior. +and could lead to unexpected results or might even crash Emacs when code +is native-compiled and loaded. Note also that when redefining (or +advising) a type declared function the replacement should respect the +original signature to avoid undefined behavior. @item no-font-lock-keyword This is valid for macros only. Macros with this declaration are From 64d3100cb5973f2e8372d29f7658c32a63e191e2 Mon Sep 17 00:00:00 2001 From: Andrea Corallo Date: Thu, 2 May 2024 16:42:45 +0200 Subject: [PATCH 11/11] etc/NEWS (Function type declaration): Mark it +++. --- etc/NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/NEWS b/etc/NEWS index 73dbe3b2b83..f6da27a794d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1935,6 +1935,7 @@ unibyte string. * Lisp Changes in Emacs 30.1 ++++ ** Function type declaration It is now possible, using the 'declare' macro, to declare expected types of function arguments and return type.