From: Ivan Donchevskii Date: Fri, 25 May 2018 12:56:26 +0000 (+0000) Subject: Optionally add code completion results for arrow instead of dot X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=ea1c0a4ef3f3f320ab29b3b40b8a821c52a7a438;p=clang Optionally add code completion results for arrow instead of dot Currently getting such completions requires source correction, reparsing and calling completion again. And if it shows no results and rollback is required then it costs one more reparse. With this change it's possible to get all results which can be later filtered to split changes which require correction. Differential Revision: https://reviews.llvm.org/D41537 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@333272 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Driver/CC1Options.td b/include/clang/Driver/CC1Options.td index 7844630457..5d56b4264b 100644 --- a/include/clang/Driver/CC1Options.td +++ b/include/clang/Driver/CC1Options.td @@ -445,6 +445,8 @@ def no_code_completion_ns_level_decls : Flag<["-"], "no-code-completion-ns-level HelpText<"Do not include declarations inside namespaces (incl. global namespace) in the code-completion results.">; def code_completion_brief_comments : Flag<["-"], "code-completion-brief-comments">, HelpText<"Include brief documentation comments in code-completion results.">; +def code_completion_with_fixits : Flag<["-"], "code-completion-with-fixits">, + HelpText<"Include code completion results which require small fix-its.">; def disable_free : Flag<["-"], "disable-free">, HelpText<"Disable freeing of memory on exit">; def discard_value_names : Flag<["-"], "discard-value-names">, diff --git a/include/clang/Sema/CodeCompleteConsumer.h b/include/clang/Sema/CodeCompleteConsumer.h index 4780532fef..b71a3582f5 100644 --- a/include/clang/Sema/CodeCompleteConsumer.h +++ b/include/clang/Sema/CodeCompleteConsumer.h @@ -783,6 +783,33 @@ public: /// The availability of this result. CXAvailabilityKind Availability = CXAvailability_Available; + /// FixIts that *must* be applied before inserting the text for the + /// corresponding completion item. + /// + /// Completion items with non-empty fixits will not be returned by default, + /// they should be explicitly requested by setting + /// CompletionOptions::IncludeFixIts. For the editors to be able to + /// compute position of the cursor for the completion item itself, the + /// following conditions are guaranteed to hold for RemoveRange of the stored + /// fixits: + /// - Ranges in the fixits are guaranteed to never contain the completion + /// point (or identifier under completion point, if any) inside them, except + /// at the start or at the end of the range. + /// - If a fixit range starts or ends with completion point (or starts or + /// ends after the identifier under completion point), it will contain at + /// least one character. It allows to unambiguously recompute completion + /// point after applying the fixit. + /// The intuition is that provided fixits change code around the identifier we + /// complete, but are not allowed to touch the identifier itself or the + /// completion point. One example of completion items with corrections are the + /// ones replacing '.' with '->' and vice versa: + /// std::unique_ptr> vec_ptr; + /// In 'vec_ptr.^', one of completion items is 'push_back', it requires + /// replacing '.' with '->'. + /// In 'vec_ptr->^', one of completion items is 'release', it requires + /// replacing '->' with '.'. + std::vector FixIts; + /// Whether this result is hidden by another name. bool Hidden : 1; @@ -807,15 +834,17 @@ public: NestedNameSpecifier *Qualifier = nullptr; /// Build a result that refers to a declaration. - CodeCompletionResult(const NamedDecl *Declaration, - unsigned Priority, + CodeCompletionResult(const NamedDecl *Declaration, unsigned Priority, NestedNameSpecifier *Qualifier = nullptr, bool QualifierIsInformative = false, - bool Accessible = true) + bool Accessible = true, + std::vector FixIts = std::vector()) : Declaration(Declaration), Priority(Priority), Kind(RK_Declaration), Hidden(false), QualifierIsInformative(QualifierIsInformative), StartsNestedNameSpecifier(false), AllParametersAreInformative(false), - DeclaringEntity(false), Qualifier(Qualifier) { + DeclaringEntity(false), Qualifier(Qualifier), + FixIts(std::move(FixIts)) { + //FIXME: Add assert to check FixIts range requirements. computeCursorKindAndAvailability(Accessible); } @@ -1027,6 +1056,10 @@ public: return CodeCompleteOpts.IncludeBriefComments; } + /// Whether to include completion items with small fix-its, e.g. change + /// '.' to '->' on member access, etc. + bool includeFixIts() const { return CodeCompleteOpts.IncludeFixIts; } + /// Hint whether to load data from the external AST in order to provide /// full results. If false, declarations from the preamble may be omitted. bool loadExternal() const { diff --git a/include/clang/Sema/CodeCompleteOptions.h b/include/clang/Sema/CodeCompleteOptions.h index bdd4732fd7..1d3bbb4e58 100644 --- a/include/clang/Sema/CodeCompleteOptions.h +++ b/include/clang/Sema/CodeCompleteOptions.h @@ -39,10 +39,14 @@ public: /// If false, namespace-level declarations from the preamble may be omitted. unsigned LoadExternal : 1; + /// Include results after corrections (small fix-its), e.g. change '.' to '->' + /// on member access, etc. + unsigned IncludeFixIts : 1; + CodeCompleteOptions() : IncludeMacros(0), IncludeCodePatterns(0), IncludeGlobals(1), IncludeNamespaceLevelDecls(1), IncludeBriefComments(0), - LoadExternal(1) {} + LoadExternal(1), IncludeFixIts(0) {} }; } // namespace clang diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index bc393e91ea..06ecb57bc1 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -10231,7 +10231,7 @@ public: struct CodeCompleteExpressionData; void CodeCompleteExpression(Scope *S, const CodeCompleteExpressionData &Data); - void CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, + void CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, Expr *OtherOpBase, SourceLocation OpLoc, bool IsArrow, bool IsBaseExprStatement); void CodeCompletePostfixExpression(Scope *S, ExprResult LHS); diff --git a/lib/Frontend/ASTUnit.cpp b/lib/Frontend/ASTUnit.cpp index 2214d82745..e0e8e8b22a 100644 --- a/lib/Frontend/ASTUnit.cpp +++ b/lib/Frontend/ASTUnit.cpp @@ -2122,6 +2122,7 @@ void ASTUnit::CodeComplete( CodeCompleteOpts.IncludeGlobals = CachedCompletionResults.empty(); CodeCompleteOpts.IncludeBriefComments = IncludeBriefComments; CodeCompleteOpts.LoadExternal = Consumer.loadExternal(); + CodeCompleteOpts.IncludeFixIts = Consumer.includeFixIts(); assert(IncludeBriefComments == this->IncludeBriefCommentsInCodeCompletion); diff --git a/lib/Frontend/CompilerInvocation.cpp b/lib/Frontend/CompilerInvocation.cpp index 45822a0716..29115c2f6d 100644 --- a/lib/Frontend/CompilerInvocation.cpp +++ b/lib/Frontend/CompilerInvocation.cpp @@ -1536,6 +1536,8 @@ static InputKind ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, = !Args.hasArg(OPT_no_code_completion_ns_level_decls); Opts.CodeCompleteOpts.IncludeBriefComments = Args.hasArg(OPT_code_completion_brief_comments); + Opts.CodeCompleteOpts.IncludeFixIts + = Args.hasArg(OPT_code_completion_with_fixits); Opts.OverrideRecordLayoutsFile = Args.getLastArgValue(OPT_foverride_record_layout_EQ); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index ec0af38c5b..8d51bc82d3 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -1703,8 +1703,10 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) { CXXScopeSpec SS; ParsedType ObjectType; bool MayBePseudoDestructor = false; + Expr* OrigLHS = !LHS.isInvalid() ? LHS.get() : nullptr; + if (getLangOpts().CPlusPlus && !LHS.isInvalid()) { - Expr *Base = LHS.get(); + Expr *Base = OrigLHS; const Type* BaseType = Base->getType().getTypePtrOrNull(); if (BaseType && Tok.is(tok::l_paren) && (BaseType->isFunctionType() || @@ -1729,11 +1731,25 @@ Parser::ParsePostfixExpressionSuffix(ExprResult LHS) { } if (Tok.is(tok::code_completion)) { + tok::TokenKind CorrectedOpKind = + OpKind == tok::arrow ? tok::period : tok::arrow; + ExprResult CorrectedLHS(/*IsInvalid=*/true); + if (getLangOpts().CPlusPlus && OrigLHS) { + const bool DiagsAreSuppressed = Diags.getSuppressAllDiagnostics(); + Diags.setSuppressAllDiagnostics(true); + CorrectedLHS = Actions.ActOnStartCXXMemberReference( + getCurScope(), OrigLHS, OpLoc, CorrectedOpKind, ObjectType, + MayBePseudoDestructor); + Diags.setSuppressAllDiagnostics(DiagsAreSuppressed); + } + + Expr *Base = LHS.get(); + Expr *CorrectedBase = CorrectedLHS.get(); + // Code completion for a member access expression. - if (Expr *Base = LHS.get()) - Actions.CodeCompleteMemberReferenceExpr( - getCurScope(), Base, OpLoc, OpKind == tok::arrow, - ExprStatementTokLoc == Base->getLocStart()); + Actions.CodeCompleteMemberReferenceExpr( + getCurScope(), Base, CorrectedBase, OpLoc, OpKind == tok::arrow, + Base && ExprStatementTokLoc == Base->getLocStart()); cutOffParsing(); return ExprError(); diff --git a/lib/Sema/CodeCompleteConsumer.cpp b/lib/Sema/CodeCompleteConsumer.cpp index 70b3189756..9c4d315a69 100644 --- a/lib/Sema/CodeCompleteConsumer.cpp +++ b/lib/Sema/CodeCompleteConsumer.cpp @@ -554,6 +554,24 @@ PrintingCodeCompleteConsumer::ProcessCodeCompleteResults(Sema &SemaRef, if (const char *BriefComment = CCS->getBriefComment()) OS << " : " << BriefComment; } + for (const FixItHint &FixIt : Results[I].FixIts) { + const SourceLocation BLoc = FixIt.RemoveRange.getBegin(); + const SourceLocation ELoc = FixIt.RemoveRange.getEnd(); + + SourceManager &SM = SemaRef.SourceMgr; + std::pair BInfo = SM.getDecomposedLoc(BLoc); + std::pair EInfo = SM.getDecomposedLoc(ELoc); + // Adjust for token ranges. + if (FixIt.RemoveRange.isTokenRange()) + EInfo.second += Lexer::MeasureTokenLength(ELoc, SM, SemaRef.LangOpts); + + OS << " (requires fix-it:" + << " {" << SM.getLineNumber(BInfo.first, BInfo.second) << ':' + << SM.getColumnNumber(BInfo.first, BInfo.second) << '-' + << SM.getLineNumber(EInfo.first, EInfo.second) << ':' + << SM.getColumnNumber(EInfo.first, EInfo.second) << "}" + << " to \"" << FixIt.CodeToInsert << "\")"; + } OS << '\n'; break; diff --git a/lib/Sema/SemaCodeComplete.cpp b/lib/Sema/SemaCodeComplete.cpp index f08159b79f..5bc428a04a 100644 --- a/lib/Sema/SemaCodeComplete.cpp +++ b/lib/Sema/SemaCodeComplete.cpp @@ -1291,10 +1291,13 @@ namespace { class CodeCompletionDeclConsumer : public VisibleDeclConsumer { ResultBuilder &Results; DeclContext *CurContext; + std::vector FixIts; public: - CodeCompletionDeclConsumer(ResultBuilder &Results, DeclContext *CurContext) - : Results(Results), CurContext(CurContext) { } + CodeCompletionDeclConsumer( + ResultBuilder &Results, DeclContext *CurContext, + std::vector FixIts = std::vector()) + : Results(Results), CurContext(CurContext), FixIts(std::move(FixIts)) {} void FoundDecl(NamedDecl *ND, NamedDecl *Hiding, DeclContext *Ctx, bool InBaseClass) override { @@ -1303,7 +1306,7 @@ namespace { Accessible = Results.getSema().IsSimplyAccessible(ND, Ctx); ResultBuilder::Result Result(ND, Results.getBasePriority(ND), nullptr, - false, Accessible); + false, Accessible, FixIts); Results.AddResult(Result, CurContext, Hiding, InBaseClass); } @@ -3979,14 +3982,18 @@ static void AddObjCProperties( static void AddRecordMembersCompletionResults(Sema &SemaRef, ResultBuilder &Results, Scope *S, QualType BaseType, - RecordDecl *RD) { + RecordDecl *RD, + Optional AccessOpFixIt) { // Indicate that we are performing a member access, and the cv-qualifiers // for the base object type. Results.setObjectTypeQualifiers(BaseType.getQualifiers()); // Access to a C/C++ class, struct, or union. Results.allowNestedNameSpecifiers(); - CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext); + std::vector FixIts; + if (AccessOpFixIt) + FixIts.emplace_back(AccessOpFixIt.getValue()); + CodeCompletionDeclConsumer Consumer(Results, SemaRef.CurContext, std::move(FixIts)); SemaRef.LookupVisibleDecls(RD, Sema::LookupMemberName, Consumer, SemaRef.CodeCompleter->includeGlobals(), /*IncludeDependentBases=*/true, @@ -4013,107 +4020,138 @@ static void AddRecordMembersCompletionResults(Sema &SemaRef, } void Sema::CodeCompleteMemberReferenceExpr(Scope *S, Expr *Base, + Expr *OtherOpBase, SourceLocation OpLoc, bool IsArrow, bool IsBaseExprStatement) { if (!Base || !CodeCompleter) return; - + ExprResult ConvertedBase = PerformMemberExprBaseConversion(Base, IsArrow); if (ConvertedBase.isInvalid()) return; - Base = ConvertedBase.get(); - - QualType BaseType = Base->getType(); + QualType ConvertedBaseType = ConvertedBase.get()->getType(); + + enum CodeCompletionContext::Kind contextKind; if (IsArrow) { - if (const PointerType *Ptr = BaseType->getAs()) - BaseType = Ptr->getPointeeType(); - else if (BaseType->isObjCObjectPointerType()) - /*Do nothing*/ ; - else - return; + if (const PointerType *Ptr = ConvertedBaseType->getAs()) + ConvertedBaseType = Ptr->getPointeeType(); } - - enum CodeCompletionContext::Kind contextKind; - + if (IsArrow) { contextKind = CodeCompletionContext::CCC_ArrowMemberAccess; - } - else { - if (BaseType->isObjCObjectPointerType() || - BaseType->isObjCObjectOrInterfaceType()) { + } else { + if (ConvertedBaseType->isObjCObjectPointerType() || + ConvertedBaseType->isObjCObjectOrInterfaceType()) { contextKind = CodeCompletionContext::CCC_ObjCPropertyAccess; - } - else { + } else { contextKind = CodeCompletionContext::CCC_DotMemberAccess; } } - CodeCompletionContext CCContext(contextKind, BaseType); + CodeCompletionContext CCContext(contextKind, ConvertedBaseType); ResultBuilder Results(*this, CodeCompleter->getAllocator(), - CodeCompleter->getCodeCompletionTUInfo(), - CCContext, + CodeCompleter->getCodeCompletionTUInfo(), CCContext, &ResultBuilder::IsMember); - Results.EnterNewScope(); - if (const RecordType *Record = BaseType->getAs()) { - AddRecordMembersCompletionResults(*this, Results, S, BaseType, - Record->getDecl()); - } else if (const auto *TST = BaseType->getAs()) { - TemplateName TN = TST->getTemplateName(); - if (const auto *TD = - dyn_cast_or_null(TN.getAsTemplateDecl())) { - CXXRecordDecl *RD = TD->getTemplatedDecl(); - AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD); - } - } else if (const auto *ICNT = BaseType->getAs()) { - if (auto *RD = ICNT->getDecl()) - AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD); - } else if (!IsArrow && BaseType->isObjCObjectPointerType()) { - // Objective-C property reference. - AddedPropertiesSet AddedProperties; - - if (const ObjCObjectPointerType *ObjCPtr = - BaseType->getAsObjCInterfacePointerType()) { - // Add property results based on our interface. - assert(ObjCPtr && "Non-NULL pointer guaranteed above!"); - AddObjCProperties(CCContext, ObjCPtr->getInterfaceDecl(), true, - /*AllowNullaryMethods=*/true, CurContext, - AddedProperties, Results, IsBaseExprStatement); - } - - // Add properties from the protocols in a qualified interface. - for (auto *I : BaseType->getAs()->quals()) - AddObjCProperties(CCContext, I, true, /*AllowNullaryMethods=*/true, - CurContext, AddedProperties, Results, - IsBaseExprStatement); - } else if ((IsArrow && BaseType->isObjCObjectPointerType()) || - (!IsArrow && BaseType->isObjCObjectType())) { - // Objective-C instance variable access. - ObjCInterfaceDecl *Class = nullptr; - if (const ObjCObjectPointerType *ObjCPtr - = BaseType->getAs()) - Class = ObjCPtr->getInterfaceDecl(); - else - Class = BaseType->getAs()->getInterface(); - - // Add all ivars from this class and its superclasses. - if (Class) { - CodeCompletionDeclConsumer Consumer(Results, CurContext); - Results.setFilter(&ResultBuilder::IsObjCIvar); - LookupVisibleDecls( - Class, LookupMemberName, Consumer, CodeCompleter->includeGlobals(), - /*IncludeDependentBases=*/false, CodeCompleter->loadExternal()); + + auto DoCompletion = [&](Expr *Base, bool IsArrow, Optional AccessOpFixIt) -> bool { + if (!Base) + return false; + + ExprResult ConvertedBase = PerformMemberExprBaseConversion(Base, IsArrow); + if (ConvertedBase.isInvalid()) + return false; + Base = ConvertedBase.get(); + + QualType BaseType = Base->getType(); + + if (IsArrow) { + if (const PointerType *Ptr = BaseType->getAs()) + BaseType = Ptr->getPointeeType(); + else if (BaseType->isObjCObjectPointerType()) + /*Do nothing*/; + else + return false; + } + + if (const RecordType *Record = BaseType->getAs()) { + AddRecordMembersCompletionResults(*this, Results, S, BaseType, + Record->getDecl(), + std::move(AccessOpFixIt)); + } else if (const auto *TST = + BaseType->getAs()) { + TemplateName TN = TST->getTemplateName(); + if (const auto *TD = + dyn_cast_or_null(TN.getAsTemplateDecl())) { + CXXRecordDecl *RD = TD->getTemplatedDecl(); + AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD, + std::move(AccessOpFixIt)); + } + } else if (const auto *ICNT = BaseType->getAs()) { + if (auto *RD = ICNT->getDecl()) + AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD, + std::move(AccessOpFixIt)); + } else if (!IsArrow && BaseType->isObjCObjectPointerType()) { + // Objective-C property reference. + AddedPropertiesSet AddedProperties; + + if (const ObjCObjectPointerType *ObjCPtr = + BaseType->getAsObjCInterfacePointerType()) { + // Add property results based on our interface. + assert(ObjCPtr && "Non-NULL pointer guaranteed above!"); + AddObjCProperties(CCContext, ObjCPtr->getInterfaceDecl(), true, + /*AllowNullaryMethods=*/true, CurContext, + AddedProperties, Results, IsBaseExprStatement); + } + + // Add properties from the protocols in a qualified interface. + for (auto *I : BaseType->getAs()->quals()) + AddObjCProperties(CCContext, I, true, /*AllowNullaryMethods=*/true, + CurContext, AddedProperties, Results, + IsBaseExprStatement); + } else if ((IsArrow && BaseType->isObjCObjectPointerType()) || + (!IsArrow && BaseType->isObjCObjectType())) { + // Objective-C instance variable access. + ObjCInterfaceDecl *Class = nullptr; + if (const ObjCObjectPointerType *ObjCPtr = + BaseType->getAs()) + Class = ObjCPtr->getInterfaceDecl(); + else + Class = BaseType->getAs()->getInterface(); + + // Add all ivars from this class and its superclasses. + if (Class) { + CodeCompletionDeclConsumer Consumer(Results, CurContext); + Results.setFilter(&ResultBuilder::IsObjCIvar); + LookupVisibleDecls( + Class, LookupMemberName, Consumer, CodeCompleter->includeGlobals(), + /*IncludeDependentBases=*/false, CodeCompleter->loadExternal()); + } } + + // FIXME: How do we cope with isa? + return true; + }; + + Results.EnterNewScope(); + + bool CompletionSucceded = DoCompletion(Base, IsArrow, None); + if (CodeCompleter->includeFixIts()) { + const CharSourceRange OpRange = + CharSourceRange::getTokenRange(OpLoc, OpLoc); + CompletionSucceded |= DoCompletion( + OtherOpBase, !IsArrow, + FixItHint::CreateReplacement(OpRange, IsArrow ? "." : "->")); } - - // FIXME: How do we cope with isa? - + Results.ExitScope(); + if (!CompletionSucceded) + return; + // Hand off the results found for code completion. - HandleCodeCompleteResults(this, CodeCompleter, - Results.getCompletionContext(), - Results.data(),Results.size()); + HandleCodeCompleteResults(this, CodeCompleter, Results.getCompletionContext(), + Results.data(), Results.size()); } void Sema::CodeCompleteObjCClassPropertyRefExpr(Scope *S, diff --git a/test/CodeCompletion/member-access.cpp b/test/CodeCompletion/member-access.cpp index c21265ec11..008e223716 100644 --- a/test/CodeCompletion/member-access.cpp +++ b/test/CodeCompletion/member-access.cpp @@ -166,3 +166,47 @@ void dependentColonColonCompletion() { typename Template::Nested m; // RUN: %clang_cc1 -fsyntax-only -code-completion-at=%s:166:25 %s -o - | FileCheck -check-prefix=CHECK-CC7 %s } + +class Proxy2 { +public: + Derived *operator->() const; + int member5; +}; + +void test2(const Proxy2 &p) { + p-> +} + +void test3(const Proxy2 &p) { + p. +} + +// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:177:6 %s -o - | FileCheck -check-prefix=CHECK-CC8 --implicit-check-not="Derived : Derived(" %s +// CHECK-CC8: Base1 : Base1:: +// CHECK-CC8: member1 : [#int#][#Base1::#]member1 +// CHECK-CC8: member1 : [#int#][#Base2::#]member1 +// CHECK-CC8: member2 : [#float#][#Base1::#]member2 +// CHECK-CC8: member3 : [#double#][#Base2::#]member3 +// CHECK-CC8: member4 : [#int#]member4 +// CHECK-CC8: member5 : [#int#]member5 (requires fix-it: {177:4-177:6} to ".") +// CHECK-CC8: memfun1 : [#void#][#Base3::#]memfun1(<#float#>) +// CHECK-CC8: memfun1 : [#void#][#Base3::#]memfun1(<#double#>)[# const#] +// CHECK-CC8: memfun1 (Hidden) : [#void#]Base2::memfun1(<#int#>) +// CHECK-CC8: memfun2 : [#void#][#Base3::#]memfun2(<#int#>) +// CHECK-CC8: memfun3 : [#int#]memfun3(<#int#>) +// CHECK-CC8: operator-> : [#Derived *#]operator->()[# const#] (requires fix-it: {177:4-177:6} to ".") + +// RUN: %clang_cc1 -fsyntax-only -code-completion-with-fixits -code-completion-at=%s:181:6 %s -o - | FileCheck -check-prefix=CHECK-CC9 --implicit-check-not="Derived : Derived(" %s +// CHECK-CC9: Base1 : Base1:: +// CHECK-CC9: member1 : [#int#][#Base1::#]member1 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member1 : [#int#][#Base2::#]member1 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member2 : [#float#][#Base1::#]member2 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member3 : [#double#][#Base2::#]member3 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member4 : [#int#]member4 (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: member5 : [#int#]member5 +// CHECK-CC9: memfun1 : [#void#][#Base3::#]memfun1(<#float#>) (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: memfun1 : [#void#][#Base3::#]memfun1(<#double#>)[# const#] (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: memfun1 (Hidden) : [#void#]Base2::memfun1(<#int#>) (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: memfun2 : [#void#][#Base3::#]memfun2(<#int#>) (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: memfun3 : [#int#]memfun3(<#int#>) (requires fix-it: {181:4-181:5} to "->") +// CHECK-CC9: operator-> : [#Derived *#]operator->()[# const#]