diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -1628,6 +1628,14 @@ let SimpleHandler = 1; } +def EqualityOperatorComparesMembersLexicographically : InheritableAttr { + let Spellings = [Clang<"equality_operator_compares_members_lexicographically", 0>]; + let Subjects = SubjectList<[CXXRecord, Enum]>; + let Documentation = [EqualityOperatorComparesMembersLexicographicallyDocs]; + let LangOpts = [CPlusPlus]; + let SimpleHandler = 1; +} + def MaxFieldAlignment : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly. let Spellings = []; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -538,8 +538,8 @@ def NoMergeDocs : Documentation { let Category = DocCatStmt; let Content = [{ -If a statement is marked ``nomerge`` and contains call expressions, those call -expressions inside the statement will not be merged during optimization. This +If a statement is marked ``nomerge`` and contains call expressions, those call +expressions inside the statement will not be merged during optimization. This attribute can be used to prevent the optimizer from obscuring the source location of certain calls. For example, it will prevent tail merging otherwise identical code sequences that raise an exception or terminate the program. Tail @@ -1584,7 +1584,7 @@ ``watchos`` Apple's watchOS operating system. The minimum deployment target is specified by the ``-mwatchos-version-min=*version*`` command-line argument. - + ``driverkit`` Apple's DriverKit userspace kernel extensions. The minimum deployment target is specified as part of the triple. @@ -3519,6 +3519,21 @@ }]; } +def EqualityOperatorComparesMembersLexicographicallyDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +On classes, the ``equality_operator_compares_members_lexicographically`` +attribute informs clang that a call to ``operator==(const T&, const T&)`` is +equivalent to comparing the members lexicographically (i.e. the call is +equivalent to a call to ``bool operator==(const T&) const = default``). This is +useful for libraries which have strict requirements on ADL uses. If possible, a +defaulted equality comparison operator should be preferred over this attribute. +On enums, the attribute guarantees that there are either no custom comparison +operators defined, or any that defined are quivalent to the builtin comparison +operator. + }]; +} + def MSInheritanceDocs : Documentation { let Category = DocCatDecl; let Heading = "__single_inhertiance, __multiple_inheritance, __virtual_inheritance"; diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2649,7 +2649,8 @@ Decl->isTriviallyCopyable()); }; - if (llvm::none_of(Decl->methods(), IsDefaultedOperatorEqualEqual) && + if (!Decl->hasAttr() && + llvm::none_of(Decl->methods(), IsDefaultedOperatorEqualEqual) && llvm::none_of(Decl->friends(), [&](const FriendDecl *Friend) { if (NamedDecl *ND = Friend->getFriendDecl()) { return ND->isFunctionOrFunctionTemplate() && @@ -2667,7 +2668,11 @@ }) && llvm::all_of(Decl->fields(), [](const FieldDecl *FD) { auto Type = FD->getType(); - if (Type->isReferenceType() || Type->isEnumeralType()) + if (Type->isReferenceType() || + (Type->isEnumeralType() && + !Type->getAsTagDecl() + ->hasAttr< + EqualityOperatorComparesMembersLexicographicallyAttr>())) return false; if (const auto *RD = Type->getAsCXXRecordDecl()) return HasNonDeletedDefaultedEqualityComparison(RD); @@ -2679,7 +2684,9 @@ const ASTContext &Context) const { QualType CanonicalType = getCanonicalType(); if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType() || - CanonicalType->isEnumeralType()) + (CanonicalType->isEnumeralType() && + !CanonicalType->getAsTagDecl() + ->hasAttr())) return false; if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) { diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -187,6 +187,7 @@ // CHECK-NEXT: TargetVersion (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) // CHECK-NEXT: TrivialABI (SubjectMatchRule_record) +// CHECK-NEXT: EqualityOperatorComparesMembersLexicographically (SubjectMatchRule_function) // CHECK-NEXT: Uninitialized (SubjectMatchRule_variable_is_local) // CHECK-NEXT: UnsafeBufferUsage (SubjectMatchRule_function) // CHECK-NEXT: UseHandle (SubjectMatchRule_variable_is_parameter) diff --git a/clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp b/clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/attr-equality-operator-compares-members-lexicographically.cpp @@ -0,0 +1,139 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -fsyntax-only -verify %s -std=c++20 + +void __attribute__((equality_operator_compares_members_lexicographically)) foo(); // expected-warning {{'equality_operator_compares_members_lexicographically' attribute only applies to classes and enums}} + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparabe1 { + int i; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparabe1)); + +struct __attribute__((equality_operator_compares_members_lexicographically)) TriviallyEqualityComparabe2 { + int i; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparabe2)); + +struct [[clang::equality_operator_compares_members_lexicographically(1)]] TestMessage {}; // expected-error {{'equality_operator_compares_members_lexicographically' attribute takes no arguments}} + + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasPadding { + short i; + int j; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasPadding), ""); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasFloat { + float i; + int j; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasFloat), ""); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasTailPadding { + int i; + char j; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasTailPadding), ""); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBase : NotTriviallyEqualityComparableHasTailPadding { + char j; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBase), ""); + +class [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparablePaddedOutBase { + int i; + char c; +}; +static_assert(!__is_trivially_equality_comparable(TriviallyEqualityComparablePaddedOutBase), ""); + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparablePaddedOut : TriviallyEqualityComparablePaddedOutBase { + char j[3]; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparablePaddedOut), ""); + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparable1 { + char i; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparable1)); + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparable2 { + int i; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparable2)); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableTriviallyEqualityComparableBases + : TriviallyEqualityComparable1, TriviallyEqualityComparable2 { +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableTriviallyEqualityComparableBases)); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBitfield { + int i : 1; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBitfield)); + +// TODO: This is trivially equality comparable +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableBitfieldFilled { + char i : __CHAR_BIT__; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableBitfield)); + +union [[clang::equality_operator_compares_members_lexicographically]] U { + int i; +}; + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableImplicitlyDeletedOperatorByUnion { + U u; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableImplicitlyDeletedOperatorByUnion)); + +struct NotTriviallyEqualityComparableExplicitlyDeleted { + int i; + + friend bool operator==(const NotTriviallyEqualityComparableExplicitlyDeleted&, const NotTriviallyEqualityComparableExplicitlyDeleted&) = delete; +}; + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableImplicitlyDeletedOperatorByStruct { + NotTriviallyEqualityComparableExplicitlyDeleted u; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableImplicitlyDeletedOperatorByStruct)); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasReferenceMember { + int& i; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasReferenceMember)); + +enum E { + a, + b +}; +bool operator==(E, E) { return false; } +static_assert(!__is_trivially_equality_comparable(E)); + +struct [[clang::equality_operator_compares_members_lexicographically]] NotTriviallyEqualityComparableHasEnum { + E e; +}; +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableHasEnum)); + +enum [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableEnum { + c, + d, +}; +bool operator==(TriviallyEqualityComparableEnum, TriviallyEqualityComparableEnum); + +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableEnum)); + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableHasTriviallyEqualityComparableEnum { + TriviallyEqualityComparableEnum e; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableHasTriviallyEqualityComparableEnum)); + +enum class [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableEnumClass { + e, + f, +}; +bool operator==(TriviallyEqualityComparableEnumClass, TriviallyEqualityComparableEnumClass); + +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableEnumClass)); + +struct [[clang::equality_operator_compares_members_lexicographically]] TriviallyEqualityComparableHasTriviallyEqualityComparableEnumClass { + TriviallyEqualityComparableEnumClass e; +}; +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableHasTriviallyEqualityComparableEnumClass));