/* Copyright 2009 David Nolden 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 #include #include #include #include #include "typeutils.h" #include #include #include "sourcemanipulation.h" #include #include #include "cppduchain.h" #include #include #include 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 ""; } 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("local 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 signature; int num = 1; QSet 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("%1").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(); } 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 %1:", 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 '%1' as member of %2", 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(), targetClass->type(), 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(actions().last().data()); Q_ASSERT(action); m_title = i18n("Declare '%1' 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() && !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"