From a376d10acfacf19d6dfa41069f7929739a18dd7a Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Fri, 2 Jul 2010 21:50:04 +0000 Subject: [PATCH] Lazily declare copy-assignment operators. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@107521 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/ASTContext.h | 7 +++ include/clang/AST/DeclCXX.h | 25 ++++++--- lib/AST/ASTContext.cpp | 5 ++ lib/AST/DeclCXX.cpp | 52 ++----------------- lib/Frontend/PCHReaderDecl.cpp | 1 + lib/Frontend/PCHWriterDecl.cpp | 1 + lib/Sema/SemaDeclCXX.cpp | 95 ++++++++++++++++++++++++++++------ lib/Sema/SemaLookup.cpp | 80 +++++++++++++++++++++++----- 8 files changed, 181 insertions(+), 85 deletions(-) diff --git a/include/clang/AST/ASTContext.h b/include/clang/AST/ASTContext.h index f109d48159..5e3f891e48 100644 --- a/include/clang/AST/ASTContext.h +++ b/include/clang/AST/ASTContext.h @@ -1327,6 +1327,13 @@ public: // Statistics //===--------------------------------------------------------------------===// + /// \brief The number of implicitly-declared copy assignment operators. + static unsigned NumImplicitCopyAssignmentOperators; + + /// \brief The number of implicitly-declared copy assignment operators for + /// which declarations were built. + static unsigned NumImplicitCopyAssignmentOperatorsDeclared; + /// \brief The number of implicitly-declared destructors. static unsigned NumImplicitDestructors; diff --git a/include/clang/AST/DeclCXX.h b/include/clang/AST/DeclCXX.h index f67faa86ec..624bf63119 100644 --- a/include/clang/AST/DeclCXX.h +++ b/include/clang/AST/DeclCXX.h @@ -319,6 +319,9 @@ class CXXRecordDecl : public RecordDecl { /// already computed and are available. bool ComputedVisibleConversions : 1; + /// \brief Whether we have already declared the copy-assignment operator. + bool DeclaredCopyAssignment : 1; + /// \brief Whether we have already declared a destructor within the class. bool DeclaredDestructor : 1; @@ -542,12 +545,6 @@ public: CXXConstructorDecl *getCopyConstructor(ASTContext &Context, unsigned TypeQuals) const; - /// hasConstCopyAssignment - Determines whether this class has a - /// copy assignment operator that accepts a const-qualified argument. - /// It returns its decl in MD if found. - bool hasConstCopyAssignment(ASTContext &Context, - const CXXMethodDecl *&MD) const; - /// \brief Retrieve the copy-assignment operator for this class, if available. /// /// This routine attempts to find the copy-assignment operator for this @@ -581,7 +578,7 @@ public: /// addedAssignmentOperator - Notify the class that another assignment /// operator has been added. This routine helps maintain information about the - /// class based on which operators have been added. + /// class based on which operators have been added. void addedAssignmentOperator(ASTContext &Context, CXXMethodDecl *OpDecl); /// hasUserDeclaredCopyAssignment - Whether this class has a @@ -591,6 +588,20 @@ public: return data().UserDeclaredCopyAssignment; } + /// \brief Determine whether this class has had its copy assignment operator + /// declared, either via the user or via an implicit declaration. + /// + /// This value is used for lazy creation of copy assignment operators. + bool hasDeclaredCopyAssignment() const { + return data().DeclaredCopyAssignment; + } + + /// \brief Note whether this class has already had its copy assignment + /// operator declared. + void setDeclaredCopyAssignment(bool DCA) { + data().DeclaredCopyAssignment = DCA; + } + /// hasUserDeclaredDestructor - Whether this class has a /// user-declared destructor. When false, a destructor will be /// implicitly declared. diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 40342b36ba..f44eb65721 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -31,6 +31,8 @@ using namespace clang; +unsigned ASTContext::NumImplicitCopyAssignmentOperators; +unsigned ASTContext::NumImplicitCopyAssignmentOperatorsDeclared; unsigned ASTContext::NumImplicitDestructors; unsigned ASTContext::NumImplicitDestructorsDeclared; @@ -257,6 +259,9 @@ void ASTContext::PrintStats() const { fprintf(stderr, "Total bytes = %d\n", int(TotalBytes)); // Implicit special member functions. + fprintf(stderr, " %u/%u implicit copy assignment operators created\n", + NumImplicitCopyAssignmentOperatorsDeclared, + NumImplicitCopyAssignmentOperators); fprintf(stderr, " %u/%u implicit destructors created\n", NumImplicitDestructorsDeclared, NumImplicitDestructors); diff --git a/lib/AST/DeclCXX.cpp b/lib/AST/DeclCXX.cpp index 05e3680ea9..b54f0e2ca6 100644 --- a/lib/AST/DeclCXX.cpp +++ b/lib/AST/DeclCXX.cpp @@ -32,7 +32,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) Abstract(false), HasTrivialConstructor(true), HasTrivialCopyConstructor(true), HasTrivialCopyAssignment(true), HasTrivialDestructor(true), ComputedVisibleConversions(false), - DeclaredDestructor(false), + DeclaredCopyAssignment(false), DeclaredDestructor(false), Bases(0), NumBases(0), VBases(0), NumVBases(0), Definition(D), FirstFriend(0) { } @@ -219,53 +219,6 @@ CXXConstructorDecl *CXXRecordDecl::getCopyConstructor(ASTContext &Context, GetBestOverloadCandidateSimple(Found)); } -bool CXXRecordDecl::hasConstCopyAssignment(ASTContext &Context, - const CXXMethodDecl *& MD) const { - QualType ClassType = Context.getCanonicalType(Context.getTypeDeclType( - const_cast(this))); - DeclarationName OpName =Context.DeclarationNames.getCXXOperatorName(OO_Equal); - - DeclContext::lookup_const_iterator Op, OpEnd; - for (llvm::tie(Op, OpEnd) = this->lookup(OpName); - Op != OpEnd; ++Op) { - // C++ [class.copy]p9: - // A user-declared copy assignment operator is a non-static non-template - // member function of class X with exactly one parameter of type X, X&, - // const X&, volatile X& or const volatile X&. - const CXXMethodDecl* Method = dyn_cast(*Op); - if (!Method) - continue; - - if (Method->isStatic()) - continue; - if (Method->getPrimaryTemplate()) - continue; - const FunctionProtoType *FnType = - Method->getType()->getAs(); - assert(FnType && "Overloaded operator has no prototype."); - // Don't assert on this; an invalid decl might have been left in the AST. - if (FnType->getNumArgs() != 1 || FnType->isVariadic()) - continue; - bool AcceptsConst = true; - QualType ArgType = FnType->getArgType(0); - if (const LValueReferenceType *Ref = ArgType->getAs()) { - ArgType = Ref->getPointeeType(); - // Is it a non-const lvalue reference? - if (!ArgType.isConstQualified()) - AcceptsConst = false; - } - if (!Context.hasSameUnqualifiedType(ArgType, ClassType)) - continue; - MD = Method; - // We have a single argument of type cv X or cv X&, i.e. we've found the - // copy assignment operator. Return whether it accepts const arguments. - return AcceptsConst; - } - assert(isInvalidDecl() && - "No copy assignment operator declared in valid code."); - return false; -} - CXXMethodDecl *CXXRecordDecl::getCopyAssignmentOperator(bool ArgIsConst) const { ASTContext &Context = getASTContext(); QualType Class = Context.getTypeDeclType(const_cast(this)); @@ -378,7 +331,8 @@ void CXXRecordDecl::addedAssignmentOperator(ASTContext &Context, // Suppress the implicit declaration of a copy constructor. data().UserDeclaredCopyAssignment = true; - + data().DeclaredCopyAssignment = true; + // C++ [class.copy]p11: // A copy assignment operator is trivial if it is implicitly declared. // FIXME: C++0x: don't do this for "= default" copy operators. diff --git a/lib/Frontend/PCHReaderDecl.cpp b/lib/Frontend/PCHReaderDecl.cpp index 08ab9a26f6..fa7382c2a0 100644 --- a/lib/Frontend/PCHReaderDecl.cpp +++ b/lib/Frontend/PCHReaderDecl.cpp @@ -668,6 +668,7 @@ void PCHDeclReader::VisitCXXRecordDecl(CXXRecordDecl *D) { Data.HasTrivialCopyAssignment = Record[Idx++]; Data.HasTrivialDestructor = Record[Idx++]; Data.ComputedVisibleConversions = Record[Idx++]; + Data.DeclaredCopyAssignment = Record[Idx++]; Data.DeclaredDestructor = Record[Idx++]; // setBases() is unsuitable since it may try to iterate the bases of an diff --git a/lib/Frontend/PCHWriterDecl.cpp b/lib/Frontend/PCHWriterDecl.cpp index d865e7c1b2..41e8a012a7 100644 --- a/lib/Frontend/PCHWriterDecl.cpp +++ b/lib/Frontend/PCHWriterDecl.cpp @@ -664,6 +664,7 @@ void PCHDeclWriter::VisitCXXRecordDecl(CXXRecordDecl *D) { Record.push_back(Data.HasTrivialCopyAssignment); Record.push_back(Data.HasTrivialDestructor); Record.push_back(Data.ComputedVisibleConversions); + Record.push_back(Data.DeclaredCopyAssignment); Record.push_back(Data.DeclaredDestructor); Record.push_back(D->getNumBases()); diff --git a/lib/Sema/SemaDeclCXX.cpp b/lib/Sema/SemaDeclCXX.cpp index 3299999536..93caad669d 100644 --- a/lib/Sema/SemaDeclCXX.cpp +++ b/lib/Sema/SemaDeclCXX.cpp @@ -2659,8 +2659,16 @@ void Sema::AddImplicitlyDeclaredMembersToClass(CXXRecordDecl *ClassDecl) { if (!ClassDecl->hasUserDeclaredCopyConstructor()) DeclareImplicitCopyConstructor(ClassDecl); - if (!ClassDecl->hasUserDeclaredCopyAssignment()) - DeclareImplicitCopyAssignment(ClassDecl); + if (!ClassDecl->hasUserDeclaredCopyAssignment()) { + ++ASTContext::NumImplicitCopyAssignmentOperators; + + // If we have a dynamic class, then the copy assignment operator may be + // virtual, so we have to declare it immediately. This ensures that, e.g., + // it shows up in the right place in the vtable and that we diagnose + // problems with the implicit exception specification. + if (ClassDecl->isDynamicClass()) + DeclareImplicitCopyAssignment(ClassDecl); + } if (!ClassDecl->hasUserDeclaredDestructor()) { ++ASTContext::NumImplicitDestructors; @@ -4547,6 +4555,58 @@ BuildSingleCopyAssign(Sema &S, SourceLocation Loc, QualType T, Loc, move(Copy)); } +/// \brief Determine whether the given class has a copy assignment operator +/// that accepts a const-qualified argument. +static bool hasConstCopyAssignment(Sema &S, const CXXRecordDecl *CClass) { + CXXRecordDecl *Class = const_cast(CClass); + + if (!Class->hasDeclaredCopyAssignment()) + S.DeclareImplicitCopyAssignment(Class); + + QualType ClassType = S.Context.getCanonicalType(S.Context.getTypeDeclType(Class)); + DeclarationName OpName + = S.Context.DeclarationNames.getCXXOperatorName(OO_Equal); + + DeclContext::lookup_const_iterator Op, OpEnd; + for (llvm::tie(Op, OpEnd) = Class->lookup(OpName); Op != OpEnd; ++Op) { + // C++ [class.copy]p9: + // A user-declared copy assignment operator is a non-static non-template + // member function of class X with exactly one parameter of type X, X&, + // const X&, volatile X& or const volatile X&. + const CXXMethodDecl* Method = dyn_cast(*Op); + if (!Method) + continue; + + if (Method->isStatic()) + continue; + if (Method->getPrimaryTemplate()) + continue; + const FunctionProtoType *FnType = + Method->getType()->getAs(); + assert(FnType && "Overloaded operator has no prototype."); + // Don't assert on this; an invalid decl might have been left in the AST. + if (FnType->getNumArgs() != 1 || FnType->isVariadic()) + continue; + bool AcceptsConst = true; + QualType ArgType = FnType->getArgType(0); + if (const LValueReferenceType *Ref = ArgType->getAs()){ + ArgType = Ref->getPointeeType(); + // Is it a non-const lvalue reference? + if (!ArgType.isConstQualified()) + AcceptsConst = false; + } + if (!S.Context.hasSameUnqualifiedType(ArgType, ClassType)) + continue; + + // We have a single argument of type cv X or cv X&, i.e. we've found the + // copy assignment operator. Return whether it accepts const arguments. + return AcceptsConst; + } + assert(Class->isInvalidDecl() && + "No copy assignment operator declared in valid code."); + return false; +} + CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { // Note: The following rules are largely analoguous to the copy // constructor rules. Note that virtual bases are not taken into account @@ -4574,9 +4634,7 @@ CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { "Cannot generate implicit members for class with dependent bases."); const CXXRecordDecl *BaseClassDecl = cast(Base->getType()->getAs()->getDecl()); - const CXXMethodDecl *MD = 0; - HasConstCopyAssignment = BaseClassDecl->hasConstCopyAssignment(Context, - MD); + HasConstCopyAssignment = hasConstCopyAssignment(*this, BaseClassDecl); } // -- for all the nonstatic data members of X that are of a class @@ -4591,9 +4649,7 @@ CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { if (const RecordType *FieldClassType = FieldType->getAs()) { const CXXRecordDecl *FieldClassDecl = cast(FieldClassType->getDecl()); - const CXXMethodDecl *MD = 0; - HasConstCopyAssignment - = FieldClassDecl->hasConstCopyAssignment(Context, MD); + HasConstCopyAssignment = hasConstCopyAssignment(*this, FieldClassDecl); } } @@ -4614,8 +4670,12 @@ CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { for (CXXRecordDecl::base_class_iterator Base = ClassDecl->bases_begin(), BaseEnd = ClassDecl->bases_end(); Base != BaseEnd; ++Base) { - const CXXRecordDecl *BaseClassDecl + CXXRecordDecl *BaseClassDecl = cast(Base->getType()->getAs()->getDecl()); + + if (!BaseClassDecl->hasDeclaredCopyAssignment()) + DeclareImplicitCopyAssignment(BaseClassDecl); + if (CXXMethodDecl *CopyAssign = BaseClassDecl->getCopyAssignmentOperator(HasConstCopyAssignment)) ExceptSpec.CalledDecl(CopyAssign); @@ -4626,8 +4686,12 @@ CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { ++Field) { QualType FieldType = Context.getBaseElementType((*Field)->getType()); if (const RecordType *FieldClassType = FieldType->getAs()) { - const CXXRecordDecl *FieldClassDecl + CXXRecordDecl *FieldClassDecl = cast(FieldClassType->getDecl()); + + if (!FieldClassDecl->hasDeclaredCopyAssignment()) + DeclareImplicitCopyAssignment(FieldClassDecl); + if (CXXMethodDecl *CopyAssign = FieldClassDecl->getCopyAssignmentOperator(HasConstCopyAssignment)) ExceptSpec.CalledDecl(CopyAssign); @@ -4663,12 +4727,13 @@ CXXMethodDecl *Sema::DeclareImplicitCopyAssignment(CXXRecordDecl *ClassDecl) { VarDecl::None, 0); CopyAssignment->setParams(&FromParam, 1); - // Don't call addedAssignmentOperator. The class does not need to know about - // the implicitly-declared copy assignment operator. + // Note that we have added this copy-assignment operator. + ClassDecl->setDeclaredCopyAssignment(true); + ++ASTContext::NumImplicitCopyAssignmentOperatorsDeclared; + if (Scope *S = getScopeForContext(ClassDecl)) - PushOnScopeChains(CopyAssignment, S, true); - else - ClassDecl->addDecl(CopyAssignment); + PushOnScopeChains(CopyAssignment, S, false); + ClassDecl->addDecl(CopyAssignment); AddOverriddenMethods(ClassDecl, CopyAssignment); return CopyAssignment; diff --git a/lib/Sema/SemaLookup.cpp b/lib/Sema/SemaLookup.cpp index fc5c8e868d..3a62c1151a 100644 --- a/lib/Sema/SemaLookup.cpp +++ b/lib/Sema/SemaLookup.cpp @@ -464,12 +464,65 @@ static bool CanDeclareSpecialMemberFunction(ASTContext &Context, } void Sema::ForceDeclarationOfImplicitMembers(CXXRecordDecl *Class) { + // If the copy assignment operator has not yet been declared, do so now. + if (CanDeclareSpecialMemberFunction(Context, Class) && + !Class->hasDeclaredCopyAssignment()) + DeclareImplicitCopyAssignment(Class); + // If the destructor has not yet been declared, do so now. if (CanDeclareSpecialMemberFunction(Context, Class) && !Class->hasDeclaredDestructor()) DeclareImplicitDestructor(Class); } +/// \brief Determine whether this is the name of an implicitly-declared +/// special member function. +static bool isImplicitlyDeclaredMemberFunctionName(DeclarationName Name) { + switch (Name.getNameKind()) { + case DeclarationName::CXXDestructorName: + return true; + + case DeclarationName::CXXOperatorName: + return Name.getCXXOverloadedOperator() == OO_Equal; + + default: + break; + } + + return false; +} + +/// \brief If there are any implicit member functions with the given name +/// that need to be declared in the given declaration context, do so. +static void DeclareImplicitMemberFunctionsWithName(Sema &S, + DeclarationName Name, + const DeclContext *DC) { + if (!DC) + return; + + switch (Name.getNameKind()) { + case DeclarationName::CXXDestructorName: + if (const CXXRecordDecl *Record = dyn_cast(DC)) + if (Record->getDefinition() && !Record->hasDeclaredDestructor() && + CanDeclareSpecialMemberFunction(S.Context, Record)) + S.DeclareImplicitDestructor(const_cast(Record)); + + break; + + case DeclarationName::CXXOperatorName: + if (Name.getCXXOverloadedOperator() != OO_Equal) + break; + + if (const CXXRecordDecl *Record = dyn_cast(DC)) + if (Record->getDefinition() && !Record->hasDeclaredCopyAssignment() && + CanDeclareSpecialMemberFunction(S.Context, Record)) + S.DeclareImplicitCopyAssignment(const_cast(Record)); + break; + + default: + break; + } +} // Adds all qualifying matches for a name within a decl context to the // given lookup result. Returns true if any matches were found. @@ -477,20 +530,8 @@ static bool LookupDirect(Sema &S, LookupResult &R, const DeclContext *DC) { bool Found = false; // Lazily declare C++ special member functions. - if (S.getLangOptions().CPlusPlus) { - switch (R.getLookupName().getNameKind()) { - case DeclarationName::CXXDestructorName: - if (const CXXRecordDecl *Record = dyn_cast(DC)) - if (Record->getDefinition() && !Record->hasDeclaredDestructor() && - CanDeclareSpecialMemberFunction(S.Context, Record)) - S.DeclareImplicitDestructor(const_cast(Record)); - - break; - - default: - break; - } - } + if (S.getLangOptions().CPlusPlus) + DeclareImplicitMemberFunctionsWithName(S, R.getLookupName(), DC); // Perform lookup into this declaration context. DeclContext::lookup_const_iterator I, E; @@ -681,6 +722,17 @@ bool Sema::CppLookupName(LookupResult &R, Scope *S) { DeclarationName Name = R.getLookupName(); + // If this is the name of an implicitly-declared special member function, + // go through the scope stack to implicitly declare + if (isImplicitlyDeclaredMemberFunctionName(Name)) { + for (Scope *PreS = S; PreS; PreS = PreS->getParent()) + if (DeclContext *DC = static_cast(PreS->getEntity())) + DeclareImplicitMemberFunctionsWithName(*this, Name, DC); + } + + // Implicitly declare member functions with the name we're looking for, if in + // fact we are in a scope where it matters. + Scope *Initial = S; IdentifierResolver::iterator I = IdResolver.begin(Name), -- 2.40.0