diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -2509,6 +2509,22 @@ QualType FromUT = D->getUnderlyingType(); QualType FoundUT = FoundTypedef->getUnderlyingType(); if (Importer.IsStructurallyEquivalent(FromUT, FoundUT)) { + // If the underlying declarations are unnamed records these can be + // imported as different types. We should create a distinct typedef + // node in this case. + // If we found an existing underlying type with a record in a + // different context (than the imported), this is already reason for + // having distinct typedef nodes for these. + // Again this can create situation like + // 'typedef int T; typedef int T;' but this is hard to avoid without + // a rename strategy at import. + if (!FromUT.isNull() && !FoundUT.isNull()) { + RecordDecl *FromR = FromUT->getAsRecordDecl(); + RecordDecl *FoundR = FoundUT->getAsRecordDecl(); + if (FromR && FoundR && + !hasSameVisibilityContextAndLinkage(FoundR, FromR)) + continue; + } // If the "From" context has a complete underlying type but we // already have a complete underlying type then return with that. if (!FromUT->isIncompleteType() && !FoundUT->isIncompleteType()) diff --git a/clang/unittests/AST/ASTImporterTest.cpp b/clang/unittests/AST/ASTImporterTest.cpp --- a/clang/unittests/AST/ASTImporterTest.cpp +++ b/clang/unittests/AST/ASTImporterTest.cpp @@ -8474,6 +8474,81 @@ ToVaList->getUnderlyingType(), ToBuiltinVaList->getUnderlyingType())); } +TEST_P(ASTImporterOptionSpecificTestBase, ImportExistingTypedefToRecord) { + const char *Code = + R"( + struct S { int i; }; + typedef struct S T; + extern T x; + )"; + Decl *ToTU = getToTuDecl(Code, Lang_C99); + Decl *FromTU = getTuDecl(Code, Lang_C99); + + auto *FromX = + FirstDeclMatcher().match(FromTU, varDecl(hasName("x"))); + auto *ToX = Import(FromX, Lang_C99); + EXPECT_TRUE(ToX); + + auto *Typedef1 = + FirstDeclMatcher().match(ToTU, typedefDecl(hasName("T"))); + auto *Typedef2 = + LastDeclMatcher().match(ToTU, typedefDecl(hasName("T"))); + EXPECT_EQ(Typedef1, Typedef2); +} + +TEST_P(ASTImporterOptionSpecificTestBase, + ImportExistingTypedefToUnnamedRecord) { + const char *Code = + R"( + typedef const struct { int f; } T; + extern T x; + )"; + Decl *ToTU = getToTuDecl(Code, Lang_C99); + Decl *FromTU = getTuDecl(Code, Lang_C99); + + auto *FromX = + FirstDeclMatcher().match(FromTU, varDecl(hasName("x"))); + auto *ToX = Import(FromX, Lang_C99); + EXPECT_TRUE(ToX); + + auto *Typedef1 = + FirstDeclMatcher().match(ToTU, typedefDecl(hasName("T"))); + auto *Typedef2 = + LastDeclMatcher().match(ToTU, typedefDecl(hasName("T"))); + EXPECT_NE(Typedef1, Typedef2); + EXPECT_NE(Typedef1->getUnderlyingType().getTypePtr(), + Typedef2->getUnderlyingType().getTypePtr()); + EXPECT_EQ(ToX->getType()->getAs()->getDecl(), Typedef2); +} + +TEST_P(ASTImporterOptionSpecificTestBase, ImportTwoTypedefsToUnnamedRecord) { + const char *Code = + R"( + typedef struct { int f; } T1; + typedef struct { int f; } T2; + extern T1 x1; + extern T2 x2; + )"; + Decl *ToTU = getToTuDecl("", Lang_C99); + Decl *FromTU = getTuDecl(Code, Lang_C99); + + auto *FromX1 = + FirstDeclMatcher().match(FromTU, varDecl(hasName("x1"))); + auto *FromX2 = + FirstDeclMatcher().match(FromTU, varDecl(hasName("x2"))); + auto *ToX1 = Import(FromX1, Lang_C99); + EXPECT_TRUE(ToX1); + auto *ToX2 = Import(FromX2, Lang_C99); + EXPECT_TRUE(ToX2); + + auto *Typedef1 = + FirstDeclMatcher().match(ToTU, typedefDecl(hasName("T1"))); + auto *Typedef2 = + FirstDeclMatcher().match(ToTU, typedefDecl(hasName("T2"))); + EXPECT_NE(Typedef1->getUnderlyingType().getTypePtr(), + Typedef2->getUnderlyingType().getTypePtr()); +} + INSTANTIATE_TEST_SUITE_P(ParameterizedTests, ASTImporterLookupTableTest, DefaultTestValuesForRunOptions);