diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -1347,6 +1347,42 @@ return true; } +/// Determine if context of a class is equivalent. +static bool IsRecordContextStructurallyEquivalent(RecordDecl *D1, + RecordDecl *D2) { + // The context should be completely equal, including anonymous and inline + // namespaces. + // We compare objects as part of full translation units, not subtrees of + // translation units. + DeclContext *DC1 = D1->getDeclContext()->getNonTransparentContext(); + DeclContext *DC2 = D2->getDeclContext()->getNonTransparentContext(); + while (true) { + // Special case: We allow a struct defined in a function to be equivalent + // with a similar struct defined outside of a function. + if ((DC1->isFunctionOrMethod() && DC2->isTranslationUnit()) || + (DC2->isFunctionOrMethod() && DC1->isTranslationUnit())) + return true; + + if (DC1->getDeclKind() != DC2->getDeclKind()) + return false; + if (DC1->isTranslationUnit()) + break; + if (DC1->isInlineNamespace() != DC2->isInlineNamespace()) + return false; + if (const auto *ND1 = dyn_cast(DC1)) { + const auto *ND2 = cast(DC2); + if (!DC1->isInlineNamespace() && + !IsStructurallyEquivalent(ND1->getIdentifier(), ND2->getIdentifier())) + return false; + } + + DC1 = DC1->getParent()->getNonTransparentContext(); + DC2 = DC2->getParent()->getNonTransparentContext(); + } + + return true; +} + /// Determine structural equivalence of two records. static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, RecordDecl *D1, RecordDecl *D2) { @@ -1386,6 +1422,12 @@ } } + // If the records occur in different context (namespace), these should be + // different. This is specially important if the definition of one or both + // records is missing. + if (!IsRecordContextStructurallyEquivalent(D1, D2)) + return false; + // If both declarations are class template specializations, we know // the ODR applies, so check the template and template arguments. const auto *Spec1 = dyn_cast(D1); diff --git a/clang/unittests/AST/StructuralEquivalenceTest.cpp b/clang/unittests/AST/StructuralEquivalenceTest.cpp --- a/clang/unittests/AST/StructuralEquivalenceTest.cpp +++ b/clang/unittests/AST/StructuralEquivalenceTest.cpp @@ -929,6 +929,128 @@ EXPECT_TRUE(testStructuralMatch(First, Second)); } +struct StructuralEquivalenceRecordContextTest : StructuralEquivalenceTest {}; + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNoVsNamed) { + auto Decls = + makeNamedDecls("class X;", "namespace N { class X; }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNamedVsNamed) { + auto Decls = makeNamedDecls("namespace A { class X; }", + "namespace B { class X; }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsNamed) { + auto Decls = makeNamedDecls("namespace { class X; }", + "namespace N { class X; }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNoVsAnon) { + auto Decls = + makeNamedDecls("class X;", "namespace { class X; }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsAnon) { + auto Decls = makeNamedDecls("namespace { class X; }", + "namespace { class X; }", Lang_CXX03, "X"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceAnonVsAnonAnon) { + auto Decls = + makeNamedDecls("namespace { class X; }", + "namespace { namespace { class X; } }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, + NamespaceNamedNamedVsNamedNamed) { + auto Decls = makeNamedDecls("namespace A { namespace N { class X; } }", + "namespace B { namespace N { class X; } }", + Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceNamedVsInline) { + auto Decls = makeNamedDecls("namespace A { namespace A { class X; } }", + "namespace A { inline namespace A { class X; } }", + Lang_CXX17, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceInlineVsInline) { + auto Decls = makeNamedDecls("namespace A { inline namespace A { class X; } }", + "namespace A { inline namespace B { class X; } }", + Lang_CXX17, "X"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, NamespaceInlineTopLevel) { + auto Decls = + makeNamedDecls("inline namespace A { class X; } }", + "inline namespace B { class X; } }", Lang_CXX17, "X"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, TransparentContext) { + auto Decls = + makeNamedDecls("extern \"C\" { class X; }", "class X;", Lang_CXX03, "X"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, TransparentContextNE) { + auto Decls = makeNamedDecls("extern \"C\" { class X; }", + "namespace { class X; }", Lang_CXX03, "X"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, TransparentContextInNamespace) { + auto Decls = makeNamedDecls("extern \"C\" { namespace N { class X; } }", + "namespace N { extern \"C\" { class X; } }", + Lang_CXX03, "X"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceTest, NamespaceOfRecordMember) { + auto Decls = makeNamedDecls( + R"( + class X; + class Y { X* x; }; + )", + R"( + namespace N { class X; } + class Y { N::X* x; }; + )", + Lang_CXX03, "Y"); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceTest, StructDefinitionInPrototype) { + auto Decls = + makeNamedDecls("struct Param { int a; }; void foo(struct Param *p);", + "void foo(struct Param { int a; } *p);", Lang_C89); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceTest, StructDefinitionInPrototypeDifferentName) { + auto Decls = + makeNamedDecls("struct Param1 { int a; }; void foo(struct Param1 *p);", + "void foo(struct Param2 { int a; } *p);", Lang_C89); + EXPECT_FALSE(testStructuralMatch(Decls)); +} + +TEST_F(StructuralEquivalenceRecordContextTest, RecordInsideFunction) { + auto Decls = makeNamedDecls("struct Param { int a; };", + "void f() { struct Param { int a; }; }", Lang_C89, + "Param"); + EXPECT_TRUE(testStructuralMatch(Decls)); +} + struct StructuralEquivalenceEnumTest : StructuralEquivalenceTest {}; TEST_F(StructuralEquivalenceEnumTest, FwdDeclEnumShouldBeEqualWithFwdDeclEnum) {