File indexing completed on 2024-05-12 04:39:08

0001 /*
0002     SPDX-FileCopyrightText: 2009 David Nolden <david.nolden.kdevelop@art-master.de>
0003     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "adaptsignatureassistant.h"
0009 
0010 #include <interfaces/icore.h>
0011 #include <language/assistant/renameaction.h>
0012 #include <language/duchain/duchainutils.h>
0013 #include <language/duchain/functiondefinition.h>
0014 #include <language/duchain/classfunctiondeclaration.h>
0015 #include <language/duchain/types/functiontype.h>
0016 
0017 #include <KTextEditor/Document>
0018 #include <KTextEditor/View>
0019 
0020 #include "../util/clangdebug.h"
0021 
0022 using namespace KDevelop;
0023 
0024 namespace {
0025 Declaration *getDeclarationAtCursor(const KTextEditor::Cursor &cursor, const QUrl &documentUrl)
0026 {
0027     ENSURE_CHAIN_READ_LOCKED
0028     ReferencedTopDUContext top(DUChainUtils::standardContextForUrl(documentUrl));
0029     if (!top) {
0030         clangDebug() << "no context found for document" << documentUrl;
0031         return nullptr;
0032     }
0033     const auto *context = top->findContextAt(top->transformToLocalRevision(cursor), true);
0034     return context->type() == DUContext::Function ? context->owner() : nullptr;
0035 }
0036 
0037 bool isConstructor(const Declaration *functionDecl)
0038 {
0039     auto classFun = dynamic_cast<const ClassFunctionDeclaration*>(DUChainUtils::declarationForDefinition(const_cast<Declaration*>(functionDecl)));
0040     return classFun && classFun->isConstructor();
0041 }
0042 
0043 Signature getDeclarationSignature(const Declaration *functionDecl, const DUContext *functionCtxt, bool includeDefaults)
0044 {
0045     ENSURE_CHAIN_READ_LOCKED
0046     int pos = 0;
0047     Signature signature;
0048     const auto* abstractFunDecl = dynamic_cast<const AbstractFunctionDeclaration*>(functionDecl);
0049     const auto localDeclarations = functionCtxt->localDeclarations();
0050     const int localDeclarationsCount = localDeclarations.size();
0051     signature.defaultParams.reserve(localDeclarationsCount);
0052     signature.parameters.reserve(localDeclarationsCount);
0053     for (Declaration * parameter : localDeclarations) {
0054         signature.defaultParams << (includeDefaults ? abstractFunDecl->defaultParameterForArgument(pos).str() : QString());
0055         signature.parameters << qMakePair(parameter->indexedType(), parameter->identifier().identifier().str());
0056         ++pos;
0057     }
0058     signature.isConst = functionDecl->abstractType() && functionDecl->abstractType()->modifiers() & AbstractType::ConstModifier;
0059 
0060     if (!isConstructor(functionDecl)) {
0061         if (auto funType = functionDecl->type<FunctionType>()) {
0062             signature.returnType = IndexedType(funType->returnType());
0063         }
0064     }
0065     return signature;
0066 }
0067 }
0068 
0069 AdaptSignatureAssistant::AdaptSignatureAssistant(ILanguageSupport* supportedLanguage)
0070     : StaticAssistant(supportedLanguage)
0071 {
0072 }
0073 
0074 QString AdaptSignatureAssistant::title() const
0075 {
0076     return i18n("Adapt Signature");
0077 }
0078 
0079 void AdaptSignatureAssistant::reset()
0080 {
0081     doHide();
0082     clearActions();
0083 
0084     m_editingDefinition = {};
0085     m_declarationName = {};
0086     m_otherSideId = DeclarationId();
0087     m_otherSideTopContext = {};
0088     m_otherSideContext = {};
0089     m_oldSignature = {};
0090     m_document = nullptr;
0091     m_view.clear();
0092 }
0093 
0094 void AdaptSignatureAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText)
0095 {
0096     reset();
0097 
0098     m_document = doc;
0099     m_lastEditPosition = invocationRange.end();
0100 
0101     KTextEditor::Range sigAssistRange = invocationRange;
0102     if (!removedText.isEmpty()) {
0103         sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start());
0104     }
0105 
0106     DUChainReadLocker lock(DUChain::lock(), 300);
0107     if (!lock.locked()) {
0108         clangDebug() << "failed to lock duchain in time";
0109         return;
0110     }
0111     KTextEditor::Range simpleInvocationRange = KTextEditor::Range(sigAssistRange);
0112     Declaration* funDecl = getDeclarationAtCursor(simpleInvocationRange.start(), m_document->url());
0113     if (!funDecl || !funDecl->type<FunctionType>()) {
0114         clangDebug() << "No function at cursor";
0115         return;
0116     }
0117     /*
0118        TODO: Port?
0119        if(QtFunctionDeclaration* classFun = dynamic_cast<QtFunctionDeclaration*>(funDecl)) {
0120        if (classFun->isSignal()) {
0121         // do not offer to change signature of a signal, as the implementation will be generated by moc
0122         return;
0123        }
0124        }
0125      */
0126 
0127     Declaration* otherSide = nullptr;
0128     if (auto* definition = dynamic_cast<FunctionDefinition*>(funDecl)) {
0129         m_editingDefinition = true;
0130         otherSide = definition->declaration();
0131     } else if (auto* definition = FunctionDefinition::definition(funDecl)) {
0132         m_editingDefinition = false;
0133         otherSide = definition;
0134     }
0135     if (!otherSide) {
0136         clangDebug() << "no other side for signature found";
0137         return;
0138     }
0139     m_otherSideContext = DUContextPointer(DUChainUtils::functionContext(otherSide));
0140     if (!m_otherSideContext) {
0141         clangDebug() << "no context for other side found";
0142         return;
0143     }
0144     m_declarationName = funDecl->identifier();
0145     m_otherSideId = otherSide->id();
0146     m_otherSideTopContext = ReferencedTopDUContext(otherSide->topContext());
0147     m_oldSignature = getDeclarationSignature(otherSide, m_otherSideContext.data(), true);
0148 
0149     //Schedule an update, to make sure the ranges match
0150     DUChain::self()->updateContextForUrl(m_otherSideTopContext->url(), TopDUContext::AllDeclarationsAndContexts);
0151 }
0152 
0153 bool AdaptSignatureAssistant::isUseful() const
0154 {
0155     return !m_declarationName.isEmpty() && m_otherSideId.isValid() && !actions().isEmpty();
0156 }
0157 
0158 bool AdaptSignatureAssistant::getSignatureChanges(const Signature& newSignature, QList<int>& oldPositions) const
0159 {
0160     bool changed = false;
0161     oldPositions.reserve(oldPositions.size() + newSignature.parameters.size());
0162     for (int i = 0; i < newSignature.parameters.size(); ++i) {
0163         oldPositions.append(-1);
0164     }
0165 
0166     for (int curNewParam = newSignature.parameters.size() - 1; curNewParam >= 0; --curNewParam) {
0167         int foundAt = -1;
0168 
0169         for (int curOldParam = m_oldSignature.parameters.size() - 1; curOldParam >= 0; --curOldParam) {
0170             if (newSignature.parameters[curNewParam].first != m_oldSignature.parameters[curOldParam].first) {
0171                 continue;  //Different type == different parameters
0172             }
0173             if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second || curOldParam == curNewParam) {
0174                 //given the same type and either the same position or the same name, it's (probably) the same argument
0175                 foundAt = curOldParam;
0176 
0177                 if (newSignature.parameters[curNewParam].second != m_oldSignature.parameters[curOldParam].second || curOldParam != curNewParam) {
0178                     changed = true;  //Either the name changed at this position, or position of this name has changed
0179                 }
0180                 if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second) {
0181                     break;  //Found an argument with the same name and type, no need to look further
0182                 }
0183                 //else: position/type match, but name match will trump, allowing: (int i=0, int j=1) => (int j=1, int i=0)
0184             }
0185         }
0186 
0187         if (foundAt < 0) {
0188             changed = true;
0189         }
0190         oldPositions[curNewParam] = foundAt;
0191     }
0192 
0193     if (newSignature.parameters.size() != m_oldSignature.parameters.size()) {
0194         changed = true;
0195     }
0196     if (newSignature.isConst != m_oldSignature.isConst) {
0197         changed = true;
0198     }
0199     if (newSignature.returnType != m_oldSignature.returnType) {
0200         changed = true;
0201     }
0202     return changed;
0203 }
0204 
0205 void AdaptSignatureAssistant::setDefaultParams(Signature& newSignature, const QList<int>& oldPositions) const
0206 {
0207     bool hadDefaultParam = false;
0208     for (int i = 0; i < newSignature.defaultParams.size(); ++i) {
0209         const auto oldPos = oldPositions[i];
0210         if (oldPos == -1) {
0211             // default-initialize new argument if we encountered a previous default param
0212             if (hadDefaultParam) {
0213                 newSignature.defaultParams[i] = QStringLiteral("{} /* TODO */");
0214             }
0215         } else {
0216             newSignature.defaultParams[i] = m_oldSignature.defaultParams[oldPos];
0217             hadDefaultParam = hadDefaultParam || !newSignature.defaultParams[i].isEmpty();
0218         }
0219     }
0220 }
0221 
0222 QList<RenameAction*> AdaptSignatureAssistant::getRenameActions(const Signature &newSignature, const QList<int> &oldPositions) const
0223 {
0224     ENSURE_CHAIN_READ_LOCKED
0225     QList<RenameAction*> renameActions;
0226     if (!m_otherSideContext) {
0227         return renameActions;
0228     }
0229     const auto oldDeclarations = m_otherSideContext->localDeclarations();
0230     for (int i = newSignature.parameters.size() - 1; i >= 0; --i) {
0231         if (oldPositions[i] == -1) {
0232             continue;  //new parameter
0233         }
0234         Declaration *renamedDecl = oldDeclarations.value(oldPositions[i]);
0235         if (!renamedDecl)
0236             continue;
0237         if (newSignature.parameters[i].second != m_oldSignature.parameters[oldPositions[i]].second) {
0238             const auto uses = renamedDecl->uses();
0239             if (!uses.isEmpty()) {
0240                 renameActions << new RenameAction(renamedDecl->identifier(), newSignature.parameters[i].second,
0241                                                   RevisionedFileRanges::convert(uses));
0242             }
0243         }
0244     }
0245 
0246     return renameActions;
0247 }
0248 
0249 void AdaptSignatureAssistant::updateReady(const KDevelop::IndexedString& document, const KDevelop::ReferencedTopDUContext& top)
0250 {
0251     if (!top || !m_document || document.toUrl() != m_document->url() || top->url() != IndexedString(m_document->url())) {
0252         return;
0253     }
0254     clearActions();
0255 
0256     DUChainReadLocker lock;
0257 
0258     Declaration *functionDecl = getDeclarationAtCursor(m_lastEditPosition, m_document->url());
0259     if (!functionDecl || functionDecl->identifier() != m_declarationName) {
0260         clangDebug() << "No function found at" << m_document->url() << m_lastEditPosition;
0261         return;
0262     }
0263     DUContext *functionCtxt = DUChainUtils::functionContext(functionDecl);
0264     if (!functionCtxt) {
0265         clangDebug() << "No function context found for" << functionDecl->toString();
0266         return;
0267     }
0268 #if 0 // TODO: Port
0269     if (QtFunctionDeclaration * classFun = dynamic_cast<QtFunctionDeclaration*>(functionDecl)) {
0270         if (classFun->isSignal()) {
0271             // do not offer to change signature of a signal, as the implementation will be generated by moc
0272             return;
0273         }
0274     }
0275 #endif
0276 
0277     //ParseJob having finished, get the signature that was modified
0278     Signature newSignature = getDeclarationSignature(functionDecl, functionCtxt, false);
0279 
0280     //Check for changes between m_oldSignature and newSignature, use oldPositions to store old<->new param index mapping
0281     QList<int> oldPositions;
0282     if (!getSignatureChanges(newSignature, oldPositions)) {
0283         reset();
0284         clangDebug() << "no changes to signature";
0285         return; //No changes to signature
0286     }
0287     QList<RenameAction*> renameActions;
0288     if (m_editingDefinition) {
0289         setDefaultParams(newSignature, oldPositions); //restore default parameters before updating the declarations
0290     } else {
0291         renameActions = getRenameActions(newSignature, oldPositions);  //rename as needed when updating the definition
0292     }
0293     IAssistantAction::Ptr action(new AdaptSignatureAction(m_otherSideId, m_otherSideTopContext,
0294                                                           m_oldSignature, newSignature,
0295                                                           m_editingDefinition, renameActions));
0296     connect(action.data(), &IAssistantAction::executed,
0297             this, &AdaptSignatureAssistant::reset);
0298     addAction(action);
0299     emit actionsChanged();
0300 }
0301 
0302 KTextEditor::Range AdaptSignatureAssistant::displayRange() const
0303 {
0304     if (!m_document) {
0305         return {};
0306     }
0307 
0308     auto s = m_lastEditPosition;
0309     KTextEditor::Range ran = {s.line(), 0, s.line(), m_document->lineLength(s.line())};
0310     return ran;
0311 }
0312 
0313 #include "moc_adaptsignatureassistant.cpp"