Hi! The following patch attempts to implement the C++20 P1766R1 - Mitigating minor modules maladies paper. clang++ a few years ago introduced for the diagnostics required in the paper -Wnon-c-typedef-for-linkage pedwarn and the following patch does that too. The paper was accepted as a DR, the patch enables the warning also for C++98, dunno whether it might not be better to do it only for C++11 onwards.
Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? The paper is also about differences in default arguments of functions in different TUs and in modules, I think within the same TU we diagnose it correctly (maybe I should add some testcase) and perhaps try something with modules as well. But in different TUs it is IFNDR. 2025-08-14 Jakub Jelinek <ja...@redhat.com> PR c++/121552 gcc/ * doc/invoke.texi (-Wno-non-c-typedef-for-linkage): Document. gcc/c-family/ * c.opt (Wnon-c-typedef-for-linkage): New option. * c.opt.urls: Regenerate. gcc/cp/ * decl.cc: Implement C++20 P1766R1 - Mitigating minor modules maladies. (diagnose_non_c_class_typedef_for_linkage, maybe_diagnose_non_c_class_typedef_for_linkage): New functions. (name_unnamed_type): Call maybe_diagnose_non_c_class_typedef_for_linkage. gcc/testsuite/ * g++.dg/cpp2a/typedef1.C: New test. * g++.dg/debug/dwarf2/typedef5.C: Add -Wno-non-c-typedef-for-linkage to dg-options. * g++.dg/inherit/typeinfo1.C: Add -Wno-non-c-typedef-for-linkage to dg-additional-options. * g++.dg/parse/ctor2.C: Likewise. * g++.dg/ext/anon-struct9.C: Add -Wno-non-c-typedef-for-linkage to dg-options. * g++.dg/ext/visibility/anon11.C: Add -Wno-non-c-typedef-for-linkage to dg-additional-options. * g++.dg/lto/pr69137_0.C: Add -Wno-non-c-typedef-for-linkage to dg-lto-options. * g++.dg/other/anon8.C: Add -Wno-non-c-typedef-for-linkage to dg-additional-options. * g++.dg/template/pr84973.C: Likewise. * g++.dg/template/pr84973-2.C: Likewise. * g++.dg/template/pr84973-3.C: Likewise. * g++.dg/abi/anon2.C: Likewise. * g++.dg/abi/anon3.C: Likewise. * g++.old-deja/g++.oliva/linkage1.C: Likewise. --- gcc/doc/invoke.texi.jj 2025-08-11 12:25:20.931997082 +0200 +++ gcc/doc/invoke.texi 2025-08-14 19:00:32.059492185 +0200 @@ -268,7 +268,7 @@ in the following sections. -Wrange-loop-construct -Wredundant-move -Wredundant-tags -Wreorder -Wregister -Wno-sfinae-incomplete -Wstrict-null-sentinel -Wno-subobject-linkage -Wtemplates --Wno-non-template-friend -Wold-style-cast +-Wno-non-c-typedef-for-linkage -Wno-non-template-friend -Wold-style-cast -Woverloaded-virtual -Wno-pmf-conversions -Wself-move -Wsign-promo -Wsized-deallocation -Wsuggest-final-methods -Wsuggest-final-types -Wsuggest-override -Wno-template-body @@ -4494,6 +4494,28 @@ to @code{__null}. Although it is a null null pointer, it is guaranteed to be of the same size as a pointer. But this use is not portable across different compilers. +@opindex Wno-non-c-typedef-for-linkage +@opindex Wnon-c-typedef-for-linkage +@item -Wno-non-c-typedef-for-linkage @r{(C++ and Objective-C++ only)} +Disable pedwarn for unnamed classes with a typedef name for linkage purposes +containing C++ specific members, base classes, default member initializers +or lambda expressions, including those on nested member classes. + +@smallexample +typedef struct @{ + int a; // non-static data members are ok + struct T @{ int b; @}; // member classes too + enum E @{ E1, E2, E3 @}; // member enumerations as well + int c = 42; // default member initializers are not ok + struct U : A @{ int c; @}; // classes with base classes are not ok + typedef int V; // typedef is not ok + using W = int; // using declaration is not ok + decltype([]()@{@}) x; // lambda expressions not ok +@} S; +@end smallexample + +In all these cases, the tag name S should be added after the struct keyword. + @opindex Wno-non-template-friend @opindex Wnon-template-friend @item -Wno-non-template-friend @r{(C++ and Objective-C++ only)} --- gcc/c-family/c.opt.jj 2025-08-07 08:47:14.010785427 +0200 +++ gcc/c-family/c.opt 2025-08-14 14:28:29.974178842 +0200 @@ -1110,6 +1110,10 @@ Wnoexcept-type C++ ObjC++ Warning Var(warn_noexcept_type) LangEnabledBy(C++ ObjC++,Wabi || Wc++17-compat) Warn if C++17 noexcept function type will change the mangled name of a symbol. +Wnon-c-typedef-for-linkage +C++ ObjC++ Var(warn_non_c_typedef_for_linkage) Init(1) Warning +Warn for non-C compatible unnamed classes with a typedef name for linkage purposes. + Wnon-template-friend C++ ObjC++ Var(warn_nontemplate_friend) Init(1) Warning Warn when non-templatized friend functions are declared within a template. --- gcc/c-family/c.opt.urls.jj 2025-08-07 08:47:14.030785157 +0200 +++ gcc/c-family/c.opt.urls 2025-08-14 18:54:15.399143471 +0200 @@ -613,6 +613,9 @@ UrlSuffix(gcc/C_002b_002b-Dialect-Option Wnoexcept-type UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-Wno-noexcept-type) +Wnon-c-typedef-for-linkage +UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-Wno-non-c-typedef-for-linkage) + Wnon-template-friend UrlSuffix(gcc/C_002b_002b-Dialect-Options.html#index-Wno-non-template-friend) --- gcc/cp/decl.cc.jj 2025-08-13 22:10:18.880791832 +0200 +++ gcc/cp/decl.cc 2025-08-14 17:34:09.956175700 +0200 @@ -13022,6 +13022,88 @@ mark_inline_variable (tree decl, locatio } +/* Diagnose -Wnon-c-typedef-for-linkage pedwarn. TYPE is the unnamed class + with a typedef name for linkage purposes with freshly updated TYPE_NAME, + ORIG is the anonymous TYPE_NAME before that change. */ + +static bool +diagnose_non_c_class_typedef_for_linkage (tree type, tree orig) +{ + gcc_rich_location richloc (DECL_SOURCE_LOCATION (orig)); + tree name = DECL_NAME (TYPE_NAME (type)); + richloc.add_fixit_insert_before (IDENTIFIER_POINTER (name)); + return pedwarn (&richloc, OPT_Wnon_c_typedef_for_linkage, + "anonymous non-C-compatible type given name for linkage " + "purposes by %<typedef%> declaration"); +} + +/* Diagnose -Wnon-c-typedef-for-linkage violations on T. TYPE and ORIG + like for diagnose_non_c_class_typedef_for_linkage, T is initially equal + to TYPE but during recursion can be set to nested classes. */ + +static bool +maybe_diagnose_non_c_class_typedef_for_linkage (tree type, tree orig, tree t) +{ + if (!BINFO_BASE_BINFOS (TYPE_BINFO (t))->is_empty ()) + { + auto_diagnostic_group d; + if (diagnose_non_c_class_typedef_for_linkage (type, orig)) + inform (DECL_SOURCE_LOCATION (TYPE_NAME (t)), + "type is not C-compatible because it has a base class"); + return true; + } + for (tree field = TYPE_FIELDS (t); field; field = TREE_CHAIN (field)) + switch (TREE_CODE (field)) + { + case VAR_DECL: + /* static data members have been diagnosed already. */ + continue; + case FIELD_DECL: + if (DECL_INITIAL (field)) + { + auto_diagnostic_group d; + if (diagnose_non_c_class_typedef_for_linkage (type, orig)) + inform (DECL_SOURCE_LOCATION (field), + "type is not C-compatible because %qD has default " + "member initializer", field); + return true; + } + continue; + case CONST_DECL: + continue; + case TYPE_DECL: + if (DECL_SELF_REFERENCE_P (field)) + continue; + if (DECL_IMPLICIT_TYPEDEF_P (field)) + { + if (TREE_CODE (TREE_TYPE (field)) == ENUMERAL_TYPE) + continue; + if (CLASS_TYPE_P (TREE_TYPE (field))) + { + tree tf = TREE_TYPE (field); + if (maybe_diagnose_non_c_class_typedef_for_linkage (type, orig, + tf)) + return true; + continue; + } + } + /* FALLTHRU */ + case FUNCTION_DECL: + case TEMPLATE_DECL: + { + auto_diagnostic_group d; + if (diagnose_non_c_class_typedef_for_linkage (type, orig)) + inform (DECL_SOURCE_LOCATION (field), + "type is not C-compatible because it contains %qD " + "declaration", field); + return true; + } + default: + break; + } + return false; +} + /* Assign a typedef-given name to a class or enumeration type declared as anonymous at first. This was split out of grokdeclarator because it is also used in libcc1. */ @@ -13047,6 +13129,9 @@ name_unnamed_type (tree type, tree decl) /* Adjust linkage now that we aren't unnamed anymore. */ reset_type_linkage (type); + if (CLASS_TYPE_P (type) && warn_non_c_typedef_for_linkage) + maybe_diagnose_non_c_class_typedef_for_linkage (type, orig, type); + /* FIXME remangle member functions; member functions of a type with external linkage have external linkage. */ --- gcc/testsuite/g++.dg/cpp2a/typedef1.C.jj 2025-08-14 18:16:55.020407913 +0200 +++ gcc/testsuite/g++.dg/cpp2a/typedef1.C 2025-08-14 18:59:23.937333497 +0200 @@ -0,0 +1,94 @@ +// C++20 P1766R1 - Mitigating minor modules maladies +// { dg-do compile } + +typedef struct +{ + int a; + enum B { C1, C2, C3 }; + struct T { + int b; +#if __cplusplus >= 201103L + static_assert (sizeof (b) == sizeof (int), ""); +#endif + friend int freddy (int); + friend int garply (int x) { return x; } + }; + union U { int c; long d; }; + union { int e; long f; }; + int g : 5; +#if __cplusplus >= 201103L + static_assert (sizeof (a) == sizeof (int), ""); +#endif + friend int qux (int); + friend int corge (int x) { return x; } +private: + int h; +protected: + int i; +public: + int j; +} S; +struct A {}; +typedef struct { // { dg-message "unnamed class defined here" } + static int a; // { dg-error "static data member '<unnamed struct>::a' in unnamed class" } +} B; +typedef struct : public A { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + int a; +} C; // { dg-message "type is not C-compatible because it has a base class" } +#if __cplusplus >= 201103L +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + int b = 42; // { dg-message "type is not C-compatible because 'D::b' has default member initializer" "" { target c++11 } } +} D; +#endif +struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + int foo (); } typedef E; // { dg-message "type is not C-compatible because it contains 'int E::foo\\\(\\\)' declaration" } +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + static int bar (); // { dg-message "type is not C-compatible because it contains 'static int F::bar\\\(\\\)' declaration" } +} F; +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + typedef int T; // { dg-message "type is not C-compatible because it contains 'G::T' declaration" } +} G; +#if __cplusplus >= 201103L +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + using T = int; // { dg-message "type is not C-compatible because it contains 'using H::T = int' declaration" "" { target c++11 } } +} H; +#endif +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + template <int N> struct B { int a; }; // { dg-message "type is not C-compatible because it contains 'template<int N> struct I::B' declaration" } +} I; +typedef struct { // { dg-message "unnamed class defined here" } + struct B { static int a; }; // { dg-error "static data member '<unnamed struct>::B::a' in unnamed class" } +} J; +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + struct B : public A { int c; }; // { dg-message "type is not C-compatible because it has a base class" } +} K; +#if __cplusplus >= 201103L +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + struct B { int d = 42; }; // { dg-message "type is not C-compatible because 'L::B::d' has default member initializer" "" { target c++11 } } +} L; +#endif +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + struct B { int foo (); }; // { dg-message "type is not C-compatible because it contains 'int M::B::foo\\\(\\\)' declaration" } +} M; +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + struct B { static int bar (); }; // { dg-message "type is not C-compatible because it contains 'static int N::B::bar\\\(\\\)' declaration" } +} N; +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + struct B { typedef int T; }; // { dg-message "type is not C-compatible because it contains 'O::B::T' declaration" } +} O; +#if __cplusplus >= 201103L +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + struct B { using T = int; }; // { dg-message "type is not C-compatible because it contains 'using P::B::T = int' declaration" "" { target c++11 } } +} P; +#endif +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" } + struct B { template <int N> struct C { int a; }; }; // { dg-message "type is not C-compatible because it contains 'template<int N> struct Q::B::C' declaration" } +} Q; +#if __cplusplus >= 201103L +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + decltype([](int i){ return i; }) a; // { dg-message "type is not C-compatible because it contains '\[^\n\r]*R::<lambda\\\(int\\\)>\[^\n\r]*' declaration" "" { target c++11 } } +} R; // { dg-error "lambda-expression in unevaluated context only available with" "" { target { c++11 && c++17_down } } .-1 } +typedef struct { // { dg-error "anonymous non-C-compatible type given name for linkage purposes by 'typedef' declaration" "" { target c++11 } } + struct B { decltype([](int i){ return i; }) a; }; // { dg-message "type is not C-compatible because it contains '\[^\n\r]*T::B::<lambda\\\(int\\\)>\[^\n\r]*' declaration" "" { target c++11 } } +} T; // { dg-error "lambda-expression in unevaluated context only available with" "" { target { c++11 && c++17_down } } .-1 } +#endif --- gcc/testsuite/g++.dg/debug/dwarf2/typedef5.C.jj 2020-01-14 20:02:46.812609399 +0100 +++ gcc/testsuite/g++.dg/debug/dwarf2/typedef5.C 2025-08-14 22:01:10.582208100 +0200 @@ -1,5 +1,5 @@ // Origin: PR debug/46101 -// { dg-options "-gdwarf-2" } +// { dg-options "-gdwarf-2 -Wno-non-c-typedef-for-linkage" } // { dg-do compile } typedef struct --- gcc/testsuite/g++.dg/inherit/typeinfo1.C.jj 2020-01-14 20:02:46.856608740 +0100 +++ gcc/testsuite/g++.dg/inherit/typeinfo1.C 2025-08-14 22:01:42.893820500 +0200 @@ -1,3 +1,5 @@ +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } + typedef struct { virtual const char *blah() { return "Heya::blah"; --- gcc/testsuite/g++.dg/parse/ctor2.C.jj 2020-01-14 20:02:46.912607901 +0100 +++ gcc/testsuite/g++.dg/parse/ctor2.C 2025-08-14 22:02:08.709510823 +0200 @@ -1,4 +1,5 @@ // PR c++/19244 +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } typedef struct { void f(); } f; void f::f() { } --- gcc/testsuite/g++.dg/ext/anon-struct9.C.jj 2021-08-02 22:54:01.389868056 +0200 +++ gcc/testsuite/g++.dg/ext/anon-struct9.C 2025-08-14 22:01:21.952071710 +0200 @@ -1,5 +1,5 @@ // PR c++/96636 -// { dg-options "" } +// { dg-options "-Wno-non-c-typedef-for-linkage" } typedef class { class a {}; --- gcc/testsuite/g++.dg/ext/visibility/anon11.C.jj 2020-01-14 20:02:46.843608935 +0100 +++ gcc/testsuite/g++.dg/ext/visibility/anon11.C 2025-08-14 22:01:25.944023826 +0200 @@ -1,5 +1,6 @@ // PR c++/55877 // { dg-final { scan-assembler-not "\\.local" } } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } typedef struct { typedef enum { X, Y } A; --- gcc/testsuite/g++.dg/lto/pr69137_0.C.jj 2020-01-14 20:02:46.884608321 +0100 +++ gcc/testsuite/g++.dg/lto/pr69137_0.C 2025-08-14 22:01:59.762618149 +0200 @@ -1,6 +1,6 @@ // { dg-lto-do link } // { dg-require-effective-target lto_incremental } -// { dg-lto-options { { -std=c++11 -g -flto } } } +// { dg-lto-options { { -std=c++11 -g -flto -Wno-non-c-typedef-for-linkage } } } // { dg-extra-ld-options "-r -nostdlib" } typedef struct { --- gcc/testsuite/g++.dg/other/anon8.C.jj 2020-01-14 20:02:46.897608126 +0100 +++ gcc/testsuite/g++.dg/other/anon8.C 2025-08-14 22:02:05.394550590 +0200 @@ -1,4 +1,5 @@ // PR c++/68679 +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } typedef struct { struct { --- gcc/testsuite/g++.dg/template/pr84973.C.jj 2020-01-14 20:02:46.953607287 +0100 +++ gcc/testsuite/g++.dg/template/pr84973.C 2025-08-14 22:02:20.709366875 +0200 @@ -1,4 +1,5 @@ // { dg-do compile } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } template <int> void a() { typedef struct { --- gcc/testsuite/g++.dg/template/pr84973-2.C.jj 2020-01-14 20:02:46.953607287 +0100 +++ gcc/testsuite/g++.dg/template/pr84973-2.C 2025-08-14 22:02:13.204456904 +0200 @@ -1,4 +1,5 @@ // { dg-do compile } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } template <int> void a() { typedef struct { --- gcc/testsuite/g++.dg/template/pr84973-3.C.jj 2020-01-14 20:02:46.953607287 +0100 +++ gcc/testsuite/g++.dg/template/pr84973-3.C 2025-08-14 22:02:17.079410421 +0200 @@ -1,4 +1,5 @@ // { dg-do link } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } template <int> void a() { typedef struct { --- gcc/testsuite/g++.dg/abi/anon2.C.jj 2020-01-14 20:02:46.684611317 +0100 +++ gcc/testsuite/g++.dg/abi/anon2.C 2025-08-14 22:00:37.402606118 +0200 @@ -1,5 +1,6 @@ // PR c++/55877 // { dg-require-weak "" } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } namespace N1 { typedef struct { --- gcc/testsuite/g++.dg/abi/anon3.C.jj 2020-01-14 20:02:46.684611317 +0100 +++ gcc/testsuite/g++.dg/abi/anon3.C 2025-08-14 22:00:40.631567385 +0200 @@ -1,4 +1,5 @@ // { dg-require-weak "" } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } typedef struct { // { dg-final { scan-assembler ".weak\(_definition\)?\[ \t\]_?_ZN4Heya4blahEv" } } --- gcc/testsuite/g++.old-deja/g++.oliva/linkage1.C.jj 2020-01-14 20:02:47.061605669 +0100 +++ gcc/testsuite/g++.old-deja/g++.oliva/linkage1.C 2025-08-14 22:02:26.328299473 +0200 @@ -1,5 +1,6 @@ // { dg-do link } // { dg-additional-sources " linkage1-main.cc" } +// { dg-additional-options "-Wno-non-c-typedef-for-linkage" } // Copyright 2002 Free Software Foundation Jakub