From c78e9948f0c4544e0f6d28180d8c37f09d55d013 Mon Sep 17 00:00:00 2001 From: Richard Trieu Date: Wed, 25 Jul 2018 22:52:05 +0000 Subject: [PATCH] [ODRHash] Support hashing enums. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@337978 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/Decl.h | 8 + include/clang/AST/ODRHash.h | 6 +- .../Basic/DiagnosticSerializationKinds.td | 27 +++ include/clang/Serialization/ASTReader.h | 4 + lib/AST/Decl.cpp | 11 + lib/AST/ODRHash.cpp | 36 +++- lib/Serialization/ASTReader.cpp | 204 +++++++++++++++++- lib/Serialization/ASTReaderDecl.cpp | 5 + lib/Serialization/ASTWriterDecl.cpp | 3 + test/Modules/odr_hash.cpp | 171 +++++++++++++++ 10 files changed, 471 insertions(+), 4 deletions(-) diff --git a/include/clang/AST/Decl.h b/include/clang/AST/Decl.h index 4e19434b16..dde9459963 100644 --- a/include/clang/AST/Decl.h +++ b/include/clang/AST/Decl.h @@ -3335,6 +3335,10 @@ class EnumDecl : public TagDecl { /// information. MemberSpecializationInfo *SpecializationInfo = nullptr; + /// Store the ODRHash after first calculation. + unsigned HasODRHash : 1; + unsigned ODRHash; + EnumDecl(ASTContext &C, DeclContext *DC, SourceLocation StartLoc, SourceLocation IdLoc, IdentifierInfo *Id, EnumDecl *PrevDecl, bool Scoped, bool ScopedUsingClassTag, bool Fixed) @@ -3346,6 +3350,8 @@ class EnumDecl : public TagDecl { IsScoped = Scoped; IsScopedUsingClassTag = ScopedUsingClassTag; IsFixed = Fixed; + HasODRHash = false; + ODRHash = 0; } void anchor() override; @@ -3496,6 +3502,8 @@ public: return IsFixed; } + unsigned getODRHash(); + /// Returns true if this can be considered a complete type. bool isComplete() const { // IntegerType is set for fixed type enums and non-fixed but implicitly diff --git a/include/clang/AST/ODRHash.h b/include/clang/AST/ODRHash.h index 6a66ba1dad..75b3617892 100644 --- a/include/clang/AST/ODRHash.h +++ b/include/clang/AST/ODRHash.h @@ -58,6 +58,10 @@ public: // hash as if the function has no body. void AddFunctionDecl(const FunctionDecl *Function, bool SkipBody = false); + // Use this for ODR checking enums between modules. This method compares + // more information than the AddDecl class. + void AddEnumDecl(const EnumDecl *Enum); + // Process SubDecls of the main Decl. This method calls the DeclVisitor // while AddDecl does not. void AddSubDecl(const Decl *D); @@ -83,7 +87,7 @@ public: // Save booleans until the end to lower the size of data to process. void AddBoolean(bool value); - static bool isWhitelistedDecl(const Decl* D, const CXXRecordDecl *Record); + static bool isWhitelistedDecl(const Decl* D, const DeclContext *Parent); }; } // end namespace clang diff --git a/include/clang/Basic/DiagnosticSerializationKinds.td b/include/clang/Basic/DiagnosticSerializationKinds.td index a70e48a788..3a552e2f3d 100644 --- a/include/clang/Basic/DiagnosticSerializationKinds.td +++ b/include/clang/Basic/DiagnosticSerializationKinds.td @@ -341,6 +341,33 @@ def note_module_odr_violation_function : Note<"but in '%0' found " "a different body" "}1">; +def err_module_odr_violation_enum : Error< + "%q0 has different definitions in different modules; " + "%select{definition in module '%2'|defined here}1 " + "first difference is " + "%select{" + "enum that is %select{not scoped|scoped}4|" + "enum scoped with keyword %select{struct|class}4|" + "enum %select{without|with}4 specified type|" + "enum with specified type %4|" + "enum with %4 element%s4|" + "%ordinal4 element has name %5|" + "%ordinal4 element %5 %select{has|does not have}6 an initilizer|" + "%ordinal4 element %5 has an initializer|" + "}3">; + +def note_module_odr_violation_enum : Note<"but in '%0' found " + "%select{" + "enum that is %select{not scoped|scoped}2|" + "enum scoped with keyword %select{struct|class}2|" + "enum %select{without|with}2 specified type|" + "enum with specified type %2|" + "enum with %2 element%s2|" + "%ordinal2 element has name %3|" + "%ordinal2 element %3 %select{has|does not have}4 an initializer|" + "%ordinal2 element %3 has different initializer|" + "}1">; + def err_module_odr_violation_mismatch_decl_unknown : Error< "%q0 %select{with definition in module '%2'|defined here}1 has different " "definitions in different modules; first difference is this " diff --git a/include/clang/Serialization/ASTReader.h b/include/clang/Serialization/ASTReader.h index b33f317765..82a74a6444 100644 --- a/include/clang/Serialization/ASTReader.h +++ b/include/clang/Serialization/ASTReader.h @@ -1099,6 +1099,10 @@ private: llvm::SmallDenseMap, 2> PendingFunctionOdrMergeFailures; + /// Enum definitions in which we found an ODR violation. + llvm::SmallDenseMap, 2> + PendingEnumOdrMergeFailures; + /// DeclContexts in which we have diagnosed an ODR violation. llvm::SmallPtrSet DiagnosedOdrMergeFailures; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index a23499f3fe..3b9b85a20a 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -3973,6 +3973,17 @@ void EnumDecl::setInstantiationOfMemberEnum(ASTContext &C, EnumDecl *ED, SpecializationInfo = new (C) MemberSpecializationInfo(ED, TSK); } +unsigned EnumDecl::getODRHash() { + if (HasODRHash) + return ODRHash; + + class ODRHash Hash; + Hash.AddEnumDecl(this); + HasODRHash = true; + ODRHash = Hash.CalculateHash(); + return ODRHash; +} + //===----------------------------------------------------------------------===// // RecordDecl Implementation //===----------------------------------------------------------------------===// diff --git a/lib/AST/ODRHash.cpp b/lib/AST/ODRHash.cpp index 45b8ee61ad..e710d37803 100644 --- a/lib/AST/ODRHash.cpp +++ b/lib/AST/ODRHash.cpp @@ -407,12 +407,17 @@ public: AddDecl(D->getTemplatedDecl()); Inherited::VisitFunctionTemplateDecl(D); } + + void VisitEnumConstantDecl(const EnumConstantDecl *D) { + AddStmt(D->getInitExpr()); + Inherited::VisitEnumConstantDecl(D); + } }; } // namespace // Only allow a small portion of Decl's to be processed. Remove this once // all Decl's can be handled. -bool ODRHash::isWhitelistedDecl(const Decl *D, const CXXRecordDecl *Parent) { +bool ODRHash::isWhitelistedDecl(const Decl *D, const DeclContext *Parent) { if (D->isImplicit()) return false; if (D->getDeclContext() != Parent) return false; @@ -423,6 +428,7 @@ bool ODRHash::isWhitelistedDecl(const Decl *D, const CXXRecordDecl *Parent) { case Decl::CXXConstructor: case Decl::CXXDestructor: case Decl::CXXMethod: + case Decl::EnumConstant: // Only found in EnumDecl's. case Decl::Field: case Decl::Friend: case Decl::FunctionTemplate: @@ -554,6 +560,34 @@ void ODRHash::AddFunctionDecl(const FunctionDecl *Function, } } +void ODRHash::AddEnumDecl(const EnumDecl *Enum) { + assert(Enum); + AddDeclarationName(Enum->getDeclName()); + + AddBoolean(Enum->isScoped()); + if (Enum->isScoped()) + AddBoolean(Enum->isScopedUsingClassTag()); + + if (Enum->getIntegerTypeSourceInfo()) + AddQualType(Enum->getIntegerType()); + + // Filter out sub-Decls which will not be processed in order to get an + // accurate count of Decl's. + llvm::SmallVector Decls; + for (Decl *SubDecl : Enum->decls()) { + if (isWhitelistedDecl(SubDecl, Enum)) { + assert(isa(SubDecl) && "Unexpected Decl"); + Decls.push_back(SubDecl); + } + } + + ID.AddInteger(Decls.size()); + for (auto SubDecl : Decls) { + AddSubDecl(SubDecl); + } + +} + void ODRHash::AddDecl(const Decl *D) { assert(D && "Expecting non-null pointer."); D = D->getCanonicalDecl(); diff --git a/lib/Serialization/ASTReader.cpp b/lib/Serialization/ASTReader.cpp index 4167e86880..9a3b9e1da3 100644 --- a/lib/Serialization/ASTReader.cpp +++ b/lib/Serialization/ASTReader.cpp @@ -9443,7 +9443,8 @@ void ASTReader::finishPendingActions() { void ASTReader::diagnoseOdrViolations() { if (PendingOdrMergeFailures.empty() && PendingOdrMergeChecks.empty() && - PendingFunctionOdrMergeFailures.empty()) + PendingFunctionOdrMergeFailures.empty() && + PendingEnumOdrMergeFailures.empty()) return; // Trigger the import of the full definition of each class that had any @@ -9479,6 +9480,16 @@ void ASTReader::diagnoseOdrViolations() { } } + // Trigger the import of enums. + auto EnumOdrMergeFailures = std::move(PendingEnumOdrMergeFailures); + PendingEnumOdrMergeFailures.clear(); + for (auto &Merge : EnumOdrMergeFailures) { + Merge.first->decls_begin(); + for (auto &Enum : Merge.second) { + Enum->decls_begin(); + } + } + // For each declaration from a merged context, check that the canonical // definition of that context also contains a declaration of the same // entity. @@ -9561,7 +9572,8 @@ void ASTReader::diagnoseOdrViolations() { } } - if (OdrMergeFailures.empty() && FunctionOdrMergeFailures.empty()) + if (OdrMergeFailures.empty() && FunctionOdrMergeFailures.empty() && + EnumOdrMergeFailures.empty()) return; // Ensure we don't accidentally recursively enter deserialization while @@ -11308,6 +11320,194 @@ void ASTReader::diagnoseOdrViolations() { (void)Diagnosed; assert(Diagnosed && "Unable to emit ODR diagnostic."); } + + // Issue ODR failures diagnostics for enums. + for (auto &Merge : EnumOdrMergeFailures) { + enum ODREnumDifference { + SingleScopedEnum, + EnumTagKeywordMismatch, + SingleSpecifiedType, + DifferentSpecifiedTypes, + DifferentNumberEnumConstants, + EnumConstantName, + EnumConstantSingleInitilizer, + EnumConstantDifferentInitilizer, + }; + + // If we've already pointed out a specific problem with this enum, don't + // bother issuing a general "something's different" diagnostic. + if (!DiagnosedOdrMergeFailures.insert(Merge.first).second) + continue; + + EnumDecl *FirstEnum = Merge.first; + std::string FirstModule = getOwningModuleNameForDiagnostic(FirstEnum); + + using DeclHashes = + llvm::SmallVector, 4>; + auto PopulateHashes = [&ComputeSubDeclODRHash, FirstEnum]( + DeclHashes &Hashes, EnumDecl *Enum) { + for (auto *D : Enum->decls()) { + // Due to decl merging, the first EnumDecl is the parent of + // Decls in both records. + if (!ODRHash::isWhitelistedDecl(D, FirstEnum)) + continue; + assert(isa(D) && "Unexpected Decl kind"); + Hashes.emplace_back(cast(D), + ComputeSubDeclODRHash(D)); + } + }; + DeclHashes FirstHashes; + PopulateHashes(FirstHashes, FirstEnum); + bool Diagnosed = false; + for (auto &SecondEnum : Merge.second) { + + if (FirstEnum == SecondEnum) + continue; + + std::string SecondModule = + getOwningModuleNameForDiagnostic(SecondEnum); + + auto ODRDiagError = [FirstEnum, &FirstModule, + this](SourceLocation Loc, SourceRange Range, + ODREnumDifference DiffType) { + return Diag(Loc, diag::err_module_odr_violation_enum) + << FirstEnum << FirstModule.empty() << FirstModule << Range + << DiffType; + }; + auto ODRDiagNote = [&SecondModule, this](SourceLocation Loc, + SourceRange Range, + ODREnumDifference DiffType) { + return Diag(Loc, diag::note_module_odr_violation_enum) + << SecondModule << Range << DiffType; + }; + + if (FirstEnum->isScoped() != SecondEnum->isScoped()) { + ODRDiagError(FirstEnum->getLocation(), FirstEnum->getSourceRange(), + SingleScopedEnum) + << FirstEnum->isScoped(); + ODRDiagNote(SecondEnum->getLocation(), SecondEnum->getSourceRange(), + SingleScopedEnum) + << SecondEnum->isScoped(); + Diagnosed = true; + continue; + } + + if (FirstEnum->isScoped() && SecondEnum->isScoped()) { + if (FirstEnum->isScopedUsingClassTag() != + SecondEnum->isScopedUsingClassTag()) { + ODRDiagError(FirstEnum->getLocation(), FirstEnum->getSourceRange(), + EnumTagKeywordMismatch) + << FirstEnum->isScopedUsingClassTag(); + ODRDiagNote(SecondEnum->getLocation(), SecondEnum->getSourceRange(), + EnumTagKeywordMismatch) + << SecondEnum->isScopedUsingClassTag(); + Diagnosed = true; + continue; + } + } + + QualType FirstUnderlyingType = + FirstEnum->getIntegerTypeSourceInfo() + ? FirstEnum->getIntegerTypeSourceInfo()->getType() + : QualType(); + QualType SecondUnderlyingType = + SecondEnum->getIntegerTypeSourceInfo() + ? SecondEnum->getIntegerTypeSourceInfo()->getType() + : QualType(); + if (FirstUnderlyingType.isNull() != SecondUnderlyingType.isNull()) { + ODRDiagError(FirstEnum->getLocation(), FirstEnum->getSourceRange(), + SingleSpecifiedType) + << !FirstUnderlyingType.isNull(); + ODRDiagNote(SecondEnum->getLocation(), SecondEnum->getSourceRange(), + SingleSpecifiedType) + << !SecondUnderlyingType.isNull(); + Diagnosed = true; + continue; + } + + if (!FirstUnderlyingType.isNull() && !SecondUnderlyingType.isNull()) { + if (ComputeQualTypeODRHash(FirstUnderlyingType) != + ComputeQualTypeODRHash(SecondUnderlyingType)) { + ODRDiagError(FirstEnum->getLocation(), FirstEnum->getSourceRange(), + DifferentSpecifiedTypes) + << FirstUnderlyingType; + ODRDiagNote(SecondEnum->getLocation(), SecondEnum->getSourceRange(), + DifferentSpecifiedTypes) + << SecondUnderlyingType; + Diagnosed = true; + continue; + } + } + + DeclHashes SecondHashes; + PopulateHashes(SecondHashes, SecondEnum); + + if (FirstHashes.size() != SecondHashes.size()) { + ODRDiagError(FirstEnum->getLocation(), FirstEnum->getSourceRange(), + DifferentNumberEnumConstants) + << (int)FirstHashes.size(); + ODRDiagNote(SecondEnum->getLocation(), SecondEnum->getSourceRange(), + DifferentNumberEnumConstants) + << (int)SecondHashes.size(); + Diagnosed = true; + continue; + } + + for (unsigned I = 0; I < FirstHashes.size(); ++I) { + if (FirstHashes[I].second == SecondHashes[I].second) + continue; + const EnumConstantDecl *FirstEnumConstant = FirstHashes[I].first; + const EnumConstantDecl *SecondEnumConstant = SecondHashes[I].first; + + if (FirstEnumConstant->getDeclName() != + SecondEnumConstant->getDeclName()) { + + ODRDiagError(FirstEnumConstant->getLocation(), + FirstEnumConstant->getSourceRange(), EnumConstantName) + << I + 1 << FirstEnumConstant; + ODRDiagNote(SecondEnumConstant->getLocation(), + SecondEnumConstant->getSourceRange(), EnumConstantName) + << I + 1 << SecondEnumConstant; + Diagnosed = true; + break; + } + + const Expr *FirstInit = FirstEnumConstant->getInitExpr(); + const Expr *SecondInit = SecondEnumConstant->getInitExpr(); + if (!FirstInit && !SecondInit) + continue; + + if (!FirstInit || !SecondInit) { + ODRDiagError(FirstEnumConstant->getLocation(), + FirstEnumConstant->getSourceRange(), + EnumConstantSingleInitilizer) + << I + 1 << FirstEnumConstant << (FirstInit != nullptr); + ODRDiagNote(SecondEnumConstant->getLocation(), + SecondEnumConstant->getSourceRange(), + EnumConstantSingleInitilizer) + << I + 1 << SecondEnumConstant << (SecondInit != nullptr); + Diagnosed = true; + break; + } + + if (ComputeODRHash(FirstInit) != ComputeODRHash(SecondInit)) { + ODRDiagError(FirstEnumConstant->getLocation(), + FirstEnumConstant->getSourceRange(), + EnumConstantDifferentInitilizer) + << I + 1 << FirstEnumConstant; + ODRDiagNote(SecondEnumConstant->getLocation(), + SecondEnumConstant->getSourceRange(), + EnumConstantDifferentInitilizer) + << I + 1 << SecondEnumConstant; + Diagnosed = true; + break; + } + } + } + + (void)Diagnosed; + assert(Diagnosed && "Unable to emit ODR diagnostic."); + } } void ASTReader::StartedDeserializing() { diff --git a/lib/Serialization/ASTReaderDecl.cpp b/lib/Serialization/ASTReaderDecl.cpp index b33b88c271..7e2c4829b1 100644 --- a/lib/Serialization/ASTReaderDecl.cpp +++ b/lib/Serialization/ASTReaderDecl.cpp @@ -746,6 +746,9 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) { ED->IsScopedUsingClassTag = Record.readInt(); ED->IsFixed = Record.readInt(); + ED->HasODRHash = true; + ED->ODRHash = Record.readInt(); + // If this is a definition subject to the ODR, and we already have a // definition, merge this one into it. if (ED->IsCompleteDefinition && @@ -766,6 +769,8 @@ void ASTDeclReader::VisitEnumDecl(EnumDecl *ED) { Reader.MergedDeclContexts.insert(std::make_pair(ED, OldDef)); ED->IsCompleteDefinition = false; Reader.mergeDefinitionVisibility(OldDef, ED); + if (OldDef->getODRHash() != ED->getODRHash()) + Reader.PendingEnumOdrMergeFailures[OldDef].push_back(ED); } else { OldDef = ED; } diff --git a/lib/Serialization/ASTWriterDecl.cpp b/lib/Serialization/ASTWriterDecl.cpp index 61d9fed480..77e578f6bc 100644 --- a/lib/Serialization/ASTWriterDecl.cpp +++ b/lib/Serialization/ASTWriterDecl.cpp @@ -431,6 +431,8 @@ void ASTDeclWriter::VisitEnumDecl(EnumDecl *D) { Record.push_back(D->isScoped()); Record.push_back(D->isScopedUsingClassTag()); Record.push_back(D->isFixed()); + Record.push_back(D->getODRHash()); + if (MemberSpecializationInfo *MemberInfo = D->getMemberSpecializationInfo()) { Record.AddDeclRef(MemberInfo->getInstantiatedFrom()); Record.push_back(MemberInfo->getTemplateSpecializationKind()); @@ -1865,6 +1867,7 @@ void ASTWriter::WriteDeclAbbrevs() { Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isScoped Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isScopedUsingClassTag Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // isFixed + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 32));// ODRHash Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // InstantiatedMembEnum // DC Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // LexicalOffset diff --git a/test/Modules/odr_hash.cpp b/test/Modules/odr_hash.cpp index 28ee423c74..16ddfefa53 100644 --- a/test/Modules/odr_hash.cpp +++ b/test/Modules/odr_hash.cpp @@ -3146,6 +3146,177 @@ Invalid1 i1; #undef DECLS } +namespace Enums { +#if defined(FIRST) +enum E1 { x11 }; +#elif defined(SECOND) +enum E1 {}; +#else +E1 e1; +// expected-error@first.h:* {{'Enums::x11' from module 'FirstModule' is not present in definition of 'Enums::E1' in module 'SecondModule'}} +// expected-note@second.h:* {{definition has no member 'x11'}} +#endif + +#if defined(FIRST) +enum E2 {}; +#elif defined(SECOND) +enum E2 { x21 }; +#else +E2 e2; +// expected-error@second.h:* {{'Enums::E2' has different definitions in different modules; definition in module 'SecondModule' first difference is enum with 1 element}} +// expected-note@first.h:* {{but in 'FirstModule' found enum with 0 elements}} +#endif + +#if defined(FIRST) +enum E3 { x31 }; +#elif defined(SECOND) +enum E3 { x32 }; +#else +E3 e3; +// expected-error@first.h:* {{'Enums::x31' from module 'FirstModule' is not present in definition of 'Enums::E3' in module 'SecondModule'}} +// expected-note@second.h:* {{definition has no member 'x31'}} +#endif + +#if defined(FIRST) +enum E4 { x41 }; +#elif defined(SECOND) +enum E4 { x41, x42 }; +#else +E4 e4; +// expected-error@second.h:* {{'Enums::E4' has different definitions in different modules; definition in module 'SecondModule' first difference is enum with 2 elements}} +// expected-note@first.h:* {{but in 'FirstModule' found enum with 1 element}} +#endif + +#if defined(FIRST) +enum E5 { x51, x52 }; +#elif defined(SECOND) +enum E5 { x51 }; +#else +E5 e5; +// expected-error@first.h:* {{'Enums::x52' from module 'FirstModule' is not present in definition of 'Enums::E5' in module 'SecondModule'}} +// expected-note@second.h:* {{definition has no member 'x52'}} +#endif + +#if defined(FIRST) +enum E6 { x61, x62 }; +#elif defined(SECOND) +enum E6 { x62, x61 }; +#else +E6 e6; +// expected-error@second.h:* {{'Enums::E6' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st element has name 'x62'}} +// expected-note@first.h:* {{but in 'FirstModule' found 1st element has name 'x61'}} +#endif + +#if defined(FIRST) +enum E7 { x71 = 0 }; +#elif defined(SECOND) +enum E7 { x71 }; +#else +E7 e7; +// expected-error@second.h:* {{'Enums::E7' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st element 'x71' has an initilizer}} +// expected-note@first.h:* {{but in 'FirstModule' found 1st element 'x71' does not have an initializer}} +#endif + +#if defined(FIRST) +enum E8 { x81 }; +#elif defined(SECOND) +enum E8 { x81 = 0 }; +#else +E8 e8; +// expected-error@second.h:* {{'Enums::E8' has different definitions in different modules; definition in module 'SecondModule' first difference is 1st element 'x81' does not have an initilizer}} +// expected-note@first.h:* {{but in 'FirstModule' found 1st element 'x81' has an initializer}} +#endif + +#if defined(FIRST) +enum E9 { x91 = 0, x92 = 1 }; +#elif defined(SECOND) +enum E9 { x91 = 0, x92 = 2 - 1 }; +#else +E9 e9; +// expected-error@second.h:* {{'Enums::E9' has different definitions in different modules; definition in module 'SecondModule' first difference is 2nd element 'x92' has an initializer}} +// expected-note@first.h:* {{but in 'FirstModule' found 2nd element 'x92' has different initializer}} +#endif + +#if defined(FIRST) +enum class E10 : int {}; +#elif defined(SECOND) +enum class E10 {}; +#else +E10 e10; +// expected-error@second.h:* {{'Enums::E10' has different definitions in different modules; definition in module 'SecondModule' first difference is enum without specified type}} +// expected-note@first.h:* {{but in 'FirstModule' found enum with specified type}} +#endif + +#if defined(FIRST) +enum E11 {}; +#elif defined(SECOND) +enum E11 : int {}; +#else +E11 e11; +// expected-error@second.h:* {{'Enums::E11' has different definitions in different modules; definition in module 'SecondModule' first difference is enum with specified type}} +// expected-note@first.h:* {{but in 'FirstModule' found enum without specified type}} +#endif + +#if defined(FIRST) +enum struct E12 : long {}; +#elif defined(SECOND) +enum struct E12 : int {}; +#else +E12 e12; +// expected-error@second.h:* {{'Enums::E12' has different definitions in different modules; definition in module 'SecondModule' first difference is enum with specified type 'int'}} +// expected-note@first.h:* {{but in 'FirstModule' found enum with specified type 'long'}} +#endif + +#if defined(FIRST) +enum struct E13 {}; +#elif defined(SECOND) +enum E13 {}; +#else +E13 e13; +// expected-error@second.h:* {{'Enums::E13' has different definitions in different modules; definition in module 'SecondModule' first difference is enum that is not scoped}} +// expected-note@first.h:* {{but in 'FirstModule' found enum that is scoped}} +#endif + +#if defined(FIRST) +enum E14 {}; +#elif defined(SECOND) +enum struct E14 {}; +#else +E14 e14; +// expected-error@second.h:* {{'Enums::E14' has different definitions in different modules; definition in module 'SecondModule' first difference is enum that is scoped}} +// expected-note@first.h:* {{but in 'FirstModule' found enum that is not scoped}} +#endif + +#if defined(FIRST) +enum class E15 {}; +#elif defined(SECOND) +enum struct E15 {}; +#else +E15 e15; +// expected-error@second.h:* {{'Enums::E15' has different definitions in different modules; definition in module 'SecondModule' first difference is enum scoped with keyword struct}} +// expected-note@first.h:* {{but in 'FirstModule' found enum scoped with keyword class}} +#endif + +#if defined(FIRST) +enum struct E16 {}; +#elif defined(SECOND) +enum class E16 {}; +#else +E16 e16; +// expected-error@second.h:* {{'Enums::E16' has different definitions in different modules; definition in module 'SecondModule' first difference is enum scoped with keyword class}} +// expected-note@first.h:* {{but in 'FirstModule' found enum scoped with keyword struct}} +#endif + +#if defined(FIRST) +enum Valid { v1 = (struct S*)0 == (struct S*)0 }; +#elif defined(SECOND) +struct S {}; +enum Valid { v1 = (struct S*)0 == (struct S*)0 }; +#else +Valid V; +#endif +} // namespace Enums + // Collection of interesting cases below. // Naive parsing of AST can lead to cycles in processing. Ensure -- 2.40.0