diff --git a/include/clang/AST/ExprCXX.h b/include/clang/AST/ExprCXX.h index f450834bc4..791d94ded0 100644 --- a/include/clang/AST/ExprCXX.h +++ b/include/clang/AST/ExprCXX.h @@ -851,6 +851,108 @@ public: virtual child_iterator child_end(); }; +/// \brief Represents a C++ pseudo-destructor (C++ [expr.pseudo]). +/// +/// Example: +/// +/// \code +/// template +/// void destroy(T* ptr) { +/// ptr->~T(); +/// } +/// \endcode +/// +/// When the template is parsed, the expression \c ptr->~T will be stored as +/// a member reference expression. If it then instantiated with a scalar type +/// as a template argument for T, the resulting expression will be a +/// pseudo-destructor expression. +class CXXPseudoDestructorExpr : public Expr { + /// \brief The base expression (that is being destroyed). + Stmt *Base; + + /// \brief Whether the operator was an arrow ('->'); otherwise, it was a + /// period ('.'). + bool IsArrow : 1; + + /// \brief The location of the '.' or '->' operator. + SourceLocation OperatorLoc; + + /// \brief The nested-name-specifier that follows the operator, if present. + NestedNameSpecifier *Qualifier; + + /// \brief The source range that covers the nested-name-specifier, if + /// present. + SourceRange QualifierRange; + + /// \brief The type being destroyed. + QualType DestroyedType; + + /// \brief The location of the type after the '~'. + SourceLocation DestroyedTypeLoc; + +public: + CXXPseudoDestructorExpr(ASTContext &Context, + Expr *Base, bool isArrow, SourceLocation OperatorLoc, + NestedNameSpecifier *Qualifier, + SourceRange QualifierRange, + QualType DestroyedType, + SourceLocation DestroyedTypeLoc) + : Expr(CXXPseudoDestructorExprClass, + Context.getPointerType(Context.getFunctionType(Context.VoidTy, 0, 0, + false, 0)), + /*isTypeDependent=*/false, + /*isValueDependent=*/Base->isValueDependent()), + Base(static_cast(Base)), IsArrow(isArrow), + OperatorLoc(OperatorLoc), Qualifier(Qualifier), + QualifierRange(QualifierRange), DestroyedType(DestroyedType), + DestroyedTypeLoc(DestroyedTypeLoc) { } + + void setBase(Expr *E) { Base = E; } + Expr *getBase() const { return cast(Base); } + + /// \brief Determines whether this member expression actually had + /// a C++ nested-name-specifier prior to the name of the member, e.g., + /// x->Base::foo. + bool hasQualifier() const { return Qualifier != 0; } + + /// \brief If the member name was qualified, retrieves the source range of + /// the nested-name-specifier that precedes the member name. Otherwise, + /// returns an empty source range. + SourceRange getQualifierRange() const { return QualifierRange; } + + /// \brief If the member name was qualified, retrieves the + /// nested-name-specifier that precedes the member name. Otherwise, returns + /// NULL. + NestedNameSpecifier *getQualifier() const { return Qualifier; } + + /// \brief Determine whether this pseudo-destructor expression was written + /// using an '->' (otherwise, it used a '.'). + bool isArrow() const { return IsArrow; } + void setArrow(bool A) { IsArrow = A; } + + /// \brief Retrieve the location of the '.' or '->' operator. + SourceLocation getOperatorLoc() const { return OperatorLoc; } + + /// \brief Retrieve the type that is being destroyed. + QualType getDestroyedType() const { return DestroyedType; } + + /// \brief Retrieve the location of the type being destroyed. + SourceLocation getDestroyedTypeLoc() const { return DestroyedTypeLoc; } + + virtual SourceRange getSourceRange() const { + return SourceRange(Base->getLocStart(), DestroyedTypeLoc); + } + + static bool classof(const Stmt *T) { + return T->getStmtClass() == CXXPseudoDestructorExprClass; + } + static bool classof(const CXXPseudoDestructorExpr *) { return true; } + + // Iterators + virtual child_iterator child_begin(); + virtual child_iterator child_end(); +}; + /// \brief Represents the name of a function that has not been /// resolved to any declaration. /// diff --git a/include/clang/AST/StmtNodes.def b/include/clang/AST/StmtNodes.def index af345991f1..8d7e4b5fc0 100644 --- a/include/clang/AST/StmtNodes.def +++ b/include/clang/AST/StmtNodes.def @@ -124,6 +124,7 @@ EXPR(CXXZeroInitValueExpr , Expr) EXPR(CXXConditionDeclExpr , DeclRefExpr) EXPR(CXXNewExpr , Expr) EXPR(CXXDeleteExpr , Expr) +EXPR(CXXPseudoDestructorExpr, Expr) EXPR(UnresolvedFunctionNameExpr , Expr) EXPR(UnaryTypeTraitExpr , Expr) EXPR(QualifiedDeclRefExpr , DeclRefExpr) diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 1ef4d7247c..35d91f7ad0 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -1588,9 +1588,15 @@ def err_return_in_constructor_handler : Error< def err_ident_in_pseudo_dtor_not_a_type : Error< "identifier %0 in pseudo-destructor expression does not name a type">; -def err_type_in_pseudo_dtor_not_a_class_type : Error< - "type %0 in pseudo-destructor expression is not a class type">; - +def err_pseudo_dtor_base_not_scalar : Error< + "object expression of non-scalar type %0 cannot be used in a " + "pseudo-destructor expression">; +def err_pseudo_dtor_type_mismatch : Error< + "the type of object expression (%0) does not match the type being destroyed " + "(%1) in pseudo-destructor expression">; +def err_pseudo_dtor_call_with_args : Error< + "call to pseudo-destructor cannot have any arguments">; + def err_invalid_use_of_function_type : Error< "a function type is not allowed here">; def err_invalid_use_of_array_type : Error<"an array type is not allowed here">; diff --git a/lib/AST/ExprCXX.cpp b/lib/AST/ExprCXX.cpp index 006f69890a..0ffb152872 100644 --- a/lib/AST/ExprCXX.cpp +++ b/lib/AST/ExprCXX.cpp @@ -116,6 +116,12 @@ Stmt::child_iterator CXXNewExpr::child_end() { Stmt::child_iterator CXXDeleteExpr::child_begin() { return &Argument; } Stmt::child_iterator CXXDeleteExpr::child_end() { return &Argument+1; } +// CXXPseudoDestructorExpr +Stmt::child_iterator CXXPseudoDestructorExpr::child_begin() { return &Base; } +Stmt::child_iterator CXXPseudoDestructorExpr::child_end() { + return &Base + 1; +} + // UnresolvedFunctionNameExpr Stmt::child_iterator UnresolvedFunctionNameExpr::child_begin() { return child_iterator(); diff --git a/lib/AST/StmtPrinter.cpp b/lib/AST/StmtPrinter.cpp index cde4cdc416..317486cd71 100644 --- a/lib/AST/StmtPrinter.cpp +++ b/lib/AST/StmtPrinter.cpp @@ -1107,6 +1107,20 @@ void StmtPrinter::VisitCXXDeleteExpr(CXXDeleteExpr *E) { PrintExpr(E->getArgument()); } +void StmtPrinter::VisitCXXPseudoDestructorExpr(CXXPseudoDestructorExpr *E) { + PrintExpr(E->getBase()); + if (E->isArrow()) + OS << "->"; + else + OS << '.'; + if (E->getQualifier()) + E->getQualifier()->print(OS, Policy); + + std::string TypeS; + E->getDestroyedType().getAsStringInternal(TypeS, Policy); + OS << TypeS; +} + void StmtPrinter::VisitUnresolvedFunctionNameExpr(UnresolvedFunctionNameExpr *E) { OS << E->getName().getAsString(); } diff --git a/lib/AST/StmtProfile.cpp b/lib/AST/StmtProfile.cpp index 19d313b420..806eb8de81 100644 --- a/lib/AST/StmtProfile.cpp +++ b/lib/AST/StmtProfile.cpp @@ -502,6 +502,13 @@ void StmtProfiler::VisitCXXNewExpr(CXXNewExpr *S) { ID.AddInteger(S->getNumConstructorArgs()); } +void StmtProfiler::VisitCXXPseudoDestructorExpr(CXXPseudoDestructorExpr *S) { + VisitExpr(S); + ID.AddBoolean(S->isArrow()); + VisitNestedNameSpecifier(S->getQualifier()); + VisitType(S->getDestroyedType()); +} + void StmtProfiler::VisitUnresolvedFunctionNameExpr(UnresolvedFunctionNameExpr *S) { VisitExpr(S); diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index 5c8dfa4669..4a8253d9cd 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -1234,6 +1234,16 @@ RValue CodeGenFunction::EmitCallExpr(const CallExpr *E) { if (const CXXMethodDecl *MD = dyn_cast_or_null(TargetDecl)) return EmitCXXOperatorMemberCallExpr(CE, MD); + if (isa(E->getCallee())) { + // C++ [expr.pseudo]p1: + // The result shall only be used as the operand for the function call + // operator (), and the result of such a call has type void. The only + // effect is the evaluation of the postfix-expression before the dot or + // arrow. + EmitScalarExpr(E->getCallee()); + return RValue::get(0); + } + llvm::Value *Callee = EmitScalarExpr(E->getCallee()); return EmitCall(Callee, E->getCallee()->getType(), E->arg_begin(), E->arg_end(), TargetDecl); diff --git a/lib/CodeGen/CGExprScalar.cpp b/lib/CodeGen/CGExprScalar.cpp index 4496c538d3..8732dc9130 100644 --- a/lib/CodeGen/CGExprScalar.cpp +++ b/lib/CodeGen/CGExprScalar.cpp @@ -305,7 +305,17 @@ public: CGF.EmitCXXDeleteExpr(E); return 0; } - + + Value *VisitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *E) { + // C++ [expr.pseudo]p1: + // The result shall only be used as the operand for the function call + // operator (), and the result of such a call has type void. The only + // effect is the evaluation of the postfix-expression before the dot or + // arrow. + CGF.EmitScalarExpr(E->getBase()); + return 0; + } + // Binary Operators. Value *EmitMul(const BinOpInfo &Ops) { if (CGF.getContext().getLangOptions().OverflowChecking diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 720217a910..58d6a0dbac 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -1998,7 +1998,7 @@ Sema::BuildMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc, Base = MaybeConvertParenListExprToParenExpr(S, move(Base)); Expr *BaseExpr = Base.takeAs(); - assert(BaseExpr && "no record expression"); + assert(BaseExpr && "no base expression"); // Perform default conversions. DefaultFunctionArrayConversion(BaseExpr); @@ -2230,6 +2230,47 @@ Sema::BuildMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc, << MemberName << int(OpKind == tok::arrow)); } + // Handle pseudo-destructors (C++ [expr.pseudo]). Since anything referring + // into a record type was handled above, any destructor we see here is a + // pseudo-destructor. + if (MemberName.getNameKind() == DeclarationName::CXXDestructorName) { + // C++ [expr.pseudo]p2: + // The left hand side of the dot operator shall be of scalar type. The + // left hand side of the arrow operator shall be of pointer to scalar + // type. + if (!BaseType->isScalarType()) + return Owned(Diag(OpLoc, diag::err_pseudo_dtor_base_not_scalar) + << BaseType << BaseExpr->getSourceRange()); + + // [...] The type designated by the pseudo-destructor-name shall be the + // same as the object type. + if (!MemberName.getCXXNameType()->isDependentType() && + !Context.hasSameUnqualifiedType(BaseType, MemberName.getCXXNameType())) + return Owned(Diag(OpLoc, diag::err_pseudo_dtor_type_mismatch) + << BaseType << MemberName.getCXXNameType() + << BaseExpr->getSourceRange() << SourceRange(MemberLoc)); + + // [...] Furthermore, the two type-names in a pseudo-destructor-name of + // the form + // + // ::[opt] nested-name-specifier[opt] type-name :: ̃ type-name + // + // shall designate the same scalar type. + // + // FIXME: DPG can't see any way to trigger this particular clause, so it + // isn't checked here. + + // FIXME: We've lost the precise spelling of the type by going through + // DeclarationName. Can we do better? + return Owned(new (Context) CXXPseudoDestructorExpr(Context, BaseExpr, + OpKind == tok::arrow, + OpLoc, + (NestedNameSpecifier *)(SS? SS->getScopeRep() : 0), + SS? SS->getRange() : SourceRange(), + MemberName.getCXXNameType(), + MemberLoc)); + } + // Handle properties on ObjC 'Class' types. if (OpKind == tok::period && BaseType->isObjCClassType()) { // Also must look for a getter name which uses property syntax. @@ -2684,6 +2725,25 @@ Sema::ActOnCallExpr(Scope *S, ExprArg fn, SourceLocation LParenLoc, DeclarationName UnqualifiedName; if (getLangOptions().CPlusPlus) { + // If this is a pseudo-destructor expression, build the call immediately. + if (isa(Fn)) { + if (NumArgs > 0) { + // Pseudo-destructor calls should not have any arguments. + Diag(Fn->getLocStart(), diag::err_pseudo_dtor_call_with_args) + << CodeModificationHint::CreateRemoval( + SourceRange(Args[0]->getLocStart(), + Args[NumArgs-1]->getLocEnd())); + + for (unsigned I = 0; I != NumArgs; ++I) + Args[I]->Destroy(Context); + + NumArgs = 0; + } + + return Owned(new (Context) CallExpr(Context, Fn, 0, 0, Context.VoidTy, + RParenLoc)); + } + // Determine whether this is a dependent call inside a C++ template, // in which case we won't do any semantic analysis now. // FIXME: Will need to cache the results of name lookup (including ADL) in diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp index c62159d001..4d49f87a9a 100644 --- a/lib/Sema/SemaExprCXX.cpp +++ b/lib/Sema/SemaExprCXX.cpp @@ -1821,26 +1821,19 @@ Sema::ActOnDestructorReferenceExpr(Scope *S, ExprArg Base, if (SS && SS->isInvalid()) return ExprError(); - Expr *BaseExpr = (Expr *)Base.get(); + QualType BaseType; + if (SS && isUnknownSpecialization(*SS)) + BaseType = Context.getTypenameType((NestedNameSpecifier *)SS->getScopeRep(), + ClassName); + else { + TypeTy *BaseTy = getTypeName(*ClassName, ClassNameLoc, S, SS); + if (!BaseTy) { + Diag(ClassNameLoc, diag::err_ident_in_pseudo_dtor_not_a_type) + << ClassName; + return ExprError(); + } - if (BaseExpr->isTypeDependent() || - (SS && isDependentScopeSpecifier(*SS))) { - // FIXME: Return an unresolved ref expr. - return ExprError(); - } - - TypeTy *BaseTy = getTypeName(*ClassName, ClassNameLoc, S, SS); - if (!BaseTy) { - Diag(ClassNameLoc, diag::err_ident_in_pseudo_dtor_not_a_type) - << ClassName; - return ExprError(); - } - - QualType BaseType = GetTypeFromParser(BaseTy); - if (!BaseType->isRecordType()) { - Diag(ClassNameLoc, diag::err_type_in_pseudo_dtor_not_a_class_type) - << BaseType; - return ExprError(); + BaseType = GetTypeFromParser(BaseTy); } CanQualType CanBaseType = Context.getCanonicalType(BaseType); diff --git a/lib/Sema/TreeTransform.h b/lib/Sema/TreeTransform.h index 2faaa44daf..22e01ab581 100644 --- a/lib/Sema/TreeTransform.h +++ b/lib/Sema/TreeTransform.h @@ -783,6 +783,36 @@ public: return getSema().ActOnParenExpr(LParen, RParen, move(SubExpr)); } + /// \brief Build a new pseudo-destructor expression. + /// + /// By default, performs semantic analysis to build the new expression. + /// Subclasses may override this routine to provide different behavior. + OwningExprResult RebuildCXXPseudoDestructorExpr(ExprArg Base, + SourceLocation OperatorLoc, + bool isArrow, + SourceLocation DestroyedTypeLoc, + QualType DestroyedType, + NestedNameSpecifier *Qualifier, + SourceRange QualifierRange) { + CXXScopeSpec SS; + if (Qualifier) { + SS.setRange(QualifierRange); + SS.setScopeRep(Qualifier); + } + + DeclarationName Name + = SemaRef.Context.DeclarationNames.getCXXDestructorName( + SemaRef.Context.getCanonicalType(DestroyedType)); + + return getSema().BuildMemberReferenceExpr(/*Scope=*/0, move(Base), + OperatorLoc, + isArrow? tok::arrow : tok::period, + DestroyedTypeLoc, + Name, + Sema::DeclPtrTy::make((Decl *)0), + &SS); + } + /// \brief Build a new unary operator expression. /// /// By default, performs semantic analysis to build the new expression. @@ -3812,6 +3842,43 @@ TreeTransform::TransformCXXDeleteExpr(CXXDeleteExpr *E) { move(Operand)); } +template +Sema::OwningExprResult +TreeTransform::TransformCXXPseudoDestructorExpr( + CXXPseudoDestructorExpr *E) { + OwningExprResult Base = getDerived().TransformExpr(E->getBase()); + if (Base.isInvalid()) + return SemaRef.ExprError(); + + NestedNameSpecifier *Qualifier + = getDerived().TransformNestedNameSpecifier(E->getQualifier(), + E->getQualifierRange()); + if (E->getQualifier() && !Qualifier) + return SemaRef.ExprError(); + + QualType DestroyedType; + { + TemporaryBase Rebase(*this, E->getDestroyedTypeLoc(), DeclarationName()); + DestroyedType = getDerived().TransformType(E->getDestroyedType()); + if (DestroyedType.isNull()) + return SemaRef.ExprError(); + } + + if (!getDerived().AlwaysRebuild() && + Base.get() == E->getBase() && + Qualifier == E->getQualifier() && + DestroyedType == E->getDestroyedType()) + return SemaRef.Owned(E->Retain()); + + return getDerived().RebuildCXXPseudoDestructorExpr(move(Base), + E->getOperatorLoc(), + E->isArrow(), + E->getDestroyedTypeLoc(), + DestroyedType, + Qualifier, + E->getQualifierRange()); +} + template Sema::OwningExprResult TreeTransform::TransformUnresolvedFunctionNameExpr( diff --git a/test/SemaCXX/pseudo-destructors.cpp b/test/SemaCXX/pseudo-destructors.cpp index 8c41f2266d..f1fa331aa2 100644 --- a/test/SemaCXX/pseudo-destructors.cpp +++ b/test/SemaCXX/pseudo-destructors.cpp @@ -4,10 +4,31 @@ struct A {}; enum Foo { F }; typedef Foo Bar; -void f(A* a) { +typedef int Integer; + +void g(); + +namespace N { + typedef Foo Wibble; +} + +void f(A* a, Foo *f, int *i) { a->~A(); a->A::~A(); a->~foo(); // expected-error{{identifier 'foo' in pseudo-destructor expression does not name a type}} - a->~Bar(); // expected-error{{type 'Bar' (aka 'enum Foo') in pseudo-destructor expression is not a class type}} + + // FIXME: the type printed below isn't wonderful + a->~Bar(); // expected-error{{no member named}} + + f->~Bar(); + f->~Foo(); + i->~Bar(); // expected-error{{does not match}} + + g().~Bar(); // expected-error{{non-scalar}} + + f->::~Bar(); + f->N::~Wibble(); + + f->::~Bar(17, 42); // expected-error{{cannot have any arguments}} } diff --git a/test/SemaTemplate/member-access-expr.cpp b/test/SemaTemplate/member-access-expr.cpp index 40dbffe0cf..f4922e8ff5 100644 --- a/test/SemaTemplate/member-access-expr.cpp +++ b/test/SemaTemplate/member-access-expr.cpp @@ -59,3 +59,19 @@ void test_convert(X2 x2) { convert(x2); convert(x2); // expected-note{{instantiation}} } + +template +void destruct(T* ptr) { + ptr->~T(); +} + +template +void destruct_intptr(int *ip) { + ip->~T(); +} + +void test_destruct(X2 *x2p, int *ip) { + destruct(x2p); + destruct(ip); + destruct_intptr(ip); +} \ No newline at end of file