The 2026-05-08 change of lib/gettext.h triggers new compiler warnings
  - in packages configured with --disable-nls,
  - when compiled with "clang -Wformat=2",
  - at each occurrence of
      printf (gettext ("some string"), ...)

But similar warnings are also seen, after and before that change,
  - in packages configured with --disable-nls,
  - when compiled with "gcc -Wformat=2",
  - at each occurrence of
      printf (ngettext ("singular ...", "plural ...", n), ...)

This cannot stay like this, because
  * Many distros enable -Wformat=2 (or -Wformat-security) by default.
  * Developers would think that gettext() has a security problem, which it
    hasn't.
  * Some package builds would now fail because they enable both -Wformat=2
    and -Werror.

So, I'm applying this patch to fix the warnings both with clang and gcc.

Unfortunately, with gcc, this reintroduces the -Wuseless-cast warnings
that the 2026-05-08 change removed. So, I'm applying the #pragma hammer to
silence them.

Security-aware people would be angry if I disable some -Wformat
warnings through #pragma, but they will certainly tolerate disabling
of -Wuseless-cast warnings.


2026-05-11  Bruno Haible  <[email protected]>

        gettext-h: Ensure no warnings with --disable-nls and -Wformat=2.
        * lib/gettext.h (gettext, dgettext, dcgettext): Define differently for
        clang.
        (ngettext, dngettext, dcngettext): Define differently for gcc and clang.
        With gcc in C mode, silence -Wuseless-cast warnings.

diff --git a/lib/gettext.h b/lib/gettext.h
index 1ea5c9be78..07f2dfed53 100644
--- a/lib/gettext.h
+++ b/lib/gettext.h
@@ -60,10 +60,39 @@
 # endif
 
 /* Disabled NLS.  */
+/* When gcc is used with option -Wformat=2, we need to silence
+   "warning: format not a string literal, argument types not checked 
[-Wformat-nonliteral]"
+   warnings that would occur at every invocation of a *ngettext function
+   in a *printf format string position.
+   Do this with inline functions when possible.
+   It is not ideal to ignore the possible side effects done in the
+   Domainname and Category arguments, but it's better than to have a
+   warning at every invocation in a format string position.  */
+/* When clang is used with option -Wformat=2, we need to silence
+   "warning: format string is not a string literal [-Wformat-nonliteral]"
+   warnings that would occur at every invocation of a *gettext function
+   in a *printf format string position.
+   It is not ideal to ignore the possible side effects done in the
+   Domainname and Category arguments, but it's better than to have a
+   warning at every invocation in a format string position.  */
+/* These warnings would not occur with enabled NLS.  */
+/* A test case:
+   ================================ foo.c ================================
+   #include <stdio.h>
+   #include "gettext.h"
+   void foo (int n)
+   {
+     printf (gettext ("foo %d"), n);
+     printf (dgettext ("toto", "foo %d"), n);
+     printf (dcgettext ("toto", "foo %d", LC_MESSAGES), n);
+     printf (ngettext ("foo %d", "bar %d", n), n);
+     printf (dngettext ("toto", "foo %d", "bar %d", n), n);
+     printf (dcngettext ("toto", "foo %d", "bar %d", n, LC_MESSAGES), n);
+   }
+   =======================================================================
+   $CC -Wformat=2 -S foo.c
+ */
 # if defined __GNUC__ && !defined __clang__ && !defined __cplusplus
-/* Use inline functions, to avoid warnings
-     warning: format not a string literal and no format arguments
-   that don't occur with enabled NLS.  */
 /* The return type 'const char *' serves the purpose of producing warnings
    for invalid uses of the value returned from these functions.  */
 #  if __GNUC__ >= 9
@@ -92,7 +121,7 @@ __attribute__ ((__always_inline__))
 extern inline
 #  if !defined(__sun)
 const
-#  endif
+# endif
 char *
 dgettext (const char *domain, const char *msgid)
 {
@@ -118,11 +147,21 @@ dcgettext (const char *domain, const char *msgid, int 
category)
 #  if __GNUC__ >= 9
 #   pragma GCC diagnostic pop
 #  endif
+# elif defined __clang__
+#  undef gettext
+#  define gettext(Msgid) ((const char *) (Msgid))
+#  undef dgettext
+#  define dgettext(Domainname, Msgid) gettext (Msgid)
+#  undef dcgettext
+#  define dcgettext(Domainname, Msgid, Category) dgettext (Domainname, Msgid)
 # else
-/* The 'const char *' compound literals produce warnings
-   for invalid uses of the value returned from these functions.  */
+/* The conversions to 'const char *' via compound literals serve the purpose
+   of producing warnings for invalid uses of the value returned from these
+   functions and for invalid-typed Msgid arguments.  */
 #  undef gettext
 #  define gettext(Msgid) ((const char *) {(Msgid)})
+/* The conversions via compound literals serve the purpose of producing 
warnings
+   for invalid-typed arguments.  */
 #  undef dgettext
 #  define dgettext(Domainname, Msgid) \
      ((void) (const char *) {(Domainname)}, gettext (Msgid))
@@ -130,17 +169,50 @@ dcgettext (const char *domain, const char *msgid, int 
category)
 #  define dcgettext(Domainname, Msgid, Category) \
      ((void) (int) {(Category)}, dgettext (Domainname, Msgid))
 # endif
-# undef ngettext
-# define ngettext(Msgid1, Msgid2, N) \
-    ((N) == 1 \
-     ? ((void) (Msgid2), (const char *) {(Msgid1)}) \
-     : ((void) (Msgid1), (const char *) {(Msgid2)}))
-# undef dngettext
-# define dngettext(Domainname, Msgid1, Msgid2, N) \
-    ((void) (const char *) {(Domainname)}, ngettext (Msgid1, Msgid2, N))
-# undef dcngettext
-# define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
-    ((void) (int) {(Category)}, dngettext (Domainname, Msgid1, Msgid2, N))
+
+# if (defined __GNUC__ && defined __cplusplus) || defined __clang__
+#  undef ngettext
+#  define ngettext(Msgid1, Msgid2, N) \
+     ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#  undef dngettext
+#  define dngettext(Domainname, Msgid1, Msgid2, N) \
+     ngettext (Msgid1, Msgid2, N)
+#  undef dcngettext
+#  define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+     dngettext (Domainname, Msgid1, Msgid2, N)
+# elif defined __GNUC__ && !defined __cplusplus
+/* Silence -Wuseless-cast warnings.  */
+#  if __GNUC__ >= 14
+#   pragma GCC diagnostic ignored "-Wuseless-cast"
+#  endif
+#  undef ngettext
+#  define ngettext(Msgid1, Msgid2, N) \
+     ((N) == 1 ? (const char *) (Msgid1) : (const char *) (Msgid2))
+#  undef dngettext
+#  define dngettext(Domainname, Msgid1, Msgid2, N) \
+     ((void) (const char *) (Domainname), ngettext (Msgid1, Msgid2, N))
+#  undef dcngettext
+#  define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+     ((void) (int) (Category), dngettext (Domainname, Msgid1, Msgid2, N))
+# else
+/* The conversions to 'const char *' via compound literals serve the purpose
+   of producing warnings for invalid uses of the value returned from these
+   functions and for invalid-typed Msgid1 and Msgid2 arguments.  */
+#  undef ngettext
+#  define ngettext(Msgid1, Msgid2, N) \
+     ((N) == 1 \
+      ? ((void) (Msgid2), (const char *) {(Msgid1)}) \
+      : ((void) (Msgid1), (const char *) {(Msgid2)}))
+/* The conversions via compound literals serve the purpose of producing 
warnings
+   for invalid-typed arguments.  */
+#  undef dngettext
+#  define dngettext(Domainname, Msgid1, Msgid2, N) \
+     ((void) (const char *) {(Domainname)}, ngettext (Msgid1, Msgid2, N))
+#  undef dcngettext
+#  define dcngettext(Domainname, Msgid1, Msgid2, N, Category) \
+     ((void) (int) {(Category)}, dngettext (Domainname, Msgid1, Msgid2, N))
+# endif
+
 # undef textdomain
 # define textdomain(Domainname) ((const char *) {(Domainname)})
 # undef bindtextdomain




Reply via email to