Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -183,6 +183,9 @@ initializer is not allowed this is now diagnosed as an error. - Clang now correctly emits symbols for implicitly instantiated constexpr template function. Fixes `Issue 55560 `_. +- Clang now checks ODR violations when merging concepts from different modules. + Note that this is expected to break existing code, and is done so intentionally. + Fixes `Issue 56310 `_. Improvements to Clang's diagnostics ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Index: clang/lib/AST/ASTContext.cpp =================================================================== --- clang/lib/AST/ASTContext.cpp +++ clang/lib/AST/ASTContext.cpp @@ -6561,6 +6561,20 @@ // and patterns match. if (const auto *TemplateX = dyn_cast(X)) { const auto *TemplateY = cast(Y); + + // ConceptDecl wouldn't be the same if their constraint expression differs. + if (const auto *ConceptX = dyn_cast(X)) { + const auto *ConceptY = cast(Y); + const Expr *XCE = ConceptX->getConstraintExpr(); + const Expr *YCE = ConceptY->getConstraintExpr(); + assert(XCE && YCE && "ConceptDecl without constraint expression?"); + llvm::FoldingSetNodeID XID, YID; + XCE->Profile(XID, *this, /*Canonical=*/true); + YCE->Profile(YID, *this, /*Canonical=*/true); + if (XID != YID) + return false; + } + return isSameEntity(TemplateX->getTemplatedDecl(), TemplateY->getTemplatedDecl()) && isSameTemplateParameterList(TemplateX->getTemplateParameters(), Index: clang/test/Modules/concept_differ.cpp =================================================================== --- /dev/null +++ clang/test/Modules/concept_differ.cpp @@ -0,0 +1,35 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -x c++ -std=c++20 -fmodules -fmodules-cache-path=%t -fmodule-map-file=%t/module.map %t/foo.cpp -verify + +//--- module.map +module "foo" { + export * + header "foo.h" +} +module "bar" { + export * + header "bar.h" +} + +//--- foo.h +template +concept A = true; + +//--- bar.h +template +concept A = false; + +//--- foo.cpp +#include "bar.h" +#include "foo.h" + +template void foo() requires A {} // expected-error 1+{{reference to 'A' is ambiguous}} + // expected-note@* 1+{{candidate found by name lookup}} + +int main() { + foo(); + return 0; +} Index: clang/test/Modules/concept_differ.cppm =================================================================== --- /dev/null +++ clang/test/Modules/concept_differ.cppm @@ -0,0 +1,39 @@ +// RUN: rm -rf %t +// RUN: mkdir %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -x c++ -std=c++20 %t/A.cppm -I%t -emit-module-interface -o %t/A.pcm +// RUN: %clang_cc1 -x c++ -std=c++20 %t/B.cppm -I%t -emit-module-interface -o %t/B.pcm +// RUN: %clang_cc1 -x c++ -std=c++20 -fprebuilt-module-path=%t %t/foo.cpp -verify + +//--- foo.h +template +concept A = true; + +//--- bar.h +template +concept A = false; + +//--- A.cppm +module; +#include "foo.h" +export module A; +export using ::A; + +//--- B.cppm +module; +#include "bar.h" +export module B; +export using ::A; + +//--- foo.cpp +import A; +import B; + +template void foo() requires A {} // expected-error 1+{{reference to 'A' is ambiguous}} + // expected-note@* 1+{{candidate found by name lookup}} + +int main() { + foo(); + return 0; +}