Index: include/clang-c/Index.h =================================================================== --- include/clang-c/Index.h +++ include/clang-c/Index.h @@ -32,7 +32,7 @@ * compatible, thus CINDEX_VERSION_MAJOR is expected to remain stable. */ #define CINDEX_VERSION_MAJOR 0 -#define CINDEX_VERSION_MINOR 48 +#define CINDEX_VERSION_MINOR 49 #define CINDEX_VERSION_ENCODE(major, minor) ( \ ((major) * 10000) \ @@ -5228,6 +5228,42 @@ } CXCodeCompleteResults; /** + * \brief Retrieve the number of fix-its for the given completion string. + */ +CINDEX_LINKAGE unsigned +clang_getCompletionNumFixIts(CXCodeCompleteResults *results, + unsigned completion_index); + +/** + * \brief 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 CXCodeComplete_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; + * vec_ptr.^ // completion returns an item 'push_back', replacing '.' + * with '->' vec_ptr->^ // completion returns an item 'release', + * replacing '->' with '.' + */ +CINDEX_LINKAGE CXString clang_getCompletionFixIt( + CXCodeCompleteResults *results, unsigned completion_index, + unsigned fixit_index, CXSourceRange *replacement_range); + +/** * \brief Flags that can be passed to \c clang_codeCompleteAt() to * modify its behavior. * @@ -5258,7 +5294,13 @@ * defined in the preamble. There's no guarantee any particular entity is * omitted. This may be useful if the headers are indexed externally. */ - CXCodeComplete_SkipPreamble = 0x08 + CXCodeComplete_SkipPreamble = 0x08, + + /** + * \brief Whether to include completion items with small + * fix-its, e.g. change '.' to '->' on member access, etc. + */ + CXCodeComplete_IncludeFixIts = 0x10 }; /** Index: test/Index/complete-arrow-dot.cpp =================================================================== --- test/Index/complete-arrow-dot.cpp +++ test/Index/complete-arrow-dot.cpp @@ -0,0 +1,54 @@ +struct X { + void doSomething(); + + int field; +}; + +void X::doSomething() { + // RUN: c-index-test -code-completion-at=%s:10:8 %s | FileCheck %s + // RUN: env CINDEXTEST_COMPLETION_INCLUDE_CORRECTIONS=1 c-index-test -code-completion-at=%s:10:8 %s | FileCheck -check-prefix=CHECK-WITH-CORRECTION %s + this.; +} + +void doSomething() { + // RUN: c-index-test -code-completion-at=%s:17:6 %s | FileCheck -check-prefix=CHECK-ARROW-TO-DOT %s + // RUN: env CINDEXTEST_COMPLETION_INCLUDE_CORRECTIONS=1 c-index-test -code-completion-at=%s:17:6 %s | FileCheck -check-prefix=CHECK-ARROW-TO-DOT-WITH-CORRECTION %s + X x; + x-> +} + +// CHECK-NOT: CXXMethod:{ResultType void}{TypedText doSomething}{LeftParen (}{RightParen )} (34) (requires fix: "." to "->") +// CHECK-NOT: FieldDecl:{ResultType int}{TypedText field} (35) (requires fix: "." to "->") +// CHECK-NOT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder const X &}{RightParen )} (79) (requires fix: "." to "->") +// CHECK-NOT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder X &&}{RightParen )} (79) (requires fix: "." to "->") +// CHECK-NOT: StructDecl:{TypedText X}{Text ::} (75) (requires fix: "." to "->") +// CHECK-NOT: CXXDestructor:{ResultType void}{TypedText ~X}{LeftParen (}{RightParen )} (79) (requires fix: "." to "->") +// CHECK: Completion contexts: +// CHECK-NEXT: Dot member access + +// CHECK-WITH-CORRECTION: CXXMethod:{ResultType void}{TypedText doSomething}{LeftParen (}{RightParen )} (34) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: FieldDecl:{ResultType int}{TypedText field} (35) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder const X &}{RightParen )} (79) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder X &&}{RightParen )} (79) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: StructDecl:{TypedText X}{Text ::} (75) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: CXXDestructor:{ResultType void}{TypedText ~X}{LeftParen (}{RightParen )} (79) (requires fix: "." to "->") +// CHECK-WITH-CORRECTION-NEXT: Completion contexts: +// CHECK-WITH-CORRECTION-NEXT: Dot member access + +// CHECK-ARROW-TO-DOT-NOT: CXXMethod:{ResultType void}{TypedText doSomething}{LeftParen (}{RightParen )} (34) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-NOT: FieldDecl:{ResultType int}{TypedText field} (35) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-NOT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder const X &}{RightParen )} (79) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-NOT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder X &&}{RightParen )} (79) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-NOT: StructDecl:{TypedText X}{Text ::} (75) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-NOT: CXXDestructor:{ResultType void}{TypedText ~X}{LeftParen (}{RightParen )} (79) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT: Completion contexts: +// CHECK-ARROW-TO-DOT-NEXT: Unknown + +// CHECK-ARROW-TO-DOT-WITH-CORRECTION: CXXMethod:{ResultType void}{TypedText doSomething}{LeftParen (}{RightParen )} (34) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: FieldDecl:{ResultType int}{TypedText field} (35) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder const X &}{RightParen )} (79) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: CXXMethod:{ResultType X &}{TypedText operator=}{LeftParen (}{Placeholder X &&}{RightParen )} (79) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: StructDecl:{TypedText X}{Text ::} (75) (requires fix: "->" to ".") +// CHECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: CXXDestructor:{ResultType void}{TypedText ~X}{LeftParen (}{RightParen )} (79) (requires fix: "->" to ".") +// HECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: Completion contexts: +// HECK-ARROW-TO-DOT-WITH-CORRECTION-NEXT: Arrow member access Index: tools/c-index-test/c-index-test.c =================================================================== --- tools/c-index-test/c-index-test.c +++ tools/c-index-test/c-index-test.c @@ -2268,8 +2268,24 @@ } -static void print_completion_result(CXCompletionResult *completion_result, +static void tokens_spelling_at_range(char *result_buffer, + CXTranslationUnit translation_unit, + CXSourceRange range) { + CXToken *tokens; + unsigned tokens_number; + clang_tokenize(translation_unit, range, &tokens, &tokens_number); + for (unsigned j = 0; j < tokens_number; ++j) { + strcat(result_buffer, clang_getCString(clang_getTokenSpelling( + translation_unit, tokens[j]))); + } + clang_disposeTokens(translation_unit, tokens, tokens_number); +} + +static void print_completion_result(CXTranslationUnit translation_unit, + CXCodeCompleteResults *completion_results, + unsigned index, FILE *file) { + CXCompletionResult *completion_result = completion_results->Results + index; CXString ks = clang_getCursorKindSpelling(completion_result->CursorKind); unsigned annotationCount; enum CXCursorKind ParentKind; @@ -2337,7 +2353,20 @@ fprintf(file, "(brief comment: %s)", BriefCommentCString); } clang_disposeString(BriefComment); - + + for (unsigned i = 0; + i < clang_getCompletionNumFixIts(completion_results, index); ++i) { + CXSourceRange correction_range; + CXString FixIt = clang_getCompletionFixIt(completion_results, index, i, + &correction_range); + char replaced_string[8]; + tokens_spelling_at_range(replaced_string, translation_unit, + correction_range); + fprintf(file, " (requires fix: \"%s\" to \"%s\")", replaced_string, + clang_getCString(FixIt)); + clang_disposeString(FixIt); + } + fprintf(file, "\n"); } @@ -2436,6 +2465,8 @@ completionOptions |= CXCodeComplete_IncludeBriefComments; if (getenv("CINDEXTEST_COMPLETION_SKIP_PREAMBLE")) completionOptions |= CXCodeComplete_SkipPreamble; + if (getenv("CINDEXTEST_COMPLETION_INCLUDE_CORRECTIONS")) + completionOptions |= CXCodeComplete_IncludeFixIts; if (timing_only) input += strlen("-code-completion-timing="); @@ -2500,7 +2531,7 @@ clang_sortCodeCompletionResults(results->Results, results->NumResults); for (i = 0; i != n; ++i) - print_completion_result(results->Results + i, stdout); + print_completion_result(TU, results, i, stdout); } n = clang_codeCompleteGetNumDiagnostics(results); for (i = 0; i != n; ++i) { Index: tools/libclang/CIndexCodeCompletion.cpp =================================================================== --- tools/libclang/CIndexCodeCompletion.cpp +++ tools/libclang/CIndexCodeCompletion.cpp @@ -16,6 +16,7 @@ #include "CIndexDiagnostic.h" #include "CLog.h" #include "CXCursor.h" +#include "CXSourceLocation.h" #include "CXString.h" #include "CXTranslationUnit.h" #include "clang/AST/Decl.h" @@ -302,10 +303,53 @@ /// \brief A string containing the Objective-C selector entered thus far for a /// message send. std::string Selector; + + /// \brief Vector of fix-its for each completion result that *must* be applied + /// before that result for the corresponding completion item. + std::vector> FixItsVector; }; } // end anonymous namespace +unsigned clang_getCompletionNumFixIts(CXCodeCompleteResults *results, + unsigned completion_index) { + AllocatedCXCodeCompleteResults *allocated_results = (AllocatedCXCodeCompleteResults *)results; + + if (!allocated_results || allocated_results->FixItsVector.size() <= completion_index) + return 0; + + return static_cast(allocated_results->FixItsVector[completion_index].size()); +} + +CXString clang_getCompletionFixIt(CXCodeCompleteResults *results, + unsigned completion_index, + unsigned fixit_index, + CXSourceRange *replacement_range) { + AllocatedCXCodeCompleteResults *allocated_results = (AllocatedCXCodeCompleteResults *)results; + + if (!allocated_results || allocated_results->FixItsVector.size() <= completion_index) { + if (replacement_range) + *replacement_range = clang_getNullRange(); + return cxstring::createNull(); + } + + ArrayRef FixIts = allocated_results->FixItsVector[completion_index]; + if (FixIts.size() <= fixit_index) { + if (replacement_range) + *replacement_range = clang_getNullRange(); + return cxstring::createNull(); + } + + const FixItHint &FixIt = FixIts[fixit_index]; + if (replacement_range) { + *replacement_range = cxloc::translateSourceRange( + *allocated_results->SourceMgr, allocated_results->LangOpts, + FixIt.RemoveRange); + } + + return cxstring::createRef(FixIt.CodeToInsert.c_str()); +} + /// \brief Tracks the number of code-completion result objects that are /// currently active. /// @@ -531,8 +575,10 @@ CodeCompletionResult *Results, unsigned NumResults) override { StoredResults.reserve(StoredResults.size() + NumResults); + if (includeFixIts()) + AllocatedResults.FixItsVector.reserve(NumResults); for (unsigned I = 0; I != NumResults; ++I) { - CodeCompletionString *StoredCompletion + CodeCompletionString *StoredCompletion = Results[I].CreateCodeCompletionString(S, Context, getAllocator(), getCodeCompletionTUInfo(), includeBriefComments()); @@ -541,8 +587,10 @@ R.CursorKind = Results[I].CursorKind; R.CompletionString = StoredCompletion; StoredResults.push_back(R); + if (includeFixIts()) + AllocatedResults.FixItsVector.emplace_back(std::move(Results[I].FixIts)); } - + enum CodeCompletionContext::Kind contextKind = Context.getKind(); AllocatedResults.ContextKind = contextKind; @@ -644,13 +692,13 @@ unsigned options) { bool IncludeBriefComments = options & CXCodeComplete_IncludeBriefComments; bool SkipPreamble = options & CXCodeComplete_SkipPreamble; + bool IncludeFixIts = options & CXCodeComplete_IncludeFixIts; #ifdef UDP_CODE_COMPLETION_LOGGER #ifdef UDP_CODE_COMPLETION_LOGGER_PORT const llvm::TimeRecord &StartTime = llvm::TimeRecord::getCurrentTime(); #endif #endif - bool EnableLogging = getenv("LIBCLANG_CODE_COMPLETION_LOGGING") != nullptr; if (cxtu::isNotUsableTU(TU)) { @@ -691,6 +739,7 @@ CodeCompleteOptions Opts; Opts.IncludeBriefComments = IncludeBriefComments; Opts.LoadExternal = !SkipPreamble; + Opts.IncludeFixIts = IncludeFixIts; CaptureCompletionResults Capture(Opts, *Results, &TU); // Perform completion. @@ -964,7 +1013,7 @@ = (CodeCompletionString *)XR.CompletionString; CodeCompletionString *Y = (CodeCompletionString *)YR.CompletionString; - + SmallString<256> XBuffer; StringRef XText = GetTypedName(X, XBuffer); SmallString<256> YBuffer; Index: tools/libclang/libclang.exports =================================================================== --- tools/libclang/libclang.exports +++ tools/libclang/libclang.exports @@ -171,6 +171,8 @@ clang_getCompletionChunkCompletionString clang_getCompletionChunkKind clang_getCompletionChunkText +clang_getCompletionNumFixIts +clang_getCompletionFixIt clang_getCompletionNumAnnotations clang_getCompletionParent clang_getCompletionPriority