aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDouglas Gregor <dgregor@apple.com>2010-02-15 23:54:17 +0000
committerDouglas Gregor <dgregor@apple.com>2010-02-15 23:54:17 +0000
commitea35d11905f756ad33b87bd89cd3ac1e7ce57994 (patch)
tree3d50c67c53cde3ca1f9824d098c8c2b1028a162a
parentb9bbd592c7ea72ada8d982e40a729beb9b53371e (diff)
Cope with anonymous tags defined within declarators by structurally
comparing their types under the assumption that they are equivalent, rather than importing the types and then checking for compatibility. A few minor tweaks here: - Teach structural matching to handle compatibility between function types with prototypes and those without prototypes. - Teach structural matching that an incomplete record decl is the same as any other record decl with the same name. - Keep track of pairs of declarations that we have already checked (but failed to find as structurally matching), so we don't emit diagnostics repeatedly. - When importing a typedef of an anonymous tag, be sure to link the imported tag type to its typedef. With these changes, we survive a repeated import of <stdlib.h> and <stdio.h>. Alas, the ASTNodeImporter is getting a little grotty. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@96298 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--include/clang/AST/ASTImporter.h21
-rw-r--r--lib/AST/ASTImporter.cpp224
-rw-r--r--test/ASTMerge/Inputs/enum1.c8
-rw-r--r--test/ASTMerge/Inputs/enum2.c7
-rw-r--r--test/ASTMerge/Inputs/function1.c2
-rw-r--r--test/ASTMerge/Inputs/function2.c1
-rw-r--r--test/ASTMerge/Inputs/struct1.c6
-rw-r--r--test/ASTMerge/Inputs/struct2.c6
-rw-r--r--test/ASTMerge/struct.c3
9 files changed, 195 insertions, 83 deletions
diff --git a/include/clang/AST/ASTImporter.h b/include/clang/AST/ASTImporter.h
index 9da9ea9c11..f5f11ca836 100644
--- a/include/clang/AST/ASTImporter.h
+++ b/include/clang/AST/ASTImporter.h
@@ -18,6 +18,8 @@
#include "clang/AST/DeclarationName.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
namespace clang {
class ASTContext;
@@ -34,6 +36,10 @@ namespace clang {
/// \brief Imports selected nodes from one AST context into another context,
/// merging AST nodes where appropriate.
class ASTImporter {
+ public:
+ typedef llvm::DenseSet<std::pair<Decl *, Decl *> > NonEquivalentDeclSet;
+
+ private:
/// \brief The contexts we're importing to and from.
ASTContext &ToContext, &FromContext;
@@ -59,6 +65,14 @@ namespace clang {
/// manager to the corresponding FileIDs in the "to" source manager.
llvm::DenseMap<unsigned, FileID> ImportedFileIDs;
+ /// \brief Imported, anonymous tag declarations that are missing their
+ /// corresponding typedefs.
+ llvm::SmallVector<TagDecl *, 4> AnonTagsWithPendingTypedefs;
+
+ /// \brief Declaration (from, to) pairs that are known not to be equivalent
+ /// (which we have already complained about).
+ NonEquivalentDeclSet NonEquivalentDecls;
+
public:
ASTImporter(Diagnostic &Diags,
ASTContext &ToContext, FileManager &ToFileManager,
@@ -202,11 +216,18 @@ namespace clang {
/// \brief Report a diagnostic in the "from" context.
DiagnosticBuilder FromDiag(SourceLocation Loc, unsigned DiagID);
+ /// \brief Return the set of declarations that we know are not equivalent.
+ NonEquivalentDeclSet &getNonEquivalentDecls() { return NonEquivalentDecls; }
+
/// \brief Note that we have imported the "from" declaration by mapping it
/// to the (potentially-newly-created) "to" declaration.
///
/// \returns \p To
Decl *Imported(Decl *From, Decl *To);
+
+ /// \brief Determine whether the given types are structurally
+ /// equivalent.
+ bool IsStructurallyEquivalent(QualType From, QualType To);
};
}
diff --git a/lib/AST/ASTImporter.cpp b/lib/AST/ASTImporter.cpp
index 5e4d39f8f9..129d91be25 100644
--- a/lib/AST/ASTImporter.cpp
+++ b/lib/AST/ASTImporter.cpp
@@ -81,10 +81,6 @@ namespace {
bool ImportDeclParts(NamedDecl *D, DeclContext *&DC,
DeclContext *&LexicalDC, DeclarationName &Name,
SourceLocation &Loc);
- bool ImportDeclParts(ValueDecl *D,
- DeclContext *&DC, DeclContext *&LexicalDC,
- DeclarationName &Name, SourceLocation &Loc,
- QualType &T);
bool IsStructuralMatch(RecordDecl *FromRecord, RecordDecl *ToRecord);
bool IsStructuralMatch(EnumDecl *FromEnum, EnumDecl *ToRecord);
Decl *VisitDecl(Decl *D);
@@ -128,14 +124,20 @@ namespace {
/// with a declaration in the second context still needs to be verified.
std::deque<Decl *> DeclsToCheck;
+ /// \brief Declaration (from, to) pairs that are known not to be equivalent
+ /// (which we have already complained about).
+ llvm::DenseSet<std::pair<Decl *, Decl *> > &NonEquivalentDecls;
+
/// \brief Whether we're being strict about the spelling of types when
/// unifying two types.
bool StrictTypeSpelling;
StructuralEquivalenceContext(ASTContext &C1, ASTContext &C2,
Diagnostic &Diags,
+ llvm::DenseSet<std::pair<Decl *, Decl *> > &NonEquivalentDecls,
bool StrictTypeSpelling = false)
- : C1(C1), C2(C2), Diags(Diags), StrictTypeSpelling(StrictTypeSpelling) { }
+ : C1(C1), C2(C2), Diags(Diags), NonEquivalentDecls(NonEquivalentDecls),
+ StrictTypeSpelling(StrictTypeSpelling) { }
/// \brief Determine whether the two declarations are structurally
/// equivalent.
@@ -273,11 +275,23 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
if (T1.getQualifiers() != T2.getQualifiers())
return false;
- if (T1->getTypeClass() != T2->getTypeClass())
- return false;
+ Type::TypeClass TC = T1->getTypeClass();
+
+ if (T1->getTypeClass() != T2->getTypeClass()) {
+ // Compare function types with prototypes vs. without prototypes as if
+ // both did not have prototypes.
+ if (T1->getTypeClass() == Type::FunctionProto &&
+ T2->getTypeClass() == Type::FunctionNoProto)
+ TC = Type::FunctionNoProto;
+ else if (T1->getTypeClass() == Type::FunctionNoProto &&
+ T2->getTypeClass() == Type::FunctionProto)
+ TC = Type::FunctionNoProto;
+ else
+ return false;
+ }
- switch (T1->getTypeClass()) {
- case Type::Builtin:
+ switch (TC) {
+ case Type::Builtin:
// FIXME: Deal with Char_S/Char_U.
if (cast<BuiltinType>(T1)->getKind() != cast<BuiltinType>(T2)->getKind())
return false;
@@ -641,6 +655,13 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
return false;
}
+ // Compare the definitions of these two records. If either or both are
+ // incomplete, we assume that they are equivalent.
+ D1 = D1->getDefinition();
+ D2 = D2->getDefinition();
+ if (!D1 || !D2)
+ return true;
+
if (CXXRecordDecl *D1CXX = dyn_cast<CXXRecordDecl>(D1)) {
if (CXXRecordDecl *D2CXX = dyn_cast<CXXRecordDecl>(D2)) {
if (D1CXX->getNumBases() != D2CXX->getNumBases()) {
@@ -834,6 +855,12 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context,
Decl *D1, Decl *D2) {
// FIXME: Check for known structural equivalences via a callback of some sort.
+ // Check whether we already know that these two declarations are not
+ // structurally equivalent.
+ if (Context.NonEquivalentDecls.count(std::make_pair(D1->getCanonicalDecl(),
+ D2->getCanonicalDecl())))
+ return false;
+
// Determine whether we've already produced a tentative equivalence for D1.
Decl *&EquivToD1 = Context.TentativeEquivalences[D1->getCanonicalDecl()];
if (EquivToD1)
@@ -870,6 +897,8 @@ bool StructuralEquivalenceContext::Finish() {
Decl *D2 = TentativeEquivalences[D1];
assert(D2 && "Unrecorded tentative equivalence?");
+ bool Equivalent = true;
+
// FIXME: Switch on all declaration kinds. For now, we're just going to
// check the obvious ones.
if (RecordDecl *Record1 = dyn_cast<RecordDecl>(D1)) {
@@ -881,20 +910,14 @@ bool StructuralEquivalenceContext::Finish() {
IdentifierInfo *Name2 = Record2->getIdentifier();
if (!Name2 && Record2->getTypedefForAnonDecl())
Name2 = Record2->getTypedefForAnonDecl()->getIdentifier();
- if (!::IsStructurallyEquivalent(Name1, Name2))
- return true;
-
- if (!::IsStructurallyEquivalent(*this, Record1, Record2))
- return true;
+ if (!::IsStructurallyEquivalent(Name1, Name2) ||
+ !::IsStructurallyEquivalent(*this, Record1, Record2))
+ Equivalent = false;
} else {
// Record/non-record mismatch.
- return true;
+ Equivalent = false;
}
-
- continue;
- }
-
- if (EnumDecl *Enum1 = dyn_cast<EnumDecl>(D1)) {
+ } else if (EnumDecl *Enum1 = dyn_cast<EnumDecl>(D1)) {
if (EnumDecl *Enum2 = dyn_cast<EnumDecl>(D2)) {
// Check for equivalent enum names.
IdentifierInfo *Name1 = Enum1->getIdentifier();
@@ -903,37 +926,34 @@ bool StructuralEquivalenceContext::Finish() {
IdentifierInfo *Name2 = Enum2->getIdentifier();
if (!Name2 && Enum2->getTypedefForAnonDecl())
Name2 = Enum2->getTypedefForAnonDecl()->getIdentifier();
- if (!::IsStructurallyEquivalent(Name1, Name2))
- return true;
-
- if (!::IsStructurallyEquivalent(*this, Enum1, Enum2))
- return true;
+ if (!::IsStructurallyEquivalent(Name1, Name2) ||
+ !::IsStructurallyEquivalent(*this, Enum1, Enum2))
+ Equivalent = false;
} else {
// Enum/non-enum mismatch
- return true;
+ Equivalent = false;
}
-
- continue;
- }
-
- if (TypedefDecl *Typedef1 = dyn_cast<TypedefDecl>(D1)) {
+ } else if (TypedefDecl *Typedef1 = dyn_cast<TypedefDecl>(D1)) {
if (TypedefDecl *Typedef2 = dyn_cast<TypedefDecl>(D2)) {
if (!::IsStructurallyEquivalent(Typedef1->getIdentifier(),
- Typedef2->getIdentifier()))
- return true;
-
- if (!::IsStructurallyEquivalent(*this,
+ Typedef2->getIdentifier()) ||
+ !::IsStructurallyEquivalent(*this,
Typedef1->getUnderlyingType(),
Typedef2->getUnderlyingType()))
- return true;
+ Equivalent = false;
} else {
// Typedef/non-typedef mismatch.
- return true;
+ Equivalent = false;
}
-
- continue;
}
-
+
+ if (!Equivalent) {
+ // Note that these two declarations are not equivalent (and we already
+ // know about it).
+ NonEquivalentDecls.insert(std::make_pair(D1->getCanonicalDecl(),
+ D2->getCanonicalDecl()));
+ return true;
+ }
// FIXME: Check other declaration kinds!
}
@@ -1335,35 +1355,20 @@ bool ASTNodeImporter::ImportDeclParts(NamedDecl *D, DeclContext *&DC,
return false;
}
-bool ASTNodeImporter::ImportDeclParts(ValueDecl *D,
- DeclContext *&DC,
- DeclContext *&LexicalDC,
- DeclarationName &Name,
- SourceLocation &Loc,
- QualType &T) {
- if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
- return true;
-
- // Import the type of this declaration.
- T = Importer.Import(D->getType());
- if (T.isNull())
- return true;
-
- return false;
-}
-
bool ASTNodeImporter::IsStructuralMatch(RecordDecl *FromRecord,
RecordDecl *ToRecord) {
StructuralEquivalenceContext SEC(Importer.getFromContext(),
Importer.getToContext(),
- Importer.getDiags());
+ Importer.getDiags(),
+ Importer.getNonEquivalentDecls());
return SEC.IsStructurallyEquivalent(FromRecord, ToRecord);
}
bool ASTNodeImporter::IsStructuralMatch(EnumDecl *FromEnum, EnumDecl *ToEnum) {
StructuralEquivalenceContext SEC(Importer.getFromContext(),
Importer.getToContext(),
- Importer.getDiags());
+ Importer.getDiags(),
+ Importer.getNonEquivalentDecls());
return SEC.IsStructurallyEquivalent(FromEnum, ToEnum);
}
@@ -1381,11 +1386,6 @@ Decl *ASTNodeImporter::VisitTypedefDecl(TypedefDecl *D) {
if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
return 0;
- // Import the underlying type of this typedef;
- QualType T = Importer.Import(D->getUnderlyingType());
- if (T.isNull())
- return 0;
-
// If this typedef is not in block scope, determine whether we've
// seen a typedef with the same name (that we can merge with) or any
// other entity by that name (which name lookup could conflict with).
@@ -1398,8 +1398,8 @@ Decl *ASTNodeImporter::VisitTypedefDecl(TypedefDecl *D) {
if (!(*Lookup.first)->isInIdentifierNamespace(IDNS))
continue;
if (TypedefDecl *FoundTypedef = dyn_cast<TypedefDecl>(*Lookup.first)) {
- if (Importer.getToContext().typesAreCompatible(T,
- FoundTypedef->getUnderlyingType()))
+ if (Importer.IsStructurallyEquivalent(D->getUnderlyingType(),
+ FoundTypedef->getUnderlyingType()))
return Importer.Imported(D, FoundTypedef);
}
@@ -1415,6 +1415,11 @@ Decl *ASTNodeImporter::VisitTypedefDecl(TypedefDecl *D) {
}
}
+ // Import the underlying type of this typedef;
+ QualType T = Importer.Import(D->getUnderlyingType());
+ if (T.isNull())
+ return 0;
+
// Create the new typedef node.
TypeSourceInfo *TInfo = Importer.Import(D->getTypeSourceInfo());
TypedefDecl *ToTypedef = TypedefDecl::Create(Importer.getToContext(), DC,
@@ -1423,6 +1428,7 @@ Decl *ASTNodeImporter::VisitTypedefDecl(TypedefDecl *D) {
ToTypedef->setLexicalDeclContext(LexicalDC);
Importer.Imported(D, ToTypedef);
LexicalDC->addDecl(ToTypedef);
+
return ToTypedef;
}
@@ -1648,10 +1654,13 @@ Decl *ASTNodeImporter::VisitEnumConstantDecl(EnumConstantDecl *D) {
DeclContext *DC, *LexicalDC;
DeclarationName Name;
SourceLocation Loc;
- QualType T;
- if (ImportDeclParts(D, DC, LexicalDC, Name, Loc, T))
+ if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
return 0;
-
+
+ QualType T = Importer.Import(D->getType());
+ if (T.isNull())
+ return 0;
+
// Determine whether there are any other declarations with the same name and
// in the same context.
if (!LexicalDC->isFunctionOrMethod()) {
@@ -1693,9 +1702,8 @@ Decl *ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
// Import the major distinguishing characteristics of this function.
DeclContext *DC, *LexicalDC;
DeclarationName Name;
- QualType T;
SourceLocation Loc;
- if (ImportDeclParts(D, DC, LexicalDC, Name, Loc, T))
+ if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
return 0;
// Try to find a function in our own ("to") context with the same name, same
@@ -1712,8 +1720,8 @@ Decl *ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
if (FunctionDecl *FoundFunction = dyn_cast<FunctionDecl>(*Lookup.first)) {
if (isExternalLinkage(FoundFunction->getLinkage()) &&
isExternalLinkage(D->getLinkage())) {
- if (Importer.getToContext().typesAreCompatible(T,
- FoundFunction->getType())) {
+ if (Importer.IsStructurallyEquivalent(D->getType(),
+ FoundFunction->getType())) {
// FIXME: Actually try to merge the body and other attributes.
return Importer.Imported(D, FoundFunction);
}
@@ -1727,7 +1735,7 @@ Decl *ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
// Complain about inconsistent function types.
Importer.ToDiag(Loc, diag::err_odr_function_type_inconsistent)
- << Name << T << FoundFunction->getType();
+ << Name << D->getType() << FoundFunction->getType();
Importer.ToDiag(FoundFunction->getLocation(),
diag::note_odr_value_here)
<< FoundFunction->getType();
@@ -1745,6 +1753,11 @@ Decl *ASTNodeImporter::VisitFunctionDecl(FunctionDecl *D) {
return 0;
}
}
+
+ // Import the type.
+ QualType T = Importer.Import(D->getType());
+ if (T.isNull())
+ return 0;
// Import the function parameters.
llvm::SmallVector<ParmVarDecl *, 8> Parameters;
@@ -1784,9 +1797,13 @@ Decl *ASTNodeImporter::VisitFieldDecl(FieldDecl *D) {
// Import the major distinguishing characteristics of a variable.
DeclContext *DC, *LexicalDC;
DeclarationName Name;
- QualType T;
SourceLocation Loc;
- if (ImportDeclParts(D, DC, LexicalDC, Name, Loc, T))
+ if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
+ return 0;
+
+ // Import the type.
+ QualType T = Importer.Import(D->getType());
+ if (T.isNull())
return 0;
TypeSourceInfo *TInfo = Importer.Import(D->getTypeSourceInfo());
@@ -1807,9 +1824,8 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) {
// Import the major distinguishing characteristics of a variable.
DeclContext *DC, *LexicalDC;
DeclarationName Name;
- QualType T;
SourceLocation Loc;
- if (ImportDeclParts(D, DC, LexicalDC, Name, Loc, T))
+ if (ImportDeclParts(D, DC, LexicalDC, Name, Loc))
return 0;
// Try to find a variable in our own ("to") context with the same name and
@@ -1828,8 +1844,8 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) {
// We have found a variable that we may need to merge with. Check it.
if (isExternalLinkage(FoundVar->getLinkage()) &&
isExternalLinkage(D->getLinkage())) {
- if (Importer.getToContext().typesAreCompatible(T,
- FoundVar->getType())) {
+ if (Importer.IsStructurallyEquivalent(D->getType(),
+ FoundVar->getType())) {
MergeWithVar = FoundVar;
break;
}
@@ -1837,10 +1853,15 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) {
const ArrayType *FoundArray
= Importer.getToContext().getAsArrayType(FoundVar->getType());
const ArrayType *TArray
- = Importer.getToContext().getAsArrayType(T);
+ = Importer.getToContext().getAsArrayType(D->getType());
if (FoundArray && TArray) {
if (isa<IncompleteArrayType>(FoundArray) &&
isa<ConstantArrayType>(TArray)) {
+ // Import the type.
+ QualType T = Importer.Import(D->getType());
+ if (T.isNull())
+ return 0;
+
FoundVar->setType(T);
MergeWithVar = FoundVar;
break;
@@ -1852,7 +1873,7 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) {
}
Importer.ToDiag(Loc, diag::err_odr_variable_type_inconsistent)
- << Name << T << FoundVar->getType();
+ << Name << D->getType() << FoundVar->getType();
Importer.ToDiag(FoundVar->getLocation(), diag::note_odr_value_here)
<< FoundVar->getType();
}
@@ -1890,6 +1911,11 @@ Decl *ASTNodeImporter::VisitVarDecl(VarDecl *D) {
}
}
+ // Import the type.
+ QualType T = Importer.Import(D->getType());
+ if (T.isNull())
+ return 0;
+
// Create the imported variable.
TypeSourceInfo *TInfo = Importer.Import(D->getTypeSourceInfo());
VarDecl *ToVar = VarDecl::Create(Importer.getToContext(), DC, Loc,
@@ -2045,6 +2071,29 @@ Decl *ASTImporter::Import(Decl *FromD) {
// Record the imported declaration.
ImportedDecls[FromD] = ToD;
+
+ if (TagDecl *FromTag = dyn_cast<TagDecl>(FromD)) {
+ // Keep track of anonymous tags that have an associated typedef.
+ if (FromTag->getTypedefForAnonDecl())
+ AnonTagsWithPendingTypedefs.push_back(FromTag);
+ } else if (TypedefDecl *FromTypedef = dyn_cast<TypedefDecl>(FromD)) {
+ // When we've finished transforming a typedef, see whether it was the
+ // typedef for an anonymous tag.
+ for (llvm::SmallVector<TagDecl *, 4>::iterator
+ FromTag = AnonTagsWithPendingTypedefs.begin(),
+ FromTagEnd = AnonTagsWithPendingTypedefs.end();
+ FromTag != FromTagEnd; ++FromTag) {
+ if ((*FromTag)->getTypedefForAnonDecl() == FromTypedef) {
+ if (TagDecl *ToTag = cast_or_null<TagDecl>(Import(*FromTag))) {
+ // We found the typedef for an anonymous tag; link them.
+ ToTag->setTypedefForAnonDecl(cast<TypedefDecl>(ToD));
+ AnonTagsWithPendingTypedefs.erase(FromTag);
+ break;
+ }
+ }
+ }
+ }
+
return ToD;
}
@@ -2236,3 +2285,14 @@ Decl *ASTImporter::Imported(Decl *From, Decl *To) {
ImportedDecls[From] = To;
return To;
}
+
+bool ASTImporter::IsStructurallyEquivalent(QualType From, QualType To) {
+ llvm::DenseMap<Type *, Type *>::iterator Pos
+ = ImportedTypes.find(From.getTypePtr());
+ if (Pos != ImportedTypes.end() && ToContext.hasSameType(Import(From), To))
+ return true;
+
+ StructuralEquivalenceContext SEC(FromContext, ToContext, Diags,
+ NonEquivalentDecls);
+ return SEC.IsStructurallyEquivalent(From, To);
+}
diff --git a/test/ASTMerge/Inputs/enum1.c b/test/ASTMerge/Inputs/enum1.c
index cbbed47bac..f2b9c5c98f 100644
--- a/test/ASTMerge/Inputs/enum1.c
+++ b/test/ASTMerge/Inputs/enum1.c
@@ -32,3 +32,11 @@ enum E5 {
E5Enumerator2,
E5Enumerator3
} x5;
+
+// Matching, with typedef
+typedef enum {
+ E6Enumerator1,
+ E6Enumerator2
+} E6;
+
+E6 x6;
diff --git a/test/ASTMerge/Inputs/enum2.c b/test/ASTMerge/Inputs/enum2.c
index 050af828fd..315b4dcb6e 100644
--- a/test/ASTMerge/Inputs/enum2.c
+++ b/test/ASTMerge/Inputs/enum2.c
@@ -33,3 +33,10 @@ enum E5 {
E5Enumerator4
} x5;
+// Matching, with typedef
+typedef enum {
+ E6Enumerator1,
+ E6Enumerator2
+} E6;
+
+E6 x6;
diff --git a/test/ASTMerge/Inputs/function1.c b/test/ASTMerge/Inputs/function1.c
index b999123266..4523bd3d79 100644
--- a/test/ASTMerge/Inputs/function1.c
+++ b/test/ASTMerge/Inputs/function1.c
@@ -3,4 +3,4 @@ void f1(int, float);
void f2();
void f3(void);
void f4(int, int);
-
+int f5(int) __attribute__((const));
diff --git a/test/ASTMerge/Inputs/function2.c b/test/ASTMerge/Inputs/function2.c
index ad81c07375..6ca810a6f2 100644
--- a/test/ASTMerge/Inputs/function2.c
+++ b/test/ASTMerge/Inputs/function2.c
@@ -4,3 +4,4 @@ void f1(Int, double);
void f2(int, int);
void f3(int);
static void f4(float, float);
+int f5(int) __attribute__((const));
diff --git a/test/ASTMerge/Inputs/struct1.c b/test/ASTMerge/Inputs/struct1.c
index e6d71ac1b4..af2af8abc4 100644
--- a/test/ASTMerge/Inputs/struct1.c
+++ b/test/ASTMerge/Inputs/struct1.c
@@ -55,3 +55,9 @@ struct DeepError {
int value;
struct DeeperError { int i; int f; } *Deeper;
} xDeep;
+
+// Matches
+struct {
+ Int i;
+ float f;
+} x11;
diff --git a/test/ASTMerge/Inputs/struct2.c b/test/ASTMerge/Inputs/struct2.c
index 402283137a..4b43df71d8 100644
--- a/test/ASTMerge/Inputs/struct2.c
+++ b/test/ASTMerge/Inputs/struct2.c
@@ -52,3 +52,9 @@ struct DeepError {
int value;
struct DeeperError { int i; float f; } *Deeper;
} xDeep;
+
+// Matches
+struct {
+ int i;
+ float f;
+} x11;
diff --git a/test/ASTMerge/struct.c b/test/ASTMerge/struct.c
index d71436f2fc..e72b93b249 100644
--- a/test/ASTMerge/struct.c
+++ b/test/ASTMerge/struct.c
@@ -34,6 +34,9 @@
// CHECK: struct1.c:56:10: warning: type 'struct DeeperError' has incompatible definitions in different translation units
// CHECK: struct1.c:56:35: note: field 'f' has type 'int' here
// CHECK: struct2.c:53:37: note: field 'f' has type 'float' here
+// CHECK: struct1.c:54:8: warning: type 'struct DeepError' has incompatible definitions in different translation units
+// CHECK: struct1.c:56:41: note: field 'Deeper' has type 'struct DeeperError *' here
+// CHECK: struct2.c:53:43: note: field 'Deeper' has type 'struct DeeperError *' here
// CHECK: struct2.c:54:3: error: external variable 'xDeep' declared with incompatible types in different translation units ('struct DeepError' vs. 'struct DeepError')
// CHECK: struct1.c:57:3: note: declared here with type 'struct DeepError'
// CHECK: 37 diagnostics