forked from OSchip/llvm-project
[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:
parent
5d41fe0768
commit
f6b0ae144e
|
@ -130,6 +130,7 @@ class TemplateDecl;
|
|||
class TemplateParameterList;
|
||||
class TemplateTemplateParmDecl;
|
||||
class TemplateTypeParmDecl;
|
||||
class TypeConstraint;
|
||||
class UnresolvedSetIterator;
|
||||
class UsingShadowDecl;
|
||||
class VarTemplateDecl;
|
||||
|
@ -2664,6 +2665,18 @@ public:
|
|||
/// that they may be used in declarations of the same template.
|
||||
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
|
||||
/// that they may be used in declarations of the same template.
|
||||
bool isSameDefaultTemplateArgument(const NamedDecl *X,
|
||||
|
|
|
@ -6218,6 +6218,57 @@ bool ASTContext::hasSameTemplateName(const TemplateName &X,
|
|||
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,
|
||||
const NamedDecl *Y) const {
|
||||
if (X->getKind() != Y->getKind())
|
||||
|
@ -6229,32 +6280,8 @@ bool ASTContext::isSameTemplateParameter(const NamedDecl *X,
|
|||
return false;
|
||||
if (TX->hasTypeConstraint() != TY->hasTypeConstraint())
|
||||
return false;
|
||||
const TypeConstraint *TXTC = TX->getTypeConstraint();
|
||||
const TypeConstraint *TYTC = 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;
|
||||
return isSameTypeConstraint(TX->getTypeConstraint(),
|
||||
TY->getTypeConstraint());
|
||||
}
|
||||
|
||||
if (auto *TX = dyn_cast<NonTypeTemplateParmDecl>(X)) {
|
||||
|
@ -6279,19 +6306,7 @@ bool ASTContext::isSameTemplateParameterList(
|
|||
if (!isSameTemplateParameter(X->getParam(I), Y->getParam(I)))
|
||||
return false;
|
||||
|
||||
const Expr *XRC = X->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;
|
||||
return isSameConstraintExpr(X->getRequiresClause(), Y->getRequiresClause());
|
||||
}
|
||||
|
||||
bool ASTContext::isSameDefaultTemplateArgument(const NamedDecl *X,
|
||||
|
@ -6489,17 +6504,9 @@ bool ASTContext::isSameEntity(const NamedDecl *X, const NamedDecl *Y) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
const Expr *XRC = FuncX->getTrailingRequiresClause();
|
||||
const Expr *YRC = FuncY->getTrailingRequiresClause();
|
||||
if (!XRC != !YRC)
|
||||
if (!isSameConstraintExpr(FuncX->getTrailingRequiresClause(),
|
||||
FuncY->getTrailingRequiresClause()))
|
||||
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) {
|
||||
// Map to the first declaration that we've already merged into this one.
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// 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 -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
|
||||
|
||||
//--- foo.h
|
||||
|
@ -18,6 +19,9 @@ concept __integer_like = true;
|
|||
template <class _Tp>
|
||||
concept __member_size = requires(_Tp &&t) { t.size(); };
|
||||
|
||||
template <class First, class Second>
|
||||
concept C = requires(First x, Second y) { x + y; };
|
||||
|
||||
struct A {
|
||||
public:
|
||||
template <Range T>
|
||||
|
@ -29,6 +33,29 @@ struct __fn {
|
|||
constexpr __integer_like auto operator()(_Tp&& __t) const {
|
||||
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
|
||||
|
||||
|
@ -38,12 +65,23 @@ module;
|
|||
export module A;
|
||||
|
||||
//--- B.cppm
|
||||
// expected-no-diagnostics
|
||||
module;
|
||||
#include "foo.h"
|
||||
export module B;
|
||||
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() {
|
||||
A a;
|
||||
struct S {
|
||||
|
@ -51,4 +89,8 @@ void foo() {
|
|||
auto operator+(S s) { return 0; }
|
||||
};
|
||||
__fn{}(S());
|
||||
__fn{}(S(), S());
|
||||
__fn{}(S(), S(), S());
|
||||
|
||||
__fn{}(U<int>(), U<int>());
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue