From dd0dd87e3aaf3116c400fba858cbe35ced15f04e Mon Sep 17 00:00:00 2001 From: Stefan Kangas Date: Sat, 29 Mar 2025 14:59:26 +0100 Subject: [PATCH] New function 'hash-table-contains-p' This function tests whether a given key is present in a hash table. Emacs Lisp has long lacked a standard way to do this, leading users to write one of: (not (eq (gethash key table 'missing) 'missing)) or (gethash key table) This idiom is error-prone (when 'missing' or 'nil' are valid values), and it obscures intent. The new function avoids such pitfalls, improves readability, and makes the intent explicit: (hash-table-contains-p key table) The name 'hash-table-contains-p' exists in other Lisp dialects (e.g., SRFI-125), and follows the precedent of 'seq-contains-p'. Other alternatives considered include `hash-table-has-key-p` and `hash-table-key-exists-p`, but none were clearly better. This was previously discussed in 2018, and all comments were positive, but the proposed patch (implementing it in C) was never pushed: https://lists.gnu.org/r/emacs-devel/2018-02/msg00424.html * lisp/subr.el (hash-table-contains-p): New function. * lisp/emacs-lisp/shortdoc.el (hash-table): * doc/lispref/hash.texi (Other Hash): Document the new function. * test/lisp/subr-tests.el (hash-table-contains-p): New test. --- doc/lispref/hash.texi | 5 +++++ etc/NEWS | 4 ++++ lisp/emacs-lisp/shortdoc.el | 2 ++ lisp/subr.el | 7 +++++++ test/lisp/subr-tests.el | 12 ++++++++++++ 5 files changed, 30 insertions(+) diff --git a/doc/lispref/hash.texi b/doc/lispref/hash.texi index f429d1512fd..56862a9d934 100644 --- a/doc/lispref/hash.texi +++ b/doc/lispref/hash.texi @@ -347,6 +347,11 @@ itself is copied---the keys and values are shared. This function returns the actual number of entries in @var{table}. @end defun +@defun hash-table-contains-p key table +This returns non-@code{nil} if there is an association for @var{key} in +@var{table}. +@end defun + @defun hash-table-test table This returns the @var{test} value that was given when @var{table} was created, to specify how to hash and compare keys. See diff --git a/etc/NEWS b/etc/NEWS index 8149e5d8c5b..33a2b3fd07a 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1786,6 +1786,10 @@ Optional arguments are provided to produce human-readable time-duration strings in a variety of formats, for example "6 months 3 weeks" or "5m 52.5s". ++++ +** New function 'hash-table-contains-p'. +This function returns non-nil if a given key is present in a hash table. + +++ ** The function 'purecopy' is now an obsolete alias for 'identity'. diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 15898ac9687..2e826399261 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -710,6 +710,8 @@ A FUNC form can have any number of `:no-eval' (or `:no-value'), "Other Hash Table Functions" (hash-table-p :eval (hash-table-p 123)) + (hash-table-contains-p + :no-eval (hash-table-contains-p 'key table)) (copy-hash-table :no-eval (copy-hash-table table) :result-string "#s(hash-table ...)") diff --git a/lisp/subr.el b/lisp/subr.el index af9289c0216..8c1e6f657a6 100644 --- a/lisp/subr.el +++ b/lisp/subr.el @@ -7392,6 +7392,13 @@ TRIM-LEFT and TRIM-RIGHT default to \"[ \\t\\n\\r]+\"." (declare (important-return-value t)) (string-trim-left (string-trim-right string trim-right) trim-left)) +(defsubst hash-table-contains-p (key table) + "Return non-nil if TABLE has an element with KEY." + (declare (side-effect-free t) + (important-return-value t)) + (let ((missing (make-symbol "missing"))) + (not (eq (gethash key table missing) missing)))) + ;; The initial anchoring is for better performance in searching matches. (defconst regexp-unmatchable "\\`a\\`" "Standard regexp guaranteed not to match any string at all.") diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el index 3459a653283..25f1b3403ca 100644 --- a/test/lisp/subr-tests.el +++ b/test/lisp/subr-tests.el @@ -1489,5 +1489,17 @@ final or penultimate step during initialization.")) (props-out (object-intervals out))) (should (equal props-out props-in)))))))) +(ert-deftest hash-table-contains-p () + (let ((h (make-hash-table))) + (should-not (hash-table-contains-p 'problems h)) + (should-not (hash-table-contains-p 'cookie h)) + (should-not (hash-table-contains-p 'milk h)) + (puthash 'problems 99 h) + (puthash 'cookie nil h) + (puthash 'milk 'missing h) + (should (hash-table-contains-p 'problems h)) + (should (hash-table-contains-p 'cookie h)) + (should (hash-table-contains-p 'milk h)))) + (provide 'subr-tests) ;;; subr-tests.el ends here