cppvirtualfunctionassistprovider.cpp 14.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** 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 Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/


#include "cppvirtualfunctionassistprovider.h"

#include "cppeditorconstants.h"
#include "cppelementevaluator.h"
35
#include "cppvirtualfunctionproposalitem.h"
36 37 38 39 40 41 42

#include <cplusplus/Icons.h>
#include <cplusplus/Overview.h>

#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>

43 44
#include <cpptools/symbolfinder.h>

45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
#include <texteditor/codeassist/basicproposalitemlistmodel.h>
#include <texteditor/codeassist/genericproposal.h>
#include <texteditor/codeassist/genericproposalwidget.h>
#include <texteditor/codeassist/iassistinterface.h>
#include <texteditor/codeassist/iassistprocessor.h>
#include <texteditor/codeassist/iassistproposal.h>

#include <utils/qtcassert.h>

using namespace CPlusPlus;
using namespace CppEditor::Internal;
using namespace TextEditor;

/// Activate current item with the same shortcut that is configured for Follow Symbol Under Cursor.
/// This is limited to single-key shortcuts without modifiers.
class VirtualFunctionProposalWidget : public GenericProposalWidget
{
public:
    VirtualFunctionProposalWidget(bool openInSplit)
    {
        const char *id = openInSplit
            ? TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT
            : TextEditor::Constants::FOLLOW_SYMBOL_UNDER_CURSOR;
        if (Core::Command *command = Core::ActionManager::command(id))
            m_sequence = command->keySequence();
    }

protected:
    bool eventFilter(QObject *o, QEvent *e)
    {
        if (e->type() == QEvent::ShortcutOverride && m_sequence.count() == 1) {
            QKeyEvent *ke = static_cast<QKeyEvent *>(e);
            const QKeySequence seq(ke->key());
            if (seq == m_sequence) {
                activateCurrentProposalItem();
                e->accept();
                return true;
            }
        }
        return GenericProposalWidget::eventFilter(o, e);
    }

private:
    QKeySequence m_sequence;
};

class VirtualFunctionProposal : public GenericProposal
{
public:
    VirtualFunctionProposal(int cursorPos, IGenericProposalModel *model, bool openInSplit)
        : GenericProposal(cursorPos, model)
        , m_openInSplit(openInSplit)
    {}

    bool isFragile() const
    { return true; }

    IAssistProposalWidget *createWidget() const
    { return new VirtualFunctionProposalWidget(m_openInSplit); }

private:
    bool m_openInSplit;
};

class VirtualFunctionsAssistProcessor : public IAssistProcessor
{
public:
112 113
    VirtualFunctionsAssistProcessor(const VirtualFunctionAssistProvider::Parameters &params)
        : m_params(params)
114 115
    {}

116
    IAssistProposal *immediateProposal(const TextEditor::IAssistInterface *)
117
    {
118
        QTC_ASSERT(m_params.function, return 0);
119 120 121 122 123 124 125

        BasicProposalItem *hintItem = new VirtualFunctionProposalItem(CPPEditorWidget::Link());
        hintItem->setText(QCoreApplication::translate("VirtualFunctionsAssistProcessor",
                                                      "...searching overrides"));
        hintItem->setOrder(-1000);

        QList<BasicProposalItem *> items;
126
        items << itemFromSymbol(maybeDefinitionFor(m_params.function));
127
        items << hintItem;
128
        return new VirtualFunctionProposal(m_params.cursorPosition,
129
                                           new BasicProposalItemListModel(items),
130
                                           m_params.openInNextSplit);
131 132
    }

133
    IAssistProposal *perform(const IAssistInterface *)
134
    {
135
        QTC_ASSERT(m_params.function, return 0);
136
        QTC_ASSERT(m_params.staticClass, return 0);
137
        QTC_ASSERT(!m_params.snapshot.isEmpty(), return 0);
138

139 140 141 142 143
        Class *functionsClass = m_finder.findMatchingClassDeclaration(m_params.function,
                                                                      m_params.snapshot);
        if (!functionsClass)
            return 0;

144 145
        const QList<Symbol *> overrides = FunctionHelper::overrides(
            m_params.function, functionsClass, m_params.staticClass, m_params.snapshot);
146 147 148
        if (overrides.isEmpty())
            return 0;

149 150
        QList<BasicProposalItem *> items;
        foreach (Symbol *symbol, overrides)
151 152
            items << itemFromSymbol(maybeDefinitionFor(symbol));
        items.first()->setOrder(1000); // Ensure top position for function of static type
153

154
        return new VirtualFunctionProposal(m_params.cursorPosition,
155
                                           new BasicProposalItemListModel(items),
156
                                           m_params.openInNextSplit);
157 158
    }

159 160 161
private:
    Symbol *maybeDefinitionFor(Symbol *symbol)
    {
162
        if (Function *definition = m_finder.findMatchingDefinition(symbol, m_params.snapshot))
163 164 165 166 167
            return definition;
        return symbol;
    }

    BasicProposalItem *itemFromSymbol(Symbol *symbol) const
168 169 170 171
    {
        const QString text = m_overview.prettyName(LookupContext::fullyQualifiedName(symbol));
        const CPPEditorWidget::Link link = CPPEditorWidget::linkToSymbol(symbol);

172
        BasicProposalItem *item = new VirtualFunctionProposalItem(link, m_params.openInNextSplit);
173 174 175 176 177 178
        item->setText(text);
        item->setIcon(m_icons.iconForSymbol(symbol));

        return item;
    }

179
    VirtualFunctionAssistProvider::Parameters m_params;
180 181
    Overview m_overview;
    Icons m_icons;
182
    CppTools::SymbolFinder m_finder;
183 184 185 186 187 188
};

VirtualFunctionAssistProvider::VirtualFunctionAssistProvider()
{
}

189
bool VirtualFunctionAssistProvider::configure(const Parameters &parameters)
190
{
191
    m_params = parameters;
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    return true;
}

bool VirtualFunctionAssistProvider::isAsynchronous() const
{
    return true;
}

bool VirtualFunctionAssistProvider::supportsEditor(const Core::Id &editorId) const
{
    return editorId == CppEditor::Constants::CPPEDITOR_ID;
}

IAssistProcessor *VirtualFunctionAssistProvider::createProcessor() const
{
207
    return new VirtualFunctionsAssistProcessor(m_params);
208 209 210 211
}

enum VirtualType { Virtual, PureVirtual };

212
static bool isVirtualFunction_helper(const Function *function,
213
                                     const LookupContext &context,
214 215 216 217 218
                                     VirtualType virtualType)
{
    if (!function)
        return false;

219 220 221 222
    if (virtualType == PureVirtual)
        return function->isPureVirtual();

    if (function->isVirtual())
223 224
        return true;

225 226 227 228 229 230 231 232 233 234 235 236 237 238
    QList<LookupItem> results = context.lookup(function->name(), function->enclosingScope());
    if (!results.isEmpty()) {
        const bool isDestructor = function->name()->isDestructorNameId();
        foreach (const LookupItem &item, results) {
            if (Symbol *symbol = item.declaration()) {
                if (Function *functionType = symbol->type()->asFunctionType()) {
                    if (functionType->name()->isDestructorNameId() != isDestructor)
                        continue;
                    if (functionType == function) // already tested
                        continue;
                    if (functionType->isFinal())
                        return false;
                    if (functionType->isVirtual())
                        return true;
239 240 241 242 243 244 245 246
                }
            }
        }
    }

    return false;
}

247
bool FunctionHelper::isVirtualFunction(const Function *function, const LookupContext &context)
248
{
249
    return isVirtualFunction_helper(function, context, Virtual);
250 251
}

252
bool FunctionHelper::isPureVirtualFunction(const Function *function, const LookupContext &context)
253
{
254
    return isVirtualFunction_helper(function, context, PureVirtual);
255 256
}

257
QList<Symbol *> FunctionHelper::overrides(Function *function, Class *functionsClass,
258
                                          Class *staticClass, const Snapshot &snapshot)
259 260
{
    QList<Symbol *> result;
261
    QTC_ASSERT(function && functionsClass && staticClass, return result);
262 263 264

    FullySpecifiedType referenceType = function->type();
    const Name *referenceName = function->name();
265
    QTC_ASSERT(referenceName && referenceType.isValid(), return result);
266 267

    // Find overrides
268
    CppEditor::Internal::CppClass cppClass = CppClass(functionsClass);
269
    cppClass.lookupDerived(staticClass, snapshot);
270 271 272 273 274 275 276

    QList<CppClass> l;
    l << cppClass;

    while (!l.isEmpty()) {
        // Add derived
        CppClass clazz = l.takeFirst();
277 278 279 280 281

        QTC_ASSERT(clazz.declaration, continue);
        Class *c = clazz.declaration->asClass();
        QTC_ASSERT(c, continue);

282 283 284 285 286 287 288 289 290
        foreach (const CppClass &d, clazz.derived) {
            if (!l.contains(d))
                l << d;
        }

        // Check member functions
        for (int i = 0, total = c->memberCount(); i < total; ++i) {
            Symbol *candidate = c->memberAt(i);
            const Name *candidateName = candidate->name();
291 292 293
            const FullySpecifiedType candidateType = candidate->type();
            if (!candidateName || !candidateType.isValid())
                continue;
294 295 296 297 298 299 300
            if (candidateName->isEqualTo(referenceName) && candidateType.isEqualTo(referenceType))
                result << candidate;
        }
    }

    return result;
}
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

#ifdef WITH_TESTS
#include "cppeditorplugin.h"

#include <QList>
#include <QTest>

namespace CppEditor {
namespace Internal {

enum Virtuality
{
    NotVirtual,
    Virtual,
    PureVirtual
};
typedef QList<Virtuality> VirtualityList;
318 319 320 321 322 323 324 325
} // Internal namespace
} // CppEditor namespace

Q_DECLARE_METATYPE(CppEditor::Internal::Virtuality)
Q_DECLARE_METATYPE(CppEditor::Internal::VirtualityList)

namespace CppEditor {
namespace Internal {
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

void CppEditorPlugin::test_functionhelper_virtualFunctions()
{
    // Create and parse document
    QFETCH(QByteArray, source);
    QFETCH(VirtualityList, virtualityList);
    Document::Ptr document = Document::create(QLatin1String("virtuals"));
    document->setUtf8Source(source);
    document->check(); // calls parse();
    QCOMPARE(document->diagnosticMessages().size(), 0);
    QVERIFY(document->translationUnit()->ast());

    // Iterate through Function symbols
    Snapshot snapshot;
    snapshot.insert(document);
341
    const LookupContext context(document, snapshot);
342 343 344 345 346 347 348
    Control *control = document->translationUnit()->control();
    Symbol **end = control->lastSymbol();
    for (Symbol **it = control->firstSymbol(); it != end; ++it) {
        const CPlusPlus::Symbol *symbol = *it;
        if (const Function *function = symbol->asFunction()) {
            QTC_ASSERT(!virtualityList.isEmpty(), return);
            Virtuality virtuality = virtualityList.takeFirst();
349 350
            if (FunctionHelper::isVirtualFunction(function, context)) {
                if (FunctionHelper::isPureVirtualFunction(function, context))
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
                    QCOMPARE(virtuality, PureVirtual);
                else
                    QCOMPARE(virtuality, Virtual);
            } else {
                QCOMPARE(virtuality, NotVirtual);
            }
        }
    }
    QVERIFY(virtualityList.isEmpty());
}

void CppEditorPlugin::test_functionhelper_virtualFunctions_data()
{
    typedef QByteArray _;
    QTest::addColumn<QByteArray>("source");
    QTest::addColumn<VirtualityList>("virtualityList");

    QTest::newRow("none")
            << _("struct None { void foo() {} };\n")
            << (VirtualityList() << NotVirtual);

    QTest::newRow("single-virtual")
            << _("struct V { virtual void foo() {} };\n")
            << (VirtualityList() << Virtual);

    QTest::newRow("single-pure-virtual")
            << _("struct PV { virtual void foo() = 0; };\n")
            << (VirtualityList() << PureVirtual);

    QTest::newRow("virtual-derived-with-specifier")
            << _("struct Base { virtual void foo() {} };\n"
                 "struct Derived : Base { virtual void foo() {} };\n")
            << (VirtualityList() << Virtual << Virtual);

    QTest::newRow("virtual-derived-implicit")
            << _("struct Base { virtual void foo() {} };\n"
                 "struct Derived : Base { void foo() {} };\n")
            << (VirtualityList() << Virtual << Virtual);

    QTest::newRow("not-virtual-then-virtual")
            << _("struct Base { void foo() {} };\n"
                 "struct Derived : Base { virtual void foo() {} };\n")
            << (VirtualityList() << NotVirtual << Virtual);

    QTest::newRow("virtual-final-not-virtual")
            << _("struct Base { virtual void foo() {} };\n"
                 "struct Derived : Base { void foo() final {} };\n"
                 "struct Derived2 : Derived { void foo() {} };")
            << (VirtualityList() << Virtual << Virtual << NotVirtual);

    QTest::newRow("virtual-then-pure")
            << _("struct Base { virtual void foo() {} };\n"
                 "struct Derived : Base { virtual void foo() = 0; };\n"
                 "struct Derived2 : Derived { void foo() {} };")
            << (VirtualityList() << Virtual << PureVirtual << Virtual);

    QTest::newRow("virtual-virtual-final-not-virtual")
            << _("struct Base { virtual void foo() {} };\n"
                 "struct Derived : Base { virtual void foo() final {} };\n"
                 "struct Derived2 : Derived { void foo() {} };")
            << (VirtualityList() << Virtual << Virtual << NotVirtual);
412 413 414 415

    QTest::newRow("ctor-virtual-dtor")
            << _("struct Base { Base() {} virtual ~Base() {} };\n")
            << (VirtualityList() << NotVirtual << Virtual);
416 417 418 419 420 421
}

} // namespace Internal
} // namespace CppEditor

#endif