On Wed, 19 Nov 2025 at 19:44, Peter Eisentraut <[email protected]> wrote:
> Presumably _Generic type resolution and StaticAssertExpr()'s
> definition are just too much for it.  I wonder if some other phrasing
> could help.  Posting where I got to with this, in case anyone has any
> ideas...

Yeah, I had been playing with a similar patch, which now also crashes on
CI, but I'm pretty sure this worked at some point.  So maybe an upgrade
or downgrade would fix it.

Attached is an updated version of Thomas his approach that passes CI. It
continues to use the sizeof fallback in StaticAssertVariableIsOfType for
VS2019. It still defines the pg_expr_has_type_p macro even for VS2019
though, because it's only the combination with StaticAssertExpr that
triggers compiler bugs not the _Generic usage itself.

I did not consider C++.  I'm unsure what to do about it.  For the C type
system, "compatible" is term of art, and swapping
__builtin_types_compatible_p for _Generic is semantically equivalent.  I
don't have the C++ experience to know what exactly std::is_same is, but
I don't know that we want to expose ourselves to requiring types to be
*both* C-"compatible" and C++-"same" without more guidance.

It turns out the C++ version that Thomas wrote was indeed not
equivalent. _Generic decays arrays and functions into pointers. This
starts doing the same for the C++ version. After a focussed reading of
the reference for both _Generic[1], std::decay[2] and std::is_same[3],
I'm pretty sure that these two macro definition now behave the same in
all cases. I've also included some static asserts (in the decl position
which also works on VS2019) to ensure that the behaviour is the same in
C an C++ for all cases that I expect us to use.

I don't know what the aim of the C++ support might be.

I use this macro in my "safer and easier hash table macros" patchset[4],
not for asserts but to pick the hashing function. And I'd like for those
new macros to be useable in C++ too.

[1]: https://en.cppreference.com/w/c/language/generic.html
[2]: https://en.cppreference.com/w/cpp/types/decay.html
[3]: https://en.cppreference.com/w/cpp/types/is_same.html
[4]: 
https://www.postgresql.org/message-id/flat/aS2b3LoUypW1/[email protected]
From 53faf1b66901915915423388c0955c9db46e78fd Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email protected]>
Date: Wed, 18 Mar 2026 09:37:32 +0100
Subject: [PATCH v3] Replace __builtin_types_compatible_p with _Generic

Remove all our use of GCC __builtin_types_compatible_p builtin with a
new pg_expr_has_type_p macro that relies on standard C11/C++11 features.
This allows our existing type check macros to work on Visual Studio too.

Some explicit tests for the new macro are added, both for C and C++ to
ensure they behave the same.
---
 config/c-compiler.m4                          | 19 ----------
 configure                                     | 31 +---------------
 configure.ac                                  |  1 -
 meson.build                                   | 15 --------
 src/include/c.h                               | 37 ++++++++++++++-----
 src/include/pg_config.h.in                    |  3 --
 .../test_cplusplusext/test_cplusplusext.cpp   | 17 +++++++++
 src/test/modules/test_extensions/test_ext.c   | 16 ++++++++
 8 files changed, 62 insertions(+), 77 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 629572ee350..d2d93002985 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -267,25 +267,6 @@ fi])# PGAC_CXX_TYPEOF_UNQUAL
 
 
 
-# PGAC_C_TYPES_COMPATIBLE
-# -----------------------
-# Check if the C compiler understands __builtin_types_compatible_p,
-# and define HAVE__BUILTIN_TYPES_COMPATIBLE_P if so.
-#
-# We check usage with __typeof__, though it's unlikely any compiler would
-# have the former and not the latter.
-AC_DEFUN([PGAC_C_TYPES_COMPATIBLE],
-[AC_CACHE_CHECK(for __builtin_types_compatible_p, pgac_cv__types_compatible,
-[AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
-[[ int x; static int y[__builtin_types_compatible_p(__typeof__(x), int)]; ]])],
-[pgac_cv__types_compatible=yes],
-[pgac_cv__types_compatible=no])])
-if test x"$pgac_cv__types_compatible" = xyes ; then
-AC_DEFINE(HAVE__BUILTIN_TYPES_COMPATIBLE_P, 1,
-          [Define to 1 if your compiler understands __builtin_types_compatible_p.])
-fi])# PGAC_C_TYPES_COMPATIBLE
-
-
 # PGAC_C_BUILTIN_CONSTANT_P
 # -------------------------
 # Check if the C compiler understands __builtin_constant_p(),
diff --git a/configure b/configure
index 5aec0afa9ab..e4b847e41dc 100755
--- a/configure
+++ b/configure
@@ -15182,36 +15182,7 @@ _ACEOF
 
   fi
 fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_types_compatible_p" >&5
-$as_echo_n "checking for __builtin_types_compatible_p... " >&6; }
-if ${pgac_cv__types_compatible+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
-/* end confdefs.h.  */
-
-int
-main ()
-{
- int x; static int y[__builtin_types_compatible_p(__typeof__(x), int)];
-  ;
-  return 0;
-}
-_ACEOF
-if ac_fn_c_try_compile "$LINENO"; then :
-  pgac_cv__types_compatible=yes
-else
-  pgac_cv__types_compatible=no
-fi
-rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
-fi
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv__types_compatible" >&5
-$as_echo "$pgac_cv__types_compatible" >&6; }
-if test x"$pgac_cv__types_compatible" = xyes ; then
-
-$as_echo "#define HAVE__BUILTIN_TYPES_COMPATIBLE_P 1" >>confdefs.h
-
-fi
+PGAC_C_TYPES_COMPATIBLE
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __builtin_constant_p" >&5
 $as_echo_n "checking for __builtin_constant_p... " >&6; }
 if ${pgac_cv__builtin_constant_p+:} false; then :
diff --git a/configure.ac b/configure.ac
index fead9a6ce99..47f5cd05a82 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1727,7 +1727,6 @@ PGAC_C_TYPEOF
 PGAC_CXX_TYPEOF
 PGAC_C_TYPEOF_UNQUAL
 PGAC_CXX_TYPEOF_UNQUAL
-PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
 PGAC_C_BUILTIN_UNREACHABLE
diff --git a/meson.build b/meson.build
index 46bd6b1468a..ec3e4f05628 100644
--- a/meson.build
+++ b/meson.build
@@ -2060,21 +2060,6 @@ foreach builtin : builtins
 endforeach
 
 
-# Check if the C compiler understands __builtin_types_compatible_p,
-# and define HAVE__BUILTIN_TYPES_COMPATIBLE_P if so.
-#
-# We check usage with __typeof__, though it's unlikely any compiler would
-# have the former and not the latter.
-if cc.compiles('''
-    static int x;
-    static int y[__builtin_types_compatible_p(__typeof__(x), int)];
-    ''',
-    name: '__builtin_types_compatible_p',
-    args: test_c_args)
-  cdata.set('HAVE__BUILTIN_TYPES_COMPATIBLE_P', 1)
-endif
-
-
 # Check if the C compiler understands __builtin_$op_overflow(),
 # and define HAVE__BUILTIN_OP_OVERFLOW if so.
 #
diff --git a/src/include/c.h b/src/include/c.h
index 8987a121d6a..b2f253c9088 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -387,6 +387,23 @@ extern "C++"
 #define HAVE_PG_INTEGER_CONSTANT_P
 #endif
 
+/*
+ * pg_expr_has_type_p(expr, type) - Check if an expression has a specific type.
+ *
+ * The C implementation uses _Generic, which applies lvalue conversion to the
+ * controlling expression: arrays decay to pointers, functions decay to function
+ * pointers, and top-level cv-qualifiers are stripped. The C++ implementation
+ * uses std::decay to match this behavior. Note that only top-level qualifiers
+ * are stripped — pointee qualifiers are preserved (e.g. const int * stays
+ * const int *, but int *const becomes int *).
+ */
+#if defined(__cplusplus)
+#define pg_expr_has_type_p(expr, _type) \
+	std::is_same<typename std::decay<decltype(expr)>::type, _type>::value
+#else
+#define pg_expr_has_type_p(expr, type) _Generic((expr), type: 1, default: 0)
+#endif
+
 /*
  * pg_assume(expr) states that we assume `expr` to evaluate to true. In assert
  * enabled builds pg_assume() is turned into an assertion, in optimized builds
@@ -1051,26 +1068,28 @@ pg_noreturn extern void ExceptionalCondition(const char *conditionName,
  * StaticAssertVariableIsOfType() can be used as a declaration.
  * StaticAssertVariableIsOfTypeMacro() is intended for use in macros, eg
  *		#define foo(x) (StaticAssertVariableIsOfTypeMacro(x, int), bar(x))
- *
- * If we don't have __builtin_types_compatible_p, we can still assert that
- * the types have the same size.  This is far from ideal (especially on 32-bit
- * platforms) but it provides at least some coverage.
  */
-#ifdef HAVE__BUILTIN_TYPES_COMPATIBLE_P
+#if !defined(_MSC_VER) || _MSC_VER >= 1933
 #define StaticAssertVariableIsOfType(varname, typename) \
-	StaticAssertDecl(__builtin_types_compatible_p(typeof(varname), typename), \
+	StaticAssertDecl(pg_expr_has_type_p(varname, typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
-	(StaticAssertExpr(__builtin_types_compatible_p(typeof(varname), typename), \
+	(StaticAssertExpr(pg_expr_has_type_p(varname, typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
-#else							/* !HAVE__BUILTIN_TYPES_COMPATIBLE_P */
+#else							/* _MSC_VER < 1933 */
+/*
+ * This compiler is buggy and fails to compile the previous variant; use a
+ * fallback implementation that asserts that the types have the same size.
+ * This is far from ideal (especially on 32-bit platforms) but it provides at
+ * least some coverage.
+ */
 #define StaticAssertVariableIsOfType(varname, typename) \
 	StaticAssertDecl(sizeof(varname) == sizeof(typename), \
 	CppAsString(varname) " does not have type " CppAsString(typename))
 #define StaticAssertVariableIsOfTypeMacro(varname, typename) \
 	(StaticAssertExpr(sizeof(varname) == sizeof(typename), \
 	 CppAsString(varname) " does not have type " CppAsString(typename)))
-#endif							/* HAVE__BUILTIN_TYPES_COMPATIBLE_P */
+#endif							/* _MSC_VER < 1933 */
 
 
 /* ----------------------------------------------------------------
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 79379a4d125..2be8e50465f 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -538,9 +538,6 @@
 /* Define to 1 if your compiler understands __builtin_$op_overflow. */
 #undef HAVE__BUILTIN_OP_OVERFLOW
 
-/* Define to 1 if your compiler understands __builtin_types_compatible_p. */
-#undef HAVE__BUILTIN_TYPES_COMPATIBLE_P
-
 /* Define to 1 if your compiler understands __builtin_unreachable. */
 #undef HAVE__BUILTIN_UNREACHABLE
 
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index 93cd7dd07f7..df7a592cb70 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -27,6 +27,23 @@ PG_FUNCTION_INFO_V1(test_cplusplus_add);
 
 StaticAssertDecl(sizeof(int32) == 4, "int32 should be 4 bytes");
 
+/* Same tests as in test_ext.c, but compiled with a C++ compiler to verify that
+ * the pg_expr_has_type_p macro works correctly in C++. */
+StaticAssertDecl(pg_expr_has_type_p((int32) 123, int32), "int32 expression should be int32");
+StaticAssertDecl(!pg_expr_has_type_p((int32) 123, int64), "int32 expression should not be int64");
+StaticAssertDecl(pg_expr_has_type_p(((char (*)[10]) nullptr)[0], char *),
+				 "array should decay into pointer");
+StaticAssertDecl(pg_expr_has_type_p((char (*)[10]) nullptr, char (*)[10]),
+				 "pointer to an aray should work if it has the same size");
+StaticAssertDecl(!pg_expr_has_type_p((char (*)[5]) nullptr, char (*)[10]),
+				 "pointer to an aray should not match if it does not have the same size");
+StaticAssertDecl(pg_expr_has_type_p((const int *) nullptr, const int *),
+				 "const pointers of same type should match");
+StaticAssertDecl(!pg_expr_has_type_p((const int *) nullptr, int *),
+				 "const pointer should not match non-const pointer");
+StaticAssertDecl(pg_expr_has_type_p((const int) 0, int),
+				 "top-level const should be stripped");
+
 /*
  * Simple function that returns the sum of two integers.  This verifies that
  * C++ extension modules can be loaded and called correctly at runtime.
diff --git a/src/test/modules/test_extensions/test_ext.c b/src/test/modules/test_extensions/test_ext.c
index a23165ba67a..d528a770dee 100644
--- a/src/test/modules/test_extensions/test_ext.c
+++ b/src/test/modules/test_extensions/test_ext.c
@@ -13,6 +13,22 @@ PG_MODULE_MAGIC;
 
 PG_FUNCTION_INFO_V1(test_ext);
 
+/* Confirm that C implementation of pg_expr_has_type_p works as expected on all compilers. */
+StaticAssertDecl(pg_expr_has_type_p((int32) 123, int32), "int32 expression should be int32");
+StaticAssertDecl(!pg_expr_has_type_p((int32) 123, int64), "int32 expression should not be int64");
+StaticAssertDecl(pg_expr_has_type_p(((char (*)[10]) NULL)[0], char *),
+				 "array should decay into pointer");
+StaticAssertDecl(pg_expr_has_type_p((char (*)[10]) NULL, char (*)[10]),
+				 "pointer to an aray should work if it has the same size");
+StaticAssertDecl(!pg_expr_has_type_p((char (*)[5]) NULL, char (*)[10]),
+				 "pointer to an aray should not match if it does not have the same size");
+StaticAssertDecl(pg_expr_has_type_p((const int *) NULL, const int *),
+				 "const pointers of same type should match");
+StaticAssertDecl(!pg_expr_has_type_p((const int *) NULL, int *),
+				 "const pointer should not match non-const pointer");
+StaticAssertDecl(pg_expr_has_type_p((const int) 0, int),
+				 "top-level const should be stripped");
+
 Datum
 test_ext(PG_FUNCTION_ARGS)
 {

base-commit: 182cdf5aeaf7b34a288a135d66f2893dc288a24e
-- 
2.53.0

Reply via email to