From 79e94ddae87d4f4f8922f74c8533361337316604 Mon Sep 17 00:00:00 2001 From: Ivan Donchevskii Date: Thu, 10 Aug 2017 11:05:48 +0200 Subject: [PATCH] Clang: Workaround completion for make_unique/shared Change unsaved file to provide constructor overloads for std::make_unique, std::make_shared and QSharedPointer::create Example: // Provide Foo constructor signatures at std::make_unique( Task-number: QTCREATORBUG-18615 Change-Id: I87dd17085adf99ee498db969a3cdda5ebd973476 Reviewed-by: Nikolai Kosjar --- .../ipcsource/clangcodemodelserver.cpp | 2 + .../ipcsource/clangcompletecodejob.cpp | 15 ++-- .../clangbackend/ipcsource/clangjobrequest.h | 2 + .../ipcsource/clangtranslationunit.cpp | 8 ++- .../ipcsource/clangtranslationunit.h | 3 +- .../clangbackend/ipcsource/codecompleter.cpp | 71 ++++++++++++++++++- .../clangbackend/ipcsource/codecompleter.h | 8 ++- tests/unit/unittest/codecompleter-test.cpp | 33 +++++++++ .../unittest/data/complete_smartpointer.cpp | 58 +++++++++++++++ 9 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 tests/unit/unittest/data/complete_smartpointer.cpp diff --git a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp index 4c5f3712eb5..d3f0726aff2 100644 --- a/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp +++ b/src/tools/clangbackend/ipcsource/clangcodemodelserver.cpp @@ -212,6 +212,8 @@ void ClangCodeModelServer::completeCode(const ClangBackEnd::CompleteCodeMessage JobRequest jobRequest = processor.createJobRequest(JobRequest::Type::CompleteCode); jobRequest.line = message.line(); jobRequest.column = message.column(); + jobRequest.funcNameStartLine = message.funcNameStartLine(); + jobRequest.funcNameStartColumn = message.funcNameStartColumn(); jobRequest.ticketNumber = message.ticketNumber(); processor.addJob(jobRequest); diff --git a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp index 24378b201c8..2b767604121 100644 --- a/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp +++ b/src/tools/clangbackend/ipcsource/clangcompletecodejob.cpp @@ -36,12 +36,15 @@ namespace ClangBackEnd { static CompleteCodeJob::AsyncResult runAsyncHelper(const TranslationUnit &translationUnit, UnsavedFiles unsavedFiles, quint32 line, - quint32 column) + quint32 column, + qint32 funcNameStartLine, + qint32 funcNameStartColumn) { TIME_SCOPE_DURATION("CompleteCodeJobRunner"); const TranslationUnit::CodeCompletionResult results - = translationUnit.complete(unsavedFiles, line, column); + = translationUnit.complete(unsavedFiles, line, column, + funcNameStartLine, funcNameStartColumn); CompleteCodeJob::AsyncResult asyncResult; asyncResult.completions = results.completions; @@ -63,8 +66,12 @@ IAsyncJob::AsyncPrepareResult CompleteCodeJob::prepareAsyncRun() const UnsavedFiles unsavedFiles = *context().unsavedFiles; const quint32 line = jobRequest.line; const quint32 column = jobRequest.column; - setRunner([translationUnit, unsavedFiles, line, column]() { - return runAsyncHelper(translationUnit, unsavedFiles, line, column); + const qint32 funcNameStartLine = jobRequest.funcNameStartLine; + const qint32 funcNameStartColumn = jobRequest.funcNameStartColumn; + setRunner([translationUnit, unsavedFiles, line, column, + funcNameStartLine, funcNameStartColumn]() { + return runAsyncHelper(translationUnit, unsavedFiles, line, column, + funcNameStartLine, funcNameStartColumn); }); return AsyncPrepareResult{translationUnit.id()}; diff --git a/src/tools/clangbackend/ipcsource/clangjobrequest.h b/src/tools/clangbackend/ipcsource/clangjobrequest.h index 7de60ba5825..954ac6122e0 100644 --- a/src/tools/clangbackend/ipcsource/clangjobrequest.h +++ b/src/tools/clangbackend/ipcsource/clangjobrequest.h @@ -110,6 +110,8 @@ public: // Specific to some jobs quint32 line = 0; quint32 column = 0; + qint32 funcNameStartLine = -1; + qint32 funcNameStartColumn = -1; quint64 ticketNumber = 0; Utf8StringVector dependentFiles; bool resolveTarget = true; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp index a0d2fc932e6..adc732118eb 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.cpp @@ -119,11 +119,15 @@ bool TranslationUnit::suspend() const TranslationUnit::CodeCompletionResult TranslationUnit::complete( UnsavedFiles &unsavedFiles, uint line, - uint column) const + uint column, + int funcNameStartLine, + int funcNameStartColumn) const { CodeCompleter codeCompleter(*this, unsavedFiles); - const CodeCompletions completions = codeCompleter.complete(line, column); + const CodeCompletions completions = codeCompleter.complete(line, column, + funcNameStartLine, + funcNameStartColumn); const CompletionCorrection correction = codeCompleter.neededCorrection(); return CodeCompletionResult{completions, correction}; diff --git a/src/tools/clangbackend/ipcsource/clangtranslationunit.h b/src/tools/clangbackend/ipcsource/clangtranslationunit.h index 9553138c991..3ae020478ee 100644 --- a/src/tools/clangbackend/ipcsource/clangtranslationunit.h +++ b/src/tools/clangbackend/ipcsource/clangtranslationunit.h @@ -75,7 +75,8 @@ public: TranslationUnitUpdateResult parse(const TranslationUnitUpdateInput &parseInput) const; TranslationUnitUpdateResult reparse(const TranslationUnitUpdateInput &parseInput) const; - CodeCompletionResult complete(UnsavedFiles &unsavedFiles, uint line, uint column) const; + CodeCompletionResult complete(UnsavedFiles &unsavedFiles, uint line, uint column, + int funcNameStartLine, int funcNameStartColumn) const; void extractDiagnostics(DiagnosticContainer &firstHeaderErrorDiagnostic, QVector &mainFileDiagnostics) const; diff --git a/src/tools/clangbackend/ipcsource/codecompleter.cpp b/src/tools/clangbackend/ipcsource/codecompleter.cpp index fe746c8cfd8..8fa5c9cce23 100644 --- a/src/tools/clangbackend/ipcsource/codecompleter.cpp +++ b/src/tools/clangbackend/ipcsource/codecompleter.cpp @@ -81,11 +81,22 @@ CodeCompleter::CodeCompleter(const TranslationUnit &translationUnit, { } -CodeCompletions CodeCompleter::complete(uint line, uint column) +CodeCompletions CodeCompleter::complete(uint line, uint column, + int funcNameStartLine, + int funcNameStartColumn) { neededCorrection_ = CompletionCorrection::NoCorrection; - ClangCodeCompleteResults results = completeHelper(line, column); + // Check if we have a smart pointer completion and get proper constructor signatures in results. + // Results are empty when it's not a smart pointer or this completion failed. + ClangCodeCompleteResults results = completeSmartPointerCreation(line, + column, + funcNameStartLine, + funcNameStartColumn); + + if (results.isNull() || results.isEmpty()) + results = completeHelper(line, column); + filterUnknownContextResults(results, unsavedFile(), line, column); tryDotArrowCorrectionIfNoResults(results, line, column); @@ -97,6 +108,62 @@ CompletionCorrection CodeCompleter::neededCorrection() const return neededCorrection_; } +// For given "make_unique" / "make_shared" / "QSharedPointer::create" return "new T(" +// Otherwize return empty QString +static QString tweakName(const QString &oldName) +{ + QString fullName = oldName.trimmed(); + if (!fullName.contains('>')) + return QString(); + + if (!fullName.endsWith('>')) { + // This is the class::method case - remove ::method part + if (!fullName.endsWith("create") || !fullName.contains("QSharedPointer")) + return QString(); + fullName = fullName.mid(0, fullName.lastIndexOf(':') - 1); + } else if (!fullName.contains("make_unique") && !fullName.contains("make_shared")) { + return QString(); + } + int templateStart = fullName.indexOf('<'); + QString name = fullName.mid(0, templateStart); + QString templatePart = fullName.mid(templateStart + 1, + fullName.length() - templateStart - 2); + return "new " + templatePart + "("; +} + +ClangCodeCompleteResults CodeCompleter::completeSmartPointerCreation(uint line, + uint column, + int funcNameStartLine, + int funcNameStartColumn) +{ + if (column <= 1 || funcNameStartLine == -1) + return ClangCodeCompleteResults(); + + UnsavedFile &file = unsavedFiles.unsavedFile(translationUnit.filePath()); + if (!file.hasCharacterAt(line, column - 1, '(')) + return ClangCodeCompleteResults(); + + bool ok; + const uint startPos = file.toUtf8Position(funcNameStartLine, funcNameStartColumn, &ok); + const uint endPos = file.toUtf8Position(line, column - 1, &ok); + + Utf8String content = file.fileContent(); + const QString oldName = content.mid(startPos, endPos - startPos); + const QString updatedName = tweakName(oldName); + if (updatedName.isEmpty()) + return ClangCodeCompleteResults(); + + column += updatedName.length(); + file.replaceAt(endPos + 1, 0, updatedName); + + ClangCodeCompleteResults results = completeHelper(line, column); + if (results.isEmpty()) { + column -= updatedName.length(); + file.replaceAt(endPos + 1, updatedName.length(), QString()); + } + return results; +} + ClangCodeCompleteResults CodeCompleter::completeHelper(uint line, uint column) { const Utf8String nativeFilePath = FilePath::toNativeSeparators(translationUnit.filePath()); diff --git a/src/tools/clangbackend/ipcsource/codecompleter.h b/src/tools/clangbackend/ipcsource/codecompleter.h index 1b2dc4bb37b..2026e9ba2c0 100644 --- a/src/tools/clangbackend/ipcsource/codecompleter.h +++ b/src/tools/clangbackend/ipcsource/codecompleter.h @@ -43,7 +43,9 @@ public: CodeCompleter(const TranslationUnit &translationUnit, const UnsavedFiles &unsavedFiles); - CodeCompletions complete(uint line, uint column); + CodeCompletions complete(uint line, uint column, + int funcNameStartLine = -1, + int funcNameStartColumn = -1); CompletionCorrection neededCorrection() const; @@ -56,6 +58,10 @@ private: uint column); ClangCodeCompleteResults completeHelper(uint line, uint column); + ClangCodeCompleteResults completeSmartPointerCreation(uint line, + uint column, + int funcNameStartLine, + int funcNameStartColumn); ClangCodeCompleteResults completeWithArrowInsteadOfDot(uint line, uint column, uint dotPosition); diff --git a/tests/unit/unittest/codecompleter-test.cpp b/tests/unit/unittest/codecompleter-test.cpp index 439374d45b7..76802d7ca2f 100644 --- a/tests/unit/unittest/codecompleter-test.cpp +++ b/tests/unit/unittest/codecompleter-test.cpp @@ -188,6 +188,12 @@ protected: readFileContent(QStringLiteral("/complete_withGlobalCompletionAfterForwardDeclaredClassPointer.cpp")), true }; + ClangBackEnd::FileContainer smartPointerCompletion{ + Utf8StringLiteral(TESTDATA_DIR"/complete_smartpointer.cpp"), + projectPart.projectPartId(), + readFileContent(QStringLiteral("/complete_smartpointer.cpp")), + true + }; }; using CodeCompleterSlowTest = CodeCompleter; @@ -297,6 +303,33 @@ TEST_F(CodeCompleterSlowTest, FunctionInIncludedHeader) CodeCompletion::FunctionCompletionKind))); } +TEST_F(CodeCompleterSlowTest, UniquePointerCompletion) +{ + auto myCompleter = setupCompleter(smartPointerCompletion); + + ASSERT_THAT(myCompleter.complete(55, 54, 55, 32), + Contains(IsCodeCompletion(Utf8StringLiteral("Bar"), + CodeCompletion::ConstructorCompletionKind))); +} + +TEST_F(CodeCompleterSlowTest, SharedPointerCompletion) +{ + auto myCompleter = setupCompleter(smartPointerCompletion); + + ASSERT_THAT(myCompleter.complete(56, 55, 56, 33), + Contains(IsCodeCompletion(Utf8StringLiteral("Bar"), + CodeCompletion::ConstructorCompletionKind))); +} + +TEST_F(CodeCompleterSlowTest, QSharedPointerCompletion) +{ + auto myCompleter = setupCompleter(smartPointerCompletion); + + ASSERT_THAT(myCompleter.complete(57, 60, 57, 32), + Contains(IsCodeCompletion(Utf8StringLiteral("Bar"), + CodeCompletion::ConstructorCompletionKind))); +} + TEST_F(CodeCompleterSlowTest, FunctionInUnsavedIncludedHeader) { unsavedFiles.createOrUpdate({unsavedTargetHeaderFileContainer}); diff --git a/tests/unit/unittest/data/complete_smartpointer.cpp b/tests/unit/unittest/data/complete_smartpointer.cpp new file mode 100644 index 00000000000..1a9bbaf9788 --- /dev/null +++ b/tests/unit/unittest/data/complete_smartpointer.cpp @@ -0,0 +1,58 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ +namespace std { +template +class unique_ptr {}; + +template +class shared_ptr {}; + +template +unique_ptr make_unique(Args&&... args); + +template +shared_ptr make_shared(Args&&... args); +} // namespace std + +template +class QSharedPointer +{ +public: + template + static QSharedPointer create(Args&&... args); +}; + +class Bar +{ +public: + Bar(); + Bar(int, int); +}; +void f2() +{ + std::unique_ptr bar = std::make_unique(); + std::shared_ptr bar2 = std::make_shared(); + QSharedPointer bar3 = QSharedPointer::create(); +}