From: Douglas Gregor Date: Mon, 15 Feb 2010 22:01:00 +0000 (+0000) Subject: Reimplement the structural-equality checks used to determine whether X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=73dc30b71e218ba2b776b10d07dc2aff09cb2c47;p=clang Reimplement the structural-equality checks used to determine whether two types in different AST contexts are equivalent. Rather than transforming the type from one context into the other context, we perform a deep structural comparison of the types. This change addresses a serious problem with recursive data types like struct ListNode { int value; struct ListNode *Next; } xList; git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@96278 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/AST/ASTImporter.h b/include/clang/AST/ASTImporter.h index f2df1677fb..9da9ea9c11 100644 --- a/include/clang/AST/ASTImporter.h +++ b/include/clang/AST/ASTImporter.h @@ -193,6 +193,9 @@ namespace clang { /// \brief Retrieve the file manager that AST nodes are being imported from. FileManager &getFromFileManager() const { return FromFileManager; } + /// \brief Retrieve the diagnostic formatter. + Diagnostic &getDiags() const { return Diags; } + /// \brief Report a diagnostic in the "to" context. DiagnosticBuilder ToDiag(SourceLocation Loc, unsigned DiagID); diff --git a/include/clang/AST/Type.h b/include/clang/AST/Type.h index 182ece96d7..2fe041f5e7 100644 --- a/include/clang/AST/Type.h +++ b/include/clang/AST/Type.h @@ -1193,7 +1193,8 @@ protected: } public: bool isSpelledAsLValue() const { return SpelledAsLValue; } - + bool isInnerRef() const { return InnerRef; } + QualType getPointeeTypeAsWritten() const { return PointeeType; } QualType getPointeeType() const { // FIXME: this might strip inner qualifiers; okay? @@ -2582,6 +2583,12 @@ public: /// interface type, or 0 if there are none. unsigned getNumProtocols() const { return NumProtocols; } + /// \brief Retrieve the Ith protocol. + ObjCProtocolDecl *getProtocol(unsigned I) const { + assert(I < getNumProtocols() && "Out-of-range protocol access"); + return qual_begin()[I]; + } + /// qual_iterator and friends: this provides access to the (potentially empty) /// list of protocols qualifying this interface. typedef ObjCProtocolDecl* const * qual_iterator; @@ -2683,6 +2690,12 @@ public: /// interface type, or 0 if there are none. unsigned getNumProtocols() const { return NumProtocols; } + /// \brief Retrieve the Ith protocol. + ObjCProtocolDecl *getProtocol(unsigned I) const { + assert(I < getNumProtocols() && "Out-of-range protocol access"); + return qual_begin()[I]; + } + bool isSugared() const { return false; } QualType desugar() const { return QualType(this, 0); } diff --git a/lib/AST/ASTImporter.cpp b/lib/AST/ASTImporter.cpp index 0d0a76e562..5e4d39f8f9 100644 --- a/lib/AST/ASTImporter.cpp +++ b/lib/AST/ASTImporter.cpp @@ -24,6 +24,7 @@ #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "llvm/Support/MemoryBuffer.h" +#include using namespace clang; @@ -85,7 +86,7 @@ namespace { DeclarationName &Name, SourceLocation &Loc, QualType &T); bool IsStructuralMatch(RecordDecl *FromRecord, RecordDecl *ToRecord); - bool IsStructuralMatch(EnumDecl *FromEnum, EnumDecl *ToEnum); + bool IsStructuralMatch(EnumDecl *FromEnum, EnumDecl *ToRecord); Decl *VisitDecl(Decl *D); Decl *VisitTypedefDecl(TypedefDecl *D); Decl *VisitEnumDecl(EnumDecl *D); @@ -106,6 +107,839 @@ namespace { }; } +//---------------------------------------------------------------------------- +// Structural Equivalence +//---------------------------------------------------------------------------- + +namespace { + struct StructuralEquivalenceContext { + /// \brief AST contexts for which we are checking structural equivalence. + ASTContext &C1, &C2; + + /// \brief Diagnostic object used to emit diagnostics. + Diagnostic &Diags; + + /// \brief The set of "tentative" equivalences between two canonical + /// declarations, mapping from a declaration in the first context to the + /// declaration in the second context that we believe to be equivalent. + llvm::DenseMap TentativeEquivalences; + + /// \brief Queue of declarations in the first context whose equivalence + /// with a declaration in the second context still needs to be verified. + std::deque DeclsToCheck; + + /// \brief Whether we're being strict about the spelling of types when + /// unifying two types. + bool StrictTypeSpelling; + + StructuralEquivalenceContext(ASTContext &C1, ASTContext &C2, + Diagnostic &Diags, + bool StrictTypeSpelling = false) + : C1(C1), C2(C2), Diags(Diags), StrictTypeSpelling(StrictTypeSpelling) { } + + /// \brief Determine whether the two declarations are structurally + /// equivalent. + bool IsStructurallyEquivalent(Decl *D1, Decl *D2); + + /// \brief Determine whether the two types are structurally equivalent. + bool IsStructurallyEquivalent(QualType T1, QualType T2); + + private: + /// \brief Finish checking all of the structural equivalences. + /// + /// \returns true if an error occurred, false otherwise. + bool Finish(); + + public: + DiagnosticBuilder Diag1(SourceLocation Loc, unsigned DiagID) { + return Diags.Report(FullSourceLoc(Loc, C1.getSourceManager()), DiagID); + } + + DiagnosticBuilder Diag2(SourceLocation Loc, unsigned DiagID) { + return Diags.Report(FullSourceLoc(Loc, C2.getSourceManager()), DiagID); + } + }; +} + +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + QualType T1, QualType T2); +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + Decl *D1, Decl *D2); + +/// \brief Determine if two APInts have the same value, after zero-extending +/// one of them (if needed!) to ensure that the bit-widths match. +static bool IsSameValue(const llvm::APInt &I1, const llvm::APInt &I2) { + if (I1.getBitWidth() == I2.getBitWidth()) + return I1 == I2; + + if (I1.getBitWidth() > I2.getBitWidth()) + return I1 == llvm::APInt(I2).zext(I1.getBitWidth()); + + return llvm::APInt(I1).zext(I2.getBitWidth()) == I2; +} + +/// \brief Determine if two APSInts have the same value, zero- or sign-extending +/// as needed. +static bool IsSameValue(const llvm::APSInt &I1, const llvm::APSInt &I2) { + if (I1.getBitWidth() == I2.getBitWidth() && I1.isSigned() == I2.isSigned()) + return I1 == I2; + + // Check for a bit-width mismatch. + if (I1.getBitWidth() > I2.getBitWidth()) + return IsSameValue(I1, llvm::APSInt(I2).extend(I1.getBitWidth())); + else if (I2.getBitWidth() > I1.getBitWidth()) + return IsSameValue(llvm::APSInt(I1).extend(I2.getBitWidth()), I2); + + // We have a signedness mismatch. Turn the signed value into an unsigned + // value. + if (I1.isSigned()) { + if (I1.isNegative()) + return false; + + return llvm::APSInt(I1, true) == I2; + } + + if (I2.isNegative()) + return false; + + return I1 == llvm::APSInt(I2, true); +} + +/// \brief Determine structural equivalence of two expressions. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + Expr *E1, Expr *E2) { + if (!E1 || !E2) + return E1 == E2; + + // FIXME: Actually perform a structural comparison! + return true; +} + +/// \brief Determine whether two identifiers are equivalent. +static bool IsStructurallyEquivalent(const IdentifierInfo *Name1, + const IdentifierInfo *Name2) { + if (!Name1 || !Name2) + return Name1 == Name2; + + return Name1->getName() == Name2->getName(); +} + +/// \brief Determine whether two nested-name-specifiers are equivalent. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + NestedNameSpecifier *NNS1, + NestedNameSpecifier *NNS2) { + // FIXME: Implement! + return true; +} + +/// \brief Determine whether two template arguments are equivalent. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + const TemplateArgument &Arg1, + const TemplateArgument &Arg2) { + // FIXME: Implement! + return true; +} + +/// \brief Determine structural equivalence for the common part of array +/// types. +static bool IsArrayStructurallyEquivalent(StructuralEquivalenceContext &Context, + const ArrayType *Array1, + const ArrayType *Array2) { + if (!IsStructurallyEquivalent(Context, + Array1->getElementType(), + Array2->getElementType())) + return false; + if (Array1->getSizeModifier() != Array2->getSizeModifier()) + return false; + if (Array1->getIndexTypeQualifiers() != Array2->getIndexTypeQualifiers()) + return false; + + return true; +} + +/// \brief Determine structural equivalence of two types. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + QualType T1, QualType T2) { + if (T1.isNull() || T2.isNull()) + return T1.isNull() && T2.isNull(); + + if (!Context.StrictTypeSpelling) { + // We aren't being strict about token-to-token equivalence of types, + // so map down to the canonical type. + T1 = Context.C1.getCanonicalType(T1); + T2 = Context.C2.getCanonicalType(T2); + } + + if (T1.getQualifiers() != T2.getQualifiers()) + return false; + + if (T1->getTypeClass() != T2->getTypeClass()) + return false; + + switch (T1->getTypeClass()) { + case Type::Builtin: + // FIXME: Deal with Char_S/Char_U. + if (cast(T1)->getKind() != cast(T2)->getKind()) + return false; + break; + + case Type::Complex: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getElementType(), + cast(T2)->getElementType())) + return false; + break; + + case Type::Pointer: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getPointeeType(), + cast(T2)->getPointeeType())) + return false; + break; + + case Type::BlockPointer: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getPointeeType(), + cast(T2)->getPointeeType())) + return false; + break; + + case Type::LValueReference: + case Type::RValueReference: { + const ReferenceType *Ref1 = cast(T1); + const ReferenceType *Ref2 = cast(T2); + if (Ref1->isSpelledAsLValue() != Ref2->isSpelledAsLValue()) + return false; + if (Ref1->isInnerRef() != Ref2->isInnerRef()) + return false; + if (!IsStructurallyEquivalent(Context, + Ref1->getPointeeTypeAsWritten(), + Ref2->getPointeeTypeAsWritten())) + return false; + break; + } + + case Type::MemberPointer: { + const MemberPointerType *MemPtr1 = cast(T1); + const MemberPointerType *MemPtr2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + MemPtr1->getPointeeType(), + MemPtr2->getPointeeType())) + return false; + if (!IsStructurallyEquivalent(Context, + QualType(MemPtr1->getClass(), 0), + QualType(MemPtr2->getClass(), 0))) + return false; + break; + } + + case Type::ConstantArray: { + const ConstantArrayType *Array1 = cast(T1); + const ConstantArrayType *Array2 = cast(T2); + if (!IsSameValue(Array1->getSize(), Array2->getSize())) + return false; + + if (!IsArrayStructurallyEquivalent(Context, Array1, Array2)) + return false; + break; + } + + case Type::IncompleteArray: + if (!IsArrayStructurallyEquivalent(Context, + cast(T1), + cast(T2))) + return false; + break; + + case Type::VariableArray: { + const VariableArrayType *Array1 = cast(T1); + const VariableArrayType *Array2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Array1->getSizeExpr(), Array2->getSizeExpr())) + return false; + + if (!IsArrayStructurallyEquivalent(Context, Array1, Array2)) + return false; + + break; + } + + case Type::DependentSizedArray: { + const DependentSizedArrayType *Array1 = cast(T1); + const DependentSizedArrayType *Array2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Array1->getSizeExpr(), Array2->getSizeExpr())) + return false; + + if (!IsArrayStructurallyEquivalent(Context, Array1, Array2)) + return false; + + break; + } + + case Type::DependentSizedExtVector: { + const DependentSizedExtVectorType *Vec1 + = cast(T1); + const DependentSizedExtVectorType *Vec2 + = cast(T2); + if (!IsStructurallyEquivalent(Context, + Vec1->getSizeExpr(), Vec2->getSizeExpr())) + return false; + if (!IsStructurallyEquivalent(Context, + Vec1->getElementType(), + Vec2->getElementType())) + return false; + break; + } + + case Type::Vector: + case Type::ExtVector: { + const VectorType *Vec1 = cast(T1); + const VectorType *Vec2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Vec1->getElementType(), + Vec2->getElementType())) + return false; + if (Vec1->getNumElements() != Vec2->getNumElements()) + return false; + if (Vec1->isAltiVec() != Vec2->isAltiVec()) + return false; + if (Vec1->isPixel() != Vec2->isPixel()) + return false; + } + + case Type::FunctionProto: { + const FunctionProtoType *Proto1 = cast(T1); + const FunctionProtoType *Proto2 = cast(T2); + if (Proto1->getNumArgs() != Proto2->getNumArgs()) + return false; + for (unsigned I = 0, N = Proto1->getNumArgs(); I != N; ++I) { + if (!IsStructurallyEquivalent(Context, + Proto1->getArgType(I), + Proto2->getArgType(I))) + return false; + } + if (Proto1->isVariadic() != Proto2->isVariadic()) + return false; + if (Proto1->hasExceptionSpec() != Proto2->hasExceptionSpec()) + return false; + if (Proto1->hasAnyExceptionSpec() != Proto2->hasAnyExceptionSpec()) + return false; + if (Proto1->getNumExceptions() != Proto2->getNumExceptions()) + return false; + for (unsigned I = 0, N = Proto1->getNumExceptions(); I != N; ++I) { + if (!IsStructurallyEquivalent(Context, + Proto1->getExceptionType(I), + Proto2->getExceptionType(I))) + return false; + } + if (Proto1->getTypeQuals() != Proto2->getTypeQuals()) + return false; + + // Fall through to check the bits common with FunctionNoProtoType. + } + + case Type::FunctionNoProto: { + const FunctionType *Function1 = cast(T1); + const FunctionType *Function2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Function1->getResultType(), + Function2->getResultType())) + return false; + if (Function1->getNoReturnAttr() != Function2->getNoReturnAttr()) + return false; + if (Function1->getCallConv() != Function2->getCallConv()) + return false; + break; + } + + case Type::UnresolvedUsing: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getDecl(), + cast(T2)->getDecl())) + return false; + + break; + + case Type::Typedef: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getDecl(), + cast(T2)->getDecl())) + return false; + break; + + case Type::TypeOfExpr: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getUnderlyingExpr(), + cast(T2)->getUnderlyingExpr())) + return false; + break; + + case Type::TypeOf: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getUnderlyingType(), + cast(T2)->getUnderlyingType())) + return false; + break; + + case Type::Decltype: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getUnderlyingExpr(), + cast(T2)->getUnderlyingExpr())) + return false; + break; + + case Type::Record: + case Type::Enum: + if (!IsStructurallyEquivalent(Context, + cast(T1)->getDecl(), + cast(T2)->getDecl())) + return false; + break; + + case Type::Elaborated: { + const ElaboratedType *Elab1 = cast(T1); + const ElaboratedType *Elab2 = cast(T2); + if (Elab1->getTagKind() != Elab2->getTagKind()) + return false; + if (!IsStructurallyEquivalent(Context, + Elab1->getUnderlyingType(), + Elab2->getUnderlyingType())) + return false; + break; + } + + case Type::TemplateTypeParm: { + const TemplateTypeParmType *Parm1 = cast(T1); + const TemplateTypeParmType *Parm2 = cast(T2); + if (Parm1->getDepth() != Parm2->getDepth()) + return false; + if (Parm1->getIndex() != Parm2->getIndex()) + return false; + if (Parm1->isParameterPack() != Parm2->isParameterPack()) + return false; + + // Names of template type parameters are never significant. + break; + } + + case Type::SubstTemplateTypeParm: { + const SubstTemplateTypeParmType *Subst1 + = cast(T1); + const SubstTemplateTypeParmType *Subst2 + = cast(T2); + if (!IsStructurallyEquivalent(Context, + QualType(Subst1->getReplacedParameter(), 0), + QualType(Subst2->getReplacedParameter(), 0))) + return false; + if (!IsStructurallyEquivalent(Context, + Subst1->getReplacementType(), + Subst2->getReplacementType())) + return false; + break; + } + + case Type::TemplateSpecialization: { + const TemplateSpecializationType *Spec1 + = cast(T1); + const TemplateSpecializationType *Spec2 + = cast(T2); + if (!IsStructurallyEquivalent(Context, + Spec1->getTemplateName(), + Spec2->getTemplateName())) + return false; + if (Spec1->getNumArgs() != Spec2->getNumArgs()) + return false; + for (unsigned I = 0, N = Spec1->getNumArgs(); I != N; ++I) { + if (!IsStructurallyEquivalent(Context, + Spec1->getArg(I), Spec2->getArg(I))) + return false; + } + break; + } + + case Type::QualifiedName: { + const QualifiedNameType *Qual1 = cast(T1); + const QualifiedNameType *Qual2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Qual1->getQualifier(), + Qual2->getQualifier())) + return false; + if (!IsStructurallyEquivalent(Context, + Qual1->getNamedType(), + Qual2->getNamedType())) + return false; + break; + } + + case Type::Typename: { + const TypenameType *Typename1 = cast(T1); + const TypenameType *Typename2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Typename1->getQualifier(), + Typename2->getQualifier())) + return false; + if (!IsStructurallyEquivalent(Typename1->getIdentifier(), + Typename2->getIdentifier())) + return false; + if (!IsStructurallyEquivalent(Context, + QualType(Typename1->getTemplateId(), 0), + QualType(Typename2->getTemplateId(), 0))) + return false; + + break; + } + + case Type::ObjCInterface: { + const ObjCInterfaceType *Iface1 = cast(T1); + const ObjCInterfaceType *Iface2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Iface1->getDecl(), Iface2->getDecl())) + return false; + if (Iface1->getNumProtocols() != Iface2->getNumProtocols()) + return false; + for (unsigned I = 0, N = Iface1->getNumProtocols(); I != N; ++I) { + if (!IsStructurallyEquivalent(Context, + Iface1->getProtocol(I), + Iface2->getProtocol(I))) + return false; + } + break; + } + + case Type::ObjCObjectPointer: { + const ObjCObjectPointerType *Ptr1 = cast(T1); + const ObjCObjectPointerType *Ptr2 = cast(T2); + if (!IsStructurallyEquivalent(Context, + Ptr1->getPointeeType(), + Ptr2->getPointeeType())) + return false; + if (Ptr1->getNumProtocols() != Ptr2->getNumProtocols()) + return false; + for (unsigned I = 0, N = Ptr1->getNumProtocols(); I != N; ++I) { + if (!IsStructurallyEquivalent(Context, + Ptr1->getProtocol(I), + Ptr2->getProtocol(I))) + return false; + } + break; + } + + } // end switch + + return true; +} + +/// \brief Determine structural equivalence of two records. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + RecordDecl *D1, RecordDecl *D2) { + if (D1->isUnion() != D2->isUnion()) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag1(D1->getLocation(), diag::note_odr_tag_kind_here) + << D1->getDeclName() << (unsigned)D1->getTagKind(); + return false; + } + + if (CXXRecordDecl *D1CXX = dyn_cast(D1)) { + if (CXXRecordDecl *D2CXX = dyn_cast(D2)) { + if (D1CXX->getNumBases() != D2CXX->getNumBases()) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(D2->getLocation(), diag::note_odr_number_of_bases) + << D2CXX->getNumBases(); + Context.Diag1(D1->getLocation(), diag::note_odr_number_of_bases) + << D1CXX->getNumBases(); + return false; + } + + // Check the base classes. + for (CXXRecordDecl::base_class_iterator Base1 = D1CXX->bases_begin(), + BaseEnd1 = D1CXX->bases_end(), + Base2 = D2CXX->bases_begin(); + Base1 != BaseEnd1; + ++Base1, ++Base2) { + if (!IsStructurallyEquivalent(Context, + Base1->getType(), Base2->getType())) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(Base2->getSourceRange().getBegin(), diag::note_odr_base) + << Base2->getType() + << Base2->getSourceRange(); + Context.Diag1(Base1->getSourceRange().getBegin(), diag::note_odr_base) + << Base1->getType() + << Base1->getSourceRange(); + return false; + } + + // Check virtual vs. non-virtual inheritance mismatch. + if (Base1->isVirtual() != Base2->isVirtual()) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(Base2->getSourceRange().getBegin(), + diag::note_odr_virtual_base) + << Base2->isVirtual() << Base2->getSourceRange(); + Context.Diag1(Base1->getSourceRange().getBegin(), diag::note_odr_base) + << Base1->isVirtual() + << Base1->getSourceRange(); + return false; + } + } + } else if (D1CXX->getNumBases() > 0) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + const CXXBaseSpecifier *Base1 = D1CXX->bases_begin(); + Context.Diag1(Base1->getSourceRange().getBegin(), diag::note_odr_base) + << Base1->getType() + << Base1->getSourceRange(); + Context.Diag2(D2->getLocation(), diag::note_odr_missing_base); + return false; + } + } + + // Check the fields for consistency. + CXXRecordDecl::field_iterator Field2 = D2->field_begin(), + Field2End = D2->field_end(); + for (CXXRecordDecl::field_iterator Field1 = D1->field_begin(), + Field1End = D1->field_end(); + Field1 != Field1End; + ++Field1, ++Field2) { + if (Field2 == Field2End) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag1(Field1->getLocation(), diag::note_odr_field) + << Field1->getDeclName() << Field1->getType(); + Context.Diag2(D2->getLocation(), diag::note_odr_missing_field); + return false; + } + + if (!IsStructurallyEquivalent(Context, + Field1->getType(), Field2->getType())) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(Field2->getLocation(), diag::note_odr_field) + << Field2->getDeclName() << Field2->getType(); + Context.Diag1(Field1->getLocation(), diag::note_odr_field) + << Field1->getDeclName() << Field1->getType(); + return false; + } + + if (Field1->isBitField() != Field2->isBitField()) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + if (Field1->isBitField()) { + llvm::APSInt Bits; + Field1->getBitWidth()->isIntegerConstantExpr(Bits, Context.C1); + Context.Diag1(Field1->getLocation(), diag::note_odr_bit_field) + << Field1->getDeclName() << Field1->getType() + << Bits.toString(10, false); + Context.Diag2(Field2->getLocation(), diag::note_odr_not_bit_field) + << Field2->getDeclName(); + } else { + llvm::APSInt Bits; + Field2->getBitWidth()->isIntegerConstantExpr(Bits, Context.C2); + Context.Diag2(Field2->getLocation(), diag::note_odr_bit_field) + << Field2->getDeclName() << Field2->getType() + << Bits.toString(10, false); + Context.Diag1(Field1->getLocation(), + diag::note_odr_not_bit_field) + << Field1->getDeclName(); + } + return false; + } + + if (Field1->isBitField()) { + // Make sure that the bit-fields are the same length. + llvm::APSInt Bits1, Bits2; + if (!Field1->getBitWidth()->isIntegerConstantExpr(Bits1, Context.C1)) + return false; + if (!Field2->getBitWidth()->isIntegerConstantExpr(Bits2, Context.C2)) + return false; + + if (!IsSameValue(Bits1, Bits2)) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(Field2->getLocation(), diag::note_odr_bit_field) + << Field2->getDeclName() << Field2->getType() + << Bits2.toString(10, false); + Context.Diag1(Field1->getLocation(), diag::note_odr_bit_field) + << Field1->getDeclName() << Field1->getType() + << Bits1.toString(10, false); + return false; + } + } + } + + if (Field2 != Field2End) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(Field2->getLocation(), diag::note_odr_field) + << Field2->getDeclName() << Field2->getType(); + Context.Diag1(D1->getLocation(), diag::note_odr_missing_field); + return false; + } + + return true; +} + +/// \brief Determine structural equivalence of two enums. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + EnumDecl *D1, EnumDecl *D2) { + EnumDecl::enumerator_iterator EC2 = D2->enumerator_begin(), + EC2End = D2->enumerator_end(); + for (EnumDecl::enumerator_iterator EC1 = D1->enumerator_begin(), + EC1End = D1->enumerator_end(); + EC1 != EC1End; ++EC1, ++EC2) { + if (EC2 == EC2End) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator) + << EC1->getDeclName() + << EC1->getInitVal().toString(10); + Context.Diag2(D2->getLocation(), diag::note_odr_missing_enumerator); + return false; + } + + llvm::APSInt Val1 = EC1->getInitVal(); + llvm::APSInt Val2 = EC2->getInitVal(); + if (!IsSameValue(Val1, Val2) || + !IsStructurallyEquivalent(EC1->getIdentifier(), EC2->getIdentifier())) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator) + << EC2->getDeclName() + << EC2->getInitVal().toString(10); + Context.Diag1(EC1->getLocation(), diag::note_odr_enumerator) + << EC1->getDeclName() + << EC1->getInitVal().toString(10); + return false; + } + } + + if (EC2 != EC2End) { + Context.Diag2(D2->getLocation(), diag::warn_odr_tag_type_inconsistent) + << Context.C2.getTypeDeclType(D2); + Context.Diag2(EC2->getLocation(), diag::note_odr_enumerator) + << EC2->getDeclName() + << EC2->getInitVal().toString(10); + Context.Diag1(D1->getLocation(), diag::note_odr_missing_enumerator); + return false; + } + + return true; +} + +/// \brief Determine structural equivalence of two declarations. +static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, + Decl *D1, Decl *D2) { + // FIXME: Check for known structural equivalences via a callback of some sort. + + // Determine whether we've already produced a tentative equivalence for D1. + Decl *&EquivToD1 = Context.TentativeEquivalences[D1->getCanonicalDecl()]; + if (EquivToD1) + return EquivToD1 == D2->getCanonicalDecl(); + + // Produce a tentative equivalence D1 <-> D2, which will be checked later. + EquivToD1 = D2->getCanonicalDecl(); + Context.DeclsToCheck.push_back(D1->getCanonicalDecl()); + return true; +} + +bool StructuralEquivalenceContext::IsStructurallyEquivalent(Decl *D1, + Decl *D2) { + if (!::IsStructurallyEquivalent(*this, D1, D2)) + return false; + + return !Finish(); +} + +bool StructuralEquivalenceContext::IsStructurallyEquivalent(QualType T1, + QualType T2) { + if (!::IsStructurallyEquivalent(*this, T1, T2)) + return false; + + return !Finish(); +} + +bool StructuralEquivalenceContext::Finish() { + while (!DeclsToCheck.empty()) { + // Check the next declaration. + Decl *D1 = DeclsToCheck.front(); + DeclsToCheck.pop_front(); + + Decl *D2 = TentativeEquivalences[D1]; + assert(D2 && "Unrecorded tentative equivalence?"); + + // FIXME: Switch on all declaration kinds. For now, we're just going to + // check the obvious ones. + if (RecordDecl *Record1 = dyn_cast(D1)) { + if (RecordDecl *Record2 = dyn_cast(D2)) { + // Check for equivalent structure names. + IdentifierInfo *Name1 = Record1->getIdentifier(); + if (!Name1 && Record1->getTypedefForAnonDecl()) + Name1 = Record1->getTypedefForAnonDecl()->getIdentifier(); + 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; + } else { + // Record/non-record mismatch. + return true; + } + + continue; + } + + if (EnumDecl *Enum1 = dyn_cast(D1)) { + if (EnumDecl *Enum2 = dyn_cast(D2)) { + // Check for equivalent enum names. + IdentifierInfo *Name1 = Enum1->getIdentifier(); + if (!Name1 && Enum1->getTypedefForAnonDecl()) + Name1 = Enum1->getTypedefForAnonDecl()->getIdentifier(); + 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; + } else { + // Enum/non-enum mismatch + return true; + } + + continue; + } + + if (TypedefDecl *Typedef1 = dyn_cast(D1)) { + if (TypedefDecl *Typedef2 = dyn_cast(D2)) { + if (!::IsStructurallyEquivalent(Typedef1->getIdentifier(), + Typedef2->getIdentifier())) + return true; + + if (!::IsStructurallyEquivalent(*this, + Typedef1->getUnderlyingType(), + Typedef2->getUnderlyingType())) + return true; + } else { + // Typedef/non-typedef mismatch. + return true; + } + + continue; + } + + // FIXME: Check other declaration kinds! + } + + return false; +} + //---------------------------------------------------------------------------- // Import Types //---------------------------------------------------------------------------- @@ -519,250 +1353,18 @@ bool ASTNodeImporter::ImportDeclParts(ValueDecl *D, } bool ASTNodeImporter::IsStructuralMatch(RecordDecl *FromRecord, - RecordDecl *ToRecord) { - if (FromRecord->isUnion() != ToRecord->isUnion()) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.FromDiag(FromRecord->getLocation(), diag::note_odr_tag_kind_here) - << FromRecord->getDeclName() << (unsigned)FromRecord->getTagKind(); - return false; - } - - if (CXXRecordDecl *FromCXX = dyn_cast(FromRecord)) { - if (CXXRecordDecl *ToCXX = dyn_cast(ToRecord)) { - if (FromCXX->getNumBases() != ToCXX->getNumBases()) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToRecord->getLocation(), diag::note_odr_number_of_bases) - << ToCXX->getNumBases(); - Importer.FromDiag(FromRecord->getLocation(), - diag::note_odr_number_of_bases) - << FromCXX->getNumBases(); - return false; - } - - // Check the base classes. - for (CXXRecordDecl::base_class_iterator FromBase = FromCXX->bases_begin(), - FromBaseEnd = FromCXX->bases_end(), - ToBase = ToCXX->bases_begin(); - FromBase != FromBaseEnd; - ++FromBase, ++ToBase) { - // Check the type we're inheriting from. - QualType FromBaseT = Importer.Import(FromBase->getType()); - if (FromBaseT.isNull()) - return false; - - if (!Importer.getToContext().typesAreCompatible(FromBaseT, - ToBase->getType())) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToBase->getSourceRange().getBegin(), - diag::note_odr_base) - << ToBase->getType() - << ToBase->getSourceRange(); - Importer.FromDiag(FromBase->getSourceRange().getBegin(), - diag::note_odr_base) - << FromBase->getType() - << FromBase->getSourceRange(); - return false; - } - - // Check virtual vs. non-virtual inheritance mismatch. - if (FromBase->isVirtual() != ToBase->isVirtual()) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToBase->getSourceRange().getBegin(), - diag::note_odr_virtual_base) - << ToBase->isVirtual() << ToBase->getSourceRange(); - Importer.FromDiag(FromBase->getSourceRange().getBegin(), - diag::note_odr_base) - << FromBase->isVirtual() - << FromBase->getSourceRange(); - return false; - } - } - } else if (FromCXX->getNumBases() > 0) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - const CXXBaseSpecifier *FromBase = FromCXX->bases_begin(); - Importer.FromDiag(FromBase->getSourceRange().getBegin(), - diag::note_odr_base) - << FromBase->getType() - << FromBase->getSourceRange(); - Importer.ToDiag(ToRecord->getLocation(), diag::note_odr_missing_base); - return false; - } - } - - // Check the fields for consistency. - CXXRecordDecl::field_iterator ToField = ToRecord->field_begin(), - ToFieldEnd = ToRecord->field_end(); - for (CXXRecordDecl::field_iterator FromField = FromRecord->field_begin(), - FromFieldEnd = FromRecord->field_end(); - FromField != FromFieldEnd; - ++FromField, ++ToField) { - if (ToField == ToFieldEnd) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.FromDiag(FromField->getLocation(), diag::note_odr_field) - << FromField->getDeclName() << FromField->getType(); - Importer.ToDiag(ToRecord->getLocation(), diag::note_odr_missing_field); - return false; - } - - QualType FromT = Importer.Import(FromField->getType()); - if (FromT.isNull()) - return false; - - if (!Importer.getToContext().typesAreCompatible(FromT, ToField->getType())){ - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToField->getLocation(), diag::note_odr_field) - << ToField->getDeclName() << ToField->getType(); - Importer.FromDiag(FromField->getLocation(), diag::note_odr_field) - << FromField->getDeclName() << FromField->getType(); - return false; - } - - if (FromField->isBitField() != ToField->isBitField()) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - if (FromField->isBitField()) { - llvm::APSInt Bits; - FromField->getBitWidth()->isIntegerConstantExpr(Bits, - Importer.getFromContext()); - Importer.FromDiag(FromField->getLocation(), diag::note_odr_bit_field) - << FromField->getDeclName() << FromField->getType() - << Bits.toString(10, false); - Importer.ToDiag(ToField->getLocation(), diag::note_odr_not_bit_field) - << ToField->getDeclName(); - } else { - llvm::APSInt Bits; - ToField->getBitWidth()->isIntegerConstantExpr(Bits, - Importer.getToContext()); - Importer.ToDiag(ToField->getLocation(), diag::note_odr_bit_field) - << ToField->getDeclName() << ToField->getType() - << Bits.toString(10, false); - Importer.FromDiag(FromField->getLocation(), - diag::note_odr_not_bit_field) - << FromField->getDeclName(); - } - return false; - } - - if (FromField->isBitField()) { - // Make sure that the bit-fields are the same length. - llvm::APSInt FromBits, ToBits; - if (!FromField->getBitWidth()->isIntegerConstantExpr(FromBits, - Importer.getFromContext())) - return false; - if (!ToField->getBitWidth()->isIntegerConstantExpr(ToBits, - Importer.getToContext())) - return false; - - if (FromBits.getBitWidth() > ToBits.getBitWidth()) - ToBits.extend(FromBits.getBitWidth()); - else if (ToBits.getBitWidth() > FromBits.getBitWidth()) - FromBits.extend(ToBits.getBitWidth()); - - FromBits.setIsUnsigned(true); - ToBits.setIsUnsigned(true); - - if (FromBits != ToBits) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToField->getLocation(), diag::note_odr_bit_field) - << ToField->getDeclName() << ToField->getType() - << ToBits.toString(10, false); - Importer.FromDiag(FromField->getLocation(), diag::note_odr_bit_field) - << FromField->getDeclName() << FromField->getType() - << FromBits.toString(10, false); - return false; - } - } - } - - if (ToField != ToFieldEnd) { - Importer.ToDiag(ToRecord->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToRecord); - Importer.ToDiag(ToField->getLocation(), diag::note_odr_field) - << ToField->getDeclName() << ToField->getType(); - Importer.FromDiag(FromRecord->getLocation(), diag::note_odr_missing_field); - return false; - } - - return true; + RecordDecl *ToRecord) { + StructuralEquivalenceContext SEC(Importer.getFromContext(), + Importer.getToContext(), + Importer.getDiags()); + return SEC.IsStructurallyEquivalent(FromRecord, ToRecord); } bool ASTNodeImporter::IsStructuralMatch(EnumDecl *FromEnum, EnumDecl *ToEnum) { - EnumDecl::enumerator_iterator ToEC = ToEnum->enumerator_begin(), - ToEnd = ToEnum->enumerator_end(); - for (EnumDecl::enumerator_iterator FromEC = FromEnum->enumerator_begin(), - FromEnd = FromEnum->enumerator_end(); - FromEC != FromEnd; ++FromEC, ++ToEC) { - if (ToEC == ToEnd) { - Importer.ToDiag(ToEnum->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToEnum); - Importer.FromDiag(FromEC->getLocation(), diag::note_odr_enumerator) - << FromEC->getDeclName() - << FromEC->getInitVal().toString(10); - Importer.ToDiag(ToEnum->getLocation(), - diag::note_odr_missing_enumerator); - return false; - } - - llvm::APSInt FromVal = FromEC->getInitVal(); - llvm::APSInt ToVal = ToEC->getInitVal(); - if (FromVal.getBitWidth() > ToVal.getBitWidth()) - ToVal.extend(FromVal.getBitWidth()); - else if (ToVal.getBitWidth() > FromVal.getBitWidth()) - FromVal.extend(ToVal.getBitWidth()); - if (FromVal.isSigned() != ToVal.isSigned()) { - if (FromVal.isSigned()) - ToVal.setIsSigned(true); - else - FromVal.setIsSigned(true); - } - - if (FromVal != ToVal || - ToEC->getDeclName() != Importer.Import(FromEC->getDeclName())) { - Importer.ToDiag(ToEnum->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToEnum); - Importer.ToDiag(ToEC->getLocation(), diag::note_odr_enumerator) - << ToEC->getDeclName() - << ToEC->getInitVal().toString(10); - Importer.FromDiag(FromEC->getLocation(), diag::note_odr_enumerator) - << FromEC->getDeclName() - << FromEC->getInitVal().toString(10); - return false; - } - } - - if (ToEC != ToEnd) { - Importer.ToDiag(ToEnum->getLocation(), - diag::warn_odr_tag_type_inconsistent) - << Importer.getToContext().getTypeDeclType(ToEnum); - Importer.ToDiag(ToEC->getLocation(), diag::note_odr_enumerator) - << ToEC->getDeclName() - << ToEC->getInitVal().toString(10); - Importer.FromDiag(FromEnum->getLocation(), - diag::note_odr_missing_enumerator); - return false; - } - - return true; + StructuralEquivalenceContext SEC(Importer.getFromContext(), + Importer.getToContext(), + Importer.getDiags()); + return SEC.IsStructurallyEquivalent(FromEnum, ToEnum); } Decl *ASTNodeImporter::VisitDecl(Decl *D) { @@ -872,19 +1474,19 @@ Decl *ASTNodeImporter::VisitEnumDecl(EnumDecl *D) { } // Create the enum declaration. - EnumDecl *ToEnum = EnumDecl::Create(Importer.getToContext(), DC, Loc, + EnumDecl *D2 = EnumDecl::Create(Importer.getToContext(), DC, Loc, Name.getAsIdentifierInfo(), Importer.Import(D->getTagKeywordLoc()), 0); - ToEnum->setLexicalDeclContext(LexicalDC); - Importer.Imported(D, ToEnum); - LexicalDC->addDecl(ToEnum); + D2->setLexicalDeclContext(LexicalDC); + Importer.Imported(D, D2); + LexicalDC->addDecl(D2); // Import the integer type. QualType ToIntegerType = Importer.Import(D->getIntegerType()); if (ToIntegerType.isNull()) return 0; - ToEnum->setIntegerType(ToIntegerType); + D2->setIntegerType(ToIntegerType); // Import the definition if (D->isDefinition()) { @@ -896,17 +1498,17 @@ Decl *ASTNodeImporter::VisitEnumDecl(EnumDecl *D) { if (ToPromotionType.isNull()) return 0; - ToEnum->startDefinition(); + D2->startDefinition(); for (DeclContext::decl_iterator FromMem = D->decls_begin(), FromMemEnd = D->decls_end(); FromMem != FromMemEnd; ++FromMem) Importer.Import(*FromMem); - ToEnum->completeDefinition(T, ToPromotionType); + D2->completeDefinition(T, ToPromotionType); } - return ToEnum; + return D2; } Decl *ASTNodeImporter::VisitRecordDecl(RecordDecl *D) { @@ -982,63 +1584,63 @@ Decl *ASTNodeImporter::VisitRecordDecl(RecordDecl *D) { } // Create the record declaration. - RecordDecl *ToRecord = AdoptDecl; - if (!ToRecord) { - if (CXXRecordDecl *FromCXX = dyn_cast(D)) { - CXXRecordDecl *ToCXX = CXXRecordDecl::Create(Importer.getToContext(), + RecordDecl *D2 = AdoptDecl; + if (!D2) { + if (CXXRecordDecl *D1CXX = dyn_cast(D)) { + CXXRecordDecl *D2CXX = CXXRecordDecl::Create(Importer.getToContext(), D->getTagKind(), DC, Loc, Name.getAsIdentifierInfo(), Importer.Import(D->getTagKeywordLoc())); - ToRecord = ToCXX; + D2 = D2CXX; if (D->isDefinition()) { // Add base classes. llvm::SmallVector Bases; for (CXXRecordDecl::base_class_iterator - FromBase = FromCXX->bases_begin(), - FromBaseEnd = FromCXX->bases_end(); - FromBase != FromBaseEnd; - ++FromBase) { - QualType T = Importer.Import(FromBase->getType()); + Base1 = D1CXX->bases_begin(), + FromBaseEnd = D1CXX->bases_end(); + Base1 != FromBaseEnd; + ++Base1) { + QualType T = Importer.Import(Base1->getType()); if (T.isNull()) return 0; Bases.push_back( new (Importer.getToContext()) - CXXBaseSpecifier(Importer.Import(FromBase->getSourceRange()), - FromBase->isVirtual(), - FromBase->isBaseOfClass(), - FromBase->getAccessSpecifierAsWritten(), + CXXBaseSpecifier(Importer.Import(Base1->getSourceRange()), + Base1->isVirtual(), + Base1->isBaseOfClass(), + Base1->getAccessSpecifierAsWritten(), T)); } if (!Bases.empty()) - ToCXX->setBases(Bases.data(), Bases.size()); + D2CXX->setBases(Bases.data(), Bases.size()); } } else { - ToRecord = RecordDecl::Create(Importer.getToContext(), D->getTagKind(), + D2 = RecordDecl::Create(Importer.getToContext(), D->getTagKind(), DC, Loc, Name.getAsIdentifierInfo(), Importer.Import(D->getTagKeywordLoc())); } - ToRecord->setLexicalDeclContext(LexicalDC); - LexicalDC->addDecl(ToRecord); + D2->setLexicalDeclContext(LexicalDC); + LexicalDC->addDecl(D2); } - Importer.Imported(D, ToRecord); + Importer.Imported(D, D2); if (D->isDefinition()) { - ToRecord->startDefinition(); + D2->startDefinition(); for (DeclContext::decl_iterator FromMem = D->decls_begin(), FromMemEnd = D->decls_end(); FromMem != FromMemEnd; ++FromMem) Importer.Import(*FromMem); - ToRecord->completeDefinition(); + D2->completeDefinition(); } - return ToRecord; + return D2; } Decl *ASTNodeImporter::VisitEnumConstantDecl(EnumConstantDecl *D) { diff --git a/test/ASTMerge/Inputs/struct1.c b/test/ASTMerge/Inputs/struct1.c index 10c8fce42a..e6d71ac1b4 100644 --- a/test/ASTMerge/Inputs/struct1.c +++ b/test/ASTMerge/Inputs/struct1.c @@ -44,10 +44,14 @@ struct S9 { int i; float f; } *x9; // Incomplete type struct S10 *x10; -// FIXME: Matches, but crashes the importer -#if 0 +// Matches struct ListNode { int value; struct ListNode *Next; } xList; -#endif + +// Mismatch due to struct used internally +struct DeepError { + int value; + struct DeeperError { int i; int f; } *Deeper; +} xDeep; diff --git a/test/ASTMerge/Inputs/struct2.c b/test/ASTMerge/Inputs/struct2.c index 655efd43db..402283137a 100644 --- a/test/ASTMerge/Inputs/struct2.c +++ b/test/ASTMerge/Inputs/struct2.c @@ -46,3 +46,9 @@ struct ListNode { int value; struct ListNode *Next; } xList; + +// Mismatch due to struct used internally +struct DeepError { + int value; + struct DeeperError { int i; float f; } *Deeper; +} xDeep; diff --git a/test/ASTMerge/struct.c b/test/ASTMerge/struct.c index c6e57185ab..d71436f2fc 100644 --- a/test/ASTMerge/struct.c +++ b/test/ASTMerge/struct.c @@ -31,4 +31,9 @@ // CHECK: struct2.c:33:33: note: bit-field 'j' with type 'unsigned int' and length 16 here // CHECK: struct2.c:33:43: error: external variable 'x7' declared with incompatible types in different translation units ('struct S7' vs. 'struct S7') // CHECK: struct1.c:36:42: note: declared here with type 'struct S7' -// CHECK: 29 diagnostics +// 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: 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