From fe85cedd58df7daed29201703cfb8806e12876d0 Mon Sep 17 00:00:00 2001 From: Douglas Gregor Date: Thu, 6 Aug 2009 03:17:00 +0000 Subject: [PATCH] Support nested-name-specifiers for C++ member access expressions, e.g., this->Base::foo from James Porter! git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@78278 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Parse/Action.h | 24 ++++- lib/Frontend/PrintParserCallbacks.cpp | 3 +- lib/Parse/ParseExpr.cpp | 16 +++- lib/Sema/Sema.h | 25 +++++- lib/Sema/SemaCXXScopeSpec.cpp | 50 +++++++++++ lib/Sema/SemaExpr.cpp | 39 +++++++-- lib/Sema/SemaOverload.cpp | 32 +++---- lib/Sema/SemaTemplateInstantiateExpr.cpp | 12 ++- test/SemaCXX/qual-id-test.cpp | 106 +++++++++++++++++++++++ 9 files changed, 272 insertions(+), 35 deletions(-) create mode 100644 test/SemaCXX/qual-id-test.cpp diff --git a/include/clang/Parse/Action.h b/include/clang/Parse/Action.h index 23e02e4d67..0781a04777 100644 --- a/include/clang/Parse/Action.h +++ b/include/clang/Parse/Action.h @@ -236,6 +236,27 @@ public: return 0; } + /// ActOnCXXEnterMemberScope - Called when a C++ class member accessor ('.' + /// or '->') is parsed. After this method is called, according to + /// [C++ 3.4.5p4], qualified-ids should be looked up in the contexts of both + /// the entire postfix-expression and the scope of the class of the object + /// expression. + /// 'SS' should be an empty CXXScopeSpec to be filled with the class's scope. + virtual OwningExprResult ActOnCXXEnterMemberScope(Scope *S, + CXXScopeSpec &SS, + ExprArg Base, + tok::TokenKind OpKind) { + return ExprEmpty(); + } + + /// ActOnCXXExitMemberScope - Called when a postfix-expression that previously + /// invoked ActOnCXXEnterMemberScope() is finished. 'SS' is the same + /// CXXScopeSpec that was passed to ActOnCXXEnterMemberScope. Used to + /// indicate that names should revert to being looked up in the defining + /// scope. + virtual void ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS) { + } + /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global /// scope or nested-name-specifier) is parsed, part of a declarator-id. /// After this method is called, according to [C++ 3.4.3p3], names should be @@ -820,7 +841,8 @@ public: tok::TokenKind OpKind, SourceLocation MemberLoc, IdentifierInfo &Member, - DeclPtrTy ObjCImpDecl) { + DeclPtrTy ObjCImpDecl, + const CXXScopeSpec *SS = 0) { return ExprEmpty(); } diff --git a/lib/Frontend/PrintParserCallbacks.cpp b/lib/Frontend/PrintParserCallbacks.cpp index 4231d66ef8..43a5b6f20f 100644 --- a/lib/Frontend/PrintParserCallbacks.cpp +++ b/lib/Frontend/PrintParserCallbacks.cpp @@ -532,7 +532,8 @@ namespace { tok::TokenKind OpKind, SourceLocation MemberLoc, IdentifierInfo &Member, - DeclPtrTy ImplDecl) { + DeclPtrTy ImplDecl, + const CXXScopeSpec *SS=0) { Out << __FUNCTION__ << "\n"; return ExprEmpty(); } diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 4720bcb572..62bd9ae73c 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -919,6 +919,16 @@ Parser::ParsePostfixExpressionSuffix(OwningExprResult LHS) { tok::TokenKind OpKind = Tok.getKind(); SourceLocation OpLoc = ConsumeToken(); // Eat the "." or "->" token. + CXXScopeSpec MemberSS; + CXXScopeSpec SS; + if (getLang().CPlusPlus && !LHS.isInvalid()) { + LHS = Actions.ActOnCXXEnterMemberScope(CurScope, MemberSS, move(LHS), + OpKind); + if (LHS.isInvalid()) + break; + ParseOptionalCXXScopeSpecifier(SS); + } + if (Tok.isNot(tok::identifier)) { Diag(Tok, diag::err_expected_ident); return ExprError(); @@ -928,8 +938,12 @@ Parser::ParsePostfixExpressionSuffix(OwningExprResult LHS) { LHS = Actions.ActOnMemberReferenceExpr(CurScope, move(LHS), OpLoc, OpKind, Tok.getLocation(), *Tok.getIdentifierInfo(), - ObjCImpDecl); + ObjCImpDecl, &SS); } + + if (getLang().CPlusPlus) + Actions.ActOnCXXExitMemberScope(CurScope, MemberSS); + ConsumeToken(); break; } diff --git a/lib/Sema/Sema.h b/lib/Sema/Sema.h index 63ecdcb108..5093d71ac0 100644 --- a/lib/Sema/Sema.h +++ b/lib/Sema/Sema.h @@ -833,9 +833,8 @@ public: SourceLocation *CommaLocs, SourceLocation RParenLoc); - ExprResult BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc, - SourceLocation MemberLoc, - IdentifierInfo &Member); + OwningExprResult BuildOverloadedArrowExpr(Scope *S, ExprArg Base, + SourceLocation OpLoc); /// Helpers for dealing with blocks and functions. void CheckFallThroughForFunctionDef(Decl *D, Stmt *Body); @@ -1519,7 +1518,8 @@ public: tok::TokenKind OpKind, SourceLocation MemberLoc, IdentifierInfo &Member, - DeclPtrTy ImplDecl); + DeclPtrTy ImplDecl, + const CXXScopeSpec *SS = 0); virtual void ActOnDefaultCtorInitializers(DeclPtrTy CDtorDecl); bool ConvertArgumentsForCall(CallExpr *Call, Expr *Fn, FunctionDecl *FDecl, @@ -1882,6 +1882,23 @@ public: SourceRange TypeRange, SourceLocation CCLoc); + /// ActOnCXXEnterMemberScope - Called when a C++ class member accessor ('.' + /// or '->') is parsed. After this method is called, according to + /// [C++ 3.4.5p4], qualified-ids should be looked up in the contexts of both + /// the entire postfix-expression and the scope of the class of the object + /// expression. + /// 'SS' should be an empty CXXScopeSpec to be filled with the class's scope. + virtual OwningExprResult ActOnCXXEnterMemberScope(Scope *S, CXXScopeSpec &SS, + ExprArg Base, + tok::TokenKind OpKind); + + /// ActOnCXXExitMemberScope - Called when a postfix-expression that previously + /// invoked ActOnCXXEnterMemberScope() is finished. 'SS' is the same + /// CXXScopeSpec that was passed to ActOnCXXEnterMemberScope. Used to + /// indicate that names should revert to being looked up in the defining + /// scope. + virtual void ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS); + /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global /// scope or nested-name-specifier) is parsed, part of a declarator-id. /// After this method is called, according to [C++ 3.4.3p3], names should be diff --git a/lib/Sema/SemaCXXScopeSpec.cpp b/lib/Sema/SemaCXXScopeSpec.cpp index 27e0ccf7d5..e1cba36c82 100644 --- a/lib/Sema/SemaCXXScopeSpec.cpp +++ b/lib/Sema/SemaCXXScopeSpec.cpp @@ -14,6 +14,7 @@ #include "Sema.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclTemplate.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/Parse/DeclSpec.h" #include "llvm/ADT/STLExtras.h" @@ -337,6 +338,55 @@ Sema::CXXScopeTy *Sema::ActOnCXXNestedNameSpecifier(Scope *S, T.getTypePtr()); } +Action::OwningExprResult +Sema::ActOnCXXEnterMemberScope(Scope *S, CXXScopeSpec &SS, ExprArg Base, + tok::TokenKind OpKind) { + Expr *BaseExpr = (Expr*)Base.get(); + assert(BaseExpr && "no record expansion"); + + QualType BaseType = BaseExpr->getType(); + // FIXME: handle dependent types + if (BaseType->isDependentType()) + return move(Base); + + // C++ [over.match.oper]p8: + // [...] When operator->returns, the operator-> is applied to the value + // returned, with the original second operand. + if (OpKind == tok::arrow) { + while (BaseType->isRecordType()) { + Base = BuildOverloadedArrowExpr(S, move(Base), BaseExpr->getExprLoc()); + BaseExpr = (Expr*)Base.get(); + if (BaseExpr == NULL) + return ExprError(); + BaseType = BaseExpr->getType(); + } + } + + if (BaseType->isPointerType()) + BaseType = BaseType->getPointeeType(); + + // We could end up with various non-record types here, such as extended + // vector types or Objective-C interfaces. Just return early and let + // ActOnMemberReferenceExpr do the work. + if (!BaseType->isRecordType()) + return move(Base); + + SS.setRange(BaseExpr->getSourceRange()); + SS.setScopeRep( + NestedNameSpecifier::Create(Context, 0, false, BaseType.getTypePtr()) + ); + + if (S) + ActOnCXXEnterDeclaratorScope(S,SS); + return move(Base); +} + +void Sema::ActOnCXXExitMemberScope(Scope *S, const CXXScopeSpec &SS) { + if (S && SS.isSet()) + ActOnCXXExitDeclaratorScope(S,SS); +} + + /// ActOnCXXEnterDeclaratorScope - Called when a C++ scope specifier (global /// scope or nested-name-specifier) is parsed, part of a declarator-id. /// After this method is called, according to [C++ 3.4.3p3], names should be diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index ffc6d13bb4..76296c690c 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -2103,7 +2103,11 @@ Action::OwningExprResult Sema::ActOnMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc, tok::TokenKind OpKind, SourceLocation MemberLoc, IdentifierInfo &Member, - DeclPtrTy ObjCImpDecl) { + DeclPtrTy ObjCImpDecl, const CXXScopeSpec *SS) { + // FIXME: handle the CXXScopeSpec for proper lookup of qualified-ids + if (SS && SS->isInvalid()) + return ExprError(); + Expr *BaseExpr = Base.takeAs(); assert(BaseExpr && "no record expression"); @@ -2126,9 +2130,6 @@ Sema::ActOnMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc, BaseType = PT->getPointeeType(); else if (BaseType->isObjCObjectPointerType()) ; - else if (getLangOptions().CPlusPlus && BaseType->isRecordType()) - return Owned(BuildOverloadedArrowExpr(S, BaseExpr, OpLoc, - MemberLoc, Member)); else return ExprError(Diag(MemberLoc, diag::err_typecheck_member_reference_arrow) @@ -2164,12 +2165,38 @@ Sema::ActOnMemberReferenceExpr(Scope *S, ExprArg Base, SourceLocation OpLoc, BaseExpr->getSourceRange())) return ExprError(); + DeclContext *DC = RDecl; + if (SS && SS->isSet()) { + // If the member name was a qualified-id, look into the + // nested-name-specifier. + DC = computeDeclContext(*SS, false); + + // FIXME: If DC is not computable, we should build a + // CXXUnresolvedMemberExpr. + assert(DC && "Cannot handle non-computable dependent contexts in lookup"); + } + // The record definition is complete, now make sure the member is valid. - // FIXME: Qualified name lookup for C++ is a bit more complicated than this. LookupResult Result - = LookupQualifiedName(RDecl, DeclarationName(&Member), + = LookupQualifiedName(DC, DeclarationName(&Member), LookupMemberName, false); + if (SS && SS->isSet()) + { + QualType BaseTypeCanon + = Context.getCanonicalType(BaseType).getUnqualifiedType(); + QualType MemberTypeCanon + = Context.getCanonicalType( + Context.getTypeDeclType( + dyn_cast(Result.getAsDecl()->getDeclContext()))); + + if (BaseTypeCanon != MemberTypeCanon && + !IsDerivedFrom(BaseTypeCanon, MemberTypeCanon)) + return ExprError(Diag(SS->getBeginLoc(), + diag::err_not_direct_base_or_virtual) + << MemberTypeCanon << BaseTypeCanon); + } + if (!Result) return ExprError(Diag(MemberLoc, diag::err_typecheck_no_member) << &Member << BaseExpr->getSourceRange()); diff --git a/lib/Sema/SemaOverload.cpp b/lib/Sema/SemaOverload.cpp index 8dd84c0b5d..c28e052ae6 100644 --- a/lib/Sema/SemaOverload.cpp +++ b/lib/Sema/SemaOverload.cpp @@ -4553,10 +4553,9 @@ Sema::BuildCallToObjectOfClassType(Scope *S, Expr *Object, /// BuildOverloadedArrowExpr - Build a call to an overloaded @c operator-> /// (if one exists), where @c Base is an expression of class type and /// @c Member is the name of the member we're trying to find. -Action::ExprResult -Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc, - SourceLocation MemberLoc, - IdentifierInfo &Member) { +Sema::OwningExprResult +Sema::BuildOverloadedArrowExpr(Scope *S, ExprArg BaseIn, SourceLocation OpLoc) { + Expr *Base = static_cast(BaseIn.get()); assert(Base->getType()->isRecordType() && "left-hand side must have class type"); // C++ [over.ref]p1: @@ -4569,15 +4568,13 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc, DeclarationName OpName = Context.DeclarationNames.getCXXOperatorName(OO_Arrow); OverloadCandidateSet CandidateSet; const RecordType *BaseRecord = Base->getType()->getAs(); - + DeclContext::lookup_const_iterator Oper, OperEnd; for (llvm::tie(Oper, OperEnd) = BaseRecord->getDecl()->lookup(OpName); Oper != OperEnd; ++Oper) AddMethodCandidate(cast(*Oper), Base, 0, 0, CandidateSet, /*SuppressUserConversions=*/false); - ExprOwningPtr BasePtr(this, Base); - // Perform overload resolution. OverloadCandidateSet::iterator Best; switch (BestViableFunction(CandidateSet, OpLoc, Best)) { @@ -4588,34 +4585,34 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc, case OR_No_Viable_Function: if (CandidateSet.empty()) Diag(OpLoc, diag::err_typecheck_member_reference_arrow) - << BasePtr->getType() << BasePtr->getSourceRange(); + << Base->getType() << Base->getSourceRange(); else Diag(OpLoc, diag::err_ovl_no_viable_oper) - << "operator->" << BasePtr->getSourceRange(); + << "operator->" << Base->getSourceRange(); PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/false); - return true; + return ExprError(); case OR_Ambiguous: Diag(OpLoc, diag::err_ovl_ambiguous_oper) - << "operator->" << BasePtr->getSourceRange(); + << "operator->" << Base->getSourceRange(); PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true); - return true; + return ExprError(); case OR_Deleted: Diag(OpLoc, diag::err_ovl_deleted_oper) << Best->Function->isDeleted() - << "operator->" << BasePtr->getSourceRange(); + << "operator->" << Base->getSourceRange(); PrintOverloadCandidates(CandidateSet, /*OnlyViable=*/true); - return true; + return ExprError(); } // Convert the object parameter. CXXMethodDecl *Method = cast(Best->Function); if (PerformObjectArgumentInitialization(Base, Method)) - return true; + return ExprError(); // No concerns about early exits now. - BasePtr.take(); + BaseIn.release(); // Build the operator call. Expr *FnExpr = new (Context) DeclRefExpr(Method, Method->getType(), @@ -4624,8 +4621,7 @@ Sema::BuildOverloadedArrowExpr(Scope *S, Expr *Base, SourceLocation OpLoc, Base = new (Context) CXXOperatorCallExpr(Context, OO_Arrow, FnExpr, &Base, 1, Method->getResultType().getNonReferenceType(), OpLoc); - return ActOnMemberReferenceExpr(S, ExprArg(*this, Base), OpLoc, tok::arrow, - MemberLoc, Member, DeclPtrTy()).release(); + return Owned(Base); } /// FixOverloadedFunctionReference - E is an expression that refers to diff --git a/lib/Sema/SemaTemplateInstantiateExpr.cpp b/lib/Sema/SemaTemplateInstantiateExpr.cpp index 3169998fb3..2e7ed1a632 100644 --- a/lib/Sema/SemaTemplateInstantiateExpr.cpp +++ b/lib/Sema/SemaTemplateInstantiateExpr.cpp @@ -1270,14 +1270,18 @@ TemplateExprInstantiator::VisitCXXUnresolvedMemberExpr( if (Base.isInvalid()) return SemaRef.ExprError(); + tok::TokenKind OpKind = E->isArrow() ? tok::arrow : tok::period; + CXXScopeSpec SS; + Base = SemaRef.ActOnCXXEnterMemberScope(0, SS, move(Base), OpKind); // FIXME: Instantiate the declaration name. - return SemaRef.ActOnMemberReferenceExpr(/*Scope=*/0, + Base = SemaRef.ActOnMemberReferenceExpr(/*Scope=*/0, move(Base), E->getOperatorLoc(), - E->isArrow()? tok::arrow - : tok::period, + OpKind, E->getMemberLoc(), /*FIXME:*/*E->getMember().getAsIdentifierInfo(), - /*FIXME?*/Sema::DeclPtrTy::make((Decl*)0)); + /*FIXME?*/Sema::DeclPtrTy::make((Decl*)0)); + SemaRef.ActOnCXXExitMemberScope(0, SS); + return move(Base); } //---------------------------------------------------------------------------- diff --git a/test/SemaCXX/qual-id-test.cpp b/test/SemaCXX/qual-id-test.cpp new file mode 100644 index 0000000000..ad013990ca --- /dev/null +++ b/test/SemaCXX/qual-id-test.cpp @@ -0,0 +1,106 @@ +// RUN: clang-cc -fsyntax-only -verify %s +namespace A +{ + namespace B + { + struct base + { + void x() {} + void y() {} + }; + } + + struct member + { + void foo(); + }; + + struct middleman + { + member * operator->() { return 0; } + }; + + struct sub : B::base + { + void x() {} + middleman operator->() { return middleman(); } + }; +} + +struct bad +{ + int x(); +}; + +namespace C +{ + void fun() + { + A::sub a; + + a.x(); + + a.sub::x(); + a.base::x(); + + a.B::base::x(); // expected-error{{use of undeclared identifier 'B'}} + + a.A::sub::x(); + a.A::B::base::x(); + + a.bad::x(); // expected-error{{type 'struct bad' is not a direct or virtual base of ''struct A::sub''}} + + a->foo(); + a->member::foo(); + a->A::member::foo(); + } + + void fun2() + { + A::sub *a; + + a->x(); + + a->sub::x(); + a->base::x(); + + a->B::base::x(); // expected-error{{use of undeclared identifier 'B'}} + + a->A::sub::x(); + a->A::B::base::x(); + + a->bad::x(); // expected-error{{type 'struct bad' is not a direct or virtual base of ''struct A::sub''}} + + (*a)->foo(); + (*a)->member::foo(); + (*a)->A::member::foo(); + } + + void fun3() + { + int i; + i.foo(); // expected-error{{member reference base type 'int' is not a structure or union}} + } + + template + void fun4() + { + T a; + a.x(); + a->foo(); + + // Things that work for the wrong reason + a.A::sub::x(); + a.A::B::base::x(); + a->A::member::foo(); + + // Things that work, but shouldn't + a.bad::x(); + + // Things that fail, but shouldn't + a.sub::x(); // expected-error{{use of undeclared identifier 'sub'}} + a.base::x(); // expected-error{{use of undeclared identifier 'base'}} + a.B::base::x(); // expected-error{{use of undeclared identifier 'B'}} + a->member::foo(); // expected-error{{use of undeclared identifier 'member'}} + } +} -- 2.40.0