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 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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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>());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue