kde-extraapps/kdevelop/languages/cpp/cppduchain/missingdeclarationassistant.cpp

409 lines
13 KiB
C++
Raw Normal View History

/*
Copyright 2009 David Nolden <david.nolden.kdevelop@art-master.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "missingdeclarationassistant.h"
#include <language/duchain/duchainlock.h>
#include <language/duchain/duchain.h>
#include <klocalizedstring.h>
#include <language/codegen/documentchangeset.h>
#include <language/duchain/types/constantintegraltype.h>
#include "typeutils.h"
#include <interfaces/icore.h>
#include <interfaces/iprojectcontroller.h>
#include "sourcemanipulation.h"
#include <language/duchain/duchainutils.h>
#include <interfaces/idocumentcontroller.h>
#include "cppduchain.h"
#include <interfaces/ilanguagecontroller.h>
#include <language/backgroundparser/backgroundparser.h>
#include <QTextDocument>
using namespace Cpp;
using namespace KDevelop;
class MissingDeclarationAction : public IAssistantAction
{
Q_OBJECT
public:
MissingDeclarationAction(const MissingDeclarationProblem::Ptr &problem)
: m_problem(problem)
{}
// returns a full string representing the resulting declaration, without scope or access specifiers.
virtual QString declarationString() const = 0;
protected:
/**
* @return the DUContext in which the declaration will be added.
*
* NOTE: The DUChain is locked when this is being called.
*/
virtual DUContext* targetContext() const = 0;
QString typeString(const AbstractType::Ptr& type) const
{
DUChainReadLocker lock;
if(!type) {
return "<no type>";
} else if(DUContext* container = targetContext()) {
return shortenedTypeString(type, container, 30);
} else {
return QString();
}
}
AbstractType::Ptr type(const AbstractType::Ptr& base) const
{
DUChainReadLocker lock;
AbstractType::Ptr ret = TypeUtils::removeConstants(base, m_problem->topContext());
if(ret) {
ret = TypeUtils::realTypeKeepAliases(ret);
TypeUtils::removeConstModifier(ret);
}
return ret;
}
AbstractType::Ptr assignedType() const
{
return type(m_problem->type->assigned.type.abstractType());
}
MissingDeclarationProblem::Ptr m_problem;
};
class CreateLocalDeclarationAction : public MissingDeclarationAction
{
Q_OBJECT
public:
CreateLocalDeclarationAction(const MissingDeclarationProblem::Ptr& problem)
: MissingDeclarationAction(problem)
{
}
virtual void execute()
{
DUChainReadLocker lock;
if(targetContext()) {
DocumentChangeSet changes;
SimpleCursor start = m_problem->rangeInCurrentRevision().start;
changes.addChange(DocumentChange(m_problem->url(), SimpleRange(start, start),
QString(), typeString(assignedType()) + " "));
lock.unlock();
changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange);
changes.applyAllChanges();
emit executed(this);
}
}
virtual QString description() const
{
return i18n("<b>local</b> variable");
}
virtual QString toolTip() const
{
return i18n("Create local declaration %1", declarationString());
}
virtual QString declarationString() const
{
return typeString(assignedType()) + " "
+ m_problem->type->identifier().toString();
}
virtual DUContext* targetContext() const
{
return m_problem->type->searchStartContext.data();
}
};
class CreateMemberDeclarationAction : public MissingDeclarationAction
{
Q_OBJECT
public:
CreateMemberDeclarationAction(const MissingDeclarationProblem::Ptr& problem,
Declaration::AccessPolicy access = Declaration::Public)
: MissingDeclarationAction(problem)
, m_access(access)
{
}
virtual void execute()
{
DUChainReadLocker lock;
DUContext* searchFrom = m_problem->type->searchStartContext.data();
DUContext* container = targetContext();
if(searchFrom && container) {
Cpp::SourceCodeInsertion ins(container->topContext());
ins.setContext(container);
ins.setAccess(m_access);
if(m_problem->type->isFunction) {
QList<Cpp::SourceCodeInsertion::SignatureItem> signature;
int num = 1;
QSet<QString> hadNames;
foreach(const OverloadResolver::Parameter& arg, m_problem->type->arguments) {
Cpp::SourceCodeInsertion::SignatureItem item;
item.type = type(arg.type);
item.name = QString("arg%1").arg(num);
kDebug() << "have declaration: " << arg.declaration.data();
if(arg.declaration.data())
{
// Find a unique name
QString baseName = arg.declaration.data()->identifier().identifier().str();
for(int a = 1; a < 1000; ++a)
{
if(!hadNames.contains(baseName))
{
item.name = baseName;
break;
}
QString name = QString(baseName + "%1").arg(a);
if(!hadNames.contains(name))
{
item.name = name;
break;
}
}
}
signature << item;
++num;
}
ins.insertFunctionDeclaration(m_problem->type->identifier().identifier().identifier().last(), returnType(), signature);
} else {
ins.insertVariableDeclaration(m_problem->type->identifier().identifier().identifier().last(), returnType());
}
lock.unlock();
ins.changes().setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange);
ins.changes().applyAllChanges();
const IndexedString localUrl = searchFrom->url();
const IndexedString changeUrl = container->url();
if(changeUrl != localUrl) {
ICore::self()->languageController()->backgroundParser()->addDocument(changeUrl);
ICore::self()->languageController()->backgroundParser()->addDocument(localUrl);
}
emit executed(this);
}
}
virtual QString description() const
{
return QString("<b>%1</b>").arg(accessString());
}
virtual QString containerString() const
{
DUChainReadLocker lock;
DUContext* container = targetContext();
if(container)
return container->scopeIdentifier(true).toString();
else
return QString();
}
virtual QString declarationString() const
{
DUChainReadLocker lock;
if(targetContext())
return returnString() + " " + m_problem->type->identifier().toString() + signatureString();
else
return QString();
}
virtual QString toolTip() const
{
return i18nc("%1: access, %2: identifier/signature", "Declare %1 %2",
accessString(), declarationString());
}
private:
virtual DUContext* targetContext() const
{
DUContext* container = m_problem->type->containerContext.data();
if(!container) {
Declaration* classDecl = localClassFromCodeContext(m_problem->type->searchStartContext.data());
if(classDecl)
container = classDecl->internalContext();
}
return container;
}
QString accessString() const
{
switch(m_access) {
case Declaration::Protected:
return "protected";
case Declaration::Private:
return "private";
case Declaration::Public:
return "public";
default:
return QString();
}
}
AbstractType::Ptr returnType() const
{
AbstractType::Ptr r = type(m_problem->type->convertedTo.type.abstractType());
if(r)
return r;
r = assignedType();
if(r) {
if(m_problem->type->isFunction)
{
//A function that something is assigned to must return a reference
ReferenceType::Ptr ref(new ReferenceType);
ref->setBaseType(r);
r = ref.cast<AbstractType>();
}
return r;
}
IntegralType* i = new IntegralType;
i->setDataType(IntegralType::TypeVoid);
return AbstractType::Ptr(i);
}
QString returnString() const
{
if (AbstractType::Ptr ret = returnType()) {
return typeString(ret);
} else {
return QString();
}
}
QString signatureString() const
{
if(!m_problem->type->isFunction) {
return QString();
}
QString ret = "(";
bool first = true;
foreach(const OverloadResolver::Parameter& arg, m_problem->type->arguments) {
if(!first)
ret += ", ";
else
first = false;
ret += typeString(arg.type);
}
ret += ")";
return ret;
}
Declaration::AccessPolicy m_access;
};
MissingDeclarationAssistant::MissingDeclarationAssistant(const MissingDeclarationProblem::Ptr& p)
: problem(p)
, type(p->type)
{
bool actionAdded = false;
DUChainReadLocker lock;
// support for namespaced types/variable names is broken atm, so just rather disable it
// i.e. writing 'foo::bar()' will just add 'bar' to the container
auto qualifiedIdentifier = p->type->identifier().identifier().identifier();
if (qualifiedIdentifier.isEmpty() || qualifiedIdentifier.count() > 1)
return;
kDebug() << "creating assistant for" << type->toString() << "assigned:" << type->assigned.toString();
DUContext* searchFrom = type->searchStartContext.data();
if (!searchFrom) {
return;
}
if(canCreateLocal(searchFrom)) {
//Action to just copy in the type, i.e. create local declaration
addAction(IAssistantAction::Ptr(new CreateLocalDeclarationAction(problem)));
}
Declaration* localClass = localClassFromCodeContext(searchFrom);
Declaration* targetClass = 0;
if (problem->type->containerContext.isValid()) {
targetClass = localClassFromCodeContext(problem->type->containerContext.data());
} else {
targetClass = localClass;
}
if (canAddTo(targetClass, localClass)
&& (type->convertedTo.isValid() || type->assigned.isValid() || type->isFunction))
{
// public is always possible
CreateMemberDeclarationAction* publicAction = new CreateMemberDeclarationAction(problem, Declaration::Public);
if (actionAdded) {
// place label between first action and the following actions
const QString label = i18nc("assistant, declare member in class identified by %1",
"member of <code>%1</code>:",
Qt::escape(publicAction->containerString()));
addAction(IAssistantAction::Ptr(new AssistantLabelAction(label)));
} else {
// set the title manually
m_title = i18nc("assistant, declare %1 as member of class identified by %2",
"Declare <code>'%1'</code> as member of <code>%2</code>",
publicAction->declarationString(),
Qt::escape(publicAction->containerString()));
}
if(localClass == targetClass) {
//Actions to create a declaration within the local class
addAction(IAssistantAction::Ptr(new CreateMemberDeclarationAction(problem, Declaration::Private)));
addAction(IAssistantAction::Ptr(new CreateMemberDeclarationAction(problem, Declaration::Protected)));
} else if (localClass && targetClass
&& TypeUtils::isPublicBaseClass(localClass->type<CppClassType>(),
targetClass->type<CppClassType>(),
problem->topContext()))
{
//if we are in a subclass, we can provide the protected option
addAction(IAssistantAction::Ptr(new CreateMemberDeclarationAction(problem, Declaration::Protected)));
}
// public is always possible
addAction(IAssistantAction::Ptr(publicAction));
}
if(!actions().isEmpty() && m_title.isEmpty()) {
MissingDeclarationAction* action = dynamic_cast<MissingDeclarationAction*>(actions().last().data());
Q_ASSERT(action);
m_title = i18n("Declare <code>'%1'</code> as", action->declarationString());
}
}
bool MissingDeclarationAssistant::canCreateLocal(DUContext* searchFrom) const
{
return !type->containerContext.data() && searchFrom->type() == DUContext::Other
&& type->assigned.type.isValid() && !type->assigned.type.type<DelayedType>()
&& !type->convertedTo.isValid();
}
bool MissingDeclarationAssistant::canAddTo(Declaration* toClass, Declaration* fromClass) const
{
if (!toClass) {
return false;
}
// same file, should be possible
if (fromClass && fromClass->url() == toClass->url()) {
return true;
}
// otherwise only if we currently edit the file, or if it's in an opened project
const KUrl url = toClass->url().toUrl();
return ICore::self()->projectController()->findProjectForUrl(url)
|| ICore::self()->documentController()->documentForUrl(url);
}
#include "missingdeclarationassistant.moc"