[clang][AST] Add 'IgnoreTemplateParmDepth' to structural equivalence cache (#115518)

Structural equivalence check uses a cache to store already found
non-equivalent values. This cache can be reused for calls (ASTImporter
does this). Value of "IgnoreTemplateParmDepth" can have an effect on the
structural equivalence therefore it is wrong to reuse the same cache for
checks with different values of 'IgnoreTemplateParmDepth'. The current
change adds the 'IgnoreTemplateParmDepth' to the cache key to fix the
problem.
This commit is contained in:
Balázs Kéri 2024-11-13 09:25:22 +01:00 committed by GitHub
parent 5a12881514
commit 7a1fdbb9c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 102 additions and 20 deletions

View File

@ -62,7 +62,8 @@ class TypeSourceInfo;
class ASTImporter { class ASTImporter {
friend class ASTNodeImporter; friend class ASTNodeImporter;
public: public:
using NonEquivalentDeclSet = llvm::DenseSet<std::pair<Decl *, Decl *>>; using NonEquivalentDeclSet =
llvm::DenseSet<std::tuple<Decl *, Decl *, int>>;
using ImportedCXXBaseSpecifierMap = using ImportedCXXBaseSpecifierMap =
llvm::DenseMap<const CXXBaseSpecifier *, CXXBaseSpecifier *>; llvm::DenseMap<const CXXBaseSpecifier *, CXXBaseSpecifier *>;

View File

@ -39,6 +39,10 @@ enum class StructuralEquivalenceKind {
}; };
struct StructuralEquivalenceContext { struct StructuralEquivalenceContext {
/// Store declaration pairs already found to be non-equivalent.
/// key: (from, to, IgnoreTemplateParmDepth)
using NonEquivalentDeclSet = llvm::DenseSet<std::tuple<Decl *, Decl *, int>>;
/// AST contexts for which we are checking structural equivalence. /// AST contexts for which we are checking structural equivalence.
ASTContext &FromCtx, &ToCtx; ASTContext &FromCtx, &ToCtx;
@ -52,7 +56,7 @@ struct StructuralEquivalenceContext {
/// Declaration (from, to) pairs that are known not to be equivalent /// Declaration (from, to) pairs that are known not to be equivalent
/// (which we have already complained about). /// (which we have already complained about).
llvm::DenseSet<std::pair<Decl *, Decl *>> &NonEquivalentDecls; NonEquivalentDeclSet &NonEquivalentDecls;
StructuralEquivalenceKind EqKind; StructuralEquivalenceKind EqKind;
@ -72,11 +76,12 @@ struct StructuralEquivalenceContext {
/// Whether to ignore comparing the depth of template param(TemplateTypeParm) /// Whether to ignore comparing the depth of template param(TemplateTypeParm)
bool IgnoreTemplateParmDepth; bool IgnoreTemplateParmDepth;
StructuralEquivalenceContext( StructuralEquivalenceContext(ASTContext &FromCtx, ASTContext &ToCtx,
ASTContext &FromCtx, ASTContext &ToCtx, NonEquivalentDeclSet &NonEquivalentDecls,
llvm::DenseSet<std::pair<Decl *, Decl *>> &NonEquivalentDecls, StructuralEquivalenceKind EqKind,
StructuralEquivalenceKind EqKind, bool StrictTypeSpelling = false, bool StrictTypeSpelling = false,
bool Complain = true, bool ErrorOnTagTypeMismatch = false, bool Complain = true,
bool ErrorOnTagTypeMismatch = false,
bool IgnoreTemplateParmDepth = false) bool IgnoreTemplateParmDepth = false)
: FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls), : FromCtx(FromCtx), ToCtx(ToCtx), NonEquivalentDecls(NonEquivalentDecls),
EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling), EqKind(EqKind), StrictTypeSpelling(StrictTypeSpelling),

View File

@ -2303,7 +2303,8 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
// Check whether we already know that these two declarations are not // Check whether we already know that these two declarations are not
// structurally equivalent. // structurally equivalent.
if (Context.NonEquivalentDecls.count(P)) if (Context.NonEquivalentDecls.count(
std::make_tuple(D1, D2, Context.IgnoreTemplateParmDepth)))
return false; return false;
// Check if a check for these declarations is already pending. // Check if a check for these declarations is already pending.
@ -2511,7 +2512,8 @@ bool StructuralEquivalenceContext::Finish() {
if (!Equivalent) { if (!Equivalent) {
// Note that these two declarations are not equivalent (and we already // Note that these two declarations are not equivalent (and we already
// know about it). // know about it).
NonEquivalentDecls.insert(P); NonEquivalentDecls.insert(
std::make_tuple(D1, D2, IgnoreTemplateParmDepth));
return true; return true;
} }

View File

@ -9046,7 +9046,7 @@ bool Sema::RequireCompleteType(SourceLocation Loc, QualType T,
} }
bool Sema::hasStructuralCompatLayout(Decl *D, Decl *Suggested) { bool Sema::hasStructuralCompatLayout(Decl *D, Decl *Suggested) {
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
if (!Suggested) if (!Suggested)
return false; return false;

View File

@ -9907,7 +9907,7 @@ void ASTReader::finishPendingActions() {
auto ExtensionsPair = PendingObjCExtensionIvarRedeclarations.back().first; auto ExtensionsPair = PendingObjCExtensionIvarRedeclarations.back().first;
auto DuplicateIvars = auto DuplicateIvars =
PendingObjCExtensionIvarRedeclarations.back().second; PendingObjCExtensionIvarRedeclarations.back().second;
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx( StructuralEquivalenceContext Ctx(
ExtensionsPair.first->getASTContext(), ExtensionsPair.first->getASTContext(),
ExtensionsPair.second->getASTContext(), NonEquivalentDecls, ExtensionsPair.second->getASTContext(), NonEquivalentDecls,

View File

@ -4447,7 +4447,7 @@ namespace {
ObjCCategoryDecl *&Existing = NameCategoryMap[Cat->getDeclName()]; ObjCCategoryDecl *&Existing = NameCategoryMap[Cat->getDeclName()];
if (Existing && Reader.getOwningModuleFile(Existing) != if (Existing && Reader.getOwningModuleFile(Existing) !=
Reader.getOwningModuleFile(Cat)) { Reader.getOwningModuleFile(Cat)) {
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
StructuralEquivalenceContext Ctx( StructuralEquivalenceContext Ctx(
Cat->getASTContext(), Existing->getASTContext(), Cat->getASTContext(), Existing->getASTContext(),
NonEquivalentDecls, StructuralEquivalenceKind::Default, NonEquivalentDecls, StructuralEquivalenceKind::Default,

View File

@ -134,8 +134,8 @@ struct StructuralEquivalenceTest : ::testing::Test {
bool testStructuralMatch(Decl *D0, Decl *D1, bool testStructuralMatch(Decl *D0, Decl *D1,
bool IgnoreTemplateParmDepth = false) { bool IgnoreTemplateParmDepth = false) {
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls01; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls10; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
StructuralEquivalenceContext Ctx01( StructuralEquivalenceContext Ctx01(
D0->getASTContext(), D1->getASTContext(), NonEquivalentDecls01, D0->getASTContext(), D1->getASTContext(), NonEquivalentDecls01,
StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false, StructuralEquivalenceKind::Default, /*StrictTypeSpelling=*/false,
@ -153,8 +153,8 @@ struct StructuralEquivalenceTest : ::testing::Test {
} }
bool testStructuralMatch(StmtWithASTContext S0, StmtWithASTContext S1) { bool testStructuralMatch(StmtWithASTContext S0, StmtWithASTContext S1) {
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls01; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls01;
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls10; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls10;
StructuralEquivalenceContext Ctx01( StructuralEquivalenceContext Ctx01(
*S0.Context, *S1.Context, NonEquivalentDecls01, *S0.Context, *S1.Context, NonEquivalentDecls01,
StructuralEquivalenceKind::Default, false, false); StructuralEquivalenceKind::Default, false, false);
@ -1792,7 +1792,7 @@ TEST_F(
EXPECT_FALSE(testStructuralMatch(t)); EXPECT_FALSE(testStructuralMatch(t));
} }
struct StructuralEquivalenceCacheTest : public StructuralEquivalenceTest { struct StructuralEquivalenceCacheTest : public StructuralEquivalenceTest {
llvm::DenseSet<std::pair<Decl *, Decl *>> NonEquivalentDecls; StructuralEquivalenceContext::NonEquivalentDeclSet NonEquivalentDecls;
template <typename NodeType, typename MatcherType> template <typename NodeType, typename MatcherType>
std::pair<NodeType *, NodeType *> std::pair<NodeType *, NodeType *>
@ -1804,8 +1804,10 @@ struct StructuralEquivalenceCacheTest : public StructuralEquivalenceTest {
} }
template <typename NodeType> template <typename NodeType>
bool isInNonEqCache(std::pair<NodeType *, NodeType *> D) { bool isInNonEqCache(std::pair<NodeType *, NodeType *> D,
return NonEquivalentDecls.count(D) > 0; bool IgnoreTemplateParmDepth = false) {
return NonEquivalentDecls.count(
std::make_tuple(D.first, D.second, IgnoreTemplateParmDepth)) > 0;
} }
}; };
@ -2015,6 +2017,78 @@ TEST_F(StructuralEquivalenceCacheTest, Cycle) {
findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x"))))); findDeclPair<FunctionDecl>(TU, functionDecl(hasName("x")))));
} }
TEST_F(StructuralEquivalenceCacheTest, TemplateParmDepth) {
// In 'friend struct Y' ClassTemplateDecl has the TU as parent context.
// This declaration has template depth 1 (it is already inside a template).
// It has not a previous declaration and is an "undeclared" friend.
//
// Second TU has a specialization of 'struct X'.
// In this case 'friend struct Y' has the ClassTemplateSpecializationDecl as
// parent. It has template depth 0 (it is in the specialization). It has the
// first 'struct Y' declaration as previous declaration and canonical
// declaration.
//
// When these two 'friend struct Y' are compared, only the template depth is
// different.
// FIXME: Structural equivalence checks the depth only in types, not in
// TemplateParmDecl. For this reason the second 'A1' argument is needed (as a
// type) in the template to make the check fail.
auto TU = makeTuDecls(
R"(
template <class A1, A1>
struct Y;
template <class A>
struct X {
template <class A1, A1>
friend struct Y;
};
)",
R"(
template <class A1, A1>
struct Y;
template <class A>
struct X {
template <class A1, A1>
friend struct Y;
};
X<int> x;
)",
Lang_CXX03);
auto *D0 = LastDeclMatcher<ClassTemplateDecl>().match(
get<0>(TU), classTemplateDecl(hasName("Y"), unless(isImplicit())));
auto *D1 = LastDeclMatcher<ClassTemplateDecl>().match(
get<1>(TU), classTemplateDecl(hasName("Y"), unless(isImplicit())));
ASSERT_EQ(D0->getTemplateDepth(), 1u);
ASSERT_EQ(D1->getTemplateDepth(), 0u);
StructuralEquivalenceContext Ctx_NoIgnoreTemplateParmDepth(
get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
false, false);
EXPECT_FALSE(Ctx_NoIgnoreTemplateParmDepth.IsEquivalent(D0, D1));
Decl *NonEqDecl0 =
D0->getCanonicalDecl()->getTemplateParameters()->getParam(1);
Decl *NonEqDecl1 =
D1->getCanonicalDecl()->getTemplateParameters()->getParam(1);
EXPECT_TRUE(isInNonEqCache(std::make_pair(NonEqDecl0, NonEqDecl1), false));
EXPECT_FALSE(isInNonEqCache(std::make_pair(NonEqDecl0, NonEqDecl1), true));
StructuralEquivalenceContext Ctx_IgnoreTemplateParmDepth(
get<0>(TU)->getASTContext(), get<1>(TU)->getASTContext(),
NonEquivalentDecls, StructuralEquivalenceKind::Default, false, false,
false, true);
EXPECT_TRUE(Ctx_IgnoreTemplateParmDepth.IsEquivalent(D0, D1));
EXPECT_FALSE(isInNonEqCache(std::make_pair(NonEqDecl0, NonEqDecl1), true));
}
struct StructuralEquivalenceStmtTest : StructuralEquivalenceTest {}; struct StructuralEquivalenceStmtTest : StructuralEquivalenceTest {};
/// Fallback matcher to be used only when there is no specific matcher for a /// Fallback matcher to be used only when there is no specific matcher for a