From: "Mattias Engdegård" <mattias.engdegard@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: philipk@posteo.net, 69387@debbugs.gnu.org, monnier@iro.umontreal.ca
Subject: bug#69387: 30.0.50; A string shouldn't be both a docstring and a return value
Date: Tue, 5 Mar 2024 14:16:50 +0100 [thread overview]
Message-ID: <74101BB1-B82C-4F6C-8AB1-47A131BC90E6@gmail.com> (raw)
In-Reply-To: <86bk7um4v1.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 905 bytes --]
4 mars 2024 kl. 15.46 skrev Eli Zaretskii <eliz@gnu.org>:
> Do we need to say something about this change in NEWS?
Yes, here is a complete patch.
The cl-lib definition forms (cl-defun, cl-defsubst, cl-defmacro etc) are unaffected by the change as they don't have the dual-use doc-string problem.
The doc-string mechanism change should be sound and safe. The warning is only an annoyance for people writing code like
(defun my-constant-fun ()
"some-useful-string")
which isn't common but not necessarily bad style either, which would be the main argument against the warning -- the programmer needs to add a doc string or `nil` before the string to silence the compiler.
It's more common to see no-op functions like
(defun my-no-op-fun ()
"This function is unfinished.")
and here it's probably a good idea to make the programmer be more explicit about the return value.
[-- Attachment #2: 0001-Use-single-string-literal-as-return-value-not-doc-st.patch --]
[-- Type: application/octet-stream, Size: 12944 bytes --]
From 16e3e27d26ab5fcfd61c0e8fbd4aa2a5498058a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mattias=20Engdeg=C3=A5rd?= <mattiase@acm.org>
Date: Wed, 28 Feb 2024 12:05:19 +0100
Subject: [PATCH] Use single string literal as return value, not doc string
A function or macro body consisting of a single string literal now only
uses it as a return value. Previously, it was used as a doc string as
well which was never what the programmer intended and had some
inconvenient consequences (bug#69387).
This change also adds a warning for uses of `defun` and `defmacro` (but
not plain `lambda`) that stand to lose their doc string this way, to
alert the programmer of a possible mistake.
* lisp/emacs-lisp/bytecomp.el (byte-compile-lambda):
Don't use a lone string literal as doc string.
* lisp/emacs-lisp/byte-run.el (byte-run--parse-body): Add warning
for `defun` and `defmacro`.
* doc/lispref/functions.texi (Function Documentation): Update manual.
* etc/NEWS: Announce the change and the warning.
---
doc/lispref/functions.texi | 13 ++-
etc/NEWS | 28 ++++++
lisp/emacs-lisp/byte-run.el | 92 ++++++++++---------
lisp/emacs-lisp/bytecomp.el | 9 +-
.../bytecomp-resources/fun-attr-warn.el | 11 ++-
.../warn-wide-docstring-defun.el | 3 +-
test/lisp/emacs-lisp/bytecomp-tests.el | 4 +-
7 files changed, 101 insertions(+), 59 deletions(-)
diff --git a/doc/lispref/functions.texi b/doc/lispref/functions.texi
index 344b3ff3a50..ff635fc54b2 100644
--- a/doc/lispref/functions.texi
+++ b/doc/lispref/functions.texi
@@ -498,13 +498,12 @@ Function Documentation
nice in the source code will look ugly when displayed by the help
commands.
- You may wonder how the documentation string could be optional, since
-there are required components of the function that follow it (the body).
-Since evaluation of a string returns that string, without any side effects,
-it has no effect if it is not the last form in the body. Thus, in
-practice, there is no confusion between the first form of the body and the
-documentation string; if the only body form is a string then it serves both
-as the return value and as the documentation.
+ A documentation string must always be followed by at least one Lisp
+expression; otherwise, it is not a documentation string at all but the
+single expression of the body and used as the return value.
+When there is no meaningful value to return from a function, it is
+standard practice to return @code{nil} by adding it after the
+documentation string.
The last line of the documentation string can specify calling
conventions different from the actual function arguments. Write
diff --git a/etc/NEWS b/etc/NEWS
index 06856602ea8..c31ebaea74b 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1801,6 +1801,17 @@ Tree-sitter conditionally sets 'forward-sexp-function' for major modes
that have defined 'sexp' in 'treesit-thing-settings' to enable
sexp-related motion commands.
++++
+** Returned strings are never docstrings
+Functions and macros whose body consists of a single string literal now
+only return that string; it is not used as a docstring.
+Previously, that string was used as both a docstring and return value,
+which was never what the programmer wanted. This change applies to
+'defun', 'defsubst', 'defmacro' and 'lambda' forms.
+If you want the string to be a docstring, add an explicit return value.
+
+See related warning below.
+
** New or changed byte-compilation warnings
---
@@ -1954,6 +1965,23 @@ name 'ignored-return-value'.
The warning will only be issued for calls to functions declared
'important-return-value' or 'side-effect-free' (but not 'error-free').
+---
+*** Warn about strings that look like docstrings but are just returned
+The compiler now warns about functions and macros where the body
+consists of a single string literal, which is now used as a return value
+only, not as a docstring. Example:
+
+ (defun sing-a-song ()
+ "Sing a song.")
+
+The above function returns the string '"Sing a song."' but has no
+docstring. This warning is emitted for 'defun', 'defsubst' and
+'defmacro' declarations, not 'lambda' forms.
+
+To silence the warning, assuming a string literal should be returned,
+add an explicit docstring (or 'nil') before the returned string.
+If the string should be a doc string, return something else (like 'nil').
+
---
*** Warn about docstrings that contain control characters.
The compiler now warns about docstrings with control characters other
diff --git a/lisp/emacs-lisp/byte-run.el b/lisp/emacs-lisp/byte-run.el
index cc176821026..663f405cecd 100644
--- a/lisp/emacs-lisp/byte-run.el
+++ b/lisp/emacs-lisp/byte-run.el
@@ -263,49 +263,57 @@ 'byte-run--set-no-font-lock-keyword
(defalias 'byte-run--parse-body
#'(lambda (body allow-interactive)
"Decompose BODY into (DOCSTRING DECLARE INTERACTIVE BODY-REST WARNINGS)."
- (let* ((top body)
- (docstring nil)
- (declare-form nil)
- (interactive-form nil)
- (warnings nil)
- (warn #'(lambda (msg form)
- (push (macroexp-warn-and-return
- (format-message msg) nil nil t form)
- warnings))))
- (while
- (and body
- (let* ((form (car body))
- (head (car-safe form)))
- (cond
- ((or (and (stringp form) (cdr body))
- (eq head :documentation))
- (cond
- (docstring (funcall warn "More than one doc string" top))
- (declare-form
- (funcall warn "Doc string after `declare'" declare-form))
- (interactive-form
- (funcall warn "Doc string after `interactive'"
- interactive-form))
- (t (setq docstring form)))
- t)
- ((eq head 'declare)
- (cond
- (declare-form
- (funcall warn "More than one `declare' form" form))
- (interactive-form
- (funcall warn "`declare' after `interactive'" form))
- (t (setq declare-form form)))
- t)
- ((eq head 'interactive)
+ (if (and (null (cdr body)) (stringp (car body)))
+ (list nil nil nil body
+ (list (macroexp-warn-and-return
+ "Single string treated as return value, not doc string"
+ nil nil t body)))
+ (let* ((top body)
+ (docstring nil)
+ (declare-form nil)
+ (interactive-form nil)
+ (warnings nil)
+ (warn #'(lambda (msg form)
+ (push (macroexp-warn-and-return
+ (format-message msg) nil nil t form)
+ warnings))))
+ (while
+ (and body
+ (let* ((form (car body))
+ (head (car-safe form)))
(cond
- ((not allow-interactive)
- (funcall warn "No `interactive' form allowed here" form))
- (interactive-form
- (funcall warn "More than one `interactive' form" form))
- (t (setq interactive-form form)))
- t))))
- (setq body (cdr body)))
- (list docstring declare-form interactive-form body warnings))))
+ ((or (and (stringp form) (cdr body))
+ (eq head :documentation))
+ (cond
+ (docstring
+ (funcall warn "More than one doc string" top))
+ (declare-form
+ (funcall warn "Doc string after `declare'"
+ declare-form))
+ (interactive-form
+ (funcall warn "Doc string after `interactive'"
+ interactive-form))
+ (t (setq docstring form)))
+ t)
+ ((eq head 'declare)
+ (cond
+ (declare-form
+ (funcall warn "More than one `declare' form" form))
+ (interactive-form
+ (funcall warn "`declare' after `interactive'" form))
+ (t (setq declare-form form)))
+ t)
+ ((eq head 'interactive)
+ (cond
+ ((not allow-interactive)
+ (funcall warn "No `interactive' form allowed here"
+ form))
+ (interactive-form
+ (funcall warn "More than one `interactive' form" form))
+ (t (setq interactive-form form)))
+ t))))
+ (setq body (cdr body)))
+ (list docstring declare-form interactive-form body warnings)))))
(defalias 'byte-run--parse-declarations
#'(lambda (name arglist clauses construct declarations-alist)
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index c3355eedd75..cf0e6d600dd 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -3061,12 +3061,11 @@ byte-compile-lambda
(append (if (not lexical-binding) arglistvars)
byte-compile-bound-variables))
(body (cdr (cdr fun)))
- (doc (if (stringp (car body))
+ ;; Treat a final string literal as a value, not a doc string.
+ (doc (if (and (cdr body) (stringp (car body)))
(prog1 (car body)
- ;; Discard the doc string from the body
- ;; unless it is the last element of the body.
- (if (cdr body)
- (setq body (cdr body))))))
+ ;; Discard the doc string from the body.
+ (setq body (cdr body)))))
(int (assq 'interactive body))
command-modes)
(when lexical-binding
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/fun-attr-warn.el b/test/lisp/emacs-lisp/bytecomp-resources/fun-attr-warn.el
index be907b32f47..9bec23a81e3 100644
--- a/test/lisp/emacs-lisp/bytecomp-resources/fun-attr-warn.el
+++ b/test/lisp/emacs-lisp/bytecomp-resources/fun-attr-warn.el
@@ -42,9 +42,6 @@ faw-doc-decl-int-code
;; Correct (last string is return value)
-(defun faw-str ()
- "something")
-
(defun faw-decl-str ()
(declare (pure t))
"something")
@@ -63,6 +60,9 @@ faw-doc-str
"something else")
+
+
+
;; Incorrect (bad order)
(defun faw-int-decl-code (x)
@@ -264,3 +264,8 @@ faw-doc-int-decl-int-code
(declare (pure t))
(interactive "p")
(print x))
+
+;; Incorrect (ambiguous string)
+
+(defun faw-str ()
+ "something")
diff --git a/test/lisp/emacs-lisp/bytecomp-resources/warn-wide-docstring-defun.el b/test/lisp/emacs-lisp/bytecomp-resources/warn-wide-docstring-defun.el
index 94b0e80c979..571f7f6f095 100644
--- a/test/lisp/emacs-lisp/bytecomp-resources/warn-wide-docstring-defun.el
+++ b/test/lisp/emacs-lisp/bytecomp-resources/warn-wide-docstring-defun.el
@@ -1,3 +1,4 @@
;;; -*- lexical-binding: t -*-
(defun foo ()
- "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
+ "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ nil)
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el
index 26408e8685a..f95c4084bf4 100644
--- a/test/lisp/emacs-lisp/bytecomp-tests.el
+++ b/test/lisp/emacs-lisp/bytecomp-tests.el
@@ -2052,7 +2052,9 @@ bytecomp-fun-attr-warn
"258:4: Warning: More than one `interactive' form"
"257:4: Warning: `declare' after `interactive'"
"265:4: Warning: More than one `interactive' form"
- "264:4: Warning: `declare' after `interactive'")))
+ "264:4: Warning: `declare' after `interactive'"
+ "270:2: Warning: Single string treated as return value, not doc string"
+ )))
(goto-char (point-min))
(let ((actual nil))
(while (re-search-forward
--
2.32.0 (Apple Git-132)
next prev parent reply other threads:[~2024-03-05 13:16 UTC|newest]
Thread overview: 16+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-02-25 16:29 bug#69387: 30.0.50; A string shouldn't be both a docstring and a return value Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-25 17:13 ` Eli Zaretskii
2024-02-25 18:23 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-25 17:33 ` Eshel Yaron via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-26 14:47 ` Philip Kaludercic
2024-02-26 17:15 ` Mattias Engdegård
2024-02-26 17:44 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-26 18:04 ` Mattias Engdegård
2024-02-26 18:17 ` Stefan Monnier via Bug reports for GNU Emacs, the Swiss army knife of text editors
2024-02-26 19:06 ` Mattias Engdegård
2024-03-04 14:28 ` Mattias Engdegård
2024-03-04 14:46 ` Eli Zaretskii
2024-03-05 13:16 ` Mattias Engdegård [this message]
2024-03-05 15:38 ` Eli Zaretskii
2024-03-06 11:44 ` Mattias Engdegård
2024-03-07 14:06 ` Mattias Engdegård
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=74101BB1-B82C-4F6C-8AB1-47A131BC90E6@gmail.com \
--to=mattias.engdegard@gmail.com \
--cc=69387@debbugs.gnu.org \
--cc=eliz@gnu.org \
--cc=monnier@iro.umontreal.ca \
--cc=philipk@posteo.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.