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 @@ -8180,6 +8180,9 @@ Scope *S, MultiTemplateParamsArg TemplateParameterLists, IdentifierInfo *Name, SourceLocation NameLoc, Expr *ConstraintExpr); + void CheckConceptRedefinition(ConceptDecl *NewDecl, LookupResult &Previous, + bool &AddToScope); + RequiresExprBodyDecl * ActOnStartRequiresExpr(SourceLocation RequiresKWLoc, ArrayRef LocalParameters, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -6512,6 +6512,7 @@ // and patterns match. if (const auto *TemplateX = dyn_cast(X)) { const auto *TemplateY = cast(Y); + // FIXME: for C++20 concepts, check their requirements are the same. return isSameEntity(TemplateX->getTemplatedDecl(), TemplateY->getTemplatedDecl()) && isSameTemplateParameterList(TemplateX->getTemplateParameters(), 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 @@ -8655,23 +8655,53 @@ // Check for conflicting previous declaration. DeclarationNameInfo NameInfo(NewDecl->getDeclName(), NameLoc); LookupResult Previous(*this, NameInfo, LookupOrdinaryName, - ForVisibleRedeclaration); + forRedeclarationInCurContext()); LookupName(Previous, S); - FilterLookupForScope(Previous, DC, S, /*ConsiderLinkage=*/false, /*AllowInlineNamespace*/false); - if (!Previous.empty()) { - auto *Old = Previous.getRepresentativeDecl(); - Diag(NameLoc, isa(Old) ? diag::err_redefinition : - diag::err_redefinition_different_kind) << NewDecl->getDeclName(); - Diag(Old->getLocation(), diag::note_previous_definition); - } + bool AddToScope = true; + CheckConceptRedefinition(NewDecl, Previous, AddToScope); ActOnDocumentableDecl(NewDecl); - PushOnScopeChains(NewDecl, S); + if (AddToScope) + PushOnScopeChains(NewDecl, S); return NewDecl; } +void Sema::CheckConceptRedefinition(ConceptDecl *NewDecl, + LookupResult &Previous, bool &AddToScope) { + AddToScope = true; + if (Previous.empty()) + return; + // Check if there is a concept declaration we can merge with. + auto *PrevDef = + Previous.isSingleResult() ? Previous.getAsSingle() : nullptr; + if (PrevDef && !hasVisibleDefinition(PrevDef) && + Context.isSameEntity(NewDecl, PrevDef)) { + Context.setPrimaryMergedDecl(NewDecl, PrevDef); + makeMergedDefinitionVisible(PrevDef); + AddToScope = false; + return; + } + // Filter out non-visible declaration to avoid spurious redefinition errors. + auto F = Previous.makeFilter(); + while (F.hasNext()) { + if (!isVisible(F.next())) { + F.erase(); + } + } + F.done(); + // We report redefinition if the lookup is not empty after filters. + if (Previous.empty()) + return; + auto *Old = Previous.getRepresentativeDecl(); + Diag(NewDecl->getLocation(), isa(Old) + ? diag::err_redefinition + : diag::err_redefinition_different_kind) + << NewDecl->getDeclName(); + Diag(Old->getLocation(), diag::note_previous_definition); +} + /// \brief Strips various properties off an implicit instantiation /// that has just been explicitly specialized. static void StripImplicitInstantiation(NamedDecl *D) { diff --git a/clang/test/Modules/Inputs/merge-concepts/concepts.h b/clang/test/Modules/Inputs/merge-concepts/concepts.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/merge-concepts/concepts.h @@ -0,0 +1,9 @@ +#ifndef SAMEAS_CONCEPTS_H_ +#define SAMEAS_CONCEPTS_H_ + +#include "same_as.h" + +template +concept ConflictingConcept = true; + +#endif // SAMEAS_CONCEPTS_H diff --git a/clang/test/Modules/Inputs/merge-concepts/conflicting.h b/clang/test/Modules/Inputs/merge-concepts/conflicting.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/merge-concepts/conflicting.h @@ -0,0 +1,7 @@ +#ifndef CONFLICTING_H +#define CONFLICTING_H + +template +concept ConflictingConcept = true; + +#endif // CONFLICTING_H diff --git a/clang/test/Modules/Inputs/merge-concepts/format.h b/clang/test/Modules/Inputs/merge-concepts/format.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/merge-concepts/format.h @@ -0,0 +1,11 @@ +#ifndef FORMAT_H +#define FORMAT_H + +#include "concepts.h" +#include "same_as.h" + +template void foo() + requires same_as +{} + +#endif // FORMAT_H diff --git a/clang/test/Modules/Inputs/merge-concepts/modules.map b/clang/test/Modules/Inputs/merge-concepts/modules.map new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/merge-concepts/modules.map @@ -0,0 +1,15 @@ +module "library" { + export * + module "concepts" { + export * + header "concepts.h" + } + module "format" { + export * + header "format.h" + } + module "conflicting" { + export * + header "conflicting.h" + } +} diff --git a/clang/test/Modules/Inputs/merge-concepts/same_as.h b/clang/test/Modules/Inputs/merge-concepts/same_as.h new file mode 100644 --- /dev/null +++ b/clang/test/Modules/Inputs/merge-concepts/same_as.h @@ -0,0 +1,7 @@ +#ifndef SAME_AS_H +#define SAME_AS_H + +template +concept same_as = __is_same(T, U); + +#endif // SAME_AS_H diff --git a/clang/test/Modules/merge-concepts.cpp b/clang/test/Modules/merge-concepts.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Modules/merge-concepts.cpp @@ -0,0 +1,22 @@ +// RUN: rm -rf %t.dir +// RUN: mkdir %t.dir +// +// RUN: %clang_cc1 -xc++ -std=c++20 -fmodules -fmodule-name=library \ +// RUN: -emit-module %S/Inputs/merge-concepts/modules.map \ +// RUN: -o %t.dir/module.pcm +// +// +// RUN: %clang_cc1 -xc++ -std=c++20 -fmodules -fmodule-file=%t.dir/module.pcm \ +// RUN: -fmodule-map-file=%S/Inputs/merge-concepts/modules.map \ +// RUN: -I%S/Inputs/merge-concepts \ +// RUN: -fsyntax-only -verify %s + +#include "concepts.h" +#include "conflicting.h" +#include "format.h" + +template void foo() + requires same_as +{} +ConflictingConcept auto x = 10; // expected-error {{reference to 'ConflictingConcept' is ambiguous}} + // expected-note@* 2 {{candidate}} diff --git a/clang/test/Modules/merge-concepts.m b/clang/test/Modules/merge-concepts.m new file mode 100644 --- /dev/null +++ b/clang/test/Modules/merge-concepts.m @@ -0,0 +1,9 @@ +// RUN: rm -rf %t.dir +// RUN: mkdir %t.dir +// +// RUN: %clang_cc1 -xc++ -std=c++20 -fmodules -fmodule-name=library \ +// RUN: -emit-module %S/Inputs/merge-concepts/modules.map \ +// RUN: -o %t.dir/module.pcm +// +// Note that this file merely checks the module compiles. The contents of this +// file are never read by the compiler.