Subject: Change copyObject() to use typeof_unqual

From: Peter Eisentraut <[email-masked]>
Date: Tue, 20 Jan 2026 10:37:35 +0100

Currently, when the argument of copyObject() is const-qualified, the 
return type is also, because the use of typeof carries over all the 
qualifiers.  This is incorrect, since the point of copyObject() is to 
  make a copy to mutate.  But apparently no code ran into it.

The new implementation uses typeof_unqual, which drops the qualifiers, 
making this work correctly.

typeof_unqual is standardized in C23, but all recent versions of all the 
usual compilers support it even in non-C23 mode, at least as 
__typeof_unqual__.  We add a configure/meson test for typeof_unqual and 
__typeof_unqual__ and use it if it's available, else we use the existing 
fallback of just returning void *.

(Even MSVC supports it, if we use a slightly newer version than is on 
CI.  For example, the new buildfarm member unicorn would support it.)

The second patch make some changes to take advantage of this improved 
qualifier handling.  EventTriggerCollectSimpleCommand() is a good 
example:  It takes a node tree and makes a copy that it keeps around for 
its internal purposes, but it can't communicate via its function 
signature that it promises not scribble on the passed node tree.  That 
is now fixed.

The obvious drawback is that if you use an older compiler that supports 
typeof but not typeof_unqual, this change will make you lose that check. 
  Currently, on the Cirrus CI system, only the NetBSD and OpenBSD tasks 
are affected by this.

Anyway, the reason I'm posting this now instead of, say, waiting another 
year, is that over in the thread "Make copyObject work in C++"[0], we're 
discussing, well, making copyObject() work in (standard) C++ (typeof and 
typeof_unqual are not in C++).  Assuming we want to make the 
typeof_unqual change in principle, I figured it would be worth 
considering doing that change first and then developing a C++ equivalent 
of that, instead of making a C++ equivalent of the current logic and 
then having to find another C++ solution when changing to typeof_unqual.

[0]: 
https://www.postgresql.org/message-id/flat/[email-masked]

From b136e702351ad50b3f96b8289f01f9e265000412 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Tue, 20 Jan 2026 10:01:46 +0100
Subject: [PATCH 1/2] Change copyObject() to use typeof_unqual

Currently, when the argument of copyObject() is const-qualified, the
return type is also, because the use of typeof carries over all the
qualifiers.  This is incorrect, since the point of copyObject() is to
make a copy to mutate.  But apparently no code ran into it.

The new implementation uses typeof_unqual, which drops the qualifiers,
making this work correctly.

typeof_unqual is standardized in C23, but all recent versions of all
the usual compilers support it even in non-C23 mode, at least as
__typeof_unqual__.  We add a configure/meson test for typeof_unqual
and __typeof_unqual__ and use it if it's available, else we use the
existing fallback of just returning void *.
---
 config/c-compiler.m4       | 25 +++++++++++++++++++++++
 configure                  | 42 ++++++++++++++++++++++++++++++++++++++
 configure.ac               |  1 +
 meson.build                | 24 ++++++++++++++++++++++
 src/include/nodes/nodes.h  |  4 ++--
 src/include/pg_config.h.in |  7 +++++++
 6 files changed, 101 insertions(+), 2 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 1509dbfa2ab..7179a73bd2c 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -192,6 +192,31 @@ if test "$pgac_cv_c_typeof" != no; then
 fi])# PGAC_C_TYPEOF

+# PGAC_C_TYPEOF_UNQUAL
+# --------------------
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+#
+AC_DEFUN([PGAC_C_TYPEOF_UNQUAL],
+[AC_CACHE_CHECK(for typeof_unqual, pgac_cv_c_typeof_unqual,
+[pgac_cv_c_typeof_unqual=no
+for pgac_kw in typeof_unqual __typeof_unqual__; do
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+y = x;
+return y;])],
+[pgac_cv_c_typeof_unqual=$pgac_kw])
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done])
+if test "$pgac_cv_c_typeof_unqual" != no; then
+  AC_DEFINE(HAVE_TYPEOF_UNQUAL, 1,
+            [Define to 1 if your compiler understands `typeof_unqual' or something similar.])
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+    AC_DEFINE_UNQUOTED(typeof_unqual, $pgac_cv_c_typeof_unqual, [Define to how the compiler spells `typeof_unqual'.])
+  fi
+fi])# PGAC_C_TYPEOF_UNQUAL
+
 
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
diff --git a/configure b/configure
index fb6a4914b06..b72123635c5 100755
--- a/configure
+++ b/configure
@@ -14919,6 +14919,48 @@ _ACEOF
 
   fi
 fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for typeof_unqual" >&5
+$as_echo_n "checking for typeof_unqual... " >&6; }
+if ${pgac_cv_c_typeof_unqual+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_c_typeof_unqual=no
+for pgac_kw in typeof_unqual __typeof_unqual__; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 0;
+$pgac_kw(x) y;
+y = x;
+return y;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_c_typeof_unqual=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_c_typeof_unqual" >&5
+$as_echo "$pgac_cv_c_typeof_unqual" >&6; }
+if test "$pgac_cv_c_typeof_unqual" != no; then
+
+$as_echo "#define HAVE_TYPEOF_UNQUAL 1" >>confdefs.h
+
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+
+cat >>confdefs.h <<_ACEOF
+#define typeof_unqual $pgac_cv_c_typeof_unqual
+_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 :
diff --git a/configure.ac b/configure.ac
index d3febfe58f1..42f48fc8d19 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1677,6 +1677,7 @@ PGAC_PRINTF_ARCHETYPE
 PGAC_CXX_PRINTF_ARCHETYPE
 PGAC_C_STATEMENT_EXPRESSIONS
 PGAC_C_TYPEOF
+PGAC_C_TYPEOF_UNQUAL
 PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
diff --git a/meson.build b/meson.build
index 6d304f32fb0..9b916878207 100644
--- a/meson.build
+++ b/meson.build
@@ -2851,6 +2851,30 @@ int main(void)
   endif
 endforeach
 
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+foreach kw : ['typeof_unqual', '__typeof_unqual__']
+  if cc.compiles('''
+int main(void)
+{
+    int x = 0;
+    @0@(x) y;
+    y = x;
+    return y;
+}
+'''.format(kw),
+    name: kw,
+    args: test_c_args, include_directories: postgres_inc)
+
+    cdata.set('HAVE_TYPEOF_UNQUAL', 1)
+    if kw != 'typeof_unqual'
+      cdata.set('typeof_unqual', kw)
+    endif
+
+    break
+  endif
+endforeach
+
 
 # MSVC doesn't cope well with defining restrict to __restrict, the
 # spelling it understands, because it conflicts with
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index b6ad28618ab..ba6dd7f3899 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -226,8 +226,8 @@ extern int16 *readAttrNumberCols(int numCols);
 extern void *copyObjectImpl(const void *from);
 
 /* cast result back to argument type, if supported by compiler */
-#ifdef HAVE_TYPEOF
-#define copyObject(obj) ((typeof(obj)) copyObjectImpl(obj))
+#ifdef HAVE_TYPEOF_UNQUAL
+#define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
 #else
 #define copyObject(obj) copyObjectImpl(obj)
 #endif
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 339268dc8ef..c089f2252c3 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -454,6 +454,10 @@
 /* Define to 1 if your compiler understands `typeof' or something similar. */
 #undef HAVE_TYPEOF
 
+/* Define to 1 if your compiler understands `typeof_unqual' or something
+   similar. */
+#undef HAVE_TYPEOF_UNQUAL
+
 /* Define to 1 if you have the <uchar.h> header file. */
 #undef HAVE_UCHAR_H
 
@@ -806,3 +810,6 @@
 
 /* Define to how the compiler spells `typeof'. */
 #undef typeof
+
+/* Define to how the compiler spells `typeof_unqual'. */
+#undef typeof_unqual

base-commit: 7ebb64c557570647e3fcf6f5f1549e882ed26489
-- 
2.52.0

From a394ca6303c7c88da1430b6ab66d65e6efc695ae Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Tue, 20 Jan 2026 10:01:46 +0100
Subject: [PATCH 2/2] Add some const qualifiers enabled by typeof_unqual change
 on copyObject

---
 src/backend/catalog/index.c               |  2 +-
 src/backend/commands/event_trigger.c      | 14 +++++++-------
 src/backend/commands/indexcmds.c          |  4 ++--
 src/backend/commands/trigger.c            |  4 ++--
 src/backend/optimizer/prep/prepjointree.c |  4 ++--
 src/backend/partitioning/partprune.c      | 12 ++++++------
 src/backend/rewrite/rewriteManip.c        | 17 ++++++++++-------
 src/backend/utils/cache/plancache.c       |  2 +-
 src/include/commands/defrem.h             |  2 +-
 src/include/commands/event_trigger.h      | 14 +++++++-------
 src/include/commands/trigger.h            |  4 ++--
 src/include/rewrite/rewriteManip.h        |  4 ++--
 src/include/utils/plancache.h             |  2 +-
 13 files changed, 44 insertions(+), 41 deletions(-)

diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 43de42ce39e..d4ca965df19 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -3705,7 +3705,7 @@ reindex_index(const ReindexStmt *stmt, Oid indexId,
 		ObjectAddressSet(address, RelationRelationId, indexId);
 		EventTriggerCollectSimpleCommand(address,
 										 InvalidObjectAddress,
-										 (Node *) stmt);
+										 (const Node *) stmt);
 	}
 
 	/*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 028f9e2de90..ab331eb3ccb 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1714,7 +1714,7 @@ EventTriggerUndoInhibitCommandCollection(void)
 void
 EventTriggerCollectSimpleCommand(ObjectAddress address,
 								 ObjectAddress secondaryObject,
-								 Node *parsetree)
+								 const Node *parsetree)
 {
 	MemoryContext oldcxt;
 	CollectedCommand *command;
@@ -1750,7 +1750,7 @@ EventTriggerCollectSimpleCommand(ObjectAddress address,
  * add it to the command list.
  */
 void
-EventTriggerAlterTableStart(Node *parsetree)
+EventTriggerAlterTableStart(const Node *parsetree)
 {
 	MemoryContext oldcxt;
 	CollectedCommand *command;
@@ -1802,7 +1802,7 @@ EventTriggerAlterTableRelid(Oid objectId)
  * internally, so that's all that this code needs to handle at the moment.
  */
 void
-EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
+EventTriggerCollectAlterTableSubcmd(const Node *subcmd, ObjectAddress address)
 {
 	MemoryContext oldcxt;
 	CollectedATSubcmd *newsub;
@@ -1919,7 +1919,7 @@ EventTriggerCollectGrant(InternalGrant *istmt)
  *		executed
  */
 void
-EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
+EventTriggerCollectAlterOpFam(const AlterOpFamilyStmt *stmt, Oid opfamoid,
 							  List *operators, List *procedures)
 {
 	MemoryContext oldcxt;
@@ -1952,7 +1952,7 @@ EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
  *		Save data about a CREATE OPERATOR CLASS command being executed
  */
 void
-EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
+EventTriggerCollectCreateOpClass(const CreateOpClassStmt *stmt, Oid opcoid,
 								 List *operators, List *procedures)
 {
 	MemoryContext oldcxt;
@@ -1986,7 +1986,7 @@ EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
  *		executed
  */
 void
-EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
+EventTriggerCollectAlterTSConfig(const AlterTSConfigurationStmt *stmt, Oid cfgId,
 								 Oid *dictIds, int ndicts)
 {
 	MemoryContext oldcxt;
@@ -2021,7 +2021,7 @@ EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
  *		executed
  */
 void
-EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
+EventTriggerCollectAlterDefPrivs(const AlterDefaultPrivilegesStmt *stmt)
 {
 	MemoryContext oldcxt;
 	CollectedCommand *command;
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 635679cc1f2..6c7f8180bc2 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -543,7 +543,7 @@ WaitForOlderSnapshots(TransactionId limitXmin, bool progress)
 ObjectAddress
 DefineIndex(ParseState *pstate,
 			Oid tableId,
-			IndexStmt *stmt,
+			const IndexStmt *stmt,
 			Oid indexRelationId,
 			Oid parentIndexId,
 			Oid parentConstraintId,
@@ -4047,7 +4047,7 @@ ReindexRelationConcurrently(const ReindexStmt *stmt, Oid relationOid, const Rein
 			ObjectAddressSet(address, RelationRelationId, newIndexId);
 			EventTriggerCollectSimpleCommand(address,
 											 InvalidObjectAddress,
-											 (Node *) stmt);
+											 (const Node *) stmt);
 		}
 	}
 
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index d30fda660eb..76ecdec34c7 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -157,7 +157,7 @@ static HeapTuple check_modified_virtual_generated(TupleDesc tupdesc, HeapTuple t
  * (but see CloneRowTriggersToPartition).
  */
 ObjectAddress
-CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
+CreateTrigger(const CreateTrigStmt *stmt, const char *queryString,
 			  Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
 			  Oid funcoid, Oid parentTriggerOid, Node *whenClause,
 			  bool isInternal, bool in_partition)
@@ -174,7 +174,7 @@ CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
  * (always/origin/replica/disabled) can be specified.
  */
 ObjectAddress
-CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
+CreateTriggerFiringOn(const CreateTrigStmt *stmt, const char *queryString,
 					  Oid relOid, Oid refRelOid, Oid constraintOid,
 					  Oid indexOid, Oid funcoid, Oid parentTriggerOid,
 					  Node *whenClause, bool isInternal, bool in_partition,
diff --git a/src/backend/optimizer/prep/prepjointree.c b/src/backend/optimizer/prep/prepjointree.c
index c80bfc88d82..9e439bd1522 100644
--- a/src/backend/optimizer/prep/prepjointree.c
+++ b/src/backend/optimizer/prep/prepjointree.c
@@ -148,7 +148,7 @@ static void replace_vars_in_jointree(Node *jtnode,
 									 pullup_replace_vars_context *context);
 static Node *pullup_replace_vars(Node *expr,
 								 pullup_replace_vars_context *context);
-static Node *pullup_replace_vars_callback(Var *var,
+static Node *pullup_replace_vars_callback(const Var *var,
 										  replace_rte_variables_context *context);
 static Query *pullup_replace_vars_subquery(Query *query,
 										   pullup_replace_vars_context *context);
@@ -2694,7 +2694,7 @@ pullup_replace_vars(Node *expr, pullup_replace_vars_context *context)
 }
 
 static Node *
-pullup_replace_vars_callback(Var *var,
+pullup_replace_vars_callback(const Var *var,
 							 replace_rte_variables_context *context)
 {
 	pullup_replace_vars_context *rcon = (pullup_replace_vars_context *) context->callback_arg;
diff --git a/src/backend/partitioning/partprune.c b/src/backend/partitioning/partprune.c
index a4bbb10a3b7..9dfdf315dd1 100644
--- a/src/backend/partitioning/partprune.c
+++ b/src/backend/partitioning/partprune.c
@@ -158,7 +158,7 @@ static PartitionPruneStep *gen_prune_step_combine(GeneratePruningStepsContext *c
 static List *gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
 										 List **keyclauses, Bitmapset *nullkeys);
 static PartClauseMatchStatus match_clause_to_partition_key(GeneratePruningStepsContext *context,
-														   Expr *clause, Expr *partkey, int partkeyidx,
+														   const Expr *clause, const Expr *partkey, int partkeyidx,
 														   bool *clause_is_not_null,
 														   PartClauseInfo **pc, List **clause_steps);
 static List *get_steps_using_prefix(GeneratePruningStepsContext *context,
@@ -196,8 +196,8 @@ static PruneStepResult *perform_pruning_combine_step(PartitionPruneContext *cont
 													 PartitionPruneStepCombine *cstep,
 													 PruneStepResult **step_results);
 static PartClauseMatchStatus match_boolean_partition_clause(Oid partopfamily,
-															Expr *clause,
-															Expr *partkey,
+															const Expr *clause,
+															const Expr *partkey,
 															Expr **outconst,
 															bool *notclause);
 static void partkey_datum_from_expr(PartitionPruneContext *context,
@@ -1816,7 +1816,7 @@ gen_prune_steps_from_opexps(GeneratePruningStepsContext *context,
  */
 static PartClauseMatchStatus
 match_clause_to_partition_key(GeneratePruningStepsContext *context,
-							  Expr *clause, Expr *partkey, int partkeyidx,
+							  const Expr *clause, const Expr *partkey, int partkeyidx,
 							  bool *clause_is_not_null, PartClauseInfo **pc,
 							  List **clause_steps)
 {
@@ -3697,10 +3697,10 @@ perform_pruning_combine_step(PartitionPruneContext *context,
  * 'partkey'.
  */
 static PartClauseMatchStatus
-match_boolean_partition_clause(Oid partopfamily, Expr *clause, Expr *partkey,
+match_boolean_partition_clause(Oid partopfamily, const Expr *clause, const Expr *partkey,
 							   Expr **outconst, bool *notclause)
 {
-	Expr	   *leftop;
+	const Expr *leftop;
 
 	*outconst = NULL;
 	*notclause = false;
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 6fa174412f2..acd20f61f5a 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1768,7 +1768,7 @@ typedef struct
 } ReplaceVarsFromTargetList_context;
 
 static Node *
-ReplaceVarsFromTargetList_callback(Var *var,
+ReplaceVarsFromTargetList_callback(const Var *var,
 								   replace_rte_variables_context *context)
 {
 	ReplaceVarsFromTargetList_context *rcon = (ReplaceVarsFromTargetList_context *) context->callback_arg;
@@ -1789,7 +1789,7 @@ ReplaceVarsFromTargetList_callback(Var *var,
 }
 
 Node *
-ReplaceVarFromTargetList(Var *var,
+ReplaceVarFromTargetList(const Var *var,
 						 RangeTblEntry *target_rte,
 						 List *targetlist,
 						 int result_relation,
@@ -1875,11 +1875,14 @@ ReplaceVarFromTargetList(Var *var,
 				break;
 
 			case REPLACEVARS_CHANGE_VARNO:
-				var = copyObject(var);
-				var->varno = nomatch_varno;
-				var->varlevelsup = 0;
-				/* we leave the syntactic referent alone */
-				return (Node *) var;
+				{
+					Var		   *newvar = copyObject(var);
+
+					newvar->varno = nomatch_varno;
+					newvar->varlevelsup = 0;
+					/* we leave the syntactic referent alone */
+					return (Node *) newvar;
+				}
 
 			case REPLACEVARS_SUBSTITUTE_NULL:
 				{
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index 37d5d73b7fb..fbb11812d5a 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -180,7 +180,7 @@ InitPlanCache(void)
  * commandTag: command tag for query, or UNKNOWN if empty query
  */
 CachedPlanSource *
-CreateCachedPlan(RawStmt *raw_parse_tree,
+CreateCachedPlan(const RawStmt *raw_parse_tree,
 				 const char *query_string,
 				 CommandTag commandTag)
 {
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 8f4a2d9bbc1..d080ad59b71 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -27,7 +27,7 @@ extern void RemoveObjects(DropStmt *stmt);
 /* commands/indexcmds.c */
 extern ObjectAddress DefineIndex(ParseState *pstate,
 								 Oid tableId,
-								 IndexStmt *stmt,
+								 const IndexStmt *stmt,
 								 Oid indexRelationId,
 								 Oid parentIndexId,
 								 Oid parentConstraintId,
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index c662782bb1e..27340655061 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -75,23 +75,23 @@ extern void EventTriggerUndoInhibitCommandCollection(void);
 
 extern void EventTriggerCollectSimpleCommand(ObjectAddress address,
 											 ObjectAddress secondaryObject,
-											 Node *parsetree);
+											 const Node *parsetree);
 
-extern void EventTriggerAlterTableStart(Node *parsetree);
+extern void EventTriggerAlterTableStart(const Node *parsetree);
 extern void EventTriggerAlterTableRelid(Oid objectId);
-extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd,
+extern void EventTriggerCollectAlterTableSubcmd(const Node *subcmd,
 												ObjectAddress address);
 extern void EventTriggerAlterTableEnd(void);
 
 extern void EventTriggerCollectGrant(InternalGrant *istmt);
-extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt,
+extern void EventTriggerCollectAlterOpFam(const AlterOpFamilyStmt *stmt,
 										  Oid opfamoid, List *operators,
 										  List *procedures);
-extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt,
+extern void EventTriggerCollectCreateOpClass(const CreateOpClassStmt *stmt,
 											 Oid opcoid, List *operators,
 											 List *procedures);
-extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt,
+extern void EventTriggerCollectAlterTSConfig(const AlterTSConfigurationStmt *stmt,
 											 Oid cfgId, Oid *dictIds, int ndicts);
-extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt);
+extern void EventTriggerCollectAlterDefPrivs(const AlterDefaultPrivilegesStmt *stmt);
 
 #endif							/* EVENT_TRIGGER_H */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index b60317c7a75..209b806a2db 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -151,11 +151,11 @@ extern PGDLLIMPORT int SessionReplicationRole;
 #define TRIGGER_FIRES_ON_REPLICA			'R'
 #define TRIGGER_DISABLED					'D'
 
-extern ObjectAddress CreateTrigger(CreateTrigStmt *stmt, const char *queryString,
+extern ObjectAddress CreateTrigger(const CreateTrigStmt *stmt, const char *queryString,
 								   Oid relOid, Oid refRelOid, Oid constraintOid, Oid indexOid,
 								   Oid funcoid, Oid parentTriggerOid, Node *whenClause,
 								   bool isInternal, bool in_partition);
-extern ObjectAddress CreateTriggerFiringOn(CreateTrigStmt *stmt, const char *queryString,
+extern ObjectAddress CreateTriggerFiringOn(const CreateTrigStmt *stmt, const char *queryString,
 										   Oid relOid, Oid refRelOid, Oid constraintOid,
 										   Oid indexOid, Oid funcoid, Oid parentTriggerOid,
 										   Node *whenClause, bool isInternal, bool in_partition,
diff --git a/src/include/rewrite/rewriteManip.h b/src/include/rewrite/rewriteManip.h
index f8216c22fb7..a6d4e888e06 100644
--- a/src/include/rewrite/rewriteManip.h
+++ b/src/include/rewrite/rewriteManip.h
@@ -22,7 +22,7 @@ typedef struct AttrMap AttrMap; /* avoid including attmap.h here */
 
 typedef struct replace_rte_variables_context replace_rte_variables_context;
 
-typedef Node *(*replace_rte_variables_callback) (Var *var,
+typedef Node *(*replace_rte_variables_callback) (const Var *var,
 												 replace_rte_variables_context *context);
 
 struct replace_rte_variables_context
@@ -104,7 +104,7 @@ extern Node *map_variable_attnos(Node *node,
 								 const AttrMap *attno_map,
 								 Oid to_rowtype, bool *found_whole_row);
 
-extern Node *ReplaceVarFromTargetList(Var *var,
+extern Node *ReplaceVarFromTargetList(const Var *var,
 									  RangeTblEntry *target_rte,
 									  List *targetlist,
 									  int result_relation,
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index 984c51515c6..7a4a85c8038 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -202,7 +202,7 @@ extern void ResetPlanCache(void);
 
 extern void ReleaseAllPlanCacheRefsInOwner(ResourceOwner owner);
 
-extern CachedPlanSource *CreateCachedPlan(RawStmt *raw_parse_tree,
+extern CachedPlanSource *CreateCachedPlan(const RawStmt *raw_parse_tree,
 										  const char *query_string,
 										  CommandTag commandTag);
 extern CachedPlanSource *CreateCachedPlanForQuery(Query *analyzed_parse_tree,
-- 
2.52.0

---

From: David Geier <[email-masked]>
Date: Tue, 20 Jan 2026 11:48:00 +0100

Hi Peter!

>  make a copy to mutate.  But apparently no code ran into it.

Great change. I've ran into that multiple times and had to
un-const-correct my code because of how copyObject() works.

> CI.  For example, the new buildfarm member unicorn would support it.)

I'm not too familiar with the build system, so I let someone else review
this part.

> signature that it promises not scribble on the passed node tree.  That

The changes to EvenTriggerCollectSimpleCommand() look good to me.

Are you planning to look at the rest of the code as well? At a quick
glance there seem to be more places that can be changed in similar ways,
e.g. see refresh_matview_datafill().

--
David Geier

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 21 Jan 2026 12:41:46 +0100

> e.g. see refresh_matview_datafill().

I did as much looking as I was planning for this.  If you find more 
things to improve, please go ahead.

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 4 Feb 2026 11:46:22 +0100

> fallback of just returning void *.

I committed this first part, but it ran into some trouble on the buildfarm:

https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=taipan&dt=2026-02-04%2008%3A39%3A34

The problem is that configure detected that gcc supports typeof_unqual, 
but the clang used to produce the .bc files does not.

This seems to be a possible problem in general, if we do all these 
configure checks with $CC, but then run $CLANG assuming all the results 
hold.

My first attempt to fix this was to set CLANG='clang -std=gnu23' to 
effectively put clang into approximately the same mode as gcc.  This 
fixes this particular issue but then later fails when compiling .bc 
files for the test_cplusplus module:

clang -std=gnu23 -xc++ -Wno-ignored-attributes -O2  -I. -I. 
-I../../../../src/include -D_GNU_SOURCE  -I/usr/include/libxml2 
-flto=thin -emit-llvm -c -o test_cplusplusext.bc test_cplusplusext.cpp
error: invalid argument '-std=gnu23' not allowed with 'C++'

This might be another looming problem.  Do we need to look up, say, 
CLANGXX separately from CLANG?

Another attempt was to switch the order of the configure test to check 
for __typeof_unqual__ before typeof_unqual.  This works, but it seems 
totally unprincipled.

Any thoughts?

(Btw., the buildfarm member description says gcc 13, but it's actually 
using gcc 15.)

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 6 Mar 2026 20:17:44 +0100

> but the clang used to produce the .bc files does not.

Here is a new version, after the previous one was reverted.

This is rebased over commit 1887d822f14 and now also provides a C++ 
implementation, corresponding to the C++ typeof implementation added by 
that commit.

This revealed an insufficiency in that commit, which I fix in the first 
patch.

I have addressed the above problem by swapping the order of the probes 
(putting the underscore variant first), as discussed.

There was also an issue that newer MSVC versions claimed to support 
typeof_unqual but it didn't work correctly.  I have enhanced the 
configure probes to detect this problem.

From f2f750f7c3ab6b73514ab2fd5f02185abe9ad59f Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Fri, 6 Mar 2026 13:31:01 +0100
Subject: [PATCH v2 1/3] Fixes for C++ typeof implementation

This fixes two bugs in commit 1887d822f14.

First, if we are using the fallback C++ implementation of typeof, then
we need to include the C++ header <type_traits> for
std::remove_reference_t.  This header is also likely to be used for
other C++ implementations of type tricks, so we'll put it into the
global includes.

Second, for the case that the C compiler supports typeof in a spelling
that is not "typeof" (for example, __typeof__), then we need to #undef
typeof in the C++ section to avoid warnings about duplicate macro
definitions.
---
 src/include/c.h | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/src/include/c.h b/src/include/c.h
index f66c752d4a0..5b678283469 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -82,6 +82,14 @@
 #endif
 #ifdef ENABLE_NLS
 #include <libintl.h>
+#endif
+
+#ifdef __cplusplus
+extern "C++"
+{
+/* This header is used in the definition of various C++ things below. */
+#include <type_traits>
+}
 #endif
 
  /* Pull in fundamental symbols that we also expose to applications */
@@ -435,6 +443,7 @@
  * [1]: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2927.htm#existing-decltype
  */
 #if defined(__cplusplus)
+#undef typeof
 #ifdef pg_cxx_typeof
 #define typeof(x) pg_cxx_typeof(x)
 #elif !defined(HAVE_CXX_TYPEOF)
-- 
2.53.0

From d57d96e3929cd7ef2484e9d3ac95b1c9ba774823 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Fri, 6 Mar 2026 17:29:36 +0100
Subject: [PATCH v2 2/3] Change copyObject() to use typeof_unqual

We test the underscore variant first so that there is a higher chance
that clang used for bitcode also supports it, since we don't test that
separately.

Unlike the typeof test, the typeof_unqual test also tests with a void
pointer similar to how copyObject() would use it, because that is not
handled by MSVC, so we want the test to fail there.

Reviewed-by: David Geier <[email-masked]>
Discussion: https://www.postgresql.org/message-id/flat/92f9750f-c7f6-42d8-9a4a-85a3cbe808f3%40eisentraut.org
---
 config/c-compiler.m4                          |  65 +++++++++++
 configure                                     | 108 ++++++++++++++++++
 configure.ac                                  |   2 +
 meson.build                                   |  62 ++++++++++
 src/include/c.h                               |  12 ++
 src/include/nodes/nodes.h                     |   4 +-
 src/include/pg_config.h.in                    |  14 +++
 .../test_cplusplusext/test_cplusplusext.cpp   |   3 +-
 8 files changed, 267 insertions(+), 3 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 5fd768b7332..88333ef301d 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -176,6 +176,40 @@ if test "$pgac_cv_c_typeof" != no; then
 fi])# PGAC_C_TYPEOF

+# PGAC_C_TYPEOF_UNQUAL
+# --------------------
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+#
+AC_DEFUN([PGAC_C_TYPEOF_UNQUAL],
+[AC_CACHE_CHECK(for typeof_unqual, pgac_cv_c_typeof_unqual,
+[pgac_cv_c_typeof_unqual=no
+# Test the underscore variant first so that there is a higher chance
+# that clang used for bitcode also supports it, since we don't test
+# that separately.
+#
+# Test with a void pointer, because MSVC doesn't handle that, and we
+# need that for copyObject().
+for pgac_kw in __typeof_unqual__ typeof_unqual; do
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;])],
+[pgac_cv_c_typeof_unqual=$pgac_kw])
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done])
+if test "$pgac_cv_c_typeof_unqual" != no; then
+  AC_DEFINE(HAVE_TYPEOF_UNQUAL, 1,
+            [Define to 1 if your compiler understands `typeof_unqual' or something similar.])
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+    AC_DEFINE_UNQUOTED(typeof_unqual, $pgac_cv_c_typeof_unqual, [Define to how the compiler spells `typeof_unqual'.])
+  fi
+fi])# PGAC_C_TYPEOF_UNQUAL
+
 
 # PGAC_CXX_TYPEOF
 # ----------------
@@ -205,6 +239,37 @@ if test "$pgac_cv_cxx_typeof" != no; then
 fi])# PGAC_CXX_TYPEOF

+# PGAC_CXX_TYPEOF_UNQUAL
+# ----------------------
+# Check if the C++ compiler understands typeof_unqual or a variant.  Define
+# HAVE_CXX_TYPEOF_UNQUAL if so, and define 'pg_cxx_typeof_unqual' to the actual key word.
+#
+AC_DEFUN([PGAC_CXX_TYPEOF_UNQUAL],
+[AC_CACHE_CHECK(for C++ typeof_unqual, pgac_cv_cxx_typeof_unqual,
+[pgac_cv_cxx_typeof_unqual=no
+AC_LANG_PUSH(C++)
+for pgac_kw in __typeof_unqual__ typeof_unqual; do
+  AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;])],
+[pgac_cv_cxx_typeof_unqual=$pgac_kw])
+  test "$pgac_cv_cxx_typeof_unqual" != no && break
+done
+AC_LANG_POP([])])
+if test "$pgac_cv_cxx_typeof_unqual" != no; then
+  AC_DEFINE(HAVE_CXX_TYPEOF_UNQUAL, 1,
+            [Define to 1 if your C++ compiler understands `typeof_unqual' or something similar.])
+  if test "$pgac_cv_cxx_typeof_unqual" != typeof_unqual; then
+    AC_DEFINE_UNQUOTED(pg_cxx_typeof_unqual, $pgac_cv_cxx_typeof_unqual, [Define to how the C++ compiler spells `typeof_unqual'.])
+  fi
+fi])# PGAC_CXX_TYPEOF_UNQUAL
+
+
 
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
diff --git a/configure b/configure
index 4aaaf92ba0a..bef63a8c595 100755
--- a/configure
+++ b/configure
@@ -15101,6 +15101,114 @@ _ACEOF
 
   fi
 fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for typeof_unqual" >&5
+$as_echo_n "checking for typeof_unqual... " >&6; }
+if ${pgac_cv_c_typeof_unqual+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_c_typeof_unqual=no
+# Test the underscore variant first so that there is a higher chance
+# that clang used for bitcode also supports it, since we don't test
+# that separately.
+#
+# Test with a void pointer, because MSVC doesn't handle that, and we
+# need that for copyObject().
+for pgac_kw in __typeof_unqual__ typeof_unqual; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_c_typeof_unqual=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_c_typeof_unqual" != no && break
+done
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_c_typeof_unqual" >&5
+$as_echo "$pgac_cv_c_typeof_unqual" >&6; }
+if test "$pgac_cv_c_typeof_unqual" != no; then
+
+$as_echo "#define HAVE_TYPEOF_UNQUAL 1" >>confdefs.h
+
+  if test "$pgac_cv_c_typeof_unqual" != typeof_unqual; then
+
+cat >>confdefs.h <<_ACEOF
+#define typeof_unqual $pgac_cv_c_typeof_unqual
+_ACEOF
+
+  fi
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C++ typeof_unqual" >&5
+$as_echo_n "checking for C++ typeof_unqual... " >&6; }
+if ${pgac_cv_cxx_typeof_unqual+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_cxx_typeof_unqual=no
+ac_ext=cpp
+ac_cpp='$CXXCPP $CPPFLAGS'
+ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
+
+for pgac_kw in __typeof_unqual__ typeof_unqual; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_cxx_try_compile "$LINENO"; then :
+  pgac_cv_cxx_typeof_unqual=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_cxx_typeof_unqual" != no && break
+done
+ac_ext=c
+ac_cpp='$CPP $CPPFLAGS'
+ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
+ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
+ac_compiler_gnu=$ac_cv_c_compiler_gnu
+
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_cxx_typeof_unqual" >&5
+$as_echo "$pgac_cv_cxx_typeof_unqual" >&6; }
+if test "$pgac_cv_cxx_typeof_unqual" != no; then
+
+$as_echo "#define HAVE_CXX_TYPEOF_UNQUAL 1" >>confdefs.h
+
+  if test "$pgac_cv_cxx_typeof_unqual" != typeof_unqual; then
+
+cat >>confdefs.h <<_ACEOF
+#define pg_cxx_typeof_unqual $pgac_cv_cxx_typeof_unqual
+_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 :
diff --git a/configure.ac b/configure.ac
index 9bc457bac87..4d3c69b1c4b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1732,6 +1732,8 @@ PGAC_PRINTF_ARCHETYPE
 PGAC_CXX_PRINTF_ARCHETYPE
 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
diff --git a/meson.build b/meson.build
index 2df54409ca6..70dc64c349a 100644
--- a/meson.build
+++ b/meson.build
@@ -2965,6 +2965,68 @@ int main(void)
   endforeach
 endif
 
+# Check if the C compiler understands typeof_unqual or a variant.  Define
+# HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
+#
+# Test the underscore variant first so that there is a higher chance
+# that clang used for bitcode also supports it, since we don't test
+# that separately.
+#
+# Test with a void pointer, because MSVC doesn't handle that, and we
+# need that for copyObject().
+foreach kw : ['__typeof_unqual__', 'typeof_unqual']
+  if cc.compiles('''
+int main(void)
+{
+    int x = 0;
+    @0@(x) y;
+    const void *a;
+    void *b;
+    y = x;
+    b = (@0@(*a) *) a;
+    return y;
+}
+'''.format(kw),
+    name: kw,
+    args: test_c_args, include_directories: postgres_inc)
+
+    cdata.set('HAVE_TYPEOF_UNQUAL', 1)
+    if kw != 'typeof_unqual'
+      cdata.set('typeof_unqual', kw)
+    endif
+
+    break
+  endif
+endforeach
+
+# Check if the C++ compiler understands typeof_unqual or a variant.
+if have_cxx
+  foreach kw : ['__typeof_unqual__', 'typeof_unqual']
+    if cxx.compiles('''
+int main(void)
+{
+    int x = 0;
+    @0@(x) y;
+    const void *a;
+    void *b;
+    y = x;
+    b = (@0@(*a) *) a;
+    return y;
+}
+'''.format(kw),
+      name: 'C++ ' + kw,
+      args: test_c_args, include_directories: postgres_inc)
+
+      cdata.set('HAVE_CXX_TYPEOF_UNQUAL', 1)
+      if kw != 'typeof_unqual'
+        cdata.set('pg_cxx_typeof_unqual', kw)
+      endif
+
+      break
+    endif
+  endforeach
+endif
+
 
 # MSVC doesn't cope well with defining restrict to __restrict, the
 # spelling it understands, because it conflicts with
diff --git a/src/include/c.h b/src/include/c.h
index 5b678283469..2aab74d8b0e 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -452,7 +452,19 @@ extern "C++"
 #ifndef HAVE_TYPEOF
 #define HAVE_TYPEOF 1
 #endif
+/*
+ * and analogously for typeof_unqual
+ */
+#undef typeof_unqual
+#ifdef pg_cxx_typeof_unqual
+#define typeof_unqual(x) pg_cxx_typeof_unqual(x)
+#elif !defined(HAVE_CXX_TYPEOF_UNQUAL)
+#define typeof_unqual(x) std::remove_cv_t<std::remove_reference_t<decltype(x)>>
+#endif
+#ifndef HAVE_TYPEOF_UNQUAL
+#define HAVE_TYPEOF_UNQUAL 1
 #endif
+#endif							/* __cplusplus */
 
 /*
  * CppAsString
index 59a7df31aba..a2925ae4946 100644
 
index cb0f53fade4..79379a4d125 100644
@@ -73,6 +73,10 @@
    */
 #undef HAVE_CXX_TYPEOF
 
+/* Define to 1 if your C++ compiler understands `typeof_unqual' or something
+   similar. */
+#undef HAVE_CXX_TYPEOF_UNQUAL
+
 /* Define to 1 if you have the declaration of `fdatasync', and to 0 if you
    don't. */
 #undef HAVE_DECL_FDATASYNC
@@ -458,6 +462,10 @@
 /* Define to 1 if your compiler understands `typeof' or something similar. */
 #undef HAVE_TYPEOF

@@ -784,6 +792,9 @@
 /* Define to how the C++ compiler spells `typeof'. */
 #undef pg_cxx_typeof
 
+/* Define to how the C++ compiler spells `typeof_unqual'. */
+#undef pg_cxx_typeof_unqual
+
 /* Define to keyword to use for C99 restrict support, or to nothing if not
    supported */
 #undef pg_restrict
@@ -804,3 +815,6 @@
 
 /* Define to how the compiler spells `typeof'. */
 #undef typeof
+
+/* Define to how the compiler spells `typeof_unqual'. */
+#undef typeof_unqual
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
index ea04a761184..93cd7dd07f7 100644
--- a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -37,7 +37,8 @@ test_cplusplus_add(PG_FUNCTION_ARGS)
 	int32		a = PG_GETARG_INT32(0);
 	int32		b = PG_GETARG_INT32(1);
 	RangeTblRef *node = makeNode(RangeTblRef);
-	RangeTblRef *copy = copyObject(node);
+	const RangeTblRef *nodec = node;
+	RangeTblRef *copy = copyObject(nodec);
 	List	   *list = list_make1(node);
 
 	foreach_ptr(RangeTblRef, rtr, list)
-- 
2.53.0

From fe2a62a7b8bad4ce62393ac08af67d81aee962ac Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Wed, 4 Feb 2026 08:58:02 +0100
Subject: [PATCH v2 3/3] Add some const qualifiers enabled by typeof_unqual
 change on copyObject

The recent commit to change copyObject() to use typeof_unqual allows
cleaning up some APIs to take advantage of this improved qualifier
handling.  EventTriggerCollectSimpleCommand() is a good example: It
takes a node tree and makes a copy that it keeps around for its
internal purposes, but it can't communicate via its function signature
that it promises not scribble on the passed node tree.  That is now
fixed.

Reviewed-by: David Geier <[email-masked]>
Discussion: https://www.postgresql.org/message-id/flat/92f9750f-c7f6-42d8-9a4a-85a3cbe808f3%40eisentraut.org
---

 	/*
index 2898967fa67..f9a5aba4360 100644
@@ -2024,7 +2024,7 @@ EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
 
index 98d402c0a3b..373a08340fa 100644
index c90f4b32733..ab621e281e9 100644
@@ -150,7 +150,7 @@ static void replace_vars_in_jointree(Node *jtnode,
@@ -2698,7 +2698,7 @@ pullup_replace_vars(Node *expr, pullup_replace_vars_context *context)
 }
 
 static Node *
index 6d979a08fd3..db1dd153ddb 100644
 
 	*outconst = NULL;
 	*notclause = false;
 
 static Node *
 
 Node *

index 812e2265734..d67a914d56d 100644
@@ -182,7 +182,7 @@ InitPlanCache(void)

 
 
 #endif							/* EVENT_TRIGGER_H */
diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h
index 556c86bf5e1..27af5284406 100644
--- a/src/include/commands/trigger.h
+++ b/src/include/commands/trigger.h
@@ -153,11 +153,11 @@ extern PGDLLIMPORT int SessionReplicationRole;
 #define TRIGGER_FIRES_ON_REPLICA			'R'
 #define TRIGGER_DISABLED					'D'

 

 
 
2.53.0

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 7 Mar 2026 01:17:42 +0100

> This revealed an insufficiency in that commit, which I fix in the first

Thanks!

> (putting the underscore variant first), as discussed.

Annoying that this is needed, but I agree that it's the least bad
option in this case.

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 13 Mar 2026 11:43:15 +0100

>> This revealed an insufficiency in that commit, which I fix in the first

There is a failure related to this on buildfarm member sevengill:

../pgsql/src/test/modules/test_cplusplusext/test_cplusplusext.cpp:41:22: 
error: no template named 'remove_reference_t' in namespace 'std'; did 
you mean 'remove_reference'?

I don't know how that makes sense.  Maybe this is a problem in the local 
installation of the compiler or standard library.

(This is also a bit suspicious because AFAICT, clang 15 defaults to 
C++14, but on that animal it thinks it needs to add -std=gnu++11.)

> Annoying that this is needed, but I agree that it's the least bad

I committed this and it still fails, but the failure is now narrower. 
There is a failure on buildfarm member taipan because it uses an unusual 
combination of gcc and clang (the gcc is much newer than clang).  The 
only sensible workaround I could think of is a hardcoded override based 
on the clang version, as in the attached patch.  And alternative is that 
we decide that we don't want to support this combination, meaning that 
we would effectively require that clang is approximately as-old-or-newer 
than gcc.
From 5295cbb0a4573ea886f21d4f5e89ce7beaf60f58 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Fri, 13 Mar 2026 11:20:31 +0100
Subject: [PATCH] Hardcode override of typeof_unqual for clang-for-bitcode

The fundamental problem is that when we call clang to generate
bitcode, we might be using configure results from a different
compiler, which might not be fully compatible with the clang we are
using.  In practice, clang supports most things other compilers
support, so this has apparently not been a problem in practice.

But commits 4cfce4e62c8, 0af05b5dbb4, and 59292f7aac7 have been
struggling to make typeof_unqual work in this situation.  Clang added
support in version 19, GCC in version 14, so if you are using, say,
GCC 14 and Clang 16, the compilation with the latter will fail.
Again, in practice, this might be unlikely, because GCC 14 and Clang
19 were released within a few months of each other, and so Linux
distributions are likely to have suitable combinations.  But buildfarm
member "taipan" does not (gcc 15, clang 16), so I'll try to fix it.

The fully correct solution would be to run a separate set of configure
tests for that clang-for-bitcode, but that would be very difficult to
implement, and probably of limited use in practice.  So the workaround
here is that we hardcodedly override the configure result under clang
based on the version number.  As long as we only have a few of these
cases, this should be manageable.

Discussion: https://www.postgresql.org/message-id/flat/92f9750f-c7f6-42d8-9a4a-85a3cbe808f3%40eisentraut.org
---
 src/include/c.h | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/src/include/c.h b/src/include/c.h
index 2aab74d8b0e..2e789b00594 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -432,6 +432,21 @@ extern "C++"
 #define unlikely(x) ((x) != 0)
 #endif
 
+/*
+ * When we call clang to generate bitcode, we might be using configure results
+ * from a different compiler, which might not be fully compatible with the
+ * clang we are using.  The fully correct solution would be to run a separate
+ * set of configure tests for that clang-for-bitcode, but that could be very
+ * difficult to implement, and in practice clang supports most things other
+ * compilers support.  So this section just contains some hardcoded ugliness
+ * to override some configure results where it is necessary.
+ */
+#if defined(__clang__)
+#if __clang_major__ < 19
+#undef HAVE_TYPEOF_UNQUAL
+#endif
+#endif							/* __clang__ */
+
 /*
  * Provide typeof in C++ for C++ compilers that don't support typeof natively.
  * It might be spelled __typeof__ instead of typeof, in which case
-- 
2.53.0

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 13 Mar 2026 17:00:15 +0100

On 13.03.26 14:03, Daniel Gustafsson wrote:
>> On 13 Mar 2026, at 11:43, Peter Eisentraut <[email-masked]> wrote:
> 
>> I committed this and it still fails, but the failure is now narrower. There is a failure on buildfarm member taipan because it uses an unusual combination of gcc and clang (the gcc is much newer than clang).  The only sensible workaround I could think of is a hardcoded override based on the clang version, as in the attached patch.  And alternative is that we decide that we don't want to support this combination, meaning that we would effectively require that clang is approximately as-old-or-newer than gcc.
> 
> I ran into this as well on clang 15 via XCode with no gcc involved:
> 
> ../src/test/modules/test_cplusplusext/test_cplusplusext.cpp:41:22: error: no template named 'remove_reference_t' in namespace 'std'; did you mean 'remove_reference'?
>          RangeTblRef *copy = copyObject(nodec);
>                              ^~~~~~~~~~~~~~~~~

Jelte,

I read here

https://en.cppreference.com/w/cpp/types/remove_reference.html

that remove_reference_t is actually in C++14, which might explain this 
failure, if the compiler is in C++11 mode.

I don't understand the difference between remove_reference and 
remove_reference_t.

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 13 Mar 2026 17:15:12 +0100

> failure, if the compiler is in C++11 mode.

Yeah that's almost certainly it... Sorry about that.

> I don't understand the difference between remove_reference and

They are equivalent only the _t version as a bit less verbose.
Attached should fix it.
diff --git a/src/include/c.h b/src/include/c.h
index 2aab74d8b0e..b4a37661795 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -447,7 +447,7 @@ extern "C++"
 #ifdef pg_cxx_typeof
 #define typeof(x) pg_cxx_typeof(x)
 #elif !defined(HAVE_CXX_TYPEOF)
-#define typeof(x) std::remove_reference_t<decltype(x)>
+#define typeof(x) std::remove_reference<decltype(x)>::value
 #endif
 #ifndef HAVE_TYPEOF
 #define HAVE_TYPEOF 1

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Fri, 13 Mar 2026 17:18:18 +0100

> Attached should fix it.

Okay corrected in this v2, which fixes all of the places I could find.
diff --git a/src/include/c.h b/src/include/c.h
index 2aab74d8b0e..323dd7df329 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -447,7 +447,7 @@ extern "C++"
 #ifdef pg_cxx_typeof
 #define typeof(x) pg_cxx_typeof(x)
 #elif !defined(HAVE_CXX_TYPEOF)
-#define typeof(x) std::remove_reference_t<decltype(x)>
+#define typeof(x) std::remove_reference<decltype(x)>::value
 #endif
 #ifndef HAVE_TYPEOF
 #define HAVE_TYPEOF 1
@@ -459,7 +459,7 @@ extern "C++"
 #ifdef pg_cxx_typeof_unqual
 #define typeof_unqual(x) pg_cxx_typeof_unqual(x)
 #elif !defined(HAVE_CXX_TYPEOF_UNQUAL)
-#define typeof_unqual(x) std::remove_cv_t<std::remove_reference_t<decltype(x)>>
+#define typeof_unqual(x) std::remove_cv<std::remove_reference<decltype(x)>::value>::value
 #endif
 #ifndef HAVE_TYPEOF_UNQUAL
 #define HAVE_TYPEOF_UNQUAL 1

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sat, 14 Mar 2026 14:41:08 +0100

On Sat, 14 Mar 2026 at 14:03, Peter Eisentraut <[email-masked]> wrote:
> This doesn't appear to work in this example program:

Ugh, I should not send emails end of day on a friday in a rush.

Attached is fixed v3 which uses ::type instead.

I was able to reproduce the compilation errors on my machine by using
CXXFLAGS='-std=c++11' when configuring meson, and this patch fixes them.
I think it would be good if we would run one of our CI jobs in c11 and
c++11 (non-gnu) mode so we catch these kind of issues before hitting the
build farm.
From d7acf4680fcfa81d0b046241ee5e36fd47f46b06 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sat, 14 Mar 2026 14:33:07 +0100
Subject: [PATCH v3] Make typeof and typeof_unqual fallback definitions work on
 C++11

These macros were unintentionally using C++14 features. This replaces
them with valid C++11 code.

Tested locally by compiling with -std=c++11 (which reproduced the
original issue).
---
 src/include/c.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

index 2aab74d8b0e..29fef2f54e1 100644
+#define typeof(x) std::remove_reference<decltype(x)>::type
+#define typeof_unqual(x) std::remove_cv<std::remove_reference<decltype(x)>::type>::type

base-commit: ae58189a4d523f0156ebe30f4534180555669e88
-- 
2.53.0

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Sun, 15 Mar 2026 22:54:04 +0100

On Fri, 13 Mar 2026 at 11:43, Peter Eisentraut <[email-masked]> wrote:
> on the clang version, as in the attached patch.  

If we can then start prefering typeof_unqual over __typeof_unqual__ in
the configure check for CC, then I think I like this as a workaround.

If not, then I'm starting to think that actually checking support for
this feature for CLANG is probably easier to understand (and less
fragile) than combining all of these tricks together. Attached is a
patch to do so. 

This only does it for CLANG, not for CLANG compiling C++ files.
Theoretically that could be necessary, but I couldn't find a C++
compiler that supports any spelling of typeof_unqual. So I'm not even
sure we need the current CXX check for typeof_unqual, I think we could
remove that too and always use the fallback.

> And alternative is that

Given that Tom seems to have reproduced this on Fedora 40, it sounds
like we should fix it.
From 377b3b68c073c1b62bc0e5c357eed50624022cb5 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sun, 15 Mar 2026 22:39:45 +0100
Subject: [PATCH v1] Check CLANG for typeof_unqual

Turns out that CLANG on the buildfarm is sometimes significantly older
than the CC compiler, and thus supports different features. This
explicitly checks typeof_unqual support for CLANG, because that's the
feature that sometimes seems to be lacking in practice.
---
 config/c-compiler.m4       | 45 +++++++++++++++++++++-----
 configure                  | 65 +++++++++++++++++++++++++++++++++-----
 configure.ac               |  6 ++++
 src/include/c.h            | 16 ++++++++++
 src/include/pg_config.h.in |  7 ++++
 5 files changed, 123 insertions(+), 16 deletions(-)

diff --git a/config/c-compiler.m4 b/config/c-compiler.m4
index 88333ef301d..6ae57199b1c 100644
--- a/config/c-compiler.m4
+++ b/config/c-compiler.m4
@@ -181,16 +181,12 @@ fi])# PGAC_C_TYPEOF
 # Check if the C compiler understands typeof_unqual or a variant.  Define
 # HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
 #
+# Test with a void pointer, because MSVC doesn't handle that, and we
+# need that for copyObject().
 AC_DEFUN([PGAC_C_TYPEOF_UNQUAL],
 [AC_CACHE_CHECK(for typeof_unqual, pgac_cv_c_typeof_unqual,
 [pgac_cv_c_typeof_unqual=no
-# Test the underscore variant first so that there is a higher chance
-# that clang used for bitcode also supports it, since we don't test
-# that separately.
-#
-# Test with a void pointer, because MSVC doesn't handle that, and we
-# need that for copyObject().
-for pgac_kw in __typeof_unqual__ typeof_unqual; do
+for pgac_kw in typeof_unqual __typeof_unqual__; do
   AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
 [int x = 0;
 $pgac_kw(x) y;
@@ -248,7 +244,7 @@ AC_DEFUN([PGAC_CXX_TYPEOF_UNQUAL],
 [AC_CACHE_CHECK(for C++ typeof_unqual, pgac_cv_cxx_typeof_unqual,
 [pgac_cv_cxx_typeof_unqual=no
 AC_LANG_PUSH(C++)
-for pgac_kw in __typeof_unqual__ typeof_unqual; do
+for pgac_kw in typeof_unqual __typeof_unqual__; do
   AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
 [int x = 0;
 $pgac_kw(x) y;
@@ -270,6 +266,39 @@ if test "$pgac_cv_cxx_typeof_unqual" != no; then
 fi])# PGAC_CXX_TYPEOF_UNQUAL

+# PGAC_CLANG_TYPEOF_UNQUAL
+# ------------------------
+# Check if CLANG (used for LLVM bitcode compilation) understands
+# typeof_unqual or a variant.  Define HAVE_CLANG_TYPEOF_UNQUAL if so, and
+# define 'pg_clang_typeof_unqual' to the actual key word.
+#
+AC_DEFUN([PGAC_CLANG_TYPEOF_UNQUAL],
+[AC_CACHE_CHECK(for CLANG typeof_unqual, pgac_cv_clang_typeof_unqual,
+[pgac_cv_clang_typeof_unqual=no
+pgac_save_CC=$CC
+CC="$CLANG"
+for pgac_kw in typeof_unqual __typeof_unqual__; do
+  _AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],
+[int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;])],
+[pgac_cv_clang_typeof_unqual=$pgac_kw])
+  test "$pgac_cv_clang_typeof_unqual" != no && break
+done
+CC="$pgac_save_CC"])
+if test "$pgac_cv_clang_typeof_unqual" != no; then
+  AC_DEFINE(HAVE_CLANG_TYPEOF_UNQUAL, 1,
+            [Define to 1 if CLANG for bitcode understands `typeof_unqual' or something similar.])
+  if test "$pgac_cv_clang_typeof_unqual" != typeof_unqual; then
+    AC_DEFINE_UNQUOTED(pg_clang_typeof_unqual, $pgac_cv_clang_typeof_unqual, [Define to how CLANG for bitcode spells `typeof_unqual'.])
+  fi
+fi])# PGAC_CLANG_TYPEOF_UNQUAL
+
+
 
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
diff --git a/configure b/configure
index 4c789bd9289..80e29ff3ead 100755
--- a/configure
+++ b/configure
@@ -7077,6 +7077,9 @@ fi
 if test "$with_llvm" = yes ; then
   CLANGXX="$CLANG -xc++"
 
+  BITCODE_CFLAGS="$BITCODE_CFLAGS -DPG_COMPILING_BITCODE"
+  BITCODE_CXXFLAGS="$BITCODE_CXXFLAGS -DPG_COMPILING_BITCODE"
+
   { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${CLANG} supports -fno-strict-aliasing, for BITCODE_CFLAGS" >&5
 $as_echo_n "checking whether ${CLANG} supports -fno-strict-aliasing, for BITCODE_CFLAGS... " >&6; }
 if ${pgac_cv_prog_CLANG_cflags__fno_strict_aliasing+:} false; then :
@@ -15107,13 +15110,7 @@ if ${pgac_cv_c_typeof_unqual+:} false; then :
   $as_echo_n "(cached) " >&6
 else
   pgac_cv_c_typeof_unqual=no
-# Test the underscore variant first so that there is a higher chance
-# that clang used for bitcode also supports it, since we don't test
-# that separately.
-#
-# Test with a void pointer, because MSVC doesn't handle that, and we
-# need that for copyObject().
-for pgac_kw in __typeof_unqual__ typeof_unqual; do
+for pgac_kw in typeof_unqual __typeof_unqual__; do
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -15164,7 +15161,7 @@ ac_compile='$CXX -c $CXXFLAGS $CPPFLAGS conftest.$ac_ext >&5'
 ac_link='$CXX -o conftest$ac_exeext $CXXFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
 ac_compiler_gnu=$ac_cv_cxx_compiler_gnu
 
-for pgac_kw in __typeof_unqual__ typeof_unqual; do
+for pgac_kw in typeof_unqual __typeof_unqual__; do
   cat confdefs.h - <<_ACEOF >conftest.$ac_ext
 /* end confdefs.h.  */
 
@@ -15208,6 +15205,58 @@ cat >>confdefs.h <<_ACEOF
 _ACEOF
 
   fi
+fi
+if test "$with_llvm" = yes; then :
+
+  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for CLANG typeof_unqual" >&5
+$as_echo_n "checking for CLANG typeof_unqual... " >&6; }
+if ${pgac_cv_clang_typeof_unqual+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  pgac_cv_clang_typeof_unqual=no
+pgac_save_CC=$CC
+CC="$CLANG"
+for pgac_kw in typeof_unqual __typeof_unqual__; do
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main ()
+{
+int x = 0;
+$pgac_kw(x) y;
+const void *a;
+void *b;
+y = x;
+b = ($pgac_kw(*a) *) a;
+return y;
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"; then :
+  pgac_cv_clang_typeof_unqual=$pgac_kw
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
+  test "$pgac_cv_clang_typeof_unqual" != no && break
+done
+CC="$pgac_save_CC"
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $pgac_cv_clang_typeof_unqual" >&5
+$as_echo "$pgac_cv_clang_typeof_unqual" >&6; }
+if test "$pgac_cv_clang_typeof_unqual" != no; then
+
+$as_echo "#define HAVE_CLANG_TYPEOF_UNQUAL 1" >>confdefs.h
+
+  if test "$pgac_cv_clang_typeof_unqual" != typeof_unqual; then
+
+cat >>confdefs.h <<_ACEOF
+#define pg_clang_typeof_unqual $pgac_cv_clang_typeof_unqual
+_ACEOF
+
+  fi
+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; }
diff --git a/configure.ac b/configure.ac
index 9edffe481a6..69ad014238f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -686,6 +686,9 @@ AC_SUBST(CXXFLAGS_SL_MODULE)
 if test "$with_llvm" = yes ; then
   CLANGXX="$CLANG -xc++"
 
+  BITCODE_CFLAGS="$BITCODE_CFLAGS -DPG_COMPILING_BITCODE"
+  BITCODE_CXXFLAGS="$BITCODE_CXXFLAGS -DPG_COMPILING_BITCODE"
+
   PGAC_PROG_VARCC_VARFLAGS_OPT(CLANG, BITCODE_CFLAGS, [-fno-strict-aliasing])
   PGAC_PROG_VARCXX_VARFLAGS_OPT(CLANGXX, BITCODE_CXXFLAGS, [-fno-strict-aliasing])
   PGAC_PROG_VARCC_VARFLAGS_OPT(CLANG, BITCODE_CFLAGS, [-fwrapv])
@@ -1734,6 +1737,9 @@ PGAC_C_TYPEOF
 PGAC_CXX_TYPEOF
 PGAC_C_TYPEOF_UNQUAL
 PGAC_CXX_TYPEOF_UNQUAL
+AS_IF([test "$with_llvm" = yes], [
+  PGAC_CLANG_TYPEOF_UNQUAL
+])
 PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
diff --git a/src/include/c.h b/src/include/c.h
index 29fef2f54e1..e0dfb897529 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -466,6 +466,22 @@ extern "C++"
 #endif
 #endif							/* __cplusplus */
 
+/*
+ * Override typeof_unqual for LLVM bitcode compilation when CLANG doesn't
+ * support the same keyword as CC.  Analogous to the C++ override above.
+ * PG_COMPILING_BITCODE is defined in BITCODE_CFLAGS.
+ */
+#if defined(PG_COMPILING_BITCODE) && !defined(__cplusplus)
+#undef HAVE_TYPEOF_UNQUAL
+#undef typeof_unqual
+#ifdef pg_clang_typeof_unqual
+#define typeof_unqual(x) pg_clang_typeof_unqual(x)
+#define HAVE_TYPEOF_UNQUAL 1
+#elif defined(HAVE_CLANG_TYPEOF_UNQUAL)
+#define HAVE_TYPEOF_UNQUAL 1
+#endif
+#endif							/* PG_COMPILING_BITCODE && !__cplusplus */
+
 /*
  * CppAsString
  *		Convert the argument to a string, using the C preprocessor.
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index 79379a4d125..b1c20ce754a 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -54,6 +54,10 @@
 /* Define to 1 if you have the `backtrace_symbols' function. */
 #undef HAVE_BACKTRACE_SYMBOLS
 
+/* Define to 1 if CLANG for bitcode understands `typeof_unqual' or something
+   similar. */
+#undef HAVE_CLANG_TYPEOF_UNQUAL
+
 /* Define to 1 if your compiler handles computed gotos. */
 #undef HAVE_COMPUTED_GOTO
 
@@ -789,6 +793,9 @@
 /* Define for large files, on AIX-style hosts. */
 #undef _LARGE_FILES
 
+/* Define to how CLANG for bitcode spells `typeof_unqual'. */
+#undef pg_clang_typeof_unqual
+
 /* Define to how the C++ compiler spells `typeof'. */
 #undef pg_cxx_typeof

base-commit: a793677e57bc27c674cb94b230164b2c28f4cbae
-- 
2.53.0

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Mon, 16 Mar 2026 00:57:17 +0100

On Sun, 15 Mar 2026 at 23:13, Tom Lane <[email-masked]> wrote:
>
> "Jelte Fennema-Nio" <[email-masked]> writes:
> +1 for concept, but don't you need to fix meson.build too?

I believe that we don't have fully working bc compilation for meson[1],
so I'm not entirely sure how to test out if it actually works. But the
attached now does *something* for meson too at least.

[1]: https://www.postgresql.org/message-id/flat/206b001d-1884-4081-bd02-bed5c92f02ba%40eisentraut.org
From 2058a59b52b17ce15752b7f488082929967429c3 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Sun, 15 Mar 2026 22:39:45 +0100
Subject: [PATCH v2] Check CLANG for typeof_unqual

Turns out that CLANG on the buildfarm is sometimes significantly older
than the CC compiler, and thus supports different features. This
explicitly checks typeof_unqual support for CLANG, because that's the
feature that sometimes seems to be lacking in practice.
---
 config/c-compiler.m4             | 45 ++++++++++++++++++----
 config/test_typeof_unqual.c      | 20 ++++++++++
 configure                        | 65 ++++++++++++++++++++++++++++----
 configure.ac                     |  6 +++
 meson.build                      | 32 +++++++++++++---
 src/backend/jit/llvm/meson.build |  2 +-
 src/include/c.h                  | 16 ++++++++
 src/include/pg_config.h.in       |  7 ++++
 8 files changed, 170 insertions(+), 23 deletions(-)
 create mode 100644 config/test_typeof_unqual.c

 
 # PGAC_C_TYPES_COMPATIBLE
 # -----------------------
diff --git a/config/test_typeof_unqual.c b/config/test_typeof_unqual.c
new file mode 100644
index 00000000000..108c8f5468b
--- /dev/null
+++ b/config/test_typeof_unqual.c
@@ -0,0 +1,20 @@
+/*
+ * Test program for typeof_unqual detection.
+ *
+ * Used by meson.build to check whether clang used for LLVM bitcode
+ * compilation supports typeof_unqual or a variant.  Pass -DKW=<keyword>
+ * to test a specific spelling.
+ */
+int
+main(void)
+{
+	int			x = 0;
+
+	KW(x) y;
+	const void *a;
+	void	   *b;
+
+	y = x;
+	b = (KW(*a) *) a;
+	return y;
+}
diff --git a/configure b/configure
index 4c789bd9289..80e29ff3ead 100755
--- a/configure
+++ b/configure
@@ -7077,6 +7077,9 @@ fi
 if test "$with_llvm" = yes ; then
   CLANGXX="$CLANG -xc++"

 

   fi
+fi
 
+  BITCODE_CFLAGS="$BITCODE_CFLAGS -DPG_COMPILING_BITCODE"
+  BITCODE_CXXFLAGS="$BITCODE_CXXFLAGS -DPG_COMPILING_BITCODE"
+
   PGAC_PROG_VARCC_VARFLAGS_OPT(CLANG, BITCODE_CFLAGS, [-fno-strict-aliasing])
   PGAC_PROG_VARCXX_VARFLAGS_OPT(CLANGXX, BITCODE_CXXFLAGS, [-fno-strict-aliasing])
   PGAC_PROG_VARCC_VARFLAGS_OPT(CLANG, BITCODE_CFLAGS, [-fwrapv])
@@ -1734,6 +1737,9 @@ PGAC_C_TYPEOF
 PGAC_CXX_TYPEOF
 PGAC_C_TYPEOF_UNQUAL
 PGAC_CXX_TYPEOF_UNQUAL
+AS_IF([test "$with_llvm" = yes], [
+  PGAC_CLANG_TYPEOF_UNQUAL
+])
 PGAC_C_TYPES_COMPATIBLE
 PGAC_C_BUILTIN_CONSTANT_P
 PGAC_C_BUILTIN_OP_OVERFLOW
diff --git a/meson.build b/meson.build
index f7a87edcc94..f17970f1ae2 100644
--- a/meson.build
+++ b/meson.build
@@ -2969,13 +2969,9 @@ endif
 # Check if the C compiler understands typeof_unqual or a variant.  Define
 # HAVE_TYPEOF_UNQUAL if so, and define 'typeof_unqual' to the actual key word.
 #
-# Test the underscore variant first so that there is a higher chance
-# that clang used for bitcode also supports it, since we don't test
-# that separately.
-#
 # Test with a void pointer, because MSVC doesn't handle that, and we
 # need that for copyObject().
-foreach kw : ['__typeof_unqual__', 'typeof_unqual']
+foreach kw : ['typeof_unqual', '__typeof_unqual__']
   if cc.compiles('''
 int main(void)
 {
@@ -3002,7 +2998,7 @@ endforeach
 
 # Check if the C++ compiler understands typeof_unqual or a variant.
 if have_cxx
-  foreach kw : ['__typeof_unqual__', 'typeof_unqual']
+  foreach kw : ['typeof_unqual', '__typeof_unqual__']
     if cxx.compiles('''
 int main(void)
 {
@@ -3028,6 +3024,30 @@ int main(void)
   endforeach
 endif
 
+# Check if CLANG (used for LLVM bitcode compilation) understands
+# typeof_unqual or a variant.  We use run_command with a test file
+# because clang is a program, not a meson compiler object, so
+# cc.compiles() cannot be used.
+if llvm.found()
+  clang_typeof_unqual_found = false
+  foreach kw : ['typeof_unqual', '__typeof_unqual__']
+    if not clang_typeof_unqual_found
+      r = run_command(clang, '-fsyntax-only', '-DKW=' + kw,
+        files('config/test_typeof_unqual.c'), check: false)
+      if r.returncode() == 0
+        message('CLANG supports ' + kw)
+        clang_typeof_unqual_found = true
+        cdata.set('HAVE_CLANG_TYPEOF_UNQUAL', 1)
+        if kw != 'typeof_unqual'
+          cdata.set('pg_clang_typeof_unqual', kw)
+        endif
+      else
+        message('CLANG does not support ' + kw)
+      endif
+    endif
+  endforeach
+endif
+
 
 # MSVC doesn't cope well with defining restrict to __restrict, the
 # spelling it understands, because it conflicts with
diff --git a/src/backend/jit/llvm/meson.build b/src/backend/jit/llvm/meson.build
index 7df8453ad6f..7270143771e 100644
--- a/src/backend/jit/llvm/meson.build
+++ b/src/backend/jit/llvm/meson.build
@@ -60,7 +60,7 @@ endif

 # XXX: Need to determine proper version of the function cflags for clang
-bitcode_cflags = ['-fno-strict-aliasing', '-fwrapv']
+bitcode_cflags = ['-DPG_COMPILING_BITCODE', '-fno-strict-aliasing', '-fwrapv']
 bitcode_cflags += get_option('c_args')
 bitcode_cflags += cppflags

+/*

@@ -789,6 +793,9 @@

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Mon, 16 Mar 2026 14:40:52 +0100

On Mon, 16 Mar 2026 at 13:47, Peter Eisentraut <[email-masked]> wrote:
> I'm tempted to go with my proposed patch of a version-based override for
> the time being.

Sounds good to me. But let's not forget to swap back the order of
detection for typeof_unqual vs __typeof_unqual__. Afaict that's not
needed anymore and the comment there only becomes confusing with this
new fix.

Also, it might be nice to only do your version based override, if
we're actually compiling bitcode. In my patch I used
-DPG_COMPILING_BITCODE for that. Otherwise this override can also
happen for regular compiles using clang, which I think would be a bit
confusing.

---

From: Masahiko Sawada <[email-masked]>
Date: Mon, 16 Mar 2026 15:28:07 -0700

Hi,

On Mon, Mar 16, 2026 at 8:32 AM Tom Lane <[email-masked]> wrote:
>
> Jelte Fennema-Nio <[email-masked]> writes:
> > Sounds good to me.
> I confirmed that Peter's
> 0001-Hardcode-override-of-typeof_unqual-for-clang-for-bit.patch
> fixes the problem on my Fedora 40 system.

I'm still encountering the following error while building from source
at commit f4af7849b3d when using autoconf:

execParallel.c:154:9: error: call to undeclared function
'typeof_unqual'; ISO C99 and later do not support implicit function
declarations [-Wimplicit-function-declaration]
  154 |         plan = copyObject(plan);
      |                ^
../../../src/include/nodes/nodes.h:230:27: note: expanded from macro
'copyObject'
  230 | #define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
      |                           ^
execParallel.c:154:9: error: expected expression
../../../src/include/nodes/nodes.h:230:50: note: expanded from macro
'copyObject'
  230 | #define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
      |                                                  ^
analyze.c:3213:27: error: call to undeclared function 'typeof_unqual';
ISO C99 and later do not support implicit function declarations
[-Wimplicit-function-declaration]
 3213 |                 stmt->into->viewQuery = copyObject(query);
      |                                         ^
../../../src/include/nodes/nodes.h:230:27: note: expanded from macro
'copyObject'
  230 | #define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
      |                           ^
analyze.c:3213:27: error: expected expression
../../../src/include/nodes/nodes.h:230:50: note: expanded from macro
'copyObject'
  230 | #define copyObject(obj) ((typeof_unqual(*(obj)) *) copyObjectImpl(obj))
      |                                                  ^
2 errors generated.
:
(many similar errors)

I'm using Fedora 43 and gcc (GCC) 15.2.1 20260123 (Red Hat 15.2.1-7).
The issue doesn't happen when using meson+ninja.

Regards,

-- 
Masahiko Sawada
Amazon Web Services: https://aws.amazon.com

---

From: "Jelte Fennema-Nio" <[email-masked]>
Date: Tue, 17 Mar 2026 11:56:24 +0100

On Tue Mar 17, 2026 at 3:39 AM CET, Tom Lane wrote:
> While this version of clang doesn't like typeof_unqual, it does take
> __typeof_unqual__.  So maybe we were premature to decide that we
> could prefer the typeof_unqual spelling.

Hmmm, that makes sense. How about this patch to at least keep the
all the logic related to this in one place? I was able to reproduce this
error using the following flags, and this fixes the issue for me.

CC=clang-21 CXX=clang++-21 CFLAGS=-std=c23 BITCODE_CFLAGS=-std=gnu17 CLANG=clang-19 LLVM_CONFIG=llvm-config-19

From b0261abce45ff144bc6f8c9d797984f8377fcfe0 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <[email-masked]>
Date: Tue, 17 Mar 2026 11:34:16 +0100
Subject: [PATCH v1] Hardcode typeof_unqual to __typeof_unqual__ for clang

A new attempt was made in 63275ce84d2 to make typeof_unqual work on all
configurations of CC and CLANG. This re-introduced an old problem
though, where CLANG would only support __typeof_unqual__ but the
configure check for CC detected support for typeof_unqual.

This fixes that by always defining typeof_unqual as __typeof_unqual__
under clang.
---
 src/include/c.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/include/c.h b/src/include/c.h
index 8987a121d6a..fd6b093bb3a 100644
--- a/src/include/c.h
+++ b/src/include/c.h
@@ -444,6 +444,9 @@ extern "C++"
 #if defined(__clang__)
 #if __clang_major__ < 19
 #undef HAVE_TYPEOF_UNQUAL
+#else
+#undef typeof_unqual
+#define typeof_unqual __typeof_unqual__
 #endif
 #endif							/* __clang__ */

base-commit: 182cdf5aeaf7b34a288a135d66f2893dc288a24e
-- 
2.53.0

---

From: Álvaro Herrera <[email-masked]>
Date: Sat, 13 Jun 2026 14:14:02 +0200

> other C++ implementations of type tricks, so we'll put it into the

For some reason, a couple of animals running gcc-15 or newer
(leafhopper, massasauga, parula) appear to be failing now because of
this.

ccache gcc -Wall -Wmissing-prototypes -Wpointer-arith -Wdeclaration-after-statement -Werror=vla -Wmissing-format-attribute -Wold-style-declaration -Wimplicit-fallthrough=5 -Wcast-function-type -Wshadow=compatible-local -Wformat-security -Wmissing-variable-declarations -fno-strict-aliasing -fwrapv -fexcess-precision=standard -Wno-format-truncation -Wno-stringop-truncation -g -O2 -Wstrict-prototypes -Wold-style-definition -fPIC -fvisibility=hidden -shared -o test_slru.so  test_slru.o test_multixact.o -L../../../../src/port -L../../../../src/common    -Wl,--as-needed -Wl,-rpath,'/home/bf/proj/bf/build-farm-17/HEAD/inst/lib',--enable-new-dtags -fvisibility=hidden 
In file included from ../../../../src/include/postgres.h:48,
                 from test_cplusplusext.cpp:18:
../../../../src/include/c.h:91:10: fatal error: type_traits: No such file or directory
   91 | #include <type_traits>
      |          ^~~~~~~~~~~~~
compilation terminated.
make[1]: *** [<builtin>: test_cplusplusext.o] Error 1
make[1]: Leaving directory '/home/bf/proj/bf/build-farm-17/HEAD/pgsql.build/src/test/modules/test_cplusplusext'

-- 
Álvaro Herrera        Breisgau, Deutschland  —  https://www.EnterpriseDB.com/

---

From: Tom Lane <[email-masked]>
Date: Sat, 13 Jun 2026 11:56:39 -0400

> (leafhopper, massasauga, parula) appear to be failing now because of

They just started failing a day or two ago, so it's hard to blame
it on any code change we made.  I see that all three of those animals
are using "experimental" nightly builds of gcc, so it's reasonable to
assume that gcc's git tip is broken.  Or maybe something needs updated
in the recipe for replacing their gcc builds.

			regards, tom lane

---

From: Robins Tharakan <[email-masked]>
Date: Wed, 17 Jun 2026 21:15:45 +0930

Hi,

> in the recipe for replacing their gcc builds.

Earlier, since gcc HEAD was too noisy, I moved them all to gcc v15
assuming it'd be better. The gcc build script has been stable and there's
been no change in any of the machines recently, so I also wouldn't be
surprised if it is gcc again.

For now, I've taken them all offline and unless someone has a better
idea, I'll switch them to stay pinned to the recentmost gcc release
version - rationale being that if a tagged gcc release is buggy, it
may be interesting to know about.

-
robins | robins.in

---

From: Jelte Fennema-Nio <[email-masked]>
Date: Wed, 17 Jun 2026 14:11:24 +0200

> may be interesting to know about.

FWIW the error that alvaro mentions suggests that either the C++
stdlib was not installed (i.e. some installation/configuration
problem) or that extern "C++" {...} is broken.