CppEditor: Move "create decl from use" quickfixes into dedicated files

Change-Id: Id93f4a5cdf7f2a36397458f3b00bb2a0cdefd69f
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2024-05-15 18:21:06 +02:00
parent b3e4d552d3
commit 44ab666928
10 changed files with 1272 additions and 1193 deletions

View File

@@ -109,6 +109,7 @@ add_qtc_plugin(CppEditor
quickfixes/cppquickfixsettings.cpp quickfixes/cppquickfixsettings.h
quickfixes/cppquickfixsettingspage.cpp quickfixes/cppquickfixsettingspage.h
quickfixes/cppquickfixsettingswidget.cpp quickfixes/cppquickfixsettingswidget.h
quickfixes/createdeclarationfromuse.cpp quickfixes/createdeclarationfromuse.h
quickfixes/insertfunctiondefinition.cpp quickfixes/insertfunctiondefinition.h
quickfixes/moveclasstoownfile.cpp quickfixes/moveclasstoownfile.h
quickfixes/movefunctiondefinition.cpp quickfixes/movefunctiondefinition.h

View File

@@ -247,6 +247,8 @@ QtcPlugin {
"cppquickfixsettingspage.h",
"cppquickfixsettingswidget.cpp",
"cppquickfixsettingswidget.h",
"createdeclarationfromuse.cpp",
"createdeclarationfromuse.h",
"insertfunctiondefinition.cpp",
"insertfunctiondefinition.h",
"moveclasstoownfile.cpp",

View File

@@ -1566,16 +1566,6 @@ void QuickfixTest::testGeneric_data()
<< CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
<< _("void @WhAt_TODO_hErE();\n")
<< _("void WhAtTODOHErE();\n");
QTest::newRow("AddLocalDeclaration_QTCREATORBUG-26004")
<< CppQuickFixFactoryPtr(new AddDeclarationForUndeclaredIdentifier)
<< _("void func() {\n"
" QStringList list;\n"
" @it = list.cbegin();\n"
"}\n")
<< _("void func() {\n"
" QStringList list;\n"
" auto it = list.cbegin();\n"
"}\n");
}
void QuickfixTest::testGeneric()
@@ -1624,478 +1614,6 @@ CppCodeStyleSettings CppCodeStyleSettingsChanger::currentSettings()
return CppToolsSettings::cppCodeStyle()->currentDelegate()->value().value<CppCodeStyleSettings>();
}
void QuickfixTest::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 QuickfixTest::testInsertMemberFromUse()
{
QFETCH(QByteArray, original);
QFETCH(QByteArray, expected);
QList<TestDocumentPtr> testDocuments({
CppTestDocument::create("file.h", original, expected)
});
AddDeclarationForUndeclaredIdentifier factory;
factory.setMembersOnly();
QuickFixOperationTest(testDocuments, &factory);
}
// 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);
}
/// Check from source file: Insert in header file.
void QuickfixTest::testInsertDeclFromDef()
{
insertToSectionDeclFromDef("public", 0);
insertToSectionDeclFromDef("public slots", 1);
insertToSectionDeclFromDef("protected", 2);
insertToSectionDeclFromDef("protected slots", 3);
insertToSectionDeclFromDef("private", 4);
insertToSectionDeclFromDef("private slots", 5);
}
void QuickfixTest::testInsertDeclFromDefTemplateFuncTypename()
{
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 QuickfixTest::testInsertDeclFromDefTemplateFuncInt()
{
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 QuickfixTest::testInsertDeclFromDefTemplateReturnType()
{
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 QuickfixTest::testInsertDeclFromDefNotTriggeredForTemplateFunc()
{
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);
}
void QuickfixTest::testAssignToLocalVariableTemplates()
{

View File

@@ -97,15 +97,6 @@ private slots:
void testGeneric_data();
void testGeneric();
void testInsertMemberFromUse_data();
void testInsertMemberFromUse();
void testInsertDeclFromDef();
void testInsertDeclFromDefTemplateFuncTypename();
void testInsertDeclFromDefTemplateFuncInt();
void testInsertDeclFromDefTemplateReturnType();
void testInsertDeclFromDefNotTriggeredForTemplateFunc();
void testAssignToLocalVariableTemplates();
void testExtractFunction_data();

View File

@@ -23,6 +23,7 @@
#include "cppquickfixprojectsettings.h"
#include "convertqt4connect.h"
#include "convertstringliteral.h"
#include "createdeclarationfromuse.h"
#include "insertfunctiondefinition.h"
#include "moveclasstoownfile.h"
#include "movefunctiondefinition.h"
@@ -128,79 +129,6 @@ const QList<CppQuickFixFactory *> &CppQuickFixFactory::cppQuickFixFactories()
namespace Internal {
// In the following anonymous namespace all functions are collected, which could be of interest for
// different quick fixes.
namespace {
QString nameString(const NameAST *name)
{
return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
}
static FullySpecifiedType typeOfExpr(const ExpressionAST *expr,
const CppRefactoringFilePtr &file,
const Snapshot &snapshot,
const LookupContext &context)
{
TypeOfExpression typeOfExpression;
typeOfExpression.init(file->cppDocument(), snapshot, context.bindings());
Scope *scope = file->scopeAt(expr->firstToken());
const QList<LookupItem> result = typeOfExpression(
file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess);
if (result.isEmpty())
return {};
SubstitutionEnvironment env;
env.setContext(context);
env.switchScope(result.first().scope());
ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
if (!con)
con = typeOfExpression.context().globalNamespace();
UseMinimalNames q(con);
env.enter(&q);
Control *control = context.bindings()->control().get();
return rewriteType(result.first().type(), &env, control);
}
// FIXME: Needs to consider the scope at the insertion site.
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);
}
} // anonymous namespace
namespace {
class InverseLogicalComparisonOp: public CppQuickFixOperation
@@ -1160,54 +1088,6 @@ void ConvertNumericLiteral::doMatch(const CppQuickFixInterface &interface, Quick
namespace {
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
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
QString declaration = getDeclaration();
if (!declaration.isEmpty()) {
ChangeSet changes;
changes.replace(currentFile->startOf(binaryAST),
currentFile->endOf(simpleNameAST),
declaration);
currentFile->setChangeSet(changes);
currentFile->apply();
}
}
private:
QString getDeclaration()
{
CppRefactoringChanges refactoring(snapshot());
CppRefactoringFilePtr currentFile = refactoring.cppFile(filePath());
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
const auto settings = CppQuickFixProjectsSettings::getQuickFixSettings(
ProjectExplorer::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;
};
} // anonymous namespace
@@ -1678,545 +1558,7 @@ void CompleteSwitchCaseStatement::doMatch(const CppQuickFixInterface &interface,
}
}
namespace {
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->setChangeSet(target);
targetFile->setOpenEditor(true, targetPosition);
targetFile->apply();
}
static QString generateDeclaration(const Function *function);
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;
};
} // anonymous namespace
void InsertDeclFromDef::doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
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);
}
}
QString InsertDeclOperation::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;
}
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);
const int targetPosition = targetFile->position(loc.line(), loc.column());
ChangeSet target;
target.insert(targetPosition, loc.prefix() + decl + ";\n");
targetFile->setChangeSet(target);
targetFile->apply();
}
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;
};
void AddDeclarationForUndeclaredIdentifier::doMatch(const CppQuickFixInterface &interface,
QuickFixOperations &result)
{
// 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);
};
CppModelManager::followSymbol(cursorInEditor, followSymbolFallback, false, false,
FollowSymbolMode::Exact,
CppModelManager::Backend::Builtin);
}
void AddDeclarationForUndeclaredIdentifier::collectOperations(
const CppQuickFixInterface &interface, TextEditor::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 AddDeclarationForUndeclaredIdentifier::handleCall(
const CallAST *call, const CppQuickFixInterface &interface,
TextEditor::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 (const auto returnExpr = (*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 (const auto simpleName = idExpr->name->asSimpleName()) {
maybeAddMember(interface, file->scopeAt(idExpr->firstToken()), "this",
returnTypeOrExpr, call, result);
}
}
bool AddDeclarationForUndeclaredIdentifier::checkForMemberInitializer(
const CppQuickFixInterface &interface, TextEditor::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 AddDeclarationForUndeclaredIdentifier::maybeAddMember(
const CppQuickFixInterface &interface, Scope *scope, const QByteArray &classTypeExpr,
const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::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 AddDeclarationForUndeclaredIdentifier::maybeAddStaticMember(
const CppQuickFixInterface &interface, const QualifiedNameAST *qualName,
const TypeOrExpr &typeOrExpr, const CallAST *call, TextEditor::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);
}
}
namespace {
@@ -4516,8 +3858,6 @@ void createCppQuickFixes()
new ConvertFromAndToPointer;
new ExtractFunction;
new ExtractLiteralAsParameter;
new InsertDeclFromDef;
new AddDeclarationForUndeclaredIdentifier;
new AssignToLocalVariable;
@@ -4530,6 +3870,7 @@ void createCppQuickFixes()
registerInsertFunctionDefinitionQuickfixes();
registerBringIdentifierIntoScopeQuickfixes();
registerConvertStringLiteralQuickfixes();
registerCreateDeclarationFromUseQuickfixes();
new OptimizeForLoop;

View File

@@ -250,47 +250,6 @@ private:
void doMatch(const CppQuickFixInterface &interface, QuickFixOperations &result) override;
};
/*!
Adds a declarations to a definition
*/
class InsertDeclFromDef: public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result) override;
};
class AddDeclarationForUndeclaredIdentifier : public CppQuickFixFactory
{
public:
void doMatch(const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result) override;
#ifdef WITH_TESTS
void setMembersOnly() { m_membersOnly = true; }
#endif
private:
void collectOperations(const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result);
void handleCall(const CPlusPlus::CallAST *call, const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result);
// Returns whether to still do other checks.
bool checkForMemberInitializer(const CppQuickFixInterface &interface,
TextEditor::QuickFixOperations &result);
void maybeAddMember(const CppQuickFixInterface &interface, CPlusPlus::Scope *scope,
const QByteArray &classTypeExpr, const TypeOrExpr &typeOrExpr,
const CPlusPlus::CallAST *call, TextEditor::QuickFixOperations &result);
void maybeAddStaticMember(
const CppQuickFixInterface &interface, const CPlusPlus::QualifiedNameAST *qualName,
const TypeOrExpr &typeOrExpr, const CPlusPlus::CallAST *call,
TextEditor::QuickFixOperations &result);
bool m_membersOnly = false;
};
/*!
Extracts the selected code and puts it to a function
*/

View File

@@ -3,10 +3,15 @@
#include "cppquickfixhelpers.h"
#include "../cppcodestylesettings.h"
#include "../cppprojectfile.h"
#include "../includeutils.h"
#include "cppquickfixassistant.h"
#include <cplusplus/CppRewriter.h>
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
using namespace CPlusPlus;
using namespace Utils;
@@ -153,4 +158,36 @@ CPlusPlus::Namespace *isNamespaceFunction(
return nullptr;
}
QString nameString(const CPlusPlus::NameAST *name)
{
return CppCodeStyleSettings::currentProjectCodeStyleOverview().prettyName(name->name);
}
CPlusPlus::FullySpecifiedType typeOfExpr(
const ExpressionAST *expr,
const CppRefactoringFilePtr &file,
const Snapshot &snapshot,
const LookupContext &context)
{
TypeOfExpression typeOfExpression;
typeOfExpression.init(file->cppDocument(), snapshot, context.bindings());
Scope *scope = file->scopeAt(expr->firstToken());
const QList<LookupItem> result
= typeOfExpression(file->textOf(expr).toUtf8(), scope, TypeOfExpression::Preprocess);
if (result.isEmpty())
return {};
SubstitutionEnvironment env;
env.setContext(context);
env.switchScope(result.first().scope());
ClassOrNamespace *con = typeOfExpression.context().lookupType(scope);
if (!con)
con = typeOfExpression.context().globalNamespace();
UseMinimalNames q(con);
env.enter(&q);
Control *control = context.bindings()->control().get();
return rewriteType(result.first().type(), &env, control);
}
} // namespace CppEditor::Internal

View File

@@ -4,6 +4,7 @@
#pragma once
#include "../cpprefactoringchanges.h"
#include "cppquickfixes.h"
#include <QStringList>
@@ -36,4 +37,12 @@ CPlusPlus::Class *isMemberFunction(
CPlusPlus::Namespace *isNamespaceFunction(
const CPlusPlus::LookupContext &context, CPlusPlus::Function *function);
QString nameString(const CPlusPlus::NameAST *name);
CPlusPlus::FullySpecifiedType typeOfExpr(
const CPlusPlus::ExpressionAST *expr,
const CppRefactoringFilePtr &file,
const CPlusPlus::Snapshot &snapshot,
const CPlusPlus::LookupContext &context);
} // namespace CppEditor::Internal

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,8 @@
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
namespace CppEditor::Internal {
void registerCreateDeclarationFromUseQuickfixes();
} // namespace CppEditor::Internal