diff --git a/clang/include/clang/AST/DeclBase.h b/clang/include/clang/AST/DeclBase.h --- a/clang/include/clang/AST/DeclBase.h +++ b/clang/include/clang/AST/DeclBase.h @@ -607,6 +607,20 @@ return getModuleOwnershipKind() == ModuleOwnershipKind::ModulePrivate; } + /// Whether this declaration was exported in a lexical context. + /// e.g.: + /// + /// export namespace A { + /// void f1(); // isInExportDeclContext() == true + /// } + /// void A::f1(); // isInExportDeclContext() == false + /// + /// namespace B { + /// void f2(); // isInExportDeclContext() == false + /// } + /// export void B::f2(); // isInExportDeclContext() == true + bool isInExportDeclContext() const; + /// Return true if this declaration has an attribute which acts as /// definition of the entity, such as 'alias' or 'ifunc'. bool hasDefiningAttr() const; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7803,6 +7803,11 @@ "%select{ or namespace|, namespace, or enumeration}1">; def err_invalid_declarator_scope : Error<"cannot define or redeclare %0 here " "because namespace %1 does not enclose namespace %2">; +def err_export_non_namespace_scope_name : Error< + "cannot export %0 as it is not at namespace scope">; +def err_redeclaration_non_exported : Error < + "cannot export redeclaration %0 here since the previous declaration is not " + "exported">; def err_invalid_declarator_global_scope : Error< "definition or redeclaration of %0 cannot name the global scope">; def err_invalid_declarator_in_function : Error< diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4320,6 +4320,8 @@ bool ConsiderLinkage, bool AllowInlineNamespace); bool CheckRedeclarationModuleOwnership(NamedDecl *New, NamedDecl *Old); + bool CheckRedeclarationExported(NamedDecl *New, NamedDecl *Old); + bool CheckRedeclarationInModule(NamedDecl *New, NamedDecl *Old); void DiagnoseAmbiguousLookup(LookupResult &Result); //@} diff --git a/clang/lib/AST/DeclBase.cpp b/clang/lib/AST/DeclBase.cpp --- a/clang/lib/AST/DeclBase.cpp +++ b/clang/lib/AST/DeclBase.cpp @@ -995,6 +995,15 @@ return true; } +bool Decl::isInExportDeclContext() const { + const DeclContext *DC = getLexicalDeclContext(); + + while (DC && !isa(DC)) + DC = DC->getLexicalParent(); + + return DC && isa(DC); +} + static Decl::Kind getKind(const Decl *D) { return D->getKind(); } static Decl::Kind getKind(const DeclContext *DC) { return DC->getDeclKind(); } diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -1628,6 +1628,39 @@ return false; } +// [module.interface]p6: +// A redeclaration of an entity X is implicitly exported if X was introduced by +// an exported declaration; otherwise it shall not be exported. +bool Sema::CheckRedeclarationExported(NamedDecl *New, NamedDecl *Old) { + bool IsNewExported = New->isInExportDeclContext(); + bool IsOldExported = Old->isInExportDeclContext(); + + // It should be irrevelant if both of them are not exported. + if (!IsNewExported && !IsOldExported) + return false; + + if (IsOldExported) + return false; + + assert(IsNewExported); + + Diag(New->getLocation(), diag::err_redeclaration_non_exported) << New; + Diag(Old->getLocation(), diag::note_previous_declaration); + return true; +} + +// A wrapper function for checking the semantic restrictions of +// a redeclaration within a module. +bool Sema::CheckRedeclarationInModule(NamedDecl *New, NamedDecl *Old) { + if (CheckRedeclarationModuleOwnership(New, Old)) + return true; + + if (CheckRedeclarationExported(New, Old)) + return true; + + return false; +} + static bool isUsingDecl(NamedDecl *D) { return isa(D) || isa(D) || @@ -3390,7 +3423,7 @@ } } - if (CheckRedeclarationModuleOwnership(New, Old)) + if (CheckRedeclarationInModule(New, Old)) return true; if (!getLangOpts().CPlusPlus) { @@ -4269,7 +4302,7 @@ return New->setInvalidDecl(); } - if (CheckRedeclarationModuleOwnership(New, Old)) + if (CheckRedeclarationInModule(New, Old)) return; // Variables with external linkage are analyzed in FinalizeDeclaratorGroup. @@ -5759,7 +5792,15 @@ else if (isa(Cur)) Diag(Loc, diag::err_invalid_declarator_in_block) << Name << SS.getRange(); - else + else if (isa(Cur)) { + if (!isa(DC)) + Diag(Loc, diag::err_export_non_namespace_scope_name) + << Name << SS.getRange(); + else + // The cases that DC is not NamespaceDecl should be handled in + // CheckRedeclarationExported. + return false; + } else Diag(Loc, diag::err_invalid_declarator_scope) << Name << cast(Cur) << cast(DC) << SS.getRange(); @@ -16535,7 +16576,7 @@ SetMemberAccessSpecifier(New, PrevDecl, AS); if (PrevDecl) - CheckRedeclarationModuleOwnership(New, PrevDecl); + CheckRedeclarationInModule(New, PrevDecl); if (TUK == TUK_Definition && (!SkipBody || !SkipBody->ShouldSkip)) New->startDefinition(); diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -13012,7 +13012,7 @@ NewDecl->setInvalidDecl(); else if (OldDecl) { NewDecl->setPreviousDecl(OldDecl); - CheckRedeclarationModuleOwnership(NewDecl, OldDecl); + CheckRedeclarationInModule(NewDecl, OldDecl); } NewND = NewDecl; diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -2063,7 +2063,7 @@ } if (PrevClassTemplate) - CheckRedeclarationModuleOwnership(NewTemplate, PrevClassTemplate); + CheckRedeclarationInModule(NewTemplate, PrevClassTemplate); if (Invalid) { NewTemplate->setInvalidDecl(); diff --git a/clang/test/CXX/module/module.interface/p2-2.cpp b/clang/test/CXX/module/module.interface/p2-2.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/module/module.interface/p2-2.cpp @@ -0,0 +1,37 @@ +// The intention of this file to check we could only export declarations in namesapce scope. +// +// RUN: %clang_cc1 -std=c++20 %s -verify + +export module X; + +export template +struct X { + struct iterator { + T node; + }; + void foo() {} + template + U bar(); +}; + +export template X::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}} +export template void X::foo(); // expected-error {{cannot export 'foo' as it is not at namespace scope}} +export template template U X::bar(); // expected-error {{cannot export 'bar' as it is not at namespace scope}} + +export struct Y { + struct iterator { + int node; + }; + void foo() {} + template + U bar(); +}; + +export struct Y::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}} +export void Y::foo(); // expected-error {{cannot export 'foo' as it is not at namespace scope}} +export template U Y::bar(); // expected-error {{cannot export 'bar' as it is not at namespace scope}} + +export { + template X::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}} + struct Y::iterator; // expected-error {{cannot export 'iterator' as it is not at namespace scope}} +} diff --git a/clang/test/CXX/module/module.interface/p6.cpp b/clang/test/CXX/module/module.interface/p6.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CXX/module/module.interface/p6.cpp @@ -0,0 +1,93 @@ +// The test is check we couldn't export a redeclaration which isn't exported previously and +// check it is OK to redeclare no matter exported nor not if is the previous declaration is exported. +// RUN: %clang_cc1 -std=c++20 %s -verify + +export module X; + +struct S { // expected-note {{previous declaration is here}} + int n; +}; +typedef S S; +export typedef S S; // OK, does not redeclare an entity +export struct S; // expected-error {{cannot export redeclaration 'S' here since the previous declaration is not exported}} + +namespace A { +struct X; // expected-note {{previous declaration is here}} +export struct Y; +} // namespace A + +namespace A { +export struct X; // expected-error {{cannot export redeclaration 'X' here since the previous declaration is not exported}} +export struct Y; // OK +struct Z; // expected-note {{previous declaration is here}} +export struct Z; // expected-error {{cannot export redeclaration 'Z' here since the previous declaration is not exported}} +} // namespace A + +namespace A { +struct B; // expected-note {{previous declaration is here}} +struct C {}; // expected-note {{previous declaration is here}} +} // namespace A + +namespace A { +export struct B {}; // expected-error {{cannot export redeclaration 'B' here since the previous declaration is not exported}} +export struct C; // expected-error {{cannot export redeclaration 'C' here since the previous declaration is not exported}} +} // namespace A + +template +struct TemplS; // expected-note {{previous declaration is here}} + +export template +struct TemplS {}; // expected-error {{cannot export redeclaration 'TemplS' here since the previous declaration is not exported}} + +template +struct TemplS2; // expected-note {{previous declaration is here}} + +export template +struct TemplS2 {}; // expected-error {{cannot export redeclaration 'TemplS2' here since the previous declaration is not exported}} + +void baz(); // expected-note {{previous declaration is here}} +export void baz(); // expected-error {{cannot export redeclaration 'baz' here since the previous declaration is not exported}} + +namespace A { +export void foo(); +void bar(); // expected-note {{previous declaration is here}} +export void bar(); // expected-error {{cannot export redeclaration 'bar' here since the previous declaration is not exported}} +void f1(); // expected-note {{previous declaration is here}} +} // namespace A + +// OK +// +// [module.interface]/p6 +// A redeclaration of an entity X is implicitly exported if X was introduced by an exported declaration +void A::foo(); + +// The compiler couldn't export A::f1() here since A::f1() is declared above without exported. +// See [module.interface]/p6 for details. +export void A::f1(); // expected-error {{cannot export redeclaration 'f1' here since the previous declaration is not exported}} + +template +void TemplFunc(); // expected-note {{previous declaration is here}} + +export template +void TemplFunc() { // expected-error {{cannot export redeclaration 'TemplFunc' here since the previous declaration is not exported}} +} + +namespace A { +template +void TemplFunc2(); // expected-note {{previous declaration is here}} +export template +void TemplFunc2() {} // expected-error {{cannot export redeclaration 'TemplFunc2' here since the previous declaration is not exported}} +template +void TemplFunc3(); // expected-note {{previous declaration is here}} +} // namespace A + +export template +void A::TemplFunc3() {} // expected-error {{cannot export redeclaration 'TemplFunc3' here since the previous declaration is not exported}} + +int var; // expected-note {{previous declaration is here}} +export int var; // expected-error {{cannot export redeclaration 'var' here since the previous declaration is not exported}} + +template +T TemplVar; // expected-note {{previous declaration is here}} +export template +T TemplVar; // expected-error {{cannot export redeclaration 'TemplVar' here since the previous declaration is not exported}}