The manual stated that when defn() is used on a builtin, attempts to
concatenate it with other text produce a warning and omit the builtin.
This was true for defn(`divnum', `other'), but not for define(`foo',
defn(`divnum')defn(`other')).  What's more, m4 silently behaved
differently depending on whether the builtin token was first (the
definition became a synonym of the builtin, ignoring the rest of the
text) or second (the defintion used only the text).  All of this is
confusing to the user, and most portable programs won't be trying to
concatenate builtin tokens anyways (as other m4 implementations behave
differently on how builtins are represented in defn).

* src/macro.c (expand_argument): Warn if a builtin token is used
with anything else during argument collection.  Drop builtins
consistently in any context that does not expect them.
(collect_arguments): Lower groks_macro handling to expand_argument.
* m4.texi (Defn): Expand the text to cover more cases.
* NEWS: Document this.
Reported by Xavier Wang
<https://mail.gnu.org/archive/html/m4-discuss/2025-08/msg00000.html>
---
 NEWS        | 10 ++++++++++
 doc/m4.texi | 17 ++++++++++++++---
 src/macro.c | 36 +++++++++++++++++++++++-------------
 3 files changed, 47 insertions(+), 16 deletions(-)

diff --git a/NEWS b/NEWS
index b961f9df..95437e3f 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,16 @@ GNU M4 NEWS - User visible changes.
    accept as an integer literal, rather than silently treating it as zero
    (present since "the beginning").

+** Fix the `define' and `pushdef' builtins to always warn when attempting
+   to concatenate the result of using the `defn' macro on a builtin macro
+   with anything else.  This is a counterpart to the change in m4 1.4.11
+   that issues a warning when `defn' with multiple arguments attempts to
+   perform concatenation.  Attempting to use the output of `defn' anywhere
+   other than a lone argument during macro definition has never been
+   portable, but now the outcome is always the remaining text, rather
+   than being dependent on whether `defn' was used before or after the
+   other text (present since "the beginning").
+
 ** Port to glibc 2.43, which implements functions like strchr as macros
    when compiled with a C23 compiler.

diff --git a/doc/m4.texi b/doc/m4.texi
index 83f4a273..42d1fce1 100644
--- a/doc/m4.texi
+++ b/doc/m4.texi
@@ -2267,9 +2267,10 @@ Defn
 expansion is a special token, which points to the builtin's internal
 definition.  This token is only meaningful as the second argument to
 @code{define} (and @code{pushdef}), and is silently converted to an
-empty string in most other contexts.  Combining a builtin with anything
-else is not supported; a warning is issued and the builtin is omitted
-from the final expansion.
+empty string in most other contexts.  When defining a macro, combining a
+builtin with anything else is not supported; a warning is issued and the
+builtin is omitted from the final definition (other implementations may
+do other unexpected things).

 The macro @code{defn} is recognized only with parameters.
 @end deffn
@@ -2379,6 +2380,10 @@ Defn
 @result{}0
 len(defn(`divnum'))
 @result{}0
+eval(`1'defn(`divnum')) eval(defn(`divnum')8)
+@result{}1 8
+ifelse(defn(`divnum'), defn(`define'), `indistinguishable')
+@result{}indistinguishable
 @end example

 Also note that @code{defn} with multiple arguments can only join text
@@ -2401,6 +2406,12 @@ Defn
 @error{}m4trace: -2- defn(`divnum', `divnum')
 @error{}m4trace: -1- define(`mydivnum', `')
 @result{}
+define(`mydivnum+', defn(`divnum')`+'defn(`divnum'))mydivnum+
+@error{}m4trace: -2- defn(`divnum')
+@error{}m4trace: -2- defn(`divnum')
+@error{}m4:stdin:5: Warning: cannot concatenate builtin tokens
+@error{}m4trace: -1- define(`mydivnum+', `+')
+@result{}+
 traceoff(`defn', `define')
 @result{}
 @end example
diff --git a/src/macro.c b/src/macro.c
index 1c48ce09..29466cfa 100644
--- a/src/macro.c
+++ b/src/macro.c
@@ -137,7 +137,7 @@ expand_token (struct obstack *obs, token_type t, token_data 
*td, int line)
 `-------------------------------------------------------------------*/

 static bool
-expand_argument (struct obstack *obs, token_data *argp)
+expand_argument (struct obstack *obs, token_data *argp, bool groks_macro)
 {
   token_type t;
   token_data td;
@@ -172,6 +172,12 @@ expand_argument (struct obstack *obs, token_data *argp)
               len = obstack_object_size (obs) - 1;
               text = (char *) obstack_finish (obs);

+              if (TOKEN_DATA_TYPE (argp) == TOKEN_FUNC && len)
+                {
+                  M4ERROR ((warning_status, 0,
+                            _("Warning: cannot concatenate builtin tokens")));
+                  TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
+                }
               if (TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
                 {
                   TOKEN_DATA_TYPE (argp) = TOKEN_TEXT;
@@ -204,10 +210,21 @@ expand_argument (struct obstack *obs, token_data *argp)
           break;

         case TOKEN_MACDEF:
-          if (obstack_object_size (obs) == 0)
+          /* Silently ignore macro tokens outside of certain builtins */
+          if (groks_macro)
             {
-              TOKEN_DATA_TYPE (argp) = TOKEN_FUNC;
-              TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td);
+              if (obstack_object_size (obs) == 0 &&
+                  TOKEN_DATA_TYPE (argp) == TOKEN_VOID)
+                {
+                  TOKEN_DATA_TYPE (argp) = TOKEN_FUNC;
+                  TOKEN_DATA_FUNC (argp) = TOKEN_DATA_FUNC (&td);
+                }
+              else
+                {
+                  M4ERROR ((warning_status, 0,
+                            _("Warning: cannot concatenate builtin tokens")));
+                  TOKEN_DATA_TYPE (argp) = TOKEN_VOID;
+                }
             }
           break;

@@ -234,7 +251,7 @@ collect_arguments (symbol *sym, struct obstack *argptr,
   token_data td;
   token_data *tdp;
   bool more_args;
-  bool groks_macro_args = SYMBOL_MACRO_ARGS (sym);
+  bool groks_macro = SYMBOL_MACRO_ARGS (sym);

   TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
   TOKEN_DATA_TEXT (&td) = SYMBOL_NAME (sym);
@@ -247,14 +264,7 @@ collect_arguments (symbol *sym, struct obstack *argptr,
       next_token (&td, NULL);   /* gobble parenthesis */
       do
         {
-          more_args = expand_argument (arguments, &td);
-
-          if (!groks_macro_args && TOKEN_DATA_TYPE (&td) == TOKEN_FUNC)
-            {
-              TOKEN_DATA_TYPE (&td) = TOKEN_TEXT;
-              TOKEN_DATA_TEXT (&td) = (char *) "";
-              TOKEN_DATA_LEN (&td) = 0;
-            }
+          more_args = expand_argument (arguments, &td, groks_macro);
           tdp = (token_data *) obstack_copy (arguments, &td, sizeof td);
           obstack_ptr_grow (argptr, tdp);
         }

base-commit: 900a90f624cee4a8c1c02c4d6a61ef1ed26a17d1
-- 
2.52.0


_______________________________________________
M4-patches mailing list
[email protected]
https://lists.gnu.org/mailman/listinfo/m4-patches

Reply via email to