From 6ece40b64ad1dcf44746680b07618ee783dd7e3b Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Fri, 2 Jan 2026 22:31:05 +0100
Subject: [PATCH v6 1/5] tests: Add a test C++ extension module

While we already test that our headers are valid C++ using headerscheck,
it turns out that the macros we define might still expand to invalid
C++ code. This adds a minimal test extension that is compiled using C++
to test that it's actually possible to build and run extensions written
in C++. Future commits will improve C++ compatibility of some of our
macros and add usage of them to this extension make sure that they don't
regress in the future.

To get CI green, this also fixes an issue when compiling C++ extensions
on MSVC: Our use of designated initializers in PG_MODULE_MAGIC_DATA
caused it to only be possible. This reverts to using positional
initializers instead. Sadly that means that using designated
initializers in C++20 is still not allowed in PG_MODULE_MAGIC_EXT
because mixing designated an positional initializers is a C only
feature.
---
 doc/src/sgml/xfunc.sgml                       |  6 +++
 meson.build                                   |  4 ++
 src/include/fmgr.h                            | 12 +++---
 src/test/modules/Makefile                     | 11 ++++++
 src/test/modules/meson.build                  |  1 +
 src/test/modules/test_cplusplusext/.gitignore |  3 ++
 src/test/modules/test_cplusplusext/Makefile   | 26 +++++++++++++
 src/test/modules/test_cplusplusext/README     | 10 +++++
 .../expected/test_cplusplusext.out            | 14 +++++++
 .../modules/test_cplusplusext/meson.build     | 37 +++++++++++++++++++
 .../sql/test_cplusplusext.sql                 |  6 +++
 .../test_cplusplusext--1.0.sql                |  8 ++++
 .../test_cplusplusext.control                 |  4 ++
 .../test_cplusplusext/test_cplusplusext.cpp   | 37 +++++++++++++++++++
 14 files changed, 174 insertions(+), 5 deletions(-)
 create mode 100644 src/test/modules/test_cplusplusext/.gitignore
 create mode 100644 src/test/modules/test_cplusplusext/Makefile
 create mode 100644 src/test/modules/test_cplusplusext/README
 create mode 100644 src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
 create mode 100644 src/test/modules/test_cplusplusext/meson.build
 create mode 100644 src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
 create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
 create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.control
 create mode 100644 src/test/modules/test_cplusplusext/test_cplusplusext.cpp

diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml
index 70e815b8a2c..143f87a253a 100644
--- a/doc/src/sgml/xfunc.sgml
+++ b/doc/src/sgml/xfunc.sgml
@@ -1973,6 +1973,12 @@ PG_MODULE_MAGIC_EXT(
     .name = "my_module_name",
     .version = "1.2.3"
 );
+</programlisting>
+
+    In C++ code, use positional arguments instead of designated initializers:
+
+<programlisting>
+PG_MODULE_MAGIC_EXT("my_module_name", "1.2.3");
 </programlisting>
 
     Subsequently the name and version can be examined via
diff --git a/meson.build b/meson.build
index 6d304f32fb0..aef791da7c3 100644
--- a/meson.build
+++ b/meson.build
@@ -2211,6 +2211,10 @@ if cc.get_id() == 'msvc'
     '/w24777', # 'function' : format string 'string' requires an argument of type 'type1', but variadic argument number has type 'type2' [like -Wformat]
   ]
 
+  cxxflags_warn += [
+    '/wd4200', # nonstandard extension used: zero-sized array in struct/union
+  ]
+
   cppflags += [
     '/DWIN32',
     '/DWINDOWS',
diff --git a/src/include/fmgr.h b/src/include/fmgr.h
index eabbc78b280..1ab8749cee1 100644
--- a/src/include/fmgr.h
+++ b/src/include/fmgr.h
@@ -497,13 +497,15 @@ typedef struct
 }
 
 /*
- * Macro to fill a magic block.  If any arguments are given, they should
- * be field initializers.
+ * Macro to fill a magic block.  If any arguments are given, they should be
+ * field initializers. These can be designated initialzers, or non-designated
+ * initializers. If they're non-designated, they are applied after the ABI
+ * fields.
  */
 #define PG_MODULE_MAGIC_DATA(...) \
 { \
-	.len = sizeof(Pg_magic_struct), \
-	.abi_fields = PG_MODULE_ABI_DATA, \
+	sizeof(Pg_magic_struct), \
+	PG_MODULE_ABI_DATA, \
 	__VA_ARGS__ \
 }
 
@@ -524,7 +526,7 @@ extern PGDLLEXPORT const Pg_magic_struct *PG_MAGIC_FUNCTION_NAME(void); \
 const Pg_magic_struct * \
 PG_MAGIC_FUNCTION_NAME(void) \
 { \
-	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(.name = NULL); \
+	static const Pg_magic_struct Pg_magic_data = PG_MODULE_MAGIC_DATA(); \
 	return &Pg_magic_data; \
 } \
 extern int no_such_variable
diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile
index 4c6d56d97d8..c90b1bd0dc0 100644
--- a/src/test/modules/Makefile
+++ b/src/test/modules/Makefile
@@ -75,5 +75,16 @@ else
 ALWAYS_SUBDIRS += ldap_password_func
 endif
 
+# test_cplusplusext requires a working C++ compiler. If CXX is set to g++, then
+# that could be because g++ is AC_PROG_CXX's fallback when no C++ compiler is
+# found. So in that case we do check to see if it actually exists.
+ifneq ($(CXX),g++)
+SUBDIRS += test_cplusplusext
+else ifneq ($(shell command -v $(CXX)),)
+SUBDIRS += test_cplusplusext
+else
+ALWAYS_SUBDIRS += test_cplusplusext
+endif
+
 $(recurse)
 $(recurse_always)
diff --git a/src/test/modules/meson.build b/src/test/modules/meson.build
index 1b31c5b98d6..0c7e8ad4856 100644
--- a/src/test/modules/meson.build
+++ b/src/test/modules/meson.build
@@ -19,6 +19,7 @@ subdir('test_aio')
 subdir('test_binaryheap')
 subdir('test_bitmapset')
 subdir('test_bloomfilter')
+subdir('test_cplusplusext')
 subdir('test_cloexec')
 subdir('test_copy_callbacks')
 subdir('test_custom_rmgrs')
diff --git a/src/test/modules/test_cplusplusext/.gitignore b/src/test/modules/test_cplusplusext/.gitignore
new file mode 100644
index 00000000000..913175ff6e6
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/.gitignore
@@ -0,0 +1,3 @@
+/log/
+/results/
+/tmp_check/
diff --git a/src/test/modules/test_cplusplusext/Makefile b/src/test/modules/test_cplusplusext/Makefile
new file mode 100644
index 00000000000..88cd4403823
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/Makefile
@@ -0,0 +1,26 @@
+# src/test/modules/test_cplusplusext/Makefile
+
+MODULE_big = test_cplusplusext
+OBJS = \
+	$(WIN32RES) \
+	test_cplusplusext.o
+PGFILEDESC = "test_cplusplusext - test C++ compatibility of PostgreSQL headers"
+
+EXTENSION = test_cplusplusext
+DATA = test_cplusplusext--1.0.sql
+
+REGRESS = test_cplusplusext
+
+# Use C++ compiler for linking because this module includes C++ files
+override COMPILER = $(CXX) $(CXXFLAGS)
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = src/test/modules/test_cplusplusext
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/src/test/modules/test_cplusplusext/README b/src/test/modules/test_cplusplusext/README
new file mode 100644
index 00000000000..c7a54fe8e7f
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/README
@@ -0,0 +1,10 @@
+test_cplusplusext - Test C++ Extension Compatibility
+====================================================
+
+This test module verifies that PostgreSQL headers and macros work correctly
+when compiled with a C++ compiler.
+
+While PostgreSQL already tests that headers are syntactically valid C++ using
+headerscheck, the macros defined in those headers might still expand to invalid
+C++ code.  This module catches such issues by actually compiling and running an
+extension that's written in C++.
diff --git a/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
new file mode 100644
index 00000000000..25600cfd1b4
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/expected/test_cplusplusext.out
@@ -0,0 +1,14 @@
+CREATE EXTENSION test_cplusplusext;
+SELECT test_cplusplus_add(1, 2);
+ test_cplusplus_add 
+--------------------
+                  3
+(1 row)
+
+SELECT module_name, version FROM pg_get_loaded_modules()
+  WHERE module_name = 'test_cplusplusext';
+    module_name    | version 
+-------------------+---------
+ test_cplusplusext | 1.2
+(1 row)
+
diff --git a/src/test/modules/test_cplusplusext/meson.build b/src/test/modules/test_cplusplusext/meson.build
new file mode 100644
index 00000000000..0ddb67978ef
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/meson.build
@@ -0,0 +1,37 @@
+# Copyright (c) 2025-2026, PostgreSQL Global Development Group
+
+if not add_languages('cpp', required: false, native: false)
+  subdir_done()
+endif
+
+test_cplusplusext_sources = files(
+  'test_cplusplusext.cpp',
+)
+
+if host_system == 'windows'
+  test_cplusplusext_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+    '--NAME', 'test_cplusplusext',
+    '--FILEDESC', 'test_cplusplusext - test C++ compatibility of PostgreSQL headers',])
+endif
+
+test_cplusplusext = shared_module('test_cplusplusext',
+  test_cplusplusext_sources,
+  kwargs: pg_test_mod_args,
+)
+test_install_libs += test_cplusplusext
+
+test_install_data += files(
+  'test_cplusplusext.control',
+  'test_cplusplusext--1.0.sql',
+)
+
+tests += {
+  'name': 'test_cplusplusext',
+  'sd': meson.current_source_dir(),
+  'bd': meson.current_build_dir(),
+  'regress': {
+    'sql': [
+      'test_cplusplusext',
+    ],
+  },
+}
diff --git a/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
new file mode 100644
index 00000000000..693910ba637
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/sql/test_cplusplusext.sql
@@ -0,0 +1,6 @@
+CREATE EXTENSION test_cplusplusext;
+
+SELECT test_cplusplus_add(1, 2);
+
+SELECT module_name, version FROM pg_get_loaded_modules()
+  WHERE module_name = 'test_cplusplusext';
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql b/src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
new file mode 100644
index 00000000000..c54acb76823
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql
@@ -0,0 +1,8 @@
+/* src/test/modules/test_cplusplusext/test_cplusplusext--1.0.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION test_cplusplusext" to load this file. \quit
+
+CREATE FUNCTION test_cplusplus_add(int4, int4) RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.control b/src/test/modules/test_cplusplusext/test_cplusplusext.control
new file mode 100644
index 00000000000..640a0a51f35
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.control
@@ -0,0 +1,4 @@
+comment = 'Test module for C++ extension compatibility'
+default_version = '1.0'
+module_pathname = '$libdir/test_cplusplusext'
+relocatable = true
diff --git a/src/test/modules/test_cplusplusext/test_cplusplusext.cpp b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
new file mode 100644
index 00000000000..7108e5b1cc5
--- /dev/null
+++ b/src/test/modules/test_cplusplusext/test_cplusplusext.cpp
@@ -0,0 +1,37 @@
+/*--------------------------------------------------------------------------
+ *
+ * test_cplusplusext.cpp
+ *		Test that PostgreSQL headers compile with a C++ compiler.
+ *
+ * This file is compiled with a C++ compiler to verify that PostgreSQL
+ * headers remain compatible with C++ extensions.
+ *
+ * Copyright (c) 2025-2026, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/test/modules/test_cplusplusext/test_cplusplusext.cpp
+ *
+ * -------------------------------------------------------------------------
+ */
+
+extern "C" {
+#include "postgres.h"
+#include "fmgr.h"
+
+PG_MODULE_MAGIC_EXT("test_cplusplusext", "1.2");
+
+PG_FUNCTION_INFO_V1(test_cplusplus_add);
+}
+
+/*
+ * Simple function that returns the sum of two integers.  This verifies that
+ * C++ extension modules can be loaded and called correctly at runtime.
+ */
+extern "C" Datum
+test_cplusplus_add(PG_FUNCTION_ARGS)
+{
+	int32		a = PG_GETARG_INT32(0);
+	int32		b = PG_GETARG_INT32(1);
+
+	PG_RETURN_INT32(a + b);
+}

base-commit: 6831cd9e3b082d7b830c3196742dd49e3540c49b
-- 
2.52.0

