From 53faf1b66901915915423388c0955c9db46e78fd Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
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

