https://gcc.gnu.org/g:13920d004da35b536d6ef07b0a32c0d3acc4c889
commit r17-4959-g13920d004da35b536d6ef07b0a32c0d3acc4c889 Author: Jason Merrill <[email protected]> Date: Wed Oct 7 10:03:47 2026 -0400 c++/modules: P1811: relaxing redefinition restrictions [PR99000] P1811 says it's OK to redefine an imported global module entity in a different translation unit. The simplest (and fastest) way to handle that is skipping the tokens of the redundant definition. At some later point we might want to optionally parse the new definition and compare it to the imported one for ODR checking, but it isn't necessary for supporting well-formed code. This does allow wildly ill-formed code in the redefinitions for the sake of speed, on the assumption that the the definitions satisfy the ODR, probably because they are from the same source code. The core addition is redefinable_import_p; various places that have errored about redefinition now discard the redefinition if it is true. For convenience, I'm also adding -fno-module-include-translate to use to force textual inclusion in compile-std1.C. For redefinition of classes, I've gone back and forth between trying to merge reachable non-exports in lookup_elaborated_type (as with the __class_type_info hack removed by this patch) or waiting until check_module_override. I eventually settled on the latter approach for consistency, but it does mean that we need to check for a previous definition after lookup and again after push. This patch wrongly allows multiple redefinitions after an import, as in p1811-5. I expect that fixing this should mostly mean clarifying what DECL_MODULE_IMPORT_P means for global module entities, but additional machinery would be needed for default arguments. And it doesn't seem necessary to fix in this patch, given that I'm deliberately allowing other ill-formed redefinitions; if a shared header has multiple definitions, we will diagnose it when building the imported module. Assisted-by: GPT-5.6 Sol (p1811* testcases) P1811 PR c++/99000 gcc/c-family/ChangeLog: * c.opt (fmodule-include-translate): New option. gcc/cp/ChangeLog: * module.cc (maybe_translate_include): Check -fmodule-include-translate. * name-lookup.cc (redefinable_import_p): New. (pushdecl): Check DECL_IS_UNDECLARED_BUILTIN instead of hidden_p. (lookup_elaborated_type): Don't special-case cxxabi classes. (pushtag): Handle pushdecl finding a previous definition. * decl.cc (merge_default_template_args): Check redefinable_import_p. (duplicate_decls, grokfndecl, start_enum): Likewise. (redeclaration_error_message): Likewise. * parser.cc (cp_parser_skip_to_end_of_balanced_tokens): New. (cp_parser_skip_function_definition): New. (cp_parser_skip_base_clause): New. (cp_parser_enum_specifier): Skip redundant definition. (cp_parser_class_specifier): Likewise. (cp_parser_class_head): Likewise. (cp_parser_function_definition_from_specifiers_and_declarator): Likewise. * pt.cc (redeclare_class_template): Adjust. * cp-tree.h: Adjust. * name-lookup.h: Adjust. gcc/testsuite/ChangeLog: * g++.dg/modules/compile-std1.C: Also test #include after import. * g++.dg/modules/hello-1_b.C: Likewise. * g++.dg/modules/default-arg-4_b.C: Remove xfails. * g++.dg/modules/pr122053_b.C: Likewise. * g++.dg/modules/gmf-3.h: New test. * g++.dg/modules/gmf-3_a.C: New test. * g++.dg/modules/gmf-3_b.C: New test. * g++.dg/modules/gmf-4_a.C: New test. * g++.dg/modules/gmf-4_b.C: New test. Diff: --- gcc/c-family/c.opt | 4 + gcc/cp/cp-tree.h | 2 +- gcc/cp/name-lookup.h | 1 + gcc/testsuite/g++.dg/modules/gmf-3.h | 2 + gcc/testsuite/g++.dg/modules/p1811-1.h | 9 ++ gcc/testsuite/g++.dg/modules/p1811-2.h | 5 + gcc/testsuite/g++.dg/modules/p1811-3.h | 1 + gcc/testsuite/g++.dg/modules/p1811-5.h | 1 + gcc/testsuite/g++.dg/modules/p1811-6.h | 2 + gcc/cp/decl.cc | 116 ++++++++++++++++------ gcc/cp/module.cc | 33 ++++++- gcc/cp/name-lookup.cc | 28 ++---- gcc/cp/parser.cc | 128 ++++++++++++++++++++++--- gcc/cp/pt.cc | 2 +- gcc/testsuite/g++.dg/modules/compile-std1.C | 9 +- gcc/testsuite/g++.dg/modules/default-arg-4_b.C | 9 +- gcc/testsuite/g++.dg/modules/gmf-3_a.C | 14 +++ gcc/testsuite/g++.dg/modules/gmf-3_b.C | 7 ++ gcc/testsuite/g++.dg/modules/gmf-4_a.C | 13 +++ gcc/testsuite/g++.dg/modules/gmf-4_b.C | 12 +++ gcc/testsuite/g++.dg/modules/hello-1_b.C | 4 +- gcc/testsuite/g++.dg/modules/p1811-1_a.C | 11 +++ gcc/testsuite/g++.dg/modules/p1811-1_b.C | 8 ++ gcc/testsuite/g++.dg/modules/p1811-2_a.C | 11 +++ gcc/testsuite/g++.dg/modules/p1811-2_b.C | 9 ++ gcc/testsuite/g++.dg/modules/p1811-3_a.C | 11 +++ gcc/testsuite/g++.dg/modules/p1811-3_b.C | 8 ++ gcc/testsuite/g++.dg/modules/p1811-4_a.C | 8 ++ gcc/testsuite/g++.dg/modules/p1811-4_b.C | 7 ++ gcc/testsuite/g++.dg/modules/p1811-5_a.C | 11 +++ gcc/testsuite/g++.dg/modules/p1811-5_b.C | 7 ++ gcc/testsuite/g++.dg/modules/p1811-6_a.C | 12 +++ gcc/testsuite/g++.dg/modules/p1811-6_b.C | 9 ++ gcc/testsuite/g++.dg/modules/pr122053_b.C | 4 +- 34 files changed, 445 insertions(+), 73 deletions(-) diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 3ad96a5fe783..d5b973baa855 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -2153,6 +2153,10 @@ fmodule-implicit-inline C++ ObjC++ Var(flag_module_implicit_inline,0) Integer Member functions defined within their class are inline in module purview. +fmodule-include-translate +C++ ObjC++ Var(flag_module_include_translate) Init(1) Integer +Implicitly translate #include to import when possible. + fmodule-only C++ ObjC RejectNegative Var(flag_module_only) Integer Only emit Compiled Module Interface. diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 947bf62ff982..7b461e4f5281 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7605,7 +7605,7 @@ extern bool fns_correspond (tree, tree); extern int decls_match (tree, tree, bool = true); extern bool maybe_version_functions (tree, tree); extern bool validate_constexpr_redeclaration (tree, tree); -extern bool merge_default_template_args (tree, tree, bool); +extern bool merge_default_template_args (tree, tree, tree); extern void merge_decl_arguments (tree, tree, bool, bool, bool); extern tree duplicate_decls (tree, tree, bool hiding = false, diff --git a/gcc/cp/name-lookup.h b/gcc/cp/name-lookup.h index 3737ae0ebac1..48b4a363cf53 100644 --- a/gcc/cp/name-lookup.h +++ b/gcc/cp/name-lookup.h @@ -509,6 +509,7 @@ extern void finish_nonmember_using_decl (tree scope, tree name, bool typename_p = false); extern void finish_using_directive (tree target, tree attribs); void push_local_extern_decl_alias (tree decl); +extern bool redefinable_import_p (tree decl); extern tree pushdecl (tree, bool hiding = false); extern tree pushdecl_outermost_localscope (tree); extern tree pushdecl_top_level (tree); diff --git a/gcc/testsuite/g++.dg/modules/gmf-3.h b/gcc/testsuite/g++.dg/modules/gmf-3.h new file mode 100644 index 000000000000..fc83c3d3fb5d --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/gmf-3.h @@ -0,0 +1,2 @@ +struct A { }; +template <class T> struct B { }; diff --git a/gcc/testsuite/g++.dg/modules/p1811-1.h b/gcc/testsuite/g++.dg/modules/p1811-1.h new file mode 100644 index 000000000000..f29b80a747dc --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-1.h @@ -0,0 +1,9 @@ +inline int +f () try +{ + return 1; +} +catch (...) +{ + return 2; +} diff --git a/gcc/testsuite/g++.dg/modules/p1811-2.h b/gcc/testsuite/g++.dg/modules/p1811-2.h new file mode 100644 index 000000000000..433344d3b178 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-2.h @@ -0,0 +1,5 @@ +struct C { + enum E : int; +}; + +enum C::E : int { e }; diff --git a/gcc/testsuite/g++.dg/modules/p1811-3.h b/gcc/testsuite/g++.dg/modules/p1811-3.h new file mode 100644 index 000000000000..8ae11367b646 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-3.h @@ -0,0 +1 @@ +struct A { }; diff --git a/gcc/testsuite/g++.dg/modules/p1811-5.h b/gcc/testsuite/g++.dg/modules/p1811-5.h new file mode 100644 index 000000000000..8ae11367b646 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-5.h @@ -0,0 +1 @@ +struct A { }; diff --git a/gcc/testsuite/g++.dg/modules/p1811-6.h b/gcc/testsuite/g++.dg/modules/p1811-6.h new file mode 100644 index 000000000000..1b99bed4babd --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-6.h @@ -0,0 +1,2 @@ +template <class T> struct A { }; +template <class T> struct B { }; diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index a14e9a4cb719..fe210c48cfe5 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -1754,12 +1754,13 @@ duplicate_function_template_decls (tree newdecl, tree olddecl) /* OLD_PARMS is the innermost set of template parameters for some template declaration, and NEW_PARMS is the corresponding set of template parameters for a redeclaration of that template. Merge the default arguments within - these two sets of parameters. CLASS_P is true iff the template in - question is a class template. */ + these two sets of parameters. OLDDECL is the origin of OLD_PARMS. */ bool -merge_default_template_args (tree new_parms, tree old_parms, bool class_p) +merge_default_template_args (tree new_parms, tree old_parms, tree olddecl) { + bool class_p = DECL_IMPLICIT_TYPEDEF_P (STRIP_TEMPLATE (olddecl)); + gcc_checking_assert (TREE_VEC_LENGTH (new_parms) == TREE_VEC_LENGTH (old_parms)); for (int i = 0; i < TREE_VEC_LENGTH (new_parms); i++) @@ -1770,7 +1771,9 @@ merge_default_template_args (tree new_parms, tree old_parms, bool class_p) tree& old_default = TREE_PURPOSE (TREE_VEC_ELT (old_parms, i)); if (error_operand_p (new_parm) || error_operand_p (old_parm)) return false; - if (new_default != NULL_TREE && old_default != NULL_TREE) + if (new_default != NULL_TREE && old_default != NULL_TREE + && !(redefinable_import_p (olddecl) + && cp_tree_equal (new_default, old_default))) { auto_diagnostic_group d; error ("redefinition of default argument for %q+#D", new_parm); @@ -1942,6 +1945,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) || TREE_TYPE (olddecl) == error_mark_node) return error_mark_node; + bool mod_redefinable = redefinable_import_p (olddecl); + /* Check for redeclaration and other discrepancies. */ if (TREE_CODE (olddecl) == FUNCTION_DECL && DECL_IS_UNDECLARED_BUILTIN (olddecl)) @@ -2228,6 +2233,14 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) if (TREE_CODE (oldres) == TYPE_DECL || TREE_CODE (newres) == TYPE_DECL) { + /* E.g. cstddef __byte_operand. Alternatively we could find a + mergeable definition in lookup_elaborated_type, but we'd still + need to push it into scope in the current TU. */ + if (mod_redefinable + && (DECL_IMPLICIT_TYPEDEF_P (oldres) + == DECL_IMPLICIT_TYPEDEF_P (newres))) + goto return_old; + auto_diagnostic_group d; error_at (newdecl_loc, "conflicting declaration of template %q#D", newdecl); @@ -2283,6 +2296,18 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) else return NULL_TREE; } + else if (mod_redefinable + && TREE_CODE (newdecl) == TYPE_DECL + && (DECL_IMPLICIT_TYPEDEF_P (newdecl) + == DECL_IMPLICIT_TYPEDEF_P (olddecl))) + /* Can also get here with e.g. typedef class {...} foo. */ + goto return_old; + else if (mod_redefinable + && TREE_CODE (newdecl) == VAR_DECL + && undeduced_auto_decl (newdecl) + && !undeduced_auto_decl (olddecl)) + /* We don't have fndecl_declared_return_type for vars. */ + goto return_old; else { auto_diagnostic_group d; @@ -2478,7 +2503,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) of the member function within the class template. */ || CLASSTYPE_TEMPLATE_INFO (CP_DECL_CONTEXT (olddecl)))) { - check_redeclaration_no_default_args (newdecl); + if (!mod_redefinable) + check_redeclaration_no_default_args (newdecl); if (DECL_TEMPLATE_INFO (olddecl) && DECL_MEMBER_TEMPLATE_P (DECL_TI_TEMPLATE (olddecl))) @@ -2488,8 +2514,7 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) : INNERMOST_TEMPLATE_PARMS (current_template_parms); tree old_parms = DECL_INNERMOST_TEMPLATE_PARMS (DECL_TI_TEMPLATE (olddecl)); - merge_default_template_args (new_parms, old_parms, - /*class_p=*/false); + merge_default_template_args (new_parms, old_parms, olddecl); } } else @@ -2502,18 +2527,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) t1 = TREE_CHAIN (t1), t2 = TREE_CHAIN (t2), i++) if (TREE_PURPOSE (t1) && TREE_PURPOSE (t2)) { - if (simple_cst_equal (TREE_PURPOSE (t1), - TREE_PURPOSE (t2)) == 1) - { - auto_diagnostic_group d; - if (permerror (newdecl_loc, - "default argument given for parameter " - "%d of %q#D", i, newdecl)) - inform (olddecl_loc, - "previous specification in %q#D here", - olddecl); - } - else + if (!cp_tree_equal (TREE_PURPOSE (t1), + TREE_PURPOSE (t2))) { auto_diagnostic_group d; error_at (newdecl_loc, @@ -2523,6 +2538,16 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) "previous specification in %q#D here", olddecl); } + else if (!mod_redefinable) + { + auto_diagnostic_group d; + if (permerror (newdecl_loc, + "default argument given for parameter " + "%d of %q#D", i, newdecl)) + inform (olddecl_loc, + "previous specification in %q#D here", + olddecl); + } } /* C++17 11.3.6/4: "If a friend declaration specifies a default @@ -2653,6 +2678,17 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) = DECL_OVERLOADED_OPERATOR_CODE_RAW (olddecl); new_defines_function = DECL_INITIAL (newdecl) != NULL_TREE; + if (new_defines_function && mod_redefinable + && DECL_INITIAL (olddecl)) + { + if (GNU_INLINE_P (STRIP_TEMPLATE (olddecl)) + && !GNU_INLINE_P (STRIP_TEMPLATE (newdecl))) + /* Normal out-of-line redefinition of gnu_inline. */ + mod_redefinable = false; + else + new_defines_function = false; + } + check_redecl_contract (newdecl, olddecl); /* Optionally warn about more than one declaration for the same @@ -2684,7 +2720,9 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) if (!(DECL_TEMPLATE_INSTANTIATION (olddecl) && DECL_TEMPLATE_SPECIALIZATION (newdecl))) { - if (DECL_DELETED_FN (newdecl)) + if (DECL_DELETED_FN (newdecl) + && !(mod_redefinable + && DECL_DELETED_FN (olddecl))) { auto_diagnostic_group d; if (pedwarn (newdecl_loc, 0, "deleted definition of %qD " @@ -2748,7 +2786,8 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) { /* Per C++11 8.3.6/4, default arguments cannot be added in later declarations of a function template. */ - check_redeclaration_no_default_args (newdecl); + if (!mod_redefinable) + check_redeclaration_no_default_args (newdecl); /* C++17 11.3.6/4: "If a friend declaration specifies a default argument expression, that declaration... shall be the only declaration of the function or function template in the @@ -2758,8 +2797,7 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) tree new_parms = DECL_INNERMOST_TEMPLATE_PARMS (newdecl); tree old_parms = DECL_INNERMOST_TEMPLATE_PARMS (olddecl); - merge_default_template_args (new_parms, old_parms, - /*class_p=*/false); + merge_default_template_args (new_parms, old_parms, olddecl); } if (!DECL_UNIQUE_FRIEND_P (new_result)) DECL_UNIQUE_FRIEND_P (old_result) = false; @@ -2769,6 +2807,7 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) if (GNU_INLINE_P (old_result) != GNU_INLINE_P (new_result) && DECL_INITIAL (new_result)) { + mod_redefinable = false; if (DECL_INITIAL (old_result)) DECL_UNINLINABLE (old_result) = 1; else @@ -2798,7 +2837,9 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) /* If the new declaration is a definition, update the file and line information on the declaration, and also make the old declaration the same definition. */ - if (DECL_INITIAL (new_result) != NULL_TREE) + if (DECL_INITIAL (new_result) != NULL_TREE + && !(DECL_INITIAL (old_result) + && mod_redefinable)) { DECL_SOURCE_LOCATION (olddecl) = DECL_SOURCE_LOCATION (old_result) @@ -2982,8 +3023,9 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) } /* Merge the initialization information. */ - if (DECL_INITIAL (newdecl) == NULL_TREE - && DECL_INITIAL (olddecl) != NULL_TREE) + if (DECL_INITIAL (olddecl) != NULL_TREE + && (DECL_INITIAL (newdecl) == NULL_TREE + || mod_redefinable)) { DECL_INITIAL (newdecl) = DECL_INITIAL (olddecl); DECL_SOURCE_LOCATION (newdecl) = DECL_SOURCE_LOCATION (olddecl); @@ -3611,6 +3653,9 @@ duplicate_decls (tree newdecl, tree olddecl, bool hiding, bool was_hidden) static const char * redeclaration_error_message (tree newdecl, tree olddecl) { + if (redefinable_import_p (olddecl)) + return NULL; + if (TREE_CODE (newdecl) == TYPE_DECL) { /* Because C++ can put things into name space for free, @@ -12871,7 +12916,9 @@ grokfndecl (tree ctype, error ("definition of implicitly-declared %qD", old_decl); return NULL_TREE; } - else if (DECL_DEFAULTED_FN (old_decl)) + else if (DECL_DEFAULTED_FN (old_decl) + && !(initialized == SD_DEFAULTED + && redefinable_import_p (old_decl))) { auto_diagnostic_group d; error ("definition of explicitly-defaulted %q+D", decl); @@ -18498,7 +18545,11 @@ lookup_and_check_tag (enum tag_types tag_code, tree name, { tree u = decl; decl = strip_using_decl (decl); - if (how == TAG_how::CURRENT_ONLY) + if (how == TAG_how::CURRENT_ONLY + && TREE_CODE (decl) == TYPE_DECL + && !(DECL_IMPLICIT_TYPEDEF_P (decl) + && CP_DECL_CONTEXT (decl) == CP_DECL_CONTEXT (u) + && redefinable_import_p (decl))) { auto_diagnostic_group _; error ("%qD conflicts with a previous declaration", name); @@ -19095,6 +19146,9 @@ start_enum (tree name, tree enumtype, tree underlying_type, tree decl = TYPE_NAME (enumtype); if (!module_may_redeclare (decl)) enumtype = error_mark_node; + else if (COMPLETE_TYPE_P (enumtype) + && redefinable_import_p (decl)) + return enumtype; else set_instantiating_module (decl); } @@ -19163,6 +19217,12 @@ start_enum (tree name, tree enumtype, tree underlying_type, if (enumtype != error_mark_node) { + /* ??? We wouldn't need to repeat this if lookup_elaborated_type + found (and pushed) mergeable types. */ + if (COMPLETE_TYPE_P (enumtype) + && redefinable_import_p (TYPE_NAME (enumtype))) + return enumtype; + /* The enum is considered opaque until the opening '{' of the enumerator list. */ SET_OPAQUE_ENUM_P (enumtype, true); diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index 1e9e27a3ec20..6d7d650c638f 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -22220,6 +22220,37 @@ set_originating_module (tree decl, bool friend_p ATTRIBUTE_UNUSED) DECL_MODULE_EXPORT_P (decl) = true; } +/* True if it's OK to redefine DECL under P1811 because it is attached to the + global module and its definition was imported; [basic.def.odr]/12 "there can + be more than one definition...provided that each definition appears in a + different translation unit" + + This does not depend on whether DECL was already defined, so that for + instance it can affect redefinition of default arguments. This should only + be used when we notice a redefinition. */ + +bool +redefinable_import_p (tree decl) +{ + if (!modules_p () || named_module_attach_p ()) + return false; + + decl = STRIP_TEMPLATE (decl); + + if (TREE_CODE (decl) == TYPE_DECL + && !DECL_IMPLICIT_TYPEDEF_P (decl) + && !TYPE_DECL_FOR_LINKAGE_PURPOSES_P (decl)) + /* Typedefs are always redefinable, use normal handling. */ + return false; + + return (DECL_LANG_SPECIFIC (decl) + && DECL_MODULE_ENTITY_P (decl) + /* Use this instead of DECL_MODULE_IMPORT_P so we don't get confused + by partitions or set_instantiating_module. */ + /* FIXME this also means we allow multiple redefinitions. */ + && get_importing_module (decl, /*flexible*/true) > 0); +} + /* Checks whether DECL within a module unit has valid linkage for its kind. Must be called after visibility for DECL has been finalised. */ @@ -23171,7 +23202,7 @@ static char * maybe_translate_include (cpp_reader *reader, line_maps *lmaps, location_t loc, _cpp_file *file, bool angle, const char **alternate) { - if (!modules_p ()) + if (!modules_p () || !flag_module_include_translate) { /* Turn off. */ cpp_get_callbacks (reader)->translate_include = NULL; diff --git a/gcc/cp/name-lookup.cc b/gcc/cp/name-lookup.cc index 56d21bf50d61..e2fc89e6b32e 100644 --- a/gcc/cp/name-lookup.cc +++ b/gcc/cp/name-lookup.cc @@ -4071,7 +4071,7 @@ pushdecl (tree decl, bool hiding) for (ovl_iterator iter (oldi); iter; ++iter) if (iter.using_p ()) ; /* Ignore using decls here. */ - else if (iter.hidden_p () + else if (DECL_IS_UNDECLARED_BUILTIN (*iter) && TREE_CODE (*iter) == FUNCTION_DECL && DECL_LANG_SPECIFIC (*iter) && DECL_MODULE_IMPORT_P (*iter)) @@ -8643,24 +8643,8 @@ lookup_elaborated_type (tree name, TAG_how how) return bind; } - if (!module_purview_p ()) - { - /* We're in the global module, perhaps there's a tag - there? */ - - /* FIXME: In general we should probably merge global module - classes in check_module_override rather than here, but for - GCC14 let's just fix lazy declarations of __class_type_info in - build_dynamic_cast_1. */ - if (current_namespace == abi_node) - { - tree g = (BINDING_VECTOR_CLUSTER (*slot, 0) - .slots[BINDING_SLOT_GLOBAL]); - for (ovl_iterator iter (g); iter; ++iter) - if (qualify_lookup (*iter, LOOK_want::TYPE)) - return *iter; - } - } + /* We'll find mergeable types in check_module_override when we try to + push a new one, no need to handle them specially here. */ } } @@ -8825,6 +8809,9 @@ pushtag (tree name, tree type, TAG_how how) (type, how == TAG_how::HIDDEN_FRIEND, b); if (decl == error_mark_node) return decl; + if (TREE_TYPE (decl) != type) + /* Found an imported version of the same type. */ + return TREE_TYPE (decl); if (b->kind == sk_class) { @@ -8854,6 +8841,9 @@ pushtag (tree name, tree type, TAG_how how) (decl, b, /*hiding=*/(how == TAG_how::HIDDEN_FRIEND)); if (decl == error_mark_node) return decl; + if (TREE_TYPE (decl) != type) + /* Found an imported version of the same type. */ + return TREE_TYPE (decl); if (DECL_CONTEXT (decl) == std_node && init_list_identifier == DECL_NAME (TYPE_NAME (type)) diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc index 9c54b2f22e2d..48e3117a71e7 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -4435,6 +4435,81 @@ cp_parser_consume_semicolon_at_end_of_statement (cp_parser *parser) } } +/* Consume the entire balanced token sequence at point. Returns true + if that was successful. */ + +static bool +cp_parser_skip_to_end_of_balanced_tokens (cp_parser *parser) +{ + size_t n = cp_parser_skip_balanced_tokens (parser, 1); + if (n == 1) + return false; + while (--n) + cp_lexer_consume_token (parser->lexer); + return true; +} + +/* Consume the entire function definition at point, including + function-try-block and mem-initializers. */ + +static void +cp_parser_skip_function_definition (cp_parser *parser) +{ + bool fn_try = cp_lexer_next_token_is_keyword (parser->lexer, RID_TRY); + if (fn_try) + cp_lexer_consume_token (parser->lexer); + if (cp_lexer_next_token_is (parser->lexer, CPP_COLON)) + do + { + cp_lexer_consume_token (parser->lexer); + cp_parser_mem_initializer_id (parser); + cp_parser_skip_to_end_of_balanced_tokens (parser); + } + while (cp_lexer_next_token_is (parser->lexer, CPP_COMMA)); + cp_parser_skip_to_end_of_block_or_statement (parser); + if (fn_try) + while (cp_lexer_next_token_is_keyword (parser->lexer, RID_CATCH)) + { + cp_lexer_consume_token (parser->lexer); + cp_parser_skip_to_end_of_balanced_tokens (parser); + cp_parser_skip_to_end_of_block_or_statement (parser); + } + /* Parse error if what we skipped didn't end with }. */ + if (cp_token *prev = cp_lexer_safe_previous_token (parser->lexer)) + if (!prev || prev->type != CPP_CLOSE_BRACE) + cp_parser_require (parser, CPP_CLOSE_BRACE, RT_CLOSE_BRACE); +} + +/* Skip past any base clause to the open brace of the class definition. */ + +static void +cp_parser_skip_base_clause (cp_parser *parser) +{ + while (true) + { + cp_token *t = cp_lexer_peek_token (parser->lexer); + switch (t->type) + { + case CPP_EOF: + case CPP_OPEN_BRACE: + goto done; + case CPP_OPEN_PAREN: + case CPP_OPEN_SPLICE: + case CPP_OPEN_SQUARE: + if (!cp_parser_skip_to_end_of_balanced_tokens (parser)) + goto done; + break; + default: + cp_lexer_consume_token (parser->lexer); + break; + } + } + done: + /* Parse error if this didn't get us to the {. */ + if (cp_lexer_next_token_is_not (parser->lexer, CPP_OPEN_BRACE)) + cp_parser_require (parser, CPP_OPEN_BRACE, RT_OPEN_BRACE); +} + /* Skip tokens until we have consumed an entire block, or until we have consumed a non-nested `;'. */ @@ -24121,6 +24196,23 @@ cp_parser_enum_specifier (cp_parser* parser) type = start_enum (identifier, type, underlying_type, attributes, scoped_enum_p, &is_new_type); + if (type != error_mark_node && COMPLETE_TYPE_P (type) + && redefinable_import_p (TYPE_NAME (type))) + { + /* We already have an imported definition of this enum; skip over + the redefinition and push any non-scoped enumerators. */ + if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) + { + cp_parser_skip_to_end_of_block_or_statement (parser); + if (!SCOPED_ENUM_P (type)) + for (tree l = TYPE_VALUES (type); l; l = TREE_CHAIN (l)) + pushdecl (TREE_VALUE (l)); + } + if (cp_parser_allow_gnu_extensions_p (parser)) + cp_parser_gnu_attributes_opt (parser); + goto done; + } + /* If the next token is not '{' it is an opaque-enum-specifier or an elaborated-type-specifier. */ if (cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)) @@ -24269,6 +24361,7 @@ cp_parser_enum_specifier (cp_parser* parser) finish_enum (type); } + done: if (nested_name_specifier) { if (CLASS_TYPE_P (nested_name_specifier)) @@ -29854,11 +29947,17 @@ cp_parser_class_specifier (cp_parser* parser) &nested_name_specifier_p); /* If the class-head was a semantic disaster, skip the entire body of the class. */ - if (!type) + if (!type + /* Or if the class is already complete from an import, skip the + redefinition. */ + || (CLASS_TYPE_P (type) && COMPLETE_TYPE_P (type))) { cp_parser_skip_to_end_of_block_or_statement (parser); pop_deferring_access_checks (); - return error_mark_node; + /* Consume trailing attributes like below. */ + if (cp_parser_allow_gnu_extensions_p (parser)) + cp_parser_gnu_attributes_opt (parser); + return type ? type : error_mark_node; } /* Look for the `{'. */ @@ -30835,12 +30934,19 @@ cp_parser_class_head (cp_parser* parser, if (type != error_mark_node && (COMPLETE_TYPE_P (type) || TYPE_BEING_DEFINED (type))) { - auto_diagnostic_group d; - error_at (type_start_token->location, "redefinition of %q#T", - type); - inform (location_of (type), "previous definition of %q#T", - type); - type = NULL_TREE; + if (redefinable_import_p (TYPE_NAME (type))) + /* Redefinition of an imported type is OK under P1811; + cp_parser_class_specifier will skip the body. */ + cp_parser_skip_base_clause (parser); + else + { + auto_diagnostic_group d; + error_at (type_start_token->location, "redefinition of %q#T", + type); + inform (location_of (type), "previous definition of %q#T", + type); + type = NULL_TREE; + } goto done; } else if (type == error_mark_node) @@ -36265,13 +36371,13 @@ cp_parser_function_definition_from_specifiers_and_declarator if (!success_p) { /* Skip the entire function. */ - cp_parser_skip_to_end_of_block_or_statement (parser); + cp_parser_skip_function_definition (parser); fn = error_mark_node; } else if (DECL_INITIAL (current_function_decl) != error_mark_node) { - /* Seen already, skip it. An error message has already been output. */ - cp_parser_skip_to_end_of_block_or_statement (parser); + /* start_preparsed_function exited early, also skip parsing. */ + cp_parser_skip_function_definition (parser); fn = current_function_decl; current_function_decl = NULL_TREE; /* If this is a function from a class, pop the nested class. */ diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index fd60878ef2a6..945554663a2c 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -6687,7 +6687,7 @@ redeclare_class_template (tree type, tree parms, tree cons) } } - if (!merge_default_template_args (parms, tmpl_parms, /*class_p=*/true)) + if (!merge_default_template_args (parms, tmpl_parms, tmpl)) return false; tree ci = get_constraints (tmpl); diff --git a/gcc/testsuite/g++.dg/modules/compile-std1.C b/gcc/testsuite/g++.dg/modules/compile-std1.C index 1de9ea789769..069ec32e8581 100644 --- a/gcc/testsuite/g++.dg/modules/compile-std1.C +++ b/gcc/testsuite/g++.dg/modules/compile-std1.C @@ -1,5 +1,9 @@ +// Test both that --compile-std-module works and that P1811 redefinition works +// for all of libstdc++. + // { dg-additional-options "-fmodules --compile-std-module -g -O" } -// { dg-additional-options "-flang-info-include-translate" } +// { dg-additional-options "-fno-module-include-translate" } +// { dg-additional-options "-fno-module-lazy" } // { dg-do compile { target c++20 } } // { dg-module-cmi std } // { dg-module-cmi std.compat } @@ -7,8 +11,7 @@ import std; import std.compat; -#include <vector> // { dg-message "translated to import" } -import <bits/stdc++.h>; +#include <bits/stdc++.h> void f() { diff --git a/gcc/testsuite/g++.dg/modules/default-arg-4_b.C b/gcc/testsuite/g++.dg/modules/default-arg-4_b.C index 5800ea208072..fcc07d31aaa0 100644 --- a/gcc/testsuite/g++.dg/modules/default-arg-4_b.C +++ b/gcc/testsuite/g++.dg/modules/default-arg-4_b.C @@ -1,24 +1,23 @@ // C++20 P1766R1 - Mitigating minor modules maladies -// FIXME mark as run once we no longer xfail below. -// { dg-module-do compile } +// { dg-module-do run } // { dg-additional-options "-fmodules-ts" } import M; int -foo (int i = 42) // { dg-bogus "default argument given for parameter 1 of 'int foo\\\(int\\\)'" "PR99000" { xfail *-*-* } } +foo (int i = 42) // { dg-bogus "default argument given for parameter 1 of 'int foo\\\(int\\\)'" "PR99000" } { return i; } -template <typename T, typename U = int> // { dg-bogus "redefinition of default argument for 'class U'" "PR99000" { xfail *-*-* } } +template <typename T, typename U = int> // { dg-bogus "redefinition of default argument for 'class U'" "PR99000" } int bar () { return sizeof (U); } -template <typename T, int N = 42> // { dg-bogus "redefinition of default argument for 'int N'" "PR99000" { xfail *-*-* } } +template <typename T, int N = 42> // { dg-bogus "redefinition of default argument for 'int N'" "PR99000" } int baz () { diff --git a/gcc/testsuite/g++.dg/modules/gmf-3_a.C b/gcc/testsuite/g++.dg/modules/gmf-3_a.C new file mode 100644 index 000000000000..bb28144ea139 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/gmf-3_a.C @@ -0,0 +1,14 @@ +// Test that a forward declaration of a class (template) in the global module +// combines with a reachable imported definition. + +// { dg-additional-options -fmodules-ts } + +module; +#include "gmf-3.h" + +// { dg-module-cmi M1 } +export module M1; + +// Keep A and B from being discarded per [module.global.frag] +export A f1(); +export B<int> f2(); diff --git a/gcc/testsuite/g++.dg/modules/gmf-3_b.C b/gcc/testsuite/g++.dg/modules/gmf-3_b.C new file mode 100644 index 000000000000..be5de6b38623 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/gmf-3_b.C @@ -0,0 +1,7 @@ +// { dg-additional-options -fmodules-ts } + +import M1; + +struct A a; +template <class T> struct B; +B<int> b; diff --git a/gcc/testsuite/g++.dg/modules/gmf-4_a.C b/gcc/testsuite/g++.dg/modules/gmf-4_a.C new file mode 100644 index 000000000000..c1e08da16ada --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/gmf-4_a.C @@ -0,0 +1,13 @@ +// Test P1811 redefinition of class and class template + +// { dg-additional-options -fmodules-ts } + +module; +#include "gmf-3.h" + +// { dg-module-cmi M1 } +export module M1; + +// Keep A and B from being discarded per [module.global.frag] +export A f1(); +export B<int> f2(); diff --git a/gcc/testsuite/g++.dg/modules/gmf-4_b.C b/gcc/testsuite/g++.dg/modules/gmf-4_b.C new file mode 100644 index 000000000000..6fb01f83d94e --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/gmf-4_b.C @@ -0,0 +1,12 @@ +// { dg-additional-options -fmodules-ts } + +import M1; + +// Force lazy loading of A since gmf-3 does. +auto x1 = f1(); +auto x2 = f2(); + +#include "gmf-3.h" + +A a; +B<int> b; diff --git a/gcc/testsuite/g++.dg/modules/hello-1_b.C b/gcc/testsuite/g++.dg/modules/hello-1_b.C index b7d78c476c66..624af70ed187 100644 --- a/gcc/testsuite/g++.dg/modules/hello-1_b.C +++ b/gcc/testsuite/g++.dg/modules/hello-1_b.C @@ -1,7 +1,7 @@ -// { dg-additional-options -fmodules-ts } +// { dg-additional-options "-fmodules -fno-module-lazy" } -#include <string_view> import hello; +#include <string_view> int main (void) { greeter ("world"); diff --git a/gcc/testsuite/g++.dg/modules/p1811-1_a.C b/gcc/testsuite/g++.dg/modules/p1811-1_a.C new file mode 100644 index 000000000000..608ea10a6db7 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-1_a.C @@ -0,0 +1,11 @@ +// Test P1811 redefinition of a function-try-block. + +// { dg-additional-options "-fmodules" } + +module; +#include "p1811-1.h" + +// { dg-module-cmi M } +export module M; + +export inline auto pf = &f; diff --git a/gcc/testsuite/g++.dg/modules/p1811-1_b.C b/gcc/testsuite/g++.dg/modules/p1811-1_b.C new file mode 100644 index 000000000000..46b18f19bb00 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-1_b.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fmodules" } + +import M; + +auto p = pf; +#include "p1811-1.h" + +int after; diff --git a/gcc/testsuite/g++.dg/modules/p1811-2_a.C b/gcc/testsuite/g++.dg/modules/p1811-2_a.C new file mode 100644 index 000000000000..6b816cb47e8f --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-2_a.C @@ -0,0 +1,11 @@ +// Test P1811 redefinition of a qualified enum. + +// { dg-additional-options "-fmodules" } + +module; +#include "p1811-2.h" + +// { dg-module-cmi M } +export module M; + +export C make_c (); diff --git a/gcc/testsuite/g++.dg/modules/p1811-2_b.C b/gcc/testsuite/g++.dg/modules/p1811-2_b.C new file mode 100644 index 000000000000..5ea46095c35e --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-2_b.C @@ -0,0 +1,9 @@ +// { dg-additional-options "-fmodules" } + +import M; + +auto imported_c = make_c (); +using C = decltype (imported_c); +enum C::E : int { e }; + +int after; diff --git a/gcc/testsuite/g++.dg/modules/p1811-3_a.C b/gcc/testsuite/g++.dg/modules/p1811-3_a.C new file mode 100644 index 000000000000..7444dd131234 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-3_a.C @@ -0,0 +1,11 @@ +// Test recovery from an incomplete redundant class definition. + +// { dg-additional-options "-fmodules" } + +module; +#include "p1811-3.h" + +// { dg-module-cmi M } +export module M; + +export A make_a (); diff --git a/gcc/testsuite/g++.dg/modules/p1811-3_b.C b/gcc/testsuite/g++.dg/modules/p1811-3_b.C new file mode 100644 index 000000000000..5985dedc5aa4 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-3_b.C @@ -0,0 +1,8 @@ +// { dg-additional-options "-fmodules" } +// { dg-timeout-factor 0.05 } + +import M; + +auto imported_a = make_a (); +struct A : ( // { dg-error "expected" } +// { dg-prune-output "at end of input" } diff --git a/gcc/testsuite/g++.dg/modules/p1811-4_a.C b/gcc/testsuite/g++.dg/modules/p1811-4_a.C new file mode 100644 index 000000000000..10c2a6ffdd4b --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-4_a.C @@ -0,0 +1,8 @@ +// Definitions attached to a named module cannot be repeated. + +// { dg-additional-options "-fmodules" } +// { dg-module-cmi P:a } + +export module P:a; + +export struct A { }; diff --git a/gcc/testsuite/g++.dg/modules/p1811-4_b.C b/gcc/testsuite/g++.dg/modules/p1811-4_b.C new file mode 100644 index 000000000000..5d31bb1883bc --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-4_b.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fmodules" } +// { dg-module-cmi !P:b } + +module P:b; +import :a; + +struct A { }; // { dg-error "redefinition" } diff --git a/gcc/testsuite/g++.dg/modules/p1811-5_a.C b/gcc/testsuite/g++.dg/modules/p1811-5_a.C new file mode 100644 index 000000000000..1b87579e8dc3 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-5_a.C @@ -0,0 +1,11 @@ +// Test that P1811 does not allow multiple definitions in one TU. + +// { dg-additional-options "-fmodules" } + +module; +#include "p1811-5.h" + +// { dg-module-cmi M } +export module M; + +export A make_a (); diff --git a/gcc/testsuite/g++.dg/modules/p1811-5_b.C b/gcc/testsuite/g++.dg/modules/p1811-5_b.C new file mode 100644 index 000000000000..63c86631c7b4 --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-5_b.C @@ -0,0 +1,7 @@ +// { dg-additional-options "-fmodules" } + +import M; + +auto imported_a = make_a (); +struct A { }; +struct A { }; // { dg-error "redefinition" "" { xfail *-*-* } } diff --git a/gcc/testsuite/g++.dg/modules/p1811-6_a.C b/gcc/testsuite/g++.dg/modules/p1811-6_a.C new file mode 100644 index 000000000000..83180c3bcd6a --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-6_a.C @@ -0,0 +1,12 @@ +// Test that P1811 does not allow changing the kind of a template. + +// { dg-additional-options "-fmodules" } + +module; +#include "p1811-6.h" + +// { dg-module-cmi M } +export module M; + +export A<int> make_a (); +export B<int> make_b (); diff --git a/gcc/testsuite/g++.dg/modules/p1811-6_b.C b/gcc/testsuite/g++.dg/modules/p1811-6_b.C new file mode 100644 index 000000000000..a815da5c9b9f --- /dev/null +++ b/gcc/testsuite/g++.dg/modules/p1811-6_b.C @@ -0,0 +1,9 @@ +// { dg-additional-options "-fmodules" } + +import M; + +auto imported_a = make_a (); +auto imported_b = make_b (); + +template <class T> using A = T; // { dg-error "conflicting declaration" } +template <class T> int B; // { dg-error "conflicting declaration" } diff --git a/gcc/testsuite/g++.dg/modules/pr122053_b.C b/gcc/testsuite/g++.dg/modules/pr122053_b.C index a7084b74040a..e7038fa07230 100644 --- a/gcc/testsuite/g++.dg/modules/pr122053_b.C +++ b/gcc/testsuite/g++.dg/modules/pr122053_b.C @@ -3,7 +3,7 @@ // Test we don't ICE when redefining a type coming from an import. import M; -struct mytime { // { dg-bogus "conflicting" "PR99000" { xfail *-*-* } } +struct mytime { // { dg-bogus "conflicting" "PR99000" } long a, b; }; -mytime m = foo(); // { dg-bogus "" "PR99000" { xfail *-*-* } } +mytime m = foo(); // { dg-bogus "" "PR99000" }
