[AST] Accept identical TypeConstraint referring to other template

parameters.

The current implementation to judge the similarity of TypeConstraint in
ASTContext::isSameTemplateParameter is problematic, it couldn't handle
the following case:

```C++
template <__integer_like _Tp, C<_Tp> Sentinel>
constexpr _Tp operator()(_Tp &&__t, Sentinel &&last) const {
    return __t;
}
```

When we see 2 such declarations from different modules, we would judge
their similarity by `ASTContext::isSame*` methods. But problems come for
the TypeConstraint. Originally, we would profile each argument one by
one. But it is not right. Since the profiling result of `_Tp` would
refer to two different template type declarations. So it would get
different results. It is right since the `_Tp` in different modules
refers to different declarations indeed. So the same declaration in
different modules would meet incorrect our-checking results.

It is not the thing we want. We want to know if the TypeConstraint have
the same expression.

Reviewer: vsapsai, ilya-biryukov

Differential Revision: https://reviews.llvm.org/D129068
This commit is contained in:
Chuanqi Xu 2022-07-12 23:47:37 +08:00
parent 5d41fe0768
commit f6b0ae144e
3 changed files with 112 additions and 50 deletions

View File

@ -130,6 +130,7 @@ class TemplateDecl;
class TemplateParameterList; class TemplateParameterList;
class TemplateTemplateParmDecl; class TemplateTemplateParmDecl;
class TemplateTypeParmDecl; class TemplateTypeParmDecl;
class TypeConstraint;
class UnresolvedSetIterator; class UnresolvedSetIterator;
class UsingShadowDecl; class UsingShadowDecl;
class VarTemplateDecl; class VarTemplateDecl;
@ -2664,6 +2665,18 @@ public:
/// that they may be used in declarations of the same template. /// that they may be used in declarations of the same template.
bool isSameTemplateParameter(const NamedDecl *X, const NamedDecl *Y) const; bool isSameTemplateParameter(const NamedDecl *X, const NamedDecl *Y) const;
/// Determine whether two 'requires' expressions are similar enough that they
/// may be used in re-declarations.
///
/// Use of 'requires' isn't mandatory, works with constraints expressed in
/// other ways too.
bool isSameConstraintExpr(const Expr *XCE, const Expr *YCE) const;
/// Determine whether two type contraint are similar enough that they could
/// used in declarations of the same template.
bool isSameTypeConstraint(const TypeConstraint *XTC,
const TypeConstraint *YTC) const;
/// Determine whether two default template arguments are similar enough /// Determine whether two default template arguments are similar enough
/// that they may be used in declarations of the same template. /// that they may be used in declarations of the same template.
bool isSameDefaultTemplateArgument(const NamedDecl *X, bool isSameDefaultTemplateArgument(const NamedDecl *X,

View File

@ -6218,6 +6218,57 @@ bool ASTContext::hasSameTemplateName(const TemplateName &X,
getCanonicalTemplateName(Y).getAsVoidPointer(); getCanonicalTemplateName(Y).getAsVoidPointer();
} }
bool ASTContext::isSameConstraintExpr(const Expr *XCE, const Expr *YCE) const {
if (!XCE != !YCE)
return false;
if (!XCE)
return true;
llvm::FoldingSetNodeID XCEID, YCEID;
XCE->Profile(XCEID, *this, /*Canonical=*/true);
YCE->Profile(YCEID, *this, /*Canonical=*/true);
return XCEID == YCEID;
}
bool ASTContext::isSameTypeConstraint(const TypeConstraint *XTC,
const TypeConstraint *YTC) const {
if (!XTC != !YTC)
return false;
if (!XTC)
return true;
auto *NCX = XTC->getNamedConcept();
auto *NCY = YTC->getNamedConcept();
if (!NCX || !NCY || !isSameEntity(NCX, NCY))
return false;
if (XTC->hasExplicitTemplateArgs() != YTC->hasExplicitTemplateArgs())
return false;
if (XTC->hasExplicitTemplateArgs())
if (XTC->getTemplateArgsAsWritten()->NumTemplateArgs !=
YTC->getTemplateArgsAsWritten()->NumTemplateArgs)
return false;
// Compare slowly by profiling.
//
// We couldn't compare the profiling result for the template
// args here. Consider the following example in different modules:
//
// template <__integer_like _Tp, C<_Tp> Sentinel>
// constexpr _Tp operator()(_Tp &&__t, Sentinel &&last) const {
// return __t;
// }
//
// When we compare the profiling result for `C<_Tp>` in different
// modules, it will compare the type of `_Tp` in different modules.
// However, the type of `_Tp` in different modules refer to different
// types here naturally. So we couldn't compare the profiling result
// for the template args directly.
return isSameConstraintExpr(XTC->getImmediatelyDeclaredConstraint(),
YTC->getImmediatelyDeclaredConstraint());
}
bool ASTContext::isSameTemplateParameter(const NamedDecl *X, bool ASTContext::isSameTemplateParameter(const NamedDecl *X,
const NamedDecl *Y) const { const NamedDecl *Y) const {
if (X->getKind() != Y->getKind()) if (X->getKind() != Y->getKind())
@ -6229,32 +6280,8 @@ bool ASTContext::isSameTemplateParameter(const NamedDecl *X,
return false; return false;
if (TX->hasTypeConstraint() != TY->hasTypeConstraint()) if (TX->hasTypeConstraint() != TY->hasTypeConstraint())
return false; return false;
const TypeConstraint *TXTC = TX->getTypeConstraint(); return isSameTypeConstraint(TX->getTypeConstraint(),
const TypeConstraint *TYTC = TY->getTypeConstraint(); TY->getTypeConstraint());
if (!TXTC != !TYTC)
return false;
if (TXTC && TYTC) {
auto *NCX = TXTC->getNamedConcept();
auto *NCY = TYTC->getNamedConcept();
if (!NCX || !NCY || !isSameEntity(NCX, NCY))
return false;
if (TXTC->hasExplicitTemplateArgs() != TYTC->hasExplicitTemplateArgs())
return false;
if (TXTC->hasExplicitTemplateArgs()) {
auto *TXTCArgs = TXTC->getTemplateArgsAsWritten();
auto *TYTCArgs = TYTC->getTemplateArgsAsWritten();
if (TXTCArgs->NumTemplateArgs != TYTCArgs->NumTemplateArgs)
return false;
llvm::FoldingSetNodeID XID, YID;
for (auto &ArgLoc : TXTCArgs->arguments())
ArgLoc.getArgument().Profile(XID, X->getASTContext());
for (auto &ArgLoc : TYTCArgs->arguments())
ArgLoc.getArgument().Profile(YID, Y->getASTContext());
if (XID != YID)
return false;
}
}
return true;
} }
if (auto *TX = dyn_cast<NonTypeTemplateParmDecl>(X)) { if (auto *TX = dyn_cast<NonTypeTemplateParmDecl>(X)) {
@ -6279,19 +6306,7 @@ bool ASTContext::isSameTemplateParameterList(
if (!isSameTemplateParameter(X->getParam(I), Y->getParam(I))) if (!isSameTemplateParameter(X->getParam(I), Y->getParam(I)))
return false; return false;
const Expr *XRC = X->getRequiresClause(); return isSameConstraintExpr(X->getRequiresClause(), Y->getRequiresClause());
const Expr *YRC = Y->getRequiresClause();
if (!XRC != !YRC)
return false;
if (XRC) {
llvm::FoldingSetNodeID XRCID, YRCID;
XRC->Profile(XRCID, *this, /*Canonical=*/true);
YRC->Profile(YRCID, *this, /*Canonical=*/true);
if (XRCID != YRCID)
return false;
}
return true;
} }
bool ASTContext::isSameDefaultTemplateArgument(const NamedDecl *X, bool ASTContext::isSameDefaultTemplateArgument(const NamedDecl *X,
@ -6489,17 +6504,9 @@ bool ASTContext::isSameEntity(const NamedDecl *X, const NamedDecl *Y) const {
return false; return false;
} }
const Expr *XRC = FuncX->getTrailingRequiresClause(); if (!isSameConstraintExpr(FuncX->getTrailingRequiresClause(),
const Expr *YRC = FuncY->getTrailingRequiresClause(); FuncY->getTrailingRequiresClause()))
if (!XRC != !YRC)
return false; return false;
if (XRC) {
llvm::FoldingSetNodeID XRCID, YRCID;
XRC->Profile(XRCID, *this, /*Canonical=*/true);
YRC->Profile(YRCID, *this, /*Canonical=*/true);
if (XRCID != YRCID)
return false;
}
auto GetTypeAsWritten = [](const FunctionDecl *FD) { auto GetTypeAsWritten = [](const FunctionDecl *FD) {
// Map to the first declaration that we've already merged into this one. // Map to the first declaration that we've already merged into this one.

View File

@ -3,6 +3,7 @@
// RUN: split-file %s %t // RUN: split-file %s %t
// //
// RUN: %clang_cc1 -std=c++20 %t/A.cppm -emit-module-interface -o %t/A.pcm // RUN: %clang_cc1 -std=c++20 %t/A.cppm -emit-module-interface -o %t/A.pcm
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t -DDIFFERENT %t/B.cppm -verify
// RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t %t/B.cppm -verify // RUN: %clang_cc1 -std=c++20 -fprebuilt-module-path=%t -I%t %t/B.cppm -verify
//--- foo.h //--- foo.h
@ -18,6 +19,9 @@ concept __integer_like = true;
template <class _Tp> template <class _Tp>
concept __member_size = requires(_Tp &&t) { t.size(); }; concept __member_size = requires(_Tp &&t) { t.size(); };
template <class First, class Second>
concept C = requires(First x, Second y) { x + y; };
struct A { struct A {
public: public:
template <Range T> template <Range T>
@ -29,6 +33,29 @@ struct __fn {
constexpr __integer_like auto operator()(_Tp&& __t) const { constexpr __integer_like auto operator()(_Tp&& __t) const {
return __t.size(); return __t.size();
} }
template <__integer_like _Tp, C<_Tp> Sentinel>
constexpr _Tp operator()(_Tp &&__t, Sentinel &&last) const {
return __t;
}
template <template <class> class H, class S, C<H<S>> Sentinel>
constexpr H<S> operator()(H<S> &&__s, Sentinel &&last) const {
return __s;
}
// Tests that we could find different concept definition indeed.
#ifndef DIFFERENT
template <__integer_like _Tp, __integer_like _Up, C<_Tp> Sentinel>
constexpr _Tp operator()(_Tp &&__t, _Up _u, Sentinel &&last) const {
return __t;
}
#else
template <__integer_like _Tp, __integer_like _Up, C<_Up> Sentinel>
constexpr _Tp operator()(_Tp &&__t, _Up _u, Sentinel &&last) const {
return __t;
}
#endif
}; };
#endif #endif
@ -38,12 +65,23 @@ module;
export module A; export module A;
//--- B.cppm //--- B.cppm
// expected-no-diagnostics
module; module;
#include "foo.h" #include "foo.h"
export module B; export module B;
import A; import A;
#ifdef DIFFERENT
// expected-error@foo.h:41 {{'__fn::operator()' from module 'A.<global>' is not present in definition of '__fn' provided earlier}}
// expected-note@* 1+{{declaration of 'operator()' does not match}}
#else
// expected-no-diagnostics
#endif
template <class T>
struct U {
auto operator+(U) { return 0; }
};
void foo() { void foo() {
A a; A a;
struct S { struct S {
@ -51,4 +89,8 @@ void foo() {
auto operator+(S s) { return 0; } auto operator+(S s) { return 0; }
}; };
__fn{}(S()); __fn{}(S());
__fn{}(S(), S());
__fn{}(S(), S(), S());
__fn{}(U<int>(), U<int>());
} }