]> granicus.if.org Git - clang/commitdiff
Optionally add code completion results for arrow instead of dot
authorIvan Donchevskii <ivan.donchevskii@qt.io>
Fri, 25 May 2018 12:56:26 +0000 (12:56 +0000)
committerIvan Donchevskii <ivan.donchevskii@qt.io>
Fri, 25 May 2018 12:56:26 +0000 (12:56 +0000)
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

include/clang/Driver/CC1Options.td
include/clang/Sema/CodeCompleteConsumer.h
include/clang/Sema/CodeCompleteOptions.h
include/clang/Sema/Sema.h
lib/Frontend/ASTUnit.cpp
lib/Frontend/CompilerInvocation.cpp
lib/Parse/ParseExpr.cpp
lib/Sema/CodeCompleteConsumer.cpp
lib/Sema/SemaCodeComplete.cpp
test/CodeCompletion/member-access.cpp

index 78446304575d7c452e7a28f7bc5d06652e594494..5d56b4264b95ae92804bd1a9cf5ad2836b0a125d 100644 (file)
@@ -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">,
index 4780532fefe8a04d82d1d7729c17e510355ad344..b71a3582f52fbeeb396113d6b0a62ddd82cfc485 100644 (file)
@@ -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<std::vector<int>> 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<FixItHint> 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<FixItHint> FixIts = std::vector<FixItHint>())
       : 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 {
index bdd4732fd77377201bdbf90bb9d5c675a1fe549a..1d3bbb4e585d7aea74287caa298dc42af66dabb2 100644 (file)
@@ -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
index bc393e91ea871ce83052d65b921f43eb017f8410..06ecb57bc1c830b1cd0e72e494886e3a5f917de7 100644 (file)
@@ -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);
index 2214d827455222e863bf4a8d5b0df4c50c9536a4..e0e8e8b22aca5b9deaa4c4c9546af0804381ccb2 100644 (file)
@@ -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);
 
index 45822a0716523b57feed439cc319064d11c99c5b..29115c2f6d9406de13eb4beffcd2d5e5144d82cc 100644 (file)
@@ -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);
index ec0af38c5bc01bd1fd7df9330f40aa9fab0141ea..8d51bc82d3930c01f921039c70590f6f196cfa2c 100644 (file)
@@ -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();
index 70b31897561ae72745ecf0b09e7dbe181776250f..9c4d315a692f86362837ab710dfaf98200cacc67 100644 (file)
@@ -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<FileID, unsigned> BInfo = SM.getDecomposedLoc(BLoc);
+        std::pair<FileID, unsigned> 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;
       
index f08159b79f3363e003354106b46435f3f6fdc988..5bc428a04aea4ff5dfb61533ba4edf4400066057 100644 (file)
@@ -1291,10 +1291,13 @@ namespace {
   class CodeCompletionDeclConsumer : public VisibleDeclConsumer {
     ResultBuilder &Results;
     DeclContext *CurContext;
+    std::vector<FixItHint> FixIts;
 
   public:
-    CodeCompletionDeclConsumer(ResultBuilder &Results, DeclContext *CurContext)
-      : Results(Results), CurContext(CurContext) { }
+    CodeCompletionDeclConsumer(
+        ResultBuilder &Results, DeclContext *CurContext,
+        std::vector<FixItHint> FixIts = std::vector<FixItHint>())
+        : 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<FixItHint> 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<FixItHint> 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<PointerType>())
-      BaseType = Ptr->getPointeeType();
-    else if (BaseType->isObjCObjectPointerType())
-      /*Do nothing*/ ;
-    else
-      return;
+    if (const PointerType *Ptr = ConvertedBaseType->getAs<PointerType>())
+      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<RecordType>()) {
-    AddRecordMembersCompletionResults(*this, Results, S, BaseType,
-                                      Record->getDecl());
-  } else if (const auto *TST = BaseType->getAs<TemplateSpecializationType>()) {
-    TemplateName TN = TST->getTemplateName();
-    if (const auto *TD =
-            dyn_cast_or_null<ClassTemplateDecl>(TN.getAsTemplateDecl())) {
-      CXXRecordDecl *RD = TD->getTemplatedDecl();
-      AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD);
-    }
-  } else if (const auto *ICNT = BaseType->getAs<InjectedClassNameType>()) {
-    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<ObjCObjectPointerType>()->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<ObjCObjectPointerType>())
-      Class = ObjCPtr->getInterfaceDecl();
-    else
-      Class = BaseType->getAs<ObjCObjectType>()->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<FixItHint> 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<PointerType>())
+        BaseType = Ptr->getPointeeType();
+      else if (BaseType->isObjCObjectPointerType())
+        /*Do nothing*/;
+      else
+        return false;
+    }
+
+    if (const RecordType *Record = BaseType->getAs<RecordType>()) {
+      AddRecordMembersCompletionResults(*this, Results, S, BaseType,
+                                        Record->getDecl(),
+                                        std::move(AccessOpFixIt));
+    } else if (const auto *TST =
+                   BaseType->getAs<TemplateSpecializationType>()) {
+      TemplateName TN = TST->getTemplateName();
+      if (const auto *TD =
+              dyn_cast_or_null<ClassTemplateDecl>(TN.getAsTemplateDecl())) {
+        CXXRecordDecl *RD = TD->getTemplatedDecl();
+        AddRecordMembersCompletionResults(*this, Results, S, BaseType, RD,
+                                          std::move(AccessOpFixIt));
+      }
+    } else if (const auto *ICNT = BaseType->getAs<InjectedClassNameType>()) {
+      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<ObjCObjectPointerType>()->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<ObjCObjectPointerType>())
+        Class = ObjCPtr->getInterfaceDecl();
+      else
+        Class = BaseType->getAs<ObjCObjectType>()->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,
index c21265ec1130c255f9c760598f843750d8894a2a..008e223716bb33d5b59646028bc0d3acf9a5ac5f 100644 (file)
@@ -166,3 +166,47 @@ void dependentColonColonCompletion() {
   typename Template<T>::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#]