diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b211e8255ec0c5..ceead8c362e9c1 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -827,6 +827,9 @@ Bug Fixes in This Version - Fixed Clang crashing when failing to perform some C++ Initialization Sequences. (#GH98102) +- ``__is_trivially_equality_comparable`` no longer returns true for types which + have a constrained defaulted comparison operator (#GH89293). + Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index a2194361abd537..3aa0f05b0ab60d 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -1142,9 +1142,6 @@ class QualType { /// Return true if this is a trivially relocatable type. bool isTriviallyRelocatableType(const ASTContext &Context) const; - /// Return true if this is a trivially equality comparable type. - bool isTriviallyEqualityComparableType(const ASTContext &Context) const; - /// Returns true if it is a class and it might be dynamic. bool mayBeDynamicClass() const; diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index d8b885870de3ac..cc535aba4936e3 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2815,66 +2815,6 @@ bool QualType::isTriviallyRelocatableType(const ASTContext &Context) const { } } -static bool -HasNonDeletedDefaultedEqualityComparison(const CXXRecordDecl *Decl) { - if (Decl->isUnion()) - return false; - if (Decl->isLambda()) - return Decl->isCapturelessLambda(); - - auto IsDefaultedOperatorEqualEqual = [&](const FunctionDecl *Function) { - return Function->getOverloadedOperator() == - OverloadedOperatorKind::OO_EqualEqual && - Function->isDefaulted() && Function->getNumParams() > 0 && - (Function->getParamDecl(0)->getType()->isReferenceType() || - Decl->isTriviallyCopyable()); - }; - - if (llvm::none_of(Decl->methods(), IsDefaultedOperatorEqualEqual) && - llvm::none_of(Decl->friends(), [&](const FriendDecl *Friend) { - if (NamedDecl *ND = Friend->getFriendDecl()) { - return ND->isFunctionOrFunctionTemplate() && - IsDefaultedOperatorEqualEqual(ND->getAsFunction()); - } - return false; - })) - return false; - - return llvm::all_of(Decl->bases(), - [](const CXXBaseSpecifier &BS) { - if (const auto *RD = BS.getType()->getAsCXXRecordDecl()) - return HasNonDeletedDefaultedEqualityComparison(RD); - return true; - }) && - llvm::all_of(Decl->fields(), [](const FieldDecl *FD) { - auto Type = FD->getType(); - if (Type->isArrayType()) - Type = Type->getBaseElementTypeUnsafe()->getCanonicalTypeUnqualified(); - - if (Type->isReferenceType() || Type->isEnumeralType()) - return false; - if (const auto *RD = Type->getAsCXXRecordDecl()) - return HasNonDeletedDefaultedEqualityComparison(RD); - return true; - }); -} - -bool QualType::isTriviallyEqualityComparableType( - const ASTContext &Context) const { - QualType CanonicalType = getCanonicalType(); - if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType() || - CanonicalType->isEnumeralType() || CanonicalType->isArrayType()) - return false; - - if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) { - if (!HasNonDeletedDefaultedEqualityComparison(RD)) - return false; - } - - return Context.hasUniqueObjectRepresentations( - CanonicalType, /*CheckIfTriviallyCopyable=*/false); -} - bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index fcf2189a308a86..d0e963441d79ad 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -5129,6 +5129,83 @@ static bool HasNoThrowOperator(const RecordType *RT, OverloadedOperatorKind Op, return false; } +static bool HasNonDeletedDefaultedEqualityComparison(Sema &S, + const CXXRecordDecl *Decl, + SourceLocation KeyLoc) { + if (Decl->isUnion()) + return false; + if (Decl->isLambda()) + return Decl->isCapturelessLambda(); + + { + EnterExpressionEvaluationContext UnevaluatedContext( + S, Sema::ExpressionEvaluationContext::Unevaluated); + Sema::SFINAETrap SFINAE(S, /*AccessCheckingSFINAE=*/true); + Sema::ContextRAII TUContext(S, S.Context.getTranslationUnitDecl()); + + // const ClassT& obj; + OpaqueValueExpr Operand( + {}, Decl->getTypeForDecl()->getCanonicalTypeUnqualified().withConst(), + ExprValueKind::VK_LValue); + UnresolvedSet<16> Functions; + // obj == obj; + S.LookupBinOp(S.TUScope, {}, BinaryOperatorKind::BO_EQ, Functions); + + auto Result = S.CreateOverloadedBinOp(KeyLoc, BinaryOperatorKind::BO_EQ, + Functions, &Operand, &Operand); + if (Result.isInvalid() || SFINAE.hasErrorOccurred()) + return false; + + const auto *CallExpr = dyn_cast(Result.get()); + if (!CallExpr) + return false; + const auto *Callee = CallExpr->getDirectCallee(); + auto ParamT = Callee->getParamDecl(0)->getType(); + if (!Callee->isDefaulted()) + return false; + if (!ParamT->isReferenceType() && !Decl->isTriviallyCopyable()) + return false; + if (ParamT.getNonReferenceType()->getUnqualifiedDesugaredType() != + Decl->getTypeForDecl()) + return false; + } + + return llvm::all_of(Decl->bases(), + [&](const CXXBaseSpecifier &BS) { + if (const auto *RD = BS.getType()->getAsCXXRecordDecl()) + return HasNonDeletedDefaultedEqualityComparison( + S, RD, KeyLoc); + return true; + }) && + llvm::all_of(Decl->fields(), [&](const FieldDecl *FD) { + auto Type = FD->getType(); + if (Type->isArrayType()) + Type = Type->getBaseElementTypeUnsafe() + ->getCanonicalTypeUnqualified(); + + if (Type->isReferenceType() || Type->isEnumeralType()) + return false; + if (const auto *RD = Type->getAsCXXRecordDecl()) + return HasNonDeletedDefaultedEqualityComparison(S, RD, KeyLoc); + return true; + }); +} + +static bool isTriviallyEqualityComparableType(Sema &S, QualType Type, SourceLocation KeyLoc) { + QualType CanonicalType = Type.getCanonicalType(); + if (CanonicalType->isIncompleteType() || CanonicalType->isDependentType() || + CanonicalType->isEnumeralType() || CanonicalType->isArrayType()) + return false; + + if (const auto *RD = CanonicalType->getAsCXXRecordDecl()) { + if (!HasNonDeletedDefaultedEqualityComparison(S, RD, KeyLoc)) + return false; + } + + return S.getASTContext().hasUniqueObjectRepresentations( + CanonicalType, /*CheckIfTriviallyCopyable=*/false); +} + static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, SourceLocation KeyLoc, TypeSourceInfo *TInfo) { @@ -5561,7 +5638,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, Self.Diag(KeyLoc, diag::err_builtin_pass_in_regs_non_class) << T; return false; case UTT_IsTriviallyEqualityComparable: - return T.isTriviallyEqualityComparableType(C); + return isTriviallyEqualityComparableType(Self, T, KeyLoc); } } diff --git a/clang/test/SemaCXX/type-traits.cpp b/clang/test/SemaCXX/type-traits.cpp index d40605f56f1edd..7adbf4aad7afe0 100644 --- a/clang/test/SemaCXX/type-traits.cpp +++ b/clang/test/SemaCXX/type-traits.cpp @@ -3677,6 +3677,12 @@ struct NonTriviallyEqualityComparableNoComparator { }; static_assert(!__is_trivially_equality_comparable(NonTriviallyEqualityComparableNoComparator)); +struct NonTriviallyEqualityComparableConvertibleToBuiltin { + int i; + operator unsigned() const; +}; +static_assert(!__is_trivially_equality_comparable(NonTriviallyEqualityComparableConvertibleToBuiltin)); + struct NonTriviallyEqualityComparableNonDefaultedComparator { int i; int j; @@ -3885,8 +3891,51 @@ struct NotTriviallyEqualityComparableNonTriviallyEqualityComparableArrs2 { bool operator==(const NotTriviallyEqualityComparableNonTriviallyEqualityComparableArrs2&) const = default; }; + static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableNonTriviallyEqualityComparableArrs2)); +template +struct MaybeTriviallyEqualityComparable { + int i; + bool operator==(const MaybeTriviallyEqualityComparable&) const requires B = default; + bool operator==(const MaybeTriviallyEqualityComparable& rhs) const { return (i % 3) == (rhs.i % 3); } +}; +static_assert(__is_trivially_equality_comparable(MaybeTriviallyEqualityComparable)); +static_assert(!__is_trivially_equality_comparable(MaybeTriviallyEqualityComparable)); + +struct NotTriviallyEqualityComparableMoreConstrainedExternalOp { + int i; + bool operator==(const NotTriviallyEqualityComparableMoreConstrainedExternalOp&) const = default; +}; + +bool operator==(const NotTriviallyEqualityComparableMoreConstrainedExternalOp&, + const NotTriviallyEqualityComparableMoreConstrainedExternalOp&) __attribute__((enable_if(true, ""))) {} + +static_assert(!__is_trivially_equality_comparable(NotTriviallyEqualityComparableMoreConstrainedExternalOp)); + +struct TriviallyEqualityComparableExternalDefaultedOp { + int i; + friend bool operator==(TriviallyEqualityComparableExternalDefaultedOp, TriviallyEqualityComparableExternalDefaultedOp); +}; +bool operator==(TriviallyEqualityComparableExternalDefaultedOp, TriviallyEqualityComparableExternalDefaultedOp) = default; + +static_assert(__is_trivially_equality_comparable(TriviallyEqualityComparableExternalDefaultedOp)); + +struct EqualityComparableBase { + bool operator==(const EqualityComparableBase&) const = default; +}; + +struct ComparingBaseOnly : EqualityComparableBase { + int j_ = 0; +}; +static_assert(!__is_trivially_equality_comparable(ComparingBaseOnly)); + +template +class Template {}; + +// Make sure we don't crash when instantiating a type +static_assert(!__is_trivially_equality_comparable(Template>)); + namespace hidden_friend { struct TriviallyEqualityComparable {