Rework parsing of pseudo-destructor expressions and explicit
destructor calls, e.g.,
p->T::~T
We now detect when the member access that we've parsed, e.g.,
p-> or x.
may be a pseudo-destructor expression, either because the type of p or
x is a scalar or because it is dependent (and, therefore, may become a
scalar at template instantiation time).
We then parse the pseudo-destructor grammar specifically:
::[opt] nested-name-specifier[opt] type-name :: ∼ type-name
and hand those results to a new action, ActOnPseudoDestructorExpr,
which will cope with both dependent member accesses of destructors and
with pseudo-destructor expressions.
This commit affects the parsing of pseudo-destructors, only; the
semantic actions still go through the semantic actions for member
access expressions. That will change soon.
git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@97045 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp
index 8105eeb..46faebf 100644
--- a/lib/Parse/ParseExpr.cpp
+++ b/lib/Parse/ParseExpr.cpp
@@ -996,12 +996,16 @@
CXXScopeSpec SS;
Action::TypeTy *ObjectType = 0;
+ bool MayBePseudoDestructor = false;
if (getLang().CPlusPlus && !LHS.isInvalid()) {
LHS = Actions.ActOnStartCXXMemberReference(CurScope, move(LHS),
- OpLoc, OpKind, ObjectType);
+ OpLoc, OpKind, ObjectType,
+ MayBePseudoDestructor);
if (LHS.isInvalid())
break;
- ParseOptionalCXXScopeSpecifier(SS, ObjectType, false, true);
+
+ ParseOptionalCXXScopeSpecifier(SS, ObjectType, false,
+ &MayBePseudoDestructor);
}
if (Tok.is(tok::code_completion)) {
@@ -1012,6 +1016,17 @@
ConsumeToken();
}
+ if (MayBePseudoDestructor) {
+ LHS = ParseCXXPseudoDestructor(move(LHS), OpLoc, OpKind, SS,
+ ObjectType);
+ break;
+ }
+
+ // Either the action has told is that this cannot be a
+ // pseudo-destructor expression (based on the type of base
+ // expression), or we didn't see a '~' in the right place. We
+ // can still parse a destructor name here, but in that case it
+ // names a real destructor.
UnqualifiedId Name;
if (ParseUnqualifiedId(SS,
/*EnteringContext=*/false,
@@ -1022,10 +1037,9 @@
return ExprError();
if (!LHS.isInvalid())
- LHS = Actions.ActOnMemberAccessExpr(CurScope, move(LHS), OpLoc, OpKind,
- SS, Name, ObjCImpDecl,
+ LHS = Actions.ActOnMemberAccessExpr(CurScope, move(LHS), OpLoc,
+ OpKind, SS, Name, ObjCImpDecl,
Tok.is(tok::l_paren));
-
break;
}
case tok::plusplus: // postfix-expression: postfix-expression '++'
diff --git a/lib/Parse/ParseExprCXX.cpp b/lib/Parse/ParseExprCXX.cpp
index a8fbaff..07de34d 100644
--- a/lib/Parse/ParseExprCXX.cpp
+++ b/lib/Parse/ParseExprCXX.cpp
@@ -45,14 +45,21 @@
/// \param EnteringContext whether we will be entering into the context of
/// the nested-name-specifier after parsing it.
///
-/// \param InMemberAccessExpr Whether this scope specifier is within a
+/// \param MayBePseudoDestructor When non-NULL, points to a flag that
+/// indicates whether this nested-name-specifier may be part of a
+/// pseudo-destructor name. In this case, the flag will be set false
+/// if we don't actually end up parsing a destructor name. Moreorover,
+/// if we do end up determining that we are parsing a destructor name,
+/// the last component of the nested-name-specifier is not parsed as
+/// part of the scope specifier.
+
/// member access expression, e.g., the \p T:: in \p p->T::m.
///
/// \returns true if a scope specifier was parsed.
bool Parser::ParseOptionalCXXScopeSpecifier(CXXScopeSpec &SS,
Action::TypeTy *ObjectType,
bool EnteringContext,
- bool InMemberAccessExpr) {
+ bool *MayBePseudoDestructor) {
assert(getLang().CPlusPlus &&
"Call sites of this function should be guarded by checking for C++");
@@ -79,6 +86,12 @@
HasScopeSpecifier = true;
}
+ bool CheckForDestructor = false;
+ if (MayBePseudoDestructor && *MayBePseudoDestructor) {
+ CheckForDestructor = true;
+ *MayBePseudoDestructor = false;
+ }
+
while (true) {
if (HasScopeSpecifier) {
// C++ [basic.lookup.classref]p5:
@@ -173,8 +186,11 @@
// convert it into a type within the nested-name-specifier.
TemplateIdAnnotation *TemplateId
= static_cast<TemplateIdAnnotation *>(Tok.getAnnotationValue());
- bool MayBePseudoDestructor
- = InMemberAccessExpr && GetLookAheadToken(2).is(tok::tilde);
+ if (CheckForDestructor && GetLookAheadToken(2).is(tok::tilde)) {
+ *MayBePseudoDestructor = true;
+ return HasScopeSpecifier;
+ }
+
if (TemplateId->Kind == TNK_Type_template ||
TemplateId->Kind == TNK_Dependent_template_name) {
AnnotateTemplateIdTokenAsType(&SS);
@@ -197,7 +213,7 @@
TypeToken.getAnnotationValue(),
TypeToken.getAnnotationRange(),
CCLoc,
- MayBePseudoDestructor));
+ false));
else
SS.setScopeRep(0);
SS.setEndLoc(CCLoc);
@@ -224,11 +240,13 @@
// If we get foo:bar, this is almost certainly a typo for foo::bar. Recover
// and emit a fixit hint for it.
if (Next.is(tok::colon) && !ColonIsSacred) {
- bool MayBePseudoDestructor
- = InMemberAccessExpr && GetLookAheadToken(2).is(tok::tilde);
-
+ if (CheckForDestructor && GetLookAheadToken(2).is(tok::tilde)) {
+ *MayBePseudoDestructor = true;
+ return HasScopeSpecifier;
+ }
+
if (Actions.IsInvalidUnlessNestedName(CurScope, SS, II,
- MayBePseudoDestructor, ObjectType,
+ false, ObjectType,
EnteringContext) &&
// If the token after the colon isn't an identifier, it's still an
// error, but they probably meant something else strange so don't
@@ -243,6 +261,11 @@
}
if (Next.is(tok::coloncolon)) {
+ if (CheckForDestructor && GetLookAheadToken(2).is(tok::tilde)) {
+ *MayBePseudoDestructor = true;
+ return HasScopeSpecifier;
+ }
+
// We have an identifier followed by a '::'. Lookup this name
// as the name in a nested-name-specifier.
SourceLocation IdLoc = ConsumeToken();
@@ -258,11 +281,9 @@
if (SS.isInvalid())
continue;
- bool MayBePseudoDestructor = InMemberAccessExpr && Tok.is(tok::tilde);
-
SS.setScopeRep(
Actions.ActOnCXXNestedNameSpecifier(CurScope, SS, IdLoc, CCLoc, II,
- MayBePseudoDestructor, ObjectType,
+ false, ObjectType,
EnteringContext));
SS.setEndLoc(CCLoc);
continue;
@@ -298,6 +319,12 @@
break;
}
+ // Even if we didn't see any pieces of a nested-name-specifier, we
+ // still check whether there is a tilde in this position, which
+ // indicates a potential pseudo-destructor.
+ if (CheckForDestructor && Tok.is(tok::tilde))
+ *MayBePseudoDestructor = true;
+
return HasScopeSpecifier;
}
@@ -493,6 +520,77 @@
return move(Result);
}
+/// \brief Parse a C++ pseudo-destructor expression after the base,
+/// . or -> operator, and nested-name-specifier have already been
+/// parsed.
+///
+/// postfix-expression: [C++ 5.2]
+/// postfix-expression . pseudo-destructor-name
+/// postfix-expression -> pseudo-destructor-name
+///
+/// pseudo-destructor-name:
+/// ::[opt] nested-name-specifier[opt] type-name :: ~type-name
+/// ::[opt] nested-name-specifier template simple-template-id ::
+/// ~type-name
+/// ::[opt] nested-name-specifier[opt] ~type-name
+///
+Parser::OwningExprResult
+Parser::ParseCXXPseudoDestructor(ExprArg Base, SourceLocation OpLoc,
+ tok::TokenKind OpKind,
+ CXXScopeSpec &SS,
+ Action::TypeTy *ObjectType) {
+ // We're parsing either a pseudo-destructor-name or a dependent
+ // member access that has the same form as a
+ // pseudo-destructor-name. We parse both in the same way and let
+ // the action model sort them out.
+ //
+ // Note that the ::[opt] nested-name-specifier[opt] has already
+ // been parsed, and if there was a simple-template-id, it has
+ // been coalesced into a template-id annotation token.
+ UnqualifiedId FirstTypeName;
+ SourceLocation CCLoc;
+ if (Tok.is(tok::identifier)) {
+ FirstTypeName.setIdentifier(Tok.getIdentifierInfo(), Tok.getLocation());
+ ConsumeToken();
+ assert(Tok.is(tok::coloncolon) &&"ParseOptionalCXXScopeSpecifier fail");
+ CCLoc = ConsumeToken();
+ } else if (Tok.is(tok::annot_template_id)) {
+ FirstTypeName.setTemplateId(
+ (TemplateIdAnnotation *)Tok.getAnnotationValue());
+ ConsumeToken();
+ assert(Tok.is(tok::coloncolon) &&"ParseOptionalCXXScopeSpecifier fail");
+ CCLoc = ConsumeToken();
+ } else {
+ FirstTypeName.setIdentifier(0, SourceLocation());
+ }
+
+ // Parse the tilde.
+ assert(Tok.is(tok::tilde) && "ParseOptionalCXXScopeSpecifier fail");
+ SourceLocation TildeLoc = ConsumeToken();
+ if (!Tok.is(tok::identifier)) {
+ Diag(Tok, diag::err_destructor_tilde_identifier);
+ return ExprError();
+ }
+
+ // Parse the second type.
+ UnqualifiedId SecondTypeName;
+ IdentifierInfo *Name = Tok.getIdentifierInfo();
+ SourceLocation NameLoc = ConsumeToken();
+ SecondTypeName.setIdentifier(Name, NameLoc);
+
+ // If there is a '<', the second type name is a template-id. Parse
+ // it as such.
+ if (Tok.is(tok::less) &&
+ ParseUnqualifiedIdTemplateId(SS, Name, NameLoc, false, ObjectType,
+ SecondTypeName, /*AssumeTemplateName=*/true))
+ return ExprError();
+
+ return Actions.ActOnPseudoDestructorExpr(CurScope, move(Base), OpLoc, OpKind,
+ SS, FirstTypeName, CCLoc,
+ TildeLoc, SecondTypeName,
+ Tok.is(tok::l_paren));
+}
+
/// ParseCXXBoolLiteral - This handles the C++ Boolean literals.
///
/// boolean-literal: [C++ 2.13.5]
@@ -818,13 +916,17 @@
/// that precedes the '<'. If template arguments were parsed successfully,
/// will be updated with the template-id.
///
+/// \param AssumeTemplateId When true, this routine will assume that the name
+/// refers to a template without performing name lookup to verify.
+///
/// \returns true if a parse error occurred, false otherwise.
bool Parser::ParseUnqualifiedIdTemplateId(CXXScopeSpec &SS,
IdentifierInfo *Name,
SourceLocation NameLoc,
bool EnteringContext,
TypeTy *ObjectType,
- UnqualifiedId &Id) {
+ UnqualifiedId &Id,
+ bool AssumeTemplateId) {
assert(Tok.is(tok::less) && "Expected '<' to finish parsing a template-id");
TemplateTy Template;
@@ -833,8 +935,16 @@
case UnqualifiedId::IK_Identifier:
case UnqualifiedId::IK_OperatorFunctionId:
case UnqualifiedId::IK_LiteralOperatorId:
- TNK = Actions.isTemplateName(CurScope, SS, Id, ObjectType, EnteringContext,
- Template);
+ if (AssumeTemplateId) {
+ Template = Actions.ActOnDependentTemplateName(SourceLocation(), SS,
+ Id, ObjectType,
+ EnteringContext);
+ TNK = TNK_Dependent_template_name;
+ if (!Template.get())
+ return true;
+ } else
+ TNK = Actions.isTemplateName(CurScope, SS, Id, ObjectType,
+ EnteringContext, Template);
break;
case UnqualifiedId::IK_ConstructorName: {
diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h
index 2190e3b..f09fc54 100644
--- a/lib/Sema/Sema.h
+++ b/lib/Sema/Sema.h
@@ -2175,7 +2175,18 @@
ExprArg Base,
SourceLocation OpLoc,
tok::TokenKind OpKind,
- TypeTy *&ObjectType);
+ TypeTy *&ObjectType,
+ bool &MayBePseudoDestructor);
+
+ virtual OwningExprResult ActOnPseudoDestructorExpr(Scope *S, ExprArg Base,
+ SourceLocation OpLoc,
+ tok::TokenKind OpKind,
+ const CXXScopeSpec &SS,
+ UnqualifiedId &FirstTypeName,
+ SourceLocation CCLoc,
+ SourceLocation TildeLoc,
+ UnqualifiedId &SecondTypeName,
+ bool HasTrailingLParen);
/// MaybeCreateCXXExprWithTemporaries - If the list of temporaries is
/// non-empty, will create a new CXXExprWithTemporaries expression.
diff --git a/lib/Sema/SemaExprCXX.cpp b/lib/Sema/SemaExprCXX.cpp
index 9d41f9d..1ea2056 100644
--- a/lib/Sema/SemaExprCXX.cpp
+++ b/lib/Sema/SemaExprCXX.cpp
@@ -21,6 +21,7 @@
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Parse/DeclSpec.h"
+#include "clang/Parse/Template.h"
#include "llvm/ADT/STLExtras.h"
using namespace clang;
@@ -2325,7 +2326,8 @@
Sema::OwningExprResult
Sema::ActOnStartCXXMemberReference(Scope *S, ExprArg Base, SourceLocation OpLoc,
- tok::TokenKind OpKind, TypeTy *&ObjectType) {
+ tok::TokenKind OpKind, TypeTy *&ObjectType,
+ bool &MayBePseudoDestructor) {
// Since this might be a postfix expression, get rid of ParenListExprs.
Base = MaybeConvertParenListExprToParenExpr(S, move(Base));
@@ -2333,6 +2335,7 @@
assert(BaseExpr && "no record expansion");
QualType BaseType = BaseExpr->getType();
+ MayBePseudoDestructor = false;
if (BaseType->isDependentType()) {
// If we have a pointer to a dependent type and are using the -> operator,
// the object type is the type that the pointer points to. We might still
@@ -2342,6 +2345,7 @@
BaseType = Ptr->getPointeeType();
ObjectType = BaseType.getAsOpaquePtr();
+ MayBePseudoDestructor = true;
return move(Base);
}
@@ -2383,7 +2387,11 @@
// [...] If the type of the object expression is of pointer to scalar
// type, the unqualified-id is looked up in the context of the complete
// postfix-expression.
+ //
+ // This also indicates that we should be parsing a
+ // pseudo-destructor-name.
ObjectType = 0;
+ MayBePseudoDestructor = true;
return move(Base);
}
@@ -2399,10 +2407,149 @@
// type C (or of pointer to a class type C), the unqualified-id is looked
// up in the scope of class C. [...]
ObjectType = BaseType.getAsOpaquePtr();
-
return move(Base);
}
+Sema::OwningExprResult Sema::ActOnPseudoDestructorExpr(Scope *S, ExprArg Base,
+ SourceLocation OpLoc,
+ tok::TokenKind OpKind,
+ const CXXScopeSpec &SS,
+ UnqualifiedId &FirstTypeName,
+ SourceLocation CCLoc,
+ SourceLocation TildeLoc,
+ UnqualifiedId &SecondTypeName,
+ bool HasTrailingLParen) {
+ assert((FirstTypeName.getKind() == UnqualifiedId::IK_TemplateId ||
+ FirstTypeName.getKind() == UnqualifiedId::IK_Identifier) &&
+ "Invalid first type name in pseudo-destructor");
+ assert((SecondTypeName.getKind() == UnqualifiedId::IK_TemplateId ||
+ SecondTypeName.getKind() == UnqualifiedId::IK_Identifier) &&
+ "Invalid second type name in pseudo-destructor");
+
+ Expr *BaseE = (Expr *)Base.get();
+ QualType ObjectType;
+ if (BaseE->isTypeDependent())
+ ObjectType = Context.DependentTy;
+
+ // The nested-name-specifier provided by the parser, then extended
+ // by the "type-name ::" in the pseudo-destructor-name, if present.
+ CXXScopeSpec ExtendedSS = SS;
+
+ if (FirstTypeName.getKind() == UnqualifiedId::IK_TemplateId ||
+ FirstTypeName.Identifier) {
+ // We have a pseudo-destructor with a "type-name ::".
+ // FIXME: As a temporary hack, we go ahead and resolve this to part of
+ // a nested-name-specifier.
+ if (FirstTypeName.getKind() == UnqualifiedId::IK_Identifier) {
+ // Resolve the identifier to a nested-name-specifier.
+ CXXScopeTy *FinalScope
+ = ActOnCXXNestedNameSpecifier(S, SS,
+ FirstTypeName.StartLocation,
+ CCLoc,
+ *FirstTypeName.Identifier,
+ true,
+ ObjectType.getAsOpaquePtr(),
+ false);
+ if (SS.getBeginLoc().isInvalid())
+ ExtendedSS.setBeginLoc(FirstTypeName.StartLocation);
+ ExtendedSS.setEndLoc(CCLoc);
+ ExtendedSS.setScopeRep(FinalScope);
+ } else {
+ // Resolve the template-id to a type, and that to a
+ // nested-name-specifier.
+ TemplateIdAnnotation *TemplateId = FirstTypeName.TemplateId;
+ ASTTemplateArgsPtr TemplateArgsPtr(*this,
+ TemplateId->getTemplateArgs(),
+ TemplateId->NumArgs);
+ TypeResult T = ActOnTemplateIdType(TemplateTy::make(TemplateId->Template),
+ TemplateId->TemplateNameLoc,
+ TemplateId->LAngleLoc,
+ TemplateArgsPtr,
+ TemplateId->RAngleLoc);
+ if (!T.isInvalid()) {
+ CXXScopeTy *FinalScope
+ = ActOnCXXNestedNameSpecifier(S, SS, T.get(),
+ SourceRange(TemplateId->TemplateNameLoc,
+ TemplateId->RAngleLoc),
+ CCLoc,
+ true);
+ if (SS.getBeginLoc().isInvalid())
+ ExtendedSS.setBeginLoc(TemplateId->TemplateNameLoc);
+ ExtendedSS.setEndLoc(CCLoc);
+ ExtendedSS.setScopeRep(FinalScope);
+ }
+ }
+ }
+
+ // Produce a destructor name based on the second type-name (which
+ // follows the tilde).
+ TypeTy *DestructedType;
+ SourceLocation EndLoc;
+ if (SecondTypeName.getKind() == UnqualifiedId::IK_Identifier) {
+ const CXXScopeSpec *LookupSS = &SS;
+ bool isDependent = isDependentScopeSpecifier(ExtendedSS);
+ if (isDependent || computeDeclContext(ExtendedSS))
+ LookupSS = &ExtendedSS;
+
+ DestructedType = getTypeName(*SecondTypeName.Identifier,
+ SecondTypeName.StartLocation,
+ S, LookupSS, true, ObjectType.getTypePtr());
+ if (!DestructedType && isDependent) {
+ // We didn't find our type, but that's okay: it's dependent
+ // anyway.
+ // FIXME: We should not be building a typename type here!
+ NestedNameSpecifier *NNS = 0;
+ SourceRange Range;
+ if (SS.isSet()) {
+ NNS = (NestedNameSpecifier *)ExtendedSS.getScopeRep();
+ Range = SourceRange(ExtendedSS.getRange().getBegin(),
+ SecondTypeName.StartLocation);
+ } else {
+ NNS = NestedNameSpecifier::Create(Context, SecondTypeName.Identifier);
+ Range = SourceRange(SecondTypeName.StartLocation);
+ }
+
+ DestructedType = CheckTypenameType(NNS, *SecondTypeName.Identifier,
+ Range).getAsOpaquePtr();
+ if (!DestructedType)
+ return ExprError();
+ }
+
+ if (!DestructedType) {
+ // FIXME: Crummy diagnostic.
+ Diag(SecondTypeName.StartLocation, diag::err_destructor_class_name);
+ return ExprError();
+ }
+
+ EndLoc = SecondTypeName.EndLocation;
+ } else {
+ // Resolve the template-id to a type, and that to a
+ // nested-name-specifier.
+ TemplateIdAnnotation *TemplateId = SecondTypeName.TemplateId;
+ ASTTemplateArgsPtr TemplateArgsPtr(*this,
+ TemplateId->getTemplateArgs(),
+ TemplateId->NumArgs);
+ EndLoc = TemplateId->RAngleLoc;
+ TypeResult T = ActOnTemplateIdType(TemplateTy::make(TemplateId->Template),
+ TemplateId->TemplateNameLoc,
+ TemplateId->LAngleLoc,
+ TemplateArgsPtr,
+ TemplateId->RAngleLoc);
+ if (T.isInvalid() || !T.get())
+ return ExprError();
+
+ DestructedType = T.get();
+ }
+
+ // Form a (possibly fake) destructor name and let the member access
+ // expression code deal with this.
+ // FIXME: Don't do this! It's totally broken!
+ UnqualifiedId Destructor;
+ Destructor.setDestructorName(TildeLoc, DestructedType, EndLoc);
+ return ActOnMemberAccessExpr(S, move(Base), OpLoc, OpKind, ExtendedSS,
+ Destructor, DeclPtrTy(), HasTrailingLParen);
+}
+
CXXMemberCallExpr *Sema::BuildCXXMemberCallExpr(Expr *Exp,
CXXMethodDecl *Method) {
if (PerformObjectArgumentInitialization(Exp, Method))
diff --git a/lib/Sema/TreeTransform.h b/lib/Sema/TreeTransform.h
index 11c19eb..40237d2 100644
--- a/lib/Sema/TreeTransform.h
+++ b/lib/Sema/TreeTransform.h
@@ -5041,10 +5041,12 @@
// Start the member reference and compute the object's type.
Sema::TypeTy *ObjectTy = 0;
+ bool MayBePseudoDestructor = false;
Base = SemaRef.ActOnStartCXXMemberReference(0, move(Base),
E->getOperatorLoc(),
E->isArrow()? tok::arrow : tok::period,
- ObjectTy);
+ ObjectTy,
+ MayBePseudoDestructor);
if (Base.isInvalid())
return SemaRef.ExprError();