aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp')
-rw-r--r--src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp1208
1 files changed, 1208 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp
new file mode 100644
index 0000000000..a0e8ad2141
--- /dev/null
+++ b/src/plugins/cppeditor/quickfixes/createdeclarationfromuse.cpp
@@ -0,0 +1,1208 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "createdeclarationfromuse.h"
+
+#include "../cppcodestylesettings.h"
+#include "../cppeditortr.h"
+#include "../cppeditorwidget.h"
+#include "../cpprefactoringchanges.h"
+#include "../insertionpointlocator.h"
+#include "../symbolfinder.h"
+#include "cppquickfix.h"
+#include "cppquickfixhelpers.h"
+#include "cppquickfixprojectsettings.h"
+
+#include <coreplugin/icore.h>
+#include <cplusplus/Overview.h>
+#include <cplusplus/TypeOfExpression.h>
+#include <projectexplorer/projecttree.h>
+
+#include <QInputDialog>
+
+#ifdef WITH_TESTS
+#include "cppquickfix_test.h"
+#include <QtTest>
+#endif
+
+#include <variant>
+
+using namespace CPlusPlus;
+using namespace ProjectExplorer;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace CppEditor::Internal {
+namespace {
+
+using TypeOrExpr = std::variant<const CPlusPlus::ExpressionAST *, CPlusPlus::FullySpecifiedType>;
+
+// FIXME: Needs to consider the scope at the insertion site.
+static QString declFromExpr(
+ const TypeOrExpr &typeOrExpr,
+ const CallAST *call,
+ const NameAST *varName,
+ const Snapshot &snapshot,
+ const LookupContext &context,
+ const CppRefactoringFilePtr &file,
+ bool makeConst)
+{
+ const auto getTypeFromUser = [varName, call]() -> QString {
+ if (call)
+ return {};
+ const QString typeFromUser = QInputDialog::getText(
+ Core::ICore::dialogParent(),
+ Tr::tr("Provide the type"),
+ Tr::tr("Data type:"),
+ QLineEdit::Normal);
+ if (!typeFromUser.isEmpty())
+ return typeFromUser + ' ' + nameString(varName);
+ return {};
+ };
+ const auto getTypeOfExpr = [&](const ExpressionAST *expr) -> FullySpecifiedType {
+ return typeOfExpr(expr, file, snapshot, context);
+ };
+
+ const Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ const FullySpecifiedType type = std::holds_alternative<FullySpecifiedType>(typeOrExpr)
+ ? std::get<FullySpecifiedType>(typeOrExpr)
+ : getTypeOfExpr(std::get<const ExpressionAST *>(typeOrExpr));
+ if (!call)
+ return type.isValid() ? oo.prettyType(type, varName->name) : getTypeFromUser();
+
+ Function func(file->cppDocument()->translationUnit(), 0, varName->name);
+ func.setConst(makeConst);
+ for (ExpressionListAST *it = call->expression_list; it; it = it->next) {
+ Argument *const arg = new Argument(nullptr, 0, nullptr);
+ arg->setType(getTypeOfExpr(it->value));
+ func.addMember(arg);
+ }
+ return oo.prettyType(type) + ' ' + oo.prettyType(func.type(), varName->name);
+}
+
+
+
+
+class InsertDeclOperation: public CppQuickFixOperation
+{
+public:
+ InsertDeclOperation(const CppQuickFixInterface &interface,
+ const FilePath &targetFilePath, const Class *targetSymbol,
+ InsertionPointLocator::AccessSpec xsSpec, const QString &decl, int priority)
+ : CppQuickFixOperation(interface, priority)
+ , m_targetFilePath(targetFilePath)
+ , m_targetSymbol(targetSymbol)
+ , m_xsSpec(xsSpec)
+ , m_decl(decl)
+ {
+ setDescription(Tr::tr("Add %1 Declaration")
+ .arg(InsertionPointLocator::accessSpecToString(xsSpec)));
+ }
+
+ void perform() override
+ {
+ CppRefactoringChanges refactoring(snapshot());
+
+ InsertionPointLocator locator(refactoring);
+ const InsertionLocation loc = locator.methodDeclarationInClass(
+ m_targetFilePath, m_targetSymbol, m_xsSpec);
+ QTC_ASSERT(loc.isValid(), return);
+
+ CppRefactoringFilePtr targetFile = refactoring.cppFile(m_targetFilePath);
+ int targetPosition = targetFile->position(loc.line(), loc.column());
+
+ ChangeSet target;
+ target.insert(targetPosition, loc.prefix() + m_decl);
+ targetFile->setOpenEditor(true, targetPosition);
+ targetFile->apply(target);
+ }
+
+ static QString generateDeclaration(const Function *function)
+ {
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ oo.showFunctionSignatures = true;
+ oo.showReturnTypes = true;
+ oo.showArgumentNames = true;
+ oo.showEnclosingTemplate = true;
+
+ QString decl;
+ decl += oo.prettyType(function->type(), function->unqualifiedName());
+ decl += QLatin1String(";\n");
+
+ return decl;
+ }
+
+private:
+ FilePath m_targetFilePath;
+ const Class *m_targetSymbol;
+ InsertionPointLocator::AccessSpec m_xsSpec;
+ QString m_decl;
+};
+
+class DeclOperationFactory
+{
+public:
+ DeclOperationFactory(const CppQuickFixInterface &interface, const FilePath &filePath,
+ const Class *matchingClass, const QString &decl)
+ : m_interface(interface)
+ , m_filePath(filePath)
+ , m_matchingClass(matchingClass)
+ , m_decl(decl)
+ {}
+
+ QuickFixOperation *operator()(InsertionPointLocator::AccessSpec xsSpec, int priority)
+ {
+ return new InsertDeclOperation(m_interface, m_filePath, m_matchingClass, xsSpec, m_decl, priority);
+ }
+
+private:
+ const CppQuickFixInterface &m_interface;
+ const FilePath &m_filePath;
+ const Class *m_matchingClass;
+ const QString &m_decl;
+};
+
+class InsertMemberFromInitializationOp : public CppQuickFixOperation
+{
+public:
+ InsertMemberFromInitializationOp(
+ const CppQuickFixInterface &interface,
+ const Class *theClass,
+ const NameAST *memberName,
+ const TypeOrExpr &typeOrExpr,
+ const CallAST *call,
+ InsertionPointLocator::AccessSpec accessSpec,
+ bool makeStatic,
+ bool makeConst)
+ : CppQuickFixOperation(interface),
+ m_class(theClass), m_memberName(memberName), m_typeOrExpr(typeOrExpr), m_call(call),
+ m_accessSpec(accessSpec), m_makeStatic(makeStatic), m_makeConst(makeConst)
+ {
+ if (call)
+ setDescription(Tr::tr("Add Member Function \"%1\"").arg(nameString(memberName)));
+ else
+ setDescription(Tr::tr("Add Class Member \"%1\"").arg(nameString(memberName)));
+ }
+
+private:
+ void perform() override
+ {
+ QString decl = declFromExpr(m_typeOrExpr, m_call, m_memberName, snapshot(), context(),
+ currentFile(), m_makeConst);
+ if (decl.isEmpty())
+ return;
+ if (m_makeStatic)
+ decl.prepend("static ");
+
+ const CppRefactoringChanges refactoring(snapshot());
+ const InsertionPointLocator locator(refactoring);
+ const FilePath filePath = FilePath::fromUtf8(m_class->fileName());
+ const InsertionLocation loc = locator.methodDeclarationInClass(
+ filePath, m_class, m_accessSpec);
+ QTC_ASSERT(loc.isValid(), return);
+
+ CppRefactoringFilePtr targetFile = refactoring.cppFile(filePath);
+ targetFile->apply(ChangeSet::makeInsert(
+ targetFile->position(loc.line(), loc.column()), loc.prefix() + decl + ";\n"));
+ }
+
+ const Class * const m_class;
+ const NameAST * const m_memberName;
+ const TypeOrExpr m_typeOrExpr;
+ const CallAST * m_call;
+ const InsertionPointLocator::AccessSpec m_accessSpec;
+ const bool m_makeStatic;
+ const bool m_makeConst;
+};
+
+class AddLocalDeclarationOp: public CppQuickFixOperation
+{
+public:
+ AddLocalDeclarationOp(const CppQuickFixInterface &interface,
+ int priority,
+ const BinaryExpressionAST *binaryAST,
+ const SimpleNameAST *simpleNameAST)
+ : CppQuickFixOperation(interface, priority)
+ , binaryAST(binaryAST)
+ , simpleNameAST(simpleNameAST)
+ {
+ setDescription(Tr::tr("Add Local Declaration"));
+ }
+
+ void perform() override
+ {
+ QString declaration = getDeclaration();
+
+ if (!declaration.isEmpty()) {
+ currentFile()->apply(ChangeSet::makeReplace(
+ currentFile()->startOf(binaryAST),
+ currentFile()->endOf(simpleNameAST),
+ declaration));
+ }
+ }
+
+private:
+ QString getDeclaration()
+ {
+ Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
+ const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
+ ProjectTree::currentProject());
+
+ if (currentFile()->cppDocument()->languageFeatures().cxx11Enabled && settings->useAuto)
+ return "auto " + oo.prettyName(simpleNameAST->name);
+ return declFromExpr(binaryAST->right_expression, nullptr, simpleNameAST, snapshot(),
+ context(), currentFile(), false);
+ }
+
+ const BinaryExpressionAST *binaryAST;
+ const SimpleNameAST *simpleNameAST;
+};
+
+//! Adds a declarations to a definition
+class InsertDeclFromDef: public CppQuickFixFactory
+{
+#ifdef WITH_TESTS
+public:
+ static QObject *createTest();
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ const QList<AST *> &path = interface.path();
+ CppRefactoringFilePtr file = interface.currentFile();
+
+ FunctionDefinitionAST *funDef = nullptr;
+ int idx = 0;
+ for (; idx < path.size(); ++idx) {
+ AST *node = path.at(idx);
+ if (idx > 1) {
+ if (DeclaratorIdAST *declId = node->asDeclaratorId()) {
+ if (file->isCursorOn(declId)) {
+ if (FunctionDefinitionAST *candidate = path.at(idx - 2)->asFunctionDefinition()) {
+ funDef = candidate;
+ break;
+ }
+ }
+ }
+ }
+
+ if (node->asClassSpecifier())
+ return;
+ }
+
+ if (!funDef || !funDef->symbol)
+ return;
+
+ Function *fun = funDef->symbol;
+ if (Class *matchingClass = isMemberFunction(interface.context(), fun)) {
+ const QualifiedNameId *qName = fun->name()->asQualifiedNameId();
+ for (Symbol *symbol = matchingClass->find(qName->identifier());
+ symbol; symbol = symbol->next()) {
+ Symbol *s = symbol;
+ if (fun->enclosingScope()->asTemplate()) {
+ if (const Template *templ = s->type()->asTemplateType()) {
+ if (Symbol *decl = templ->declaration()) {
+ if (decl->type()->asFunctionType())
+ s = decl;
+ }
+ }
+ }
+ if (!s->name()
+ || !qName->identifier()->match(s->identifier())
+ || !s->type()->asFunctionType())
+ continue;
+
+ if (s->type().match(fun->type())) {
+ // Declaration exists.
+ return;
+ }
+ }
+ const FilePath fileName = matchingClass->filePath();
+ const QString decl = InsertDeclOperation::generateDeclaration(fun);
+
+ // Add several possible insertion locations for declaration
+ DeclOperationFactory operation(interface, fileName, matchingClass, decl);
+
+ result << operation(InsertionPointLocator::Public, 5)
+ << operation(InsertionPointLocator::PublicSlot, 4)
+ << operation(InsertionPointLocator::Protected, 3)
+ << operation(InsertionPointLocator::ProtectedSlot, 2)
+ << operation(InsertionPointLocator::Private, 1)
+ << operation(InsertionPointLocator::PrivateSlot, 0);
+ }
+ }
+};
+
+class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory
+{
+public:
+#ifdef WITH_TESTS
+ static QObject *createTest();
+ void setMembersOnly() { m_membersOnly = true; }
+#endif
+
+private:
+ void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override
+ {
+ // Are we on a name?
+ const QList<AST *> &path = interface.path();
+ if (path.isEmpty())
+ return;
+ if (!path.last()->asSimpleName())
+ return;
+
+ // Special case: Member initializer.
+ if (!checkForMemberInitializer(interface, result))
+ return;
+
+ // Are we inside a function?
+ const FunctionDefinitionAST *func = nullptr;
+ for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
+ func = (*it)->asFunctionDefinition();
+ if (!func)
+ return;
+
+ // Is this name declared somewhere already?
+ const CursorInEditor cursorInEditor(interface.cursor(), interface.filePath(),
+ interface.editor(), interface.editor()->textDocument());
+ const auto followSymbolFallback = [&](const Link &link) {
+ if (!link.hasValidTarget())
+ collectOperations(interface, result);
+ };
+ NonInteractiveFollowSymbolMarker niMarker;
+ CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false,
+ FollowSymbolMode::Exact,
+ CppModelManager::Backend::Builtin);
+ }
+
+ void collectOperations(const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+ const CppRefactoringFilePtr &file = interface.currentFile();
+ for (int index = path.size() - 1; index != -1; --index) {
+ if (const auto call = path.at(index)->asCall())
+ return handleCall(call, interface, result);
+
+ // We only trigger if the identifier appears on the left-hand side of an
+ // assignment expression.
+ const auto binExpr = path.at(index)->asBinaryExpression();
+ if (!binExpr)
+ continue;
+ if (!binExpr->left_expression || !binExpr->right_expression
+ || file->tokenAt(binExpr->binary_op_token).kind() != T_EQUAL
+ || !interface.isCursorOn(binExpr->left_expression)) {
+ return;
+ }
+
+ // In the case of "a.|b = c", find out the type of a, locate the class declaration
+ // and add a member b there.
+ if (const auto memberAccess = binExpr->left_expression->asMemberAccess()) {
+ if (interface.isCursorOn(memberAccess->member_name)
+ && memberAccess->member_name == path.last()) {
+ maybeAddMember(interface, file->scopeAt(memberAccess->firstToken()),
+ file->textOf(memberAccess->base_expression).toUtf8(),
+ binExpr->right_expression, nullptr, result);
+ }
+ return;
+ }
+
+ const auto idExpr = binExpr->left_expression->asIdExpression();
+ if (!idExpr || !idExpr->name)
+ return;
+
+ // In the case of "A::|b = c", add a static member b to A.
+ if (const auto qualName = idExpr->name->asQualifiedName()) {
+ return maybeAddStaticMember(interface, qualName, binExpr->right_expression, nullptr,
+ result);
+ }
+
+ // For an unqualified access, offer a local declaration and, if we are
+ // in a member function, a member declaration.
+ if (const auto simpleName = idExpr->name->asSimpleName()) {
+ if (!m_membersOnly)
+ result << new AddLocalDeclarationOp(interface, index, binExpr, simpleName);
+ maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
+ binExpr->right_expression, nullptr, result);
+ return;
+ }
+ }
+ }
+
+ void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ if (!call->base_expression)
+ return;
+
+ // In order to find out the return type, we need to check the context of the call.
+ // If it is a statement expression, the type is void, if it's a binary expression,
+ // we assume the type of the other side of the expression, if it's a return statement,
+ // we use the return type of the surrounding function, and if it's a declaration,
+ // we use the type of the variable. Other cases are not supported.
+ const QList<AST *> &path = interface.path();
+ const CppRefactoringFilePtr &file = interface.currentFile();
+ TypeOrExpr returnTypeOrExpr;
+ for (auto it = path.rbegin(); it != path.rend(); ++it) {
+ if ((*it)->asCompoundStatement())
+ return;
+ if ((*it)->asExpressionStatement()) {
+ returnTypeOrExpr = FullySpecifiedType(new VoidType);
+ break;
+ }
+ if (const auto binExpr = (*it)->asBinaryExpression()) {
+ returnTypeOrExpr = interface.isCursorOn(binExpr->left_expression)
+ ? binExpr->right_expression : binExpr->left_expression;
+ break;
+ }
+ if ((*it)->asReturnStatement()) {
+ for (auto it2 = std::next(it); it2 != path.rend(); ++it2) {
+ if (const auto func = (*it2)->asFunctionDefinition()) {
+ if (!func->symbol)
+ return;
+ returnTypeOrExpr = func->symbol->returnType();
+ break;
+ }
+ }
+ break;
+ }
+ if (const auto declarator = (*it)->asDeclarator()) {
+ if (!interface.isCursorOn(declarator->initializer))
+ return;
+ const auto decl = (*std::next(it))->asSimpleDeclaration();
+ if (!decl || !decl->symbols)
+ return;
+ if (!decl->symbols->value->type().isValid())
+ return;
+ returnTypeOrExpr = decl->symbols->value->type();
+ break;
+ }
+ }
+
+ if (std::holds_alternative<const ExpressionAST *>(returnTypeOrExpr)
+ && !std::get<const ExpressionAST *>(returnTypeOrExpr)) {
+ return;
+ }
+
+ // a.f()
+ if (const auto memberAccess = call->base_expression->asMemberAccess()) {
+ if (!interface.isCursorOn(memberAccess->member_name))
+ return;
+ maybeAddMember(
+ interface, file->scopeAt(call->firstToken()),
+ file->textOf(memberAccess->base_expression).toUtf8(), returnTypeOrExpr, call, result);
+ }
+
+ const auto idExpr = call->base_expression->asIdExpression();
+ if (!idExpr || !idExpr->name)
+ return;
+
+ // A::f()
+ if (const auto qualName = idExpr->name->asQualifiedName())
+ return maybeAddStaticMember(interface, qualName, returnTypeOrExpr, call, result);
+
+ // f()
+ if (idExpr->name->asSimpleName()) {
+ maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
+ returnTypeOrExpr, call, result);
+ }
+ }
+
+ // Returns whether to still do other checks.
+ bool checkForMemberInitializer(const CppQuickFixInterface &interface,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+ const int size = path.size();
+ if (size < 4)
+ return true;
+ const MemInitializerAST * const memInitializer = path.at(size - 2)->asMemInitializer();
+ if (!memInitializer)
+ return true;
+ if (!path.at(size - 3)->asCtorInitializer())
+ return true;
+ const FunctionDefinitionAST * ctor = path.at(size - 4)->asFunctionDefinition();
+ if (!ctor)
+ return false;
+
+ // Now find the class.
+ const Class *theClass = nullptr;
+ if (size > 4) {
+ const ClassSpecifierAST * const classSpec = path.at(size - 5)->asClassSpecifier();
+ if (classSpec) // Inline constructor. We get the class directly.
+ theClass = classSpec->symbol;
+ }
+ if (!theClass) {
+ // Out-of-line constructor. We need to find the class.
+ SymbolFinder finder;
+ const QList<Declaration *> matches = finder.findMatchingDeclaration(
+ LookupContext(interface.currentFile()->cppDocument(), interface.snapshot()),
+ ctor->symbol);
+ if (!matches.isEmpty())
+ theClass = matches.first()->enclosingClass();
+ }
+
+ if (!theClass)
+ return false;
+
+ const SimpleNameAST * const name = path.at(size - 1)->asSimpleName();
+ QTC_ASSERT(name, return false);
+
+ // Check whether the member exists already.
+ if (theClass->find(interface.currentFile()->cppDocument()->translationUnit()->identifier(
+ name->identifier_token))) {
+ return false;
+ }
+
+ result << new InsertMemberFromInitializationOp(
+ interface, theClass, memInitializer->name->asSimpleName(), memInitializer->expression,
+ nullptr, InsertionPointLocator::Private, false, false);
+ return false;
+ }
+
+ void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope,
+ const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr,
+ const CPlusPlus::CallAST *call, QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+
+ TypeOfExpression typeOfExpression;
+ typeOfExpression.init(interface.semanticInfo().doc, interface.snapshot(),
+ interface.context().bindings());
+ const QList<LookupItem> lhsTypes = typeOfExpression(
+ classTypeExpr, scope,
+ TypeOfExpression::Preprocess);
+ if (lhsTypes.isEmpty())
+ return;
+
+ const Type *type = lhsTypes.first().type().type();
+ if (!type)
+ return;
+ if (type->asPointerType()) {
+ type = type->asPointerType()->elementType().type();
+ if (!type)
+ return;
+ }
+ const auto namedType = type->asNamedType();
+ if (!namedType)
+ return;
+ const ClassOrNamespace * const classOrNamespace
+ = interface.context().lookupType(namedType->name(), scope);
+ if (!classOrNamespace || !classOrNamespace->rootClass())
+ return;
+
+ const Class * const theClass = classOrNamespace->rootClass();
+ bool needsStatic = lhsTypes.first().type().isStatic();
+
+ // If the base expression refers to the same class that the member function is in,
+ // then we want to insert a private member, otherwise a public one.
+ const FunctionDefinitionAST *func = nullptr;
+ for (auto it = path.rbegin(); !func && it != path.rend(); ++it)
+ func = (*it)->asFunctionDefinition();
+ QTC_ASSERT(func, return);
+ InsertionPointLocator::AccessSpec accessSpec = InsertionPointLocator::Public;
+ for (int i = 0; i < theClass->memberCount(); ++i) {
+ if (theClass->memberAt(i) == func->symbol) {
+ accessSpec = InsertionPointLocator::Private;
+ needsStatic = func->symbol->isStatic();
+ break;
+ }
+ }
+ if (accessSpec == InsertionPointLocator::Public) {
+ QList<Declaration *> decls;
+ QList<Declaration *> dummy;
+ SymbolFinder().findMatchingDeclaration(interface.context(), func->symbol, &decls,
+ &dummy, &dummy);
+ for (const Declaration * const decl : std::as_const(decls)) {
+ for (int i = 0; i < theClass->memberCount(); ++i) {
+ if (theClass->memberAt(i) == decl) {
+ accessSpec = InsertionPointLocator::Private;
+ needsStatic = decl->isStatic();
+ break;
+ }
+ }
+ if (accessSpec == InsertionPointLocator::Private)
+ break;
+ }
+ }
+ result << new InsertMemberFromInitializationOp(interface, theClass, path.last()->asName(),
+ typeOrExpr, call, accessSpec, needsStatic,
+ func->symbol->isConst());
+ }
+
+ void maybeAddStaticMember(
+ const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName,
+ const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call,
+ QuickFixOperations &result)
+ {
+ const QList<AST *> &path = interface.path();
+
+ if (!interface.isCursorOn(qualName->unqualified_name))
+ return;
+ if (qualName->unqualified_name != path.last())
+ return;
+ if (!qualName->nested_name_specifier_list)
+ return;
+
+ const NameAST * const topLevelName
+ = qualName->nested_name_specifier_list->value->class_or_namespace_name;
+ if (!topLevelName)
+ return;
+ ClassOrNamespace * const classOrNamespace = interface.context().lookupType(
+ topLevelName->name, interface.currentFile()->scopeAt(qualName->firstToken()));
+ if (!classOrNamespace)
+ return;
+ QList<const Name *> otherNames;
+ for (auto it = qualName->nested_name_specifier_list->next; it; it = it->next) {
+ if (!it->value || !it->value->class_or_namespace_name)
+ return;
+ otherNames << it->value->class_or_namespace_name->name;
+ }
+
+ const Class *theClass = nullptr;
+ if (!otherNames.isEmpty()) {
+ const Symbol * const symbol = classOrNamespace->lookupInScope(otherNames);
+ if (!symbol)
+ return;
+ theClass = symbol->asClass();
+ } else {
+ theClass = classOrNamespace->rootClass();
+ }
+ if (theClass) {
+ result << new InsertMemberFromInitializationOp(
+ interface, theClass, path.last()->asName(), typeOrExpr, call,
+ InsertionPointLocator::Public, true, false);
+ }
+ }
+
+ bool m_membersOnly = false;
+};
+
+#ifdef WITH_TESTS
+using namespace Tests;
+
+class AddDeclarationForUndeclaredIdentifierTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ // QTCREATORBUG-26004
+ void testLocalDeclFromUse()
+ {
+ const QByteArray original = "void func() {\n"
+ " QStringList list;\n"
+ " @it = list.cbegin();\n"
+ "}\n";
+ const QByteArray expected = "void func() {\n"
+ " QStringList list;\n"
+ " auto it = list.cbegin();\n"
+ "}\n";
+ AddDeclarationForUndeclaredIdentifier factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory);
+ }
+
+ void testInsertMemberFromUse_data()
+ {
+ QTest::addColumn<QByteArray>("original");
+ QTest::addColumn<QByteArray>("expected");
+
+ QByteArray original;
+ QByteArray expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : @m_x(x) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : m_x(x) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ " int m_x;\n"
+ "};\n";
+ QTest::addRow("inline constructor") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x, double d);\n"
+ "private:\n"
+ " int m_x;\n"
+ "};\n"
+ "C::C(int x, double d) : m_x(x), @m_d(d)\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " C(int x, double d);\n"
+ "private:\n"
+ " int m_x;\n"
+ " double m_d;\n"
+ "};\n"
+ "C::C(int x, double d) : m_x(x), m_d(d)\n";
+ QTest::addRow("out-of-line constructor") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " C(int x) : @m_x(x) {}\n"
+ "private:\n"
+ " int m_x;\n"
+ "};\n";
+ expected = "";
+ QTest::addRow("member already present") << original << expected;
+
+ original =
+ "int func() { return 0; }\n"
+ "class C {\n"
+ "public:\n"
+ " C() : @m_x(func()) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ "};\n";
+ expected =
+ "int func() { return 0; }\n"
+ "class C {\n"
+ "public:\n"
+ " C() : m_x(func()) {}\n"
+ "private:\n"
+ " int m_y;\n"
+ " int m_x;\n"
+ "};\n";
+ QTest::addRow("initialization via function call") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@value = v; }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.value = v; }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ QTest::addRow("add member to other struct") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::@value = v; }\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::value = v; }\n"
+ "};\n";
+ QTest::addRow("add static member to other struct (explicit)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@value = v; }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static int value;\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.value = v; }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ QTest::addRow("add static member to other struct (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { this->@m_value = v; }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "private:\n"
+ " int m_value;\n"
+ "};\n"
+ "void C::setValue(int v) { this->@m_value = v; }\n";
+ QTest::addRow("add member to this (explicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { @m_value = v; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_value = v; }\n"
+ "private:\n"
+ " int m_value;\n"
+ "};\n";
+ QTest::addRow("add member to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v) { @m_value = v; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v) { m_value = v; }\n"
+ "private:\n"
+ " static int m_value;\n"
+ "};\n";
+ QTest::addRow("add static member to this (inline)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { @m_value = v; }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static void setValue(int v);\n"
+ "private:\n"
+ " static int m_value;\n"
+ "};\n"
+ "void C::setValue(int v) { @m_value = v; }\n";
+ QTest::addRow("add static member to this (non-inline)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@setValue(v); }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.setValue(v); }\n"
+ "private:\n"
+ " S m_s;\n"
+ "};\n";
+ QTest::addRow("add member function to other struct") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::@setValue(v); }\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { S::setValue(v); }\n"
+ "};\n";
+ QTest::addRow("add static member function to other struct (explicit)") << original << expected;
+
+ original =
+ "struct S {\n\n};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.@setValue(v); }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ expected =
+ "struct S {\n\n"
+ " static void setValue(int);\n"
+ "};\n"
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { m_s.setValue(v); }\n"
+ "private:\n"
+ " static S m_s;\n"
+ "};\n";
+ QTest::addRow("add static member function to other struct (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "};\n"
+ "void C::setValue(int v) { this->@setValueInternal(v); }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v);\n"
+ "private:\n"
+ " void setValueInternal(int);\n"
+ "};\n"
+ "void C::setValue(int v) { this->setValueInternal(v); }\n";
+ QTest::addRow("add member function to this (explicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { @setValueInternal(v); }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " void setValue(int v) { setValueInternal(v); }\n"
+ "private:\n"
+ " void setValueInternal(int);\n"
+ "};\n";
+ QTest::addRow("add member function to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " int value() const { return @valueInternal(); }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " int value() const { return valueInternal(); }\n"
+ "private:\n"
+ " int valueInternal() const;\n"
+ "};\n";
+ QTest::addRow("add const member function to this (implicit)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static int value() { int i = @valueInternal(); return i; }\n"
+ "};\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static int value() { int i = @valueInternal(); return i; }\n"
+ "private:\n"
+ " static int valueInternal();\n"
+ "};\n";
+ QTest::addRow("add static member function to this (inline)") << original << expected;
+
+ original =
+ "class C {\n"
+ "public:\n"
+ " static int value();\n"
+ "};\n"
+ "int C::value() { return @valueInternal(); }\n";
+ expected =
+ "class C {\n"
+ "public:\n"
+ " static int value();\n"
+ "private:\n"
+ " static int valueInternal();\n"
+ "};\n"
+ "int C::value() { return valueInternal(); }\n";
+ QTest::addRow("add static member function to this (non-inline)") << original << expected;
+ }
+
+ void testInsertMemberFromUse()
+ {
+ QFETCH(QByteArray, original);
+ QFETCH(QByteArray, expected);
+
+ QList<TestDocumentPtr> testDocuments({
+ CppTestDocument::create("file.h", original, expected)
+ });
+
+ AddDeclarationForUndeclaredIdentifier factory;
+ factory.setMembersOnly();
+ QuickFixOperationTest(testDocuments, &factory);
+ }
+};
+
+class InsertDeclFromDefTest : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ /// Check from source file: Insert in header file.
+ void test()
+ {
+ insertToSectionDeclFromDef("public", 0);
+ insertToSectionDeclFromDef("public slots", 1);
+ insertToSectionDeclFromDef("protected", 2);
+ insertToSectionDeclFromDef("protected slots", 3);
+ insertToSectionDeclFromDef("private", 4);
+ insertToSectionDeclFromDef("private slots", 5);
+ }
+
+ void testTemplateFuncTypename()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " template<class T>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testTemplateFuncInt()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "template<int N>\n"
+ "void Foo::fu@nc() {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " template<int N>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<int N>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testTemplateReturnType()
+ {
+ QByteArray original =
+ "class Foo\n"
+ "{\n"
+ "};\n"
+ "\n"
+ "std::vector<int> Foo::fu@nc() const {}\n";
+
+ QByteArray expected =
+ "class Foo\n"
+ "{\n"
+ "public:\n"
+ " std::vector<int> func() const;\n"
+ "};\n"
+ "\n"
+ "std::vector<int> Foo::func() const {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(original, expected), &factory, {}, 0);
+ }
+
+ void testNotTriggeredForTemplateFunc()
+ {
+ QByteArray contents =
+ "class Foo\n"
+ "{\n"
+ " template<class T>\n"
+ " void func();\n"
+ "};\n"
+ "\n"
+ "template<class T>\n"
+ "void Foo::fu@nc() {}\n";
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(singleDocument(contents, ""), &factory);
+ }
+
+private:
+ // Function for one of InsertDeclDef section cases
+ void insertToSectionDeclFromDef(const QByteArray &section, int sectionIndex)
+ {
+ QList<TestDocumentPtr> testDocuments;
+
+ QByteArray original;
+ QByteArray expected;
+ QByteArray sectionString = section + ":\n";
+ if (sectionIndex == 4)
+ sectionString.clear();
+
+ // Header File
+ original =
+ "class Foo\n"
+ "{\n"
+ "};\n";
+ expected =
+ "class Foo\n"
+ "{\n"
+ + sectionString +
+ " Foo();\n"
+ "@};\n";
+ testDocuments << CppTestDocument::create("file.h", original, expected);
+
+ // Source File
+ original =
+ "#include \"file.h\"\n"
+ "\n"
+ "Foo::Foo@()\n"
+ "{\n"
+ "}\n"
+ ;
+ expected = original;
+ testDocuments << CppTestDocument::create("file.cpp", original, expected);
+
+ InsertDeclFromDef factory;
+ QuickFixOperationTest(testDocuments, &factory, ProjectExplorer::HeaderPaths(), sectionIndex);
+ }
+};
+
+QObject *AddDeclarationForUndeclaredIdentifier::createTest()
+{
+ return new AddDeclarationForUndeclaredIdentifierTest;
+}
+
+QObject *InsertDeclFromDef::createTest()
+{
+ return new InsertDeclFromDefTest;
+}
+
+#endif // WITH_TESTS
+} // namespace
+
+void registerCreateDeclarationFromUseQuickfixes()
+{
+ CppQuickFixFactory::registerFactory<InsertDeclFromDef>();
+ CppQuickFixFactory::registerFactory<AddDeclarationForUndeclaredIdentifier>();
+}
+
+} // namespace CppEditor::Internal
+
+#ifdef WITH_TESTS
+#include <createdeclarationfromuse.moc>
+#endif