forked from OSchip/llvm-project
[c++20] Implement P1185R2 (as modified by P2002R0).
For each defaulted operator<=> in a class that doesn't explicitly declare any operator==, also inject a matching implicit defaulted operator==.
This commit is contained in:
parent
f364686f34
commit
bc24014b97
|
@ -3840,6 +3840,7 @@ def select_ovl_candidate_kind : TextSubstitution<
|
|||
"constructor (the implicit move constructor)|"
|
||||
"function (the implicit copy assignment operator)|"
|
||||
"function (the implicit move assignment operator)|"
|
||||
"function (the implicit 'operator==' for this 'operator<=>)'|"
|
||||
"inherited constructor}0%select{| template| %2}1">;
|
||||
|
||||
def note_ovl_candidate : Note<
|
||||
|
@ -8207,7 +8208,8 @@ def warn_defaulted_comparison_deleted : Warning<
|
|||
"explicitly defaulted %sub{select_defaulted_comparison_kind}0 is implicitly "
|
||||
"deleted">, InGroup<DefaultedFunctionDeleted>;
|
||||
def err_non_first_default_compare_deletes : Error<
|
||||
"defaulting this %sub{select_defaulted_comparison_kind}0 "
|
||||
"defaulting %select{this %sub{select_defaulted_comparison_kind}1|"
|
||||
"the corresponding implicit 'operator==' for this defaulted 'operator<=>'}0 "
|
||||
"would delete it after its first declaration">;
|
||||
def note_defaulted_comparison_union : Note<
|
||||
"defaulted %0 is implicitly deleted because "
|
||||
|
@ -8237,14 +8239,19 @@ def note_defaulted_comparison_cannot_deduce : Note<
|
|||
def note_defaulted_comparison_cannot_deduce_callee : Note<
|
||||
"selected 'operator<=>' for %select{|member|base class}0 %1 declared here">;
|
||||
def err_incorrect_defaulted_comparison_constexpr : Error<
|
||||
"defaulted definition of %sub{select_defaulted_comparison_kind}0 "
|
||||
"cannot be declared %select{constexpr|consteval}1 because it invokes "
|
||||
"a non-constexpr comparison function">;
|
||||
"defaulted definition of %select{%sub{select_defaulted_comparison_kind}1|"
|
||||
"three-way comparison operator}0 "
|
||||
"cannot be declared %select{constexpr|consteval}2 because "
|
||||
"%select{it|the corresponding implicit 'operator=='}0 "
|
||||
"invokes a non-constexpr comparison function">;
|
||||
def note_defaulted_comparison_not_constexpr : Note<
|
||||
"non-constexpr comparison function would be used to compare "
|
||||
"%select{|member %1|base class %1}0">;
|
||||
def note_defaulted_comparison_not_constexpr_here : Note<
|
||||
"non-constexpr comparison function declared here">;
|
||||
def note_in_declaration_of_implicit_equality_comparison : Note<
|
||||
"while declaring the corresponding implicit 'operator==' "
|
||||
"for this defaulted 'operator<=>'">;
|
||||
|
||||
def ext_implicit_exception_spec_mismatch : ExtWarn<
|
||||
"function previously declared with an %select{explicit|implicit}0 exception "
|
||||
|
|
|
@ -6524,6 +6524,8 @@ public:
|
|||
|
||||
bool CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *MD,
|
||||
DefaultedComparisonKind DCK);
|
||||
void DeclareImplicitEqualityComparison(CXXRecordDecl *RD,
|
||||
FunctionDecl *Spaceship);
|
||||
void DefineDefaultedComparison(SourceLocation Loc, FunctionDecl *FD,
|
||||
DefaultedComparisonKind DCK);
|
||||
|
||||
|
@ -7838,6 +7840,10 @@ public:
|
|||
/// We are declaring an implicit special member function.
|
||||
DeclaringSpecialMember,
|
||||
|
||||
/// We are declaring an implicit 'operator==' for a defaulted
|
||||
/// 'operator<=>'.
|
||||
DeclaringImplicitEqualityComparison,
|
||||
|
||||
/// We are defining a synthesized function (such as a defaulted special
|
||||
/// member).
|
||||
DefiningSynthesizedFunction,
|
||||
|
@ -8468,6 +8474,11 @@ public:
|
|||
Decl *SubstDecl(Decl *D, DeclContext *Owner,
|
||||
const MultiLevelTemplateArgumentList &TemplateArgs);
|
||||
|
||||
/// Substitute the name and return type of a defaulted 'operator<=>' to form
|
||||
/// an implicit 'operator=='.
|
||||
FunctionDecl *SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD,
|
||||
FunctionDecl *Spaceship);
|
||||
|
||||
ExprResult SubstInitializer(Expr *E,
|
||||
const MultiLevelTemplateArgumentList &TemplateArgs,
|
||||
bool CXXDirectInit);
|
||||
|
|
|
@ -473,13 +473,21 @@ class VarDecl;
|
|||
|
||||
#include "clang/AST/DeclNodes.inc"
|
||||
|
||||
enum class RewriteKind { None, RewriteSpaceshipAsEqualEqual };
|
||||
|
||||
void adjustForRewrite(RewriteKind RK, FunctionDecl *Orig, QualType &T,
|
||||
TypeSourceInfo *&TInfo,
|
||||
DeclarationNameInfo &NameInfo);
|
||||
|
||||
// A few supplemental visitor functions.
|
||||
Decl *VisitCXXMethodDecl(CXXMethodDecl *D,
|
||||
TemplateParameterList *TemplateParams,
|
||||
Optional<const ASTTemplateArgumentListInfo *>
|
||||
ClassScopeSpecializationArgs = llvm::None);
|
||||
ClassScopeSpecializationArgs = llvm::None,
|
||||
RewriteKind RK = RewriteKind::None);
|
||||
Decl *VisitFunctionDecl(FunctionDecl *D,
|
||||
TemplateParameterList *TemplateParams);
|
||||
TemplateParameterList *TemplateParams,
|
||||
RewriteKind RK = RewriteKind::None);
|
||||
Decl *VisitDecl(Decl *D);
|
||||
Decl *VisitVarDecl(VarDecl *D, bool InstantiatingVarTemplate,
|
||||
ArrayRef<BindingDecl *> *Bindings = nullptr);
|
||||
|
|
|
@ -413,6 +413,8 @@ private:
|
|||
return "ExceptionSpecInstantiation";
|
||||
case CodeSynthesisContext::DeclaringSpecialMember:
|
||||
return "DeclaringSpecialMember";
|
||||
case CodeSynthesisContext::DeclaringImplicitEqualityComparison:
|
||||
return "DeclaringImplicitEqualityComparison";
|
||||
case CodeSynthesisContext::DefiningSynthesizedFunction:
|
||||
return "DefiningSynthesizedFunction";
|
||||
case CodeSynthesisContext::RewritingOperatorAsSpaceship:
|
||||
|
|
|
@ -6481,40 +6481,38 @@ void Sema::CheckCompletedCXXClass(Scope *S, CXXRecordDecl *Record) {
|
|||
|
||||
bool HasMethodWithOverrideControl = false,
|
||||
HasOverridingMethodWithoutOverrideControl = false;
|
||||
for (auto *M : Record->methods()) {
|
||||
// FIXME: We could do this check for dependent types with non-dependent
|
||||
// bases.
|
||||
if (!Record->isDependentType()) {
|
||||
// See if a method overloads virtual methods in a base
|
||||
// class without overriding any.
|
||||
if (!M->isStatic())
|
||||
DiagnoseHiddenVirtualMethods(M);
|
||||
if (M->hasAttr<OverrideAttr>())
|
||||
HasMethodWithOverrideControl = true;
|
||||
else if (M->size_overridden_methods() > 0)
|
||||
HasOverridingMethodWithoutOverrideControl = true;
|
||||
}
|
||||
for (auto *D : Record->decls()) {
|
||||
if (auto *M = dyn_cast<CXXMethodDecl>(D)) {
|
||||
// FIXME: We could do this check for dependent types with non-dependent
|
||||
// bases.
|
||||
if (!Record->isDependentType()) {
|
||||
// See if a method overloads virtual methods in a base
|
||||
// class without overriding any.
|
||||
if (!M->isStatic())
|
||||
DiagnoseHiddenVirtualMethods(M);
|
||||
if (M->hasAttr<OverrideAttr>())
|
||||
HasMethodWithOverrideControl = true;
|
||||
else if (M->size_overridden_methods() > 0)
|
||||
HasOverridingMethodWithoutOverrideControl = true;
|
||||
}
|
||||
|
||||
if (!isa<CXXDestructorDecl>(M))
|
||||
CompleteMemberFunction(M);
|
||||
if (!isa<CXXDestructorDecl>(M))
|
||||
CompleteMemberFunction(M);
|
||||
} else if (auto *F = dyn_cast<FriendDecl>(D)) {
|
||||
CheckForDefaultedFunction(
|
||||
dyn_cast_or_null<FunctionDecl>(F->getFriendDecl()));
|
||||
}
|
||||
}
|
||||
|
||||
if (HasMethodWithOverrideControl &&
|
||||
HasOverridingMethodWithoutOverrideControl) {
|
||||
// At least one method has the 'override' control declared.
|
||||
// Diagnose all other overridden methods which do not have 'override' specified on them.
|
||||
// Diagnose all other overridden methods which do not have 'override'
|
||||
// specified on them.
|
||||
for (auto *M : Record->methods())
|
||||
DiagnoseAbsenceOfOverrideControl(M);
|
||||
}
|
||||
|
||||
// Process any defaulted friends in the member-specification.
|
||||
if (!Record->isDependentType()) {
|
||||
for (FriendDecl *D : Record->friends()) {
|
||||
CheckForDefaultedFunction(
|
||||
dyn_cast_or_null<FunctionDecl>(D->getFriendDecl()));
|
||||
}
|
||||
}
|
||||
|
||||
// Check the defaulted secondary comparisons after any other member functions.
|
||||
for (FunctionDecl *FD : DefaultedSecondaryComparisons)
|
||||
CheckExplicitlyDefaultedFunction(S, FD);
|
||||
|
@ -7868,7 +7866,9 @@ static void lookupOperatorsForDefaultedComparison(Sema &Self, Scope *S,
|
|||
Lookup(ExtraOp);
|
||||
|
||||
// For 'operator<=>', we also form a 'cmp != 0' expression, and might
|
||||
// synthesize a three-way comparison from '<' and '=='.
|
||||
// synthesize a three-way comparison from '<' and '=='. In a dependent
|
||||
// context, we also need to look up '==' in case we implicitly declare a
|
||||
// defaulted 'operator=='.
|
||||
if (Op == OO_Spaceship) {
|
||||
Lookup(OO_ExclaimEqual);
|
||||
Lookup(OO_Less);
|
||||
|
@ -7904,9 +7904,13 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
for (const ParmVarDecl *Param : FD->parameters()) {
|
||||
if (!Param->getType()->isDependentType() &&
|
||||
!Context.hasSameType(Param->getType(), ExpectedParmType)) {
|
||||
Diag(FD->getLocation(), diag::err_defaulted_comparison_param)
|
||||
<< (int)DCK << Param->getType() << ExpectedParmType
|
||||
<< Param->getSourceRange();
|
||||
// Don't diagnose an implicit 'operator=='; we will have diagnosed the
|
||||
// corresponding defaulted 'operator<=>' already.
|
||||
if (!FD->isImplicit()) {
|
||||
Diag(FD->getLocation(), diag::err_defaulted_comparison_param)
|
||||
<< (int)DCK << Param->getType() << ExpectedParmType
|
||||
<< Param->getSourceRange();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -7918,8 +7922,12 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
SourceLocation InsertLoc;
|
||||
if (FunctionTypeLoc Loc = MD->getFunctionTypeLoc())
|
||||
InsertLoc = getLocForEndOfToken(Loc.getRParenLoc());
|
||||
Diag(MD->getLocation(), diag::err_defaulted_comparison_non_const)
|
||||
<< (int)DCK << FixItHint::CreateInsertion(InsertLoc, " const");
|
||||
// Don't diagnose an implicit 'operator=='; we will have diagnosed the
|
||||
// corresponding defaulted 'operator<=>' already.
|
||||
if (!MD->isImplicit()) {
|
||||
Diag(MD->getLocation(), diag::err_defaulted_comparison_non_const)
|
||||
<< (int)DCK << FixItHint::CreateInsertion(InsertLoc, " const");
|
||||
}
|
||||
|
||||
// Add the 'const' to the type to recover.
|
||||
const auto *FPT = MD->getType()->castAs<FunctionProtoType>();
|
||||
|
@ -7980,7 +7988,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
// This is really just a consequence of the general rule that you can
|
||||
// only delete a function on its first declaration.
|
||||
Diag(FD->getLocation(), diag::err_non_first_default_compare_deletes)
|
||||
<< (int)DCK;
|
||||
<< FD->isImplicit() << (int)DCK;
|
||||
DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
|
||||
DefaultedComparisonAnalyzer::ExplainDeleted)
|
||||
.visit();
|
||||
|
@ -7988,7 +7996,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
}
|
||||
|
||||
SetDeclDeleted(FD, FD->getLocation());
|
||||
if (!inTemplateInstantiation()) {
|
||||
if (!inTemplateInstantiation() && !FD->isImplicit()) {
|
||||
Diag(FD->getLocation(), diag::warn_defaulted_comparison_deleted)
|
||||
<< (int)DCK;
|
||||
DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
|
||||
|
@ -8028,7 +8036,7 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
!Info.Constexpr) {
|
||||
Diag(FD->getBeginLoc(),
|
||||
diag::err_incorrect_defaulted_comparison_constexpr)
|
||||
<< (int)DCK << FD->isConsteval();
|
||||
<< FD->isImplicit() << (int)DCK << FD->isConsteval();
|
||||
DefaultedComparisonAnalyzer(*this, RD, FD, DCK,
|
||||
DefaultedComparisonAnalyzer::ExplainConstexpr)
|
||||
.visit();
|
||||
|
@ -8051,6 +8059,20 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD,
|
|||
return false;
|
||||
}
|
||||
|
||||
void Sema::DeclareImplicitEqualityComparison(CXXRecordDecl *RD,
|
||||
FunctionDecl *Spaceship) {
|
||||
Sema::CodeSynthesisContext Ctx;
|
||||
Ctx.Kind = Sema::CodeSynthesisContext::DeclaringImplicitEqualityComparison;
|
||||
Ctx.PointOfInstantiation = Spaceship->getEndLoc();
|
||||
Ctx.Entity = Spaceship;
|
||||
pushCodeSynthesisContext(Ctx);
|
||||
|
||||
if (FunctionDecl *EqualEqual = SubstSpaceshipAsEqualEqual(RD, Spaceship))
|
||||
EqualEqual->setImplicit();
|
||||
|
||||
popCodeSynthesisContext();
|
||||
}
|
||||
|
||||
void Sema::DefineDefaultedComparison(SourceLocation UseLoc, FunctionDecl *FD,
|
||||
DefaultedComparisonKind DCK) {
|
||||
assert(FD->isDefaulted() && !FD->isDeleted() &&
|
||||
|
@ -9330,6 +9352,44 @@ void Sema::ActOnFinishCXXMemberSpecification(
|
|||
CheckCompletedCXXClass(S, cast<CXXRecordDecl>(TagDecl));
|
||||
}
|
||||
|
||||
/// Find the equality comparison functions that should be implicitly declared
|
||||
/// in a given class definition, per C++2a [class.compare.default]p3.
|
||||
static void findImplicitlyDeclaredEqualityComparisons(
|
||||
ASTContext &Ctx, CXXRecordDecl *RD,
|
||||
llvm::SmallVectorImpl<FunctionDecl *> &Spaceships) {
|
||||
DeclarationName EqEq = Ctx.DeclarationNames.getCXXOperatorName(OO_EqualEqual);
|
||||
if (!RD->lookup(EqEq).empty())
|
||||
// Member operator== explicitly declared: no implicit operator==s.
|
||||
return;
|
||||
|
||||
// Traverse friends looking for an '==' or a '<=>'.
|
||||
for (FriendDecl *Friend : RD->friends()) {
|
||||
FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(Friend->getFriendDecl());
|
||||
if (!FD) continue;
|
||||
|
||||
if (FD->getOverloadedOperator() == OO_EqualEqual) {
|
||||
// Friend operator== explicitly declared: no implicit operator==s.
|
||||
Spaceships.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (FD->getOverloadedOperator() == OO_Spaceship &&
|
||||
FD->isExplicitlyDefaulted())
|
||||
Spaceships.push_back(FD);
|
||||
}
|
||||
|
||||
// Look for members named 'operator<=>'.
|
||||
DeclarationName Cmp = Ctx.DeclarationNames.getCXXOperatorName(OO_Spaceship);
|
||||
for (NamedDecl *ND : RD->lookup(Cmp)) {
|
||||
// Note that we could find a non-function here (either a function template
|
||||
// or a using-declaration). Neither case results in an implicit
|
||||
// 'operator=='.
|
||||
if (auto *FD = dyn_cast<FunctionDecl>(ND))
|
||||
if (FD->isExplicitlyDefaulted())
|
||||
Spaceships.push_back(FD);
|
||||
}
|
||||
}
|
||||
|
||||
/// AddImplicitlyDeclaredMembersToClass - Adds any implicitly-declared
|
||||
/// special functions, such as the default constructor, copy
|
||||
/// constructor, or destructor, to the given C++ class (C++
|
||||
|
@ -9407,6 +9467,20 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) {
|
|||
ClassDecl->needsOverloadResolutionForDestructor())
|
||||
DeclareImplicitDestructor(ClassDecl);
|
||||
}
|
||||
|
||||
// C++2a [class.compare.default]p3:
|
||||
// If the member-specification does not explicitly declare any member or
|
||||
// friend named operator==, an == operator function is declared implicitly
|
||||
// for each defaulted three-way comparison operator function defined in the
|
||||
// member-specification
|
||||
// FIXME: Consider doing this lazily.
|
||||
if (getLangOpts().CPlusPlus2a) {
|
||||
llvm::SmallVector<FunctionDecl*, 4> DefaultedSpaceships;
|
||||
findImplicitlyDeclaredEqualityComparisons(Context, ClassDecl,
|
||||
DefaultedSpaceships);
|
||||
for (auto *FD : DefaultedSpaceships)
|
||||
DeclareImplicitEqualityComparison(ClassDecl, FD);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned Sema::ActOnReenterTemplateScope(Scope *S, Decl *D) {
|
||||
|
|
|
@ -9723,6 +9723,7 @@ enum OverloadCandidateKind {
|
|||
oc_implicit_move_constructor,
|
||||
oc_implicit_copy_assignment,
|
||||
oc_implicit_move_assignment,
|
||||
oc_implicit_equality_comparison,
|
||||
oc_inherited_constructor
|
||||
};
|
||||
|
||||
|
@ -9751,6 +9752,9 @@ ClassifyOverloadCandidate(Sema &S, NamedDecl *Found, FunctionDecl *Fn,
|
|||
}();
|
||||
|
||||
OverloadCandidateKind Kind = [&]() {
|
||||
if (Fn->isImplicit() && Fn->getOverloadedOperator() == OO_EqualEqual)
|
||||
return oc_implicit_equality_comparison;
|
||||
|
||||
if (CRK & CRK_Reversed)
|
||||
return oc_reversed_binary_operator;
|
||||
|
||||
|
|
|
@ -203,6 +203,7 @@ bool Sema::CodeSynthesisContext::isInstantiationRecord() const {
|
|||
|
||||
case DefaultTemplateArgumentChecking:
|
||||
case DeclaringSpecialMember:
|
||||
case DeclaringImplicitEqualityComparison:
|
||||
case DefiningSynthesizedFunction:
|
||||
case ExceptionSpecEvaluation:
|
||||
case ConstraintSubstitution:
|
||||
|
@ -671,6 +672,11 @@ void Sema::PrintInstantiationStack() {
|
|||
<< cast<CXXRecordDecl>(Active->Entity) << Active->SpecialMember;
|
||||
break;
|
||||
|
||||
case CodeSynthesisContext::DeclaringImplicitEqualityComparison:
|
||||
Diags.Report(Active->Entity->getLocation(),
|
||||
diag::note_in_declaration_of_implicit_equality_comparison);
|
||||
break;
|
||||
|
||||
case CodeSynthesisContext::DefiningSynthesizedFunction: {
|
||||
// FIXME: For synthesized functions that are not defaulted,
|
||||
// produce a note.
|
||||
|
@ -772,6 +778,7 @@ Optional<TemplateDeductionInfo *> Sema::isSFINAEContext() const {
|
|||
return Active->DeductionInfo;
|
||||
|
||||
case CodeSynthesisContext::DeclaringSpecialMember:
|
||||
case CodeSynthesisContext::DeclaringImplicitEqualityComparison:
|
||||
case CodeSynthesisContext::DefiningSynthesizedFunction:
|
||||
case CodeSynthesisContext::RewritingOperatorAsSpaceship:
|
||||
// This happens in a context unrelated to template instantiation, so
|
||||
|
|
|
@ -1794,8 +1794,9 @@ static QualType adjustFunctionTypeForInstantiation(ASTContext &Context,
|
|||
/// 1) instantiating function templates
|
||||
/// 2) substituting friend declarations
|
||||
/// 3) substituting deduction guide declarations for nested class templates
|
||||
Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D,
|
||||
TemplateParameterList *TemplateParams) {
|
||||
Decl *TemplateDeclInstantiator::VisitFunctionDecl(
|
||||
FunctionDecl *D, TemplateParameterList *TemplateParams,
|
||||
RewriteKind FunctionRewriteKind) {
|
||||
// Check whether there is already a function template specialization for
|
||||
// this declaration.
|
||||
FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplate();
|
||||
|
@ -1865,6 +1866,9 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D,
|
|||
DeclarationNameInfo NameInfo
|
||||
= SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs);
|
||||
|
||||
if (FunctionRewriteKind != RewriteKind::None)
|
||||
adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo);
|
||||
|
||||
FunctionDecl *Function;
|
||||
if (auto *DGuide = dyn_cast<CXXDeductionGuideDecl>(D)) {
|
||||
Function = CXXDeductionGuideDecl::Create(
|
||||
|
@ -2101,8 +2105,8 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl(FunctionDecl *D,
|
|||
|
||||
Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
|
||||
CXXMethodDecl *D, TemplateParameterList *TemplateParams,
|
||||
Optional<const ASTTemplateArgumentListInfo *>
|
||||
ClassScopeSpecializationArgs) {
|
||||
Optional<const ASTTemplateArgumentListInfo *> ClassScopeSpecializationArgs,
|
||||
RewriteKind FunctionRewriteKind) {
|
||||
FunctionTemplateDecl *FunctionTemplate = D->getDescribedFunctionTemplate();
|
||||
if (FunctionTemplate && !TemplateParams) {
|
||||
// We are creating a function template specialization from a function
|
||||
|
@ -2181,13 +2185,17 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
|
|||
if (!DC) return nullptr;
|
||||
}
|
||||
|
||||
DeclarationNameInfo NameInfo
|
||||
= SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs);
|
||||
|
||||
if (FunctionRewriteKind != RewriteKind::None)
|
||||
adjustForRewrite(FunctionRewriteKind, D, T, TInfo, NameInfo);
|
||||
|
||||
// Build the instantiated method declaration.
|
||||
CXXRecordDecl *Record = cast<CXXRecordDecl>(DC);
|
||||
CXXMethodDecl *Method = nullptr;
|
||||
|
||||
SourceLocation StartLoc = D->getInnerLocStart();
|
||||
DeclarationNameInfo NameInfo
|
||||
= SemaRef.SubstDeclarationNameInfo(D->getNameInfo(), TemplateArgs);
|
||||
if (CXXConstructorDecl *Constructor = dyn_cast<CXXConstructorDecl>(D)) {
|
||||
Method = CXXConstructorDecl::Create(
|
||||
SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
|
||||
|
@ -3523,6 +3531,73 @@ Decl *Sema::SubstDecl(Decl *D, DeclContext *Owner,
|
|||
return SubstD;
|
||||
}
|
||||
|
||||
void TemplateDeclInstantiator::adjustForRewrite(RewriteKind RK,
|
||||
FunctionDecl *Orig, QualType &T,
|
||||
TypeSourceInfo *&TInfo,
|
||||
DeclarationNameInfo &NameInfo) {
|
||||
assert(RK == RewriteKind::RewriteSpaceshipAsEqualEqual);
|
||||
|
||||
// C++2a [class.compare.default]p3:
|
||||
// the return type is replaced with bool
|
||||
auto *FPT = T->castAs<FunctionProtoType>();
|
||||
T = SemaRef.Context.getFunctionType(
|
||||
SemaRef.Context.BoolTy, FPT->getParamTypes(), FPT->getExtProtoInfo());
|
||||
|
||||
// Update the return type in the source info too. The most straightforward
|
||||
// way is to create new TypeSourceInfo for the new type. Use the location of
|
||||
// the '= default' as the location of the new type.
|
||||
//
|
||||
// FIXME: Set the correct return type when we initially transform the type,
|
||||
// rather than delaying it to now.
|
||||
TypeSourceInfo *NewTInfo =
|
||||
SemaRef.Context.getTrivialTypeSourceInfo(T, Orig->getEndLoc());
|
||||
auto OldLoc = TInfo->getTypeLoc().getAsAdjusted<FunctionProtoTypeLoc>();
|
||||
assert(OldLoc && "type of function is not a function type?");
|
||||
auto NewLoc = NewTInfo->getTypeLoc().castAs<FunctionProtoTypeLoc>();
|
||||
for (unsigned I = 0, N = OldLoc.getNumParams(); I != N; ++I)
|
||||
NewLoc.setParam(I, OldLoc.getParam(I));
|
||||
TInfo = NewTInfo;
|
||||
|
||||
// and the declarator-id is replaced with operator==
|
||||
NameInfo.setName(
|
||||
SemaRef.Context.DeclarationNames.getCXXOperatorName(OO_EqualEqual));
|
||||
}
|
||||
|
||||
FunctionDecl *Sema::SubstSpaceshipAsEqualEqual(CXXRecordDecl *RD,
|
||||
FunctionDecl *Spaceship) {
|
||||
if (Spaceship->isInvalidDecl())
|
||||
return nullptr;
|
||||
|
||||
// C++2a [class.compare.default]p3:
|
||||
// an == operator function is declared implicitly [...] with the same
|
||||
// access and function-definition and in the same class scope as the
|
||||
// three-way comparison operator function
|
||||
MultiLevelTemplateArgumentList NoTemplateArgs;
|
||||
TemplateDeclInstantiator Instantiator(*this, RD, NoTemplateArgs);
|
||||
Decl *R;
|
||||
if (auto *MD = dyn_cast<CXXMethodDecl>(Spaceship)) {
|
||||
R = Instantiator.VisitCXXMethodDecl(
|
||||
MD, nullptr, None,
|
||||
TemplateDeclInstantiator::RewriteKind::RewriteSpaceshipAsEqualEqual);
|
||||
} else {
|
||||
assert(Spaceship->getFriendObjectKind() &&
|
||||
"defaulted spaceship is neither a member nor a friend");
|
||||
|
||||
R = Instantiator.VisitFunctionDecl(
|
||||
Spaceship, nullptr,
|
||||
TemplateDeclInstantiator::RewriteKind::RewriteSpaceshipAsEqualEqual);
|
||||
if (!R)
|
||||
return nullptr;
|
||||
|
||||
FriendDecl *FD =
|
||||
FriendDecl::Create(Context, RD, Spaceship->getLocation(),
|
||||
cast<NamedDecl>(R), Spaceship->getBeginLoc());
|
||||
FD->setAccess(AS_public);
|
||||
RD->addDecl(FD);
|
||||
}
|
||||
return cast_or_null<FunctionDecl>(R);
|
||||
}
|
||||
|
||||
/// Instantiates a nested template parameter list in the current
|
||||
/// instantiation context.
|
||||
///
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
// RUN: %clang_cc1 -std=c++2a -verify %s
|
||||
|
||||
// This test is for [class.compare.default]p3 as modified and renumbered to p4
|
||||
// by P2002R0.
|
||||
|
||||
namespace std {
|
||||
struct strong_ordering {
|
||||
int n;
|
||||
constexpr operator int() const { return n; }
|
||||
static const strong_ordering less, equal, greater;
|
||||
};
|
||||
constexpr strong_ordering strong_ordering::less = {-1};
|
||||
constexpr strong_ordering strong_ordering::equal = {0};
|
||||
constexpr strong_ordering strong_ordering::greater = {1};
|
||||
}
|
||||
|
||||
namespace N {
|
||||
struct A {
|
||||
friend constexpr std::strong_ordering operator<=>(const A&, const A&) = default;
|
||||
};
|
||||
|
||||
constexpr bool (*test_a_not_found)(const A&, const A&) = &operator==; // expected-error {{undeclared}}
|
||||
|
||||
constexpr bool operator==(const A&, const A&);
|
||||
constexpr bool (*test_a)(const A&, const A&) = &operator==;
|
||||
static_assert((*test_a)(A(), A()));
|
||||
}
|
||||
|
||||
struct B1 {
|
||||
virtual std::strong_ordering operator<=>(const B1&) const = default;
|
||||
};
|
||||
bool (B1::*test_b)(const B1&) const = &B1::operator==;
|
||||
|
||||
struct C1 : B1 {
|
||||
// OK, B1::operator== is virtual.
|
||||
bool operator==(const B1&) const override;
|
||||
};
|
||||
|
||||
struct B2 {
|
||||
std::strong_ordering operator<=>(const B2&) const = default;
|
||||
};
|
||||
|
||||
struct C2 : B2 {
|
||||
bool operator==(const B2&) const override; // expected-error {{only virtual member functions}}
|
||||
};
|
||||
|
||||
struct D {
|
||||
std::strong_ordering operator<=>(const D&) const;
|
||||
virtual std::strong_ordering operator<=>(const struct E&) const = 0;
|
||||
};
|
||||
struct E : D {
|
||||
// expected-error@+2 {{only virtual member functions}}
|
||||
// expected-note@+1 {{while declaring the corresponding implicit 'operator==' for this defaulted 'operator<=>'}}
|
||||
std::strong_ordering operator<=>(const E&) const override = default;
|
||||
};
|
||||
|
||||
struct F {
|
||||
[[deprecated("oh no")]] std::strong_ordering operator<=>(const F&) const = default; // expected-note 4{{deprecated}}
|
||||
};
|
||||
void use_f(F f) {
|
||||
void(f <=> f); // expected-warning {{oh no}}
|
||||
void(f < f); // expected-warning {{oh no}}
|
||||
void(f == f); // expected-warning {{oh no}}
|
||||
void(f != f); // expected-warning {{oh no}}
|
||||
}
|
||||
|
||||
class G {
|
||||
// expected-note@+2 {{implicitly declared private here}}
|
||||
// expected-note-re@+1 {{{{^}}declared private here}}
|
||||
std::strong_ordering operator<=>(const G&) const = default;
|
||||
public:
|
||||
};
|
||||
void use_g(G g) {
|
||||
void(g <=> g); // expected-error {{private}}
|
||||
void(g == g); // expected-error {{private}}
|
||||
}
|
||||
|
||||
struct H {
|
||||
bool operator==(const H&) const; // expected-note {{here}}
|
||||
constexpr std::strong_ordering operator<=>(const H&) const { return std::strong_ordering::equal; }
|
||||
};
|
||||
|
||||
struct I {
|
||||
H h; // expected-note {{used to compare}}
|
||||
// expected-error@+1 {{defaulted definition of three-way comparison operator cannot be declared constexpr because the corresponding implicit 'operator==' invokes a non-constexpr comparison function}}
|
||||
constexpr std::strong_ordering operator<=>(const I&) const = default;
|
||||
};
|
||||
|
||||
struct J {
|
||||
std::strong_ordering operator<=>(const J&) const & = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}}
|
||||
friend std::strong_ordering operator<=>(const J&, const J&) = default; // expected-note {{candidate function (the implicit 'operator==' for this 'operator<=>)'}}
|
||||
};
|
||||
void use_j(J j) {
|
||||
void(j == j); // expected-error {{ambiguous}}
|
||||
}
|
||||
|
||||
namespace DeleteAfterFirstDecl {
|
||||
bool operator==(const struct Q&, const struct Q&);
|
||||
struct Q {
|
||||
struct X {
|
||||
friend std::strong_ordering operator<=>(const X&, const X&);
|
||||
} x; // expected-note {{no viable comparison}}
|
||||
// expected-error@+1 {{defaulting the corresponding implicit 'operator==' for this defaulted 'operator<=>' would delete it after its first declaration}}
|
||||
friend std::strong_ordering operator<=>(const Q&, const Q&) = default;
|
||||
};
|
||||
}
|
||||
|
||||
// Note, substitution here results in the second parameter of 'operator=='
|
||||
// referring to the first parameter of 'operator==', not to the first parameter
|
||||
// of 'operator<=>'.
|
||||
// FIXME: Find a case where this matters (attribute enable_if?).
|
||||
struct K {
|
||||
friend std::strong_ordering operator<=>(const K &k, decltype(k)) = default;
|
||||
};
|
||||
bool test_k = K() == K();
|
||||
|
||||
namespace NoInjectionIfOperatorEqualsDeclared {
|
||||
struct A {
|
||||
void operator==(int); // expected-note 2{{not viable}}
|
||||
std::strong_ordering operator<=>(const A&) const = default;
|
||||
};
|
||||
bool test_a = A() == A(); // expected-error {{invalid operands}}
|
||||
|
||||
struct B {
|
||||
friend void operator==(int, struct Q); // expected-note {{not viable}}
|
||||
std::strong_ordering operator<=>(const B&) const = default;
|
||||
};
|
||||
bool test_b = B() == B(); // expected-error {{invalid operands}}
|
||||
|
||||
struct C {
|
||||
void operator==(int); // expected-note 2{{not viable}}
|
||||
friend std::strong_ordering operator<=>(const C&, const C&) = default;
|
||||
};
|
||||
bool test_c = C() == C(); // expected-error {{invalid operands}}
|
||||
|
||||
struct D {
|
||||
void f() {
|
||||
void operator==(const D&, int);
|
||||
}
|
||||
struct X {
|
||||
friend void operator==(const D&, int);
|
||||
};
|
||||
friend std::strong_ordering operator<=>(const D&, const D&) = default;
|
||||
};
|
||||
bool test_d = D() == D();
|
||||
}
|
|
@ -1,6 +1,41 @@
|
|||
// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefix=ITANIUM
|
||||
// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefix=MSABI
|
||||
// RUN: not %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple -DBUILTIN 2>&1 | FileCheck %s --check-prefix=BUILTIN
|
||||
// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple %itanium_abi_triple | FileCheck %s --check-prefixes=CHECK,ITANIUM
|
||||
// RUN: %clang_cc1 -std=c++2a -emit-llvm %s -o - -triple x86_64-pc-win32 2>&1 | FileCheck %s --check-prefixes=CHECK,MSABI
|
||||
|
||||
namespace std {
|
||||
struct strong_ordering {
|
||||
int n;
|
||||
constexpr operator int() const { return n; }
|
||||
static const strong_ordering less, equal, greater;
|
||||
};
|
||||
constexpr strong_ordering strong_ordering::less = {-1};
|
||||
constexpr strong_ordering strong_ordering::equal = {0};
|
||||
constexpr strong_ordering strong_ordering::greater = {1};
|
||||
}
|
||||
|
||||
struct Primary {
|
||||
virtual void f();
|
||||
std::strong_ordering operator<=>(const Primary&) const = default;
|
||||
};
|
||||
struct X {
|
||||
virtual struct Y &operator=(Y&&);
|
||||
virtual struct Y &operator=(const Y&);
|
||||
std::strong_ordering operator<=>(const X&) const = default;
|
||||
};
|
||||
// The vtable for Y should contain the following entries in order:
|
||||
// - Primary::f
|
||||
// - Y::operator<=>
|
||||
// - Y::operator=(const Y&) (implicit)
|
||||
// - Y::operator=(Y&&) (implicit)
|
||||
// - Y::operator==(const Y&) const (implicit)
|
||||
// See:
|
||||
// https://github.com/itanium-cxx-abi/cxx-abi/issues/83 for assignment operator
|
||||
// https://github.com/itanium-cxx-abi/cxx-abi/issues/88 for equality comparison
|
||||
// FIXME: What rule does MSVC use?
|
||||
struct Y : Primary, X {
|
||||
virtual std::strong_ordering operator<=>(const Y&) const = default;
|
||||
};
|
||||
Y y;
|
||||
// ITANIUM: @_ZTV1Y = {{.*}}constant {{.*}} null, {{.*}} @_ZTI1Y {{.*}} @_ZN7Primary1fEv {{.*}} @_ZNK1YssERKS_ {{.*}} @_ZN1YaSERKS_ {{.*}} @_ZN1YaSEOS_ {{.*}} @_ZNK1YeqERKS_ {{.*}} -{{4|8}} {{.*}} @_ZTI1Y {{.*}} @_ZThn8_N1YaSERKS_
|
||||
|
||||
struct A {
|
||||
void operator<=>(int);
|
||||
|
@ -26,8 +61,11 @@ int f(A a) {
|
|||
return a <=> a;
|
||||
}
|
||||
|
||||
#ifdef BUILTIN
|
||||
void builtin(int a) {
|
||||
a <=> a; // BUILTIN: cannot compile this scalar expression yet
|
||||
// CHECK-LABEL: define {{.*}}builtin_cmp
|
||||
void builtin_cmp(int a) {
|
||||
// CHECK: icmp slt
|
||||
// CHECK: select
|
||||
// CHECK: icmp eq
|
||||
// CHECK: select
|
||||
a <=> a;
|
||||
}
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue