1 /*
2     SPDX-FileCopyrightText: 2009 David Nolden <david.nolden.kdevelop@art-master.de>
3     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
4 
5     SPDX-License-Identifier: LGPL-2.0-only
6 */
7 
8 #include "adaptsignatureassistant.h"
9 
10 #include <interfaces/icore.h>
11 #include <language/assistant/renameaction.h>
12 #include <language/duchain/duchainutils.h>
13 #include <language/duchain/functiondefinition.h>
14 #include <language/duchain/classfunctiondeclaration.h>
15 #include <language/duchain/types/functiontype.h>
16 
17 #include <KTextEditor/Document>
18 #include <KTextEditor/View>
19 
20 #include "../util/clangdebug.h"
21 
22 using namespace KDevelop;
23 
24 namespace {
getDeclarationAtCursor(const KTextEditor::Cursor & cursor,const QUrl & documentUrl)25 Declaration *getDeclarationAtCursor(const KTextEditor::Cursor &cursor, const QUrl &documentUrl)
26 {
27     ENSURE_CHAIN_READ_LOCKED
28     ReferencedTopDUContext top(DUChainUtils::standardContextForUrl(documentUrl));
29     if (!top) {
30         clangDebug() << "no context found for document" << documentUrl;
31         return nullptr;
32     }
33     const auto *context = top->findContextAt(top->transformToLocalRevision(cursor), true);
34     return context->type() == DUContext::Function ? context->owner() : nullptr;
35 }
36 
isConstructor(const Declaration * functionDecl)37 bool isConstructor(const Declaration *functionDecl)
38 {
39     auto classFun = dynamic_cast<const ClassFunctionDeclaration*>(DUChainUtils::declarationForDefinition(const_cast<Declaration*>(functionDecl)));
40     return classFun && classFun->isConstructor();
41 }
42 
getDeclarationSignature(const Declaration * functionDecl,const DUContext * functionCtxt,bool includeDefaults)43 Signature getDeclarationSignature(const Declaration *functionDecl, const DUContext *functionCtxt, bool includeDefaults)
44 {
45     ENSURE_CHAIN_READ_LOCKED
46     int pos = 0;
47     Signature signature;
48     const auto* abstractFunDecl = dynamic_cast<const AbstractFunctionDeclaration*>(functionDecl);
49     const auto localDeclarations = functionCtxt->localDeclarations();
50     const int localDeclarationsCount = localDeclarations.size();
51     signature.defaultParams.reserve(localDeclarationsCount);
52     signature.parameters.reserve(localDeclarationsCount);
53     for (Declaration * parameter : localDeclarations) {
54         signature.defaultParams << (includeDefaults ? abstractFunDecl->defaultParameterForArgument(pos).str() : QString());
55         signature.parameters << qMakePair(parameter->indexedType(), parameter->identifier().identifier().str());
56         ++pos;
57     }
58     signature.isConst = functionDecl->abstractType() && functionDecl->abstractType()->modifiers() & AbstractType::ConstModifier;
59 
60     if (!isConstructor(functionDecl)) {
61         if (auto funType = functionDecl->type<FunctionType>()) {
62             signature.returnType = IndexedType(funType->returnType());
63         }
64     }
65     return signature;
66 }
67 }
68 
AdaptSignatureAssistant(ILanguageSupport * supportedLanguage)69 AdaptSignatureAssistant::AdaptSignatureAssistant(ILanguageSupport* supportedLanguage)
70     : StaticAssistant(supportedLanguage)
71 {
72 }
73 
title() const74 QString AdaptSignatureAssistant::title() const
75 {
76     return i18n("Adapt Signature");
77 }
78 
reset()79 void AdaptSignatureAssistant::reset()
80 {
81     doHide();
82     clearActions();
83 
84     m_editingDefinition = {};
85     m_declarationName = {};
86     m_otherSideId = DeclarationId();
87     m_otherSideTopContext = {};
88     m_otherSideContext = {};
89     m_oldSignature = {};
90     m_document = nullptr;
91     m_view.clear();
92 }
93 
textChanged(KTextEditor::Document * doc,const KTextEditor::Range & invocationRange,const QString & removedText)94 void AdaptSignatureAssistant::textChanged(KTextEditor::Document* doc, const KTextEditor::Range& invocationRange, const QString& removedText)
95 {
96     reset();
97 
98     m_document = doc;
99     m_lastEditPosition = invocationRange.end();
100 
101     KTextEditor::Range sigAssistRange = invocationRange;
102     if (!removedText.isEmpty()) {
103         sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start());
104     }
105 
106     DUChainReadLocker lock(DUChain::lock(), 300);
107     if (!lock.locked()) {
108         clangDebug() << "failed to lock duchain in time";
109         return;
110     }
111     KTextEditor::Range simpleInvocationRange = KTextEditor::Range(sigAssistRange);
112     Declaration* funDecl = getDeclarationAtCursor(simpleInvocationRange.start(), m_document->url());
113     if (!funDecl || !funDecl->type<FunctionType>()) {
114         clangDebug() << "No function at cursor";
115         return;
116     }
117     /*
118        TODO: Port?
119        if(QtFunctionDeclaration* classFun = dynamic_cast<QtFunctionDeclaration*>(funDecl)) {
120        if (classFun->isSignal()) {
121         // do not offer to change signature of a signal, as the implementation will be generated by moc
122         return;
123        }
124        }
125      */
126 
127     Declaration* otherSide = nullptr;
128     if (auto* definition = dynamic_cast<FunctionDefinition*>(funDecl)) {
129         m_editingDefinition = true;
130         otherSide = definition->declaration();
131     } else if (auto* definition = FunctionDefinition::definition(funDecl)) {
132         m_editingDefinition = false;
133         otherSide = definition;
134     }
135     if (!otherSide) {
136         clangDebug() << "no other side for signature found";
137         return;
138     }
139     m_otherSideContext = DUContextPointer(DUChainUtils::functionContext(otherSide));
140     if (!m_otherSideContext) {
141         clangDebug() << "no context for other side found";
142         return;
143     }
144     m_declarationName = funDecl->identifier();
145     m_otherSideId = otherSide->id();
146     m_otherSideTopContext = ReferencedTopDUContext(otherSide->topContext());
147     m_oldSignature = getDeclarationSignature(otherSide, m_otherSideContext.data(), true);
148 
149     //Schedule an update, to make sure the ranges match
150     DUChain::self()->updateContextForUrl(m_otherSideTopContext->url(), TopDUContext::AllDeclarationsAndContexts);
151 }
152 
isUseful() const153 bool AdaptSignatureAssistant::isUseful() const
154 {
155     return !m_declarationName.isEmpty() && m_otherSideId.isValid() && !actions().isEmpty();
156 }
157 
getSignatureChanges(const Signature & newSignature,QList<int> & oldPositions) const158 bool AdaptSignatureAssistant::getSignatureChanges(const Signature& newSignature, QList<int>& oldPositions) const
159 {
160     bool changed = false;
161     oldPositions.reserve(oldPositions.size() + newSignature.parameters.size());
162     for (int i = 0; i < newSignature.parameters.size(); ++i) {
163         oldPositions.append(-1);
164     }
165 
166     for (int curNewParam = newSignature.parameters.size() - 1; curNewParam >= 0; --curNewParam) {
167         int foundAt = -1;
168 
169         for (int curOldParam = m_oldSignature.parameters.size() - 1; curOldParam >= 0; --curOldParam) {
170             if (newSignature.parameters[curNewParam].first != m_oldSignature.parameters[curOldParam].first) {
171                 continue;  //Different type == different parameters
172             }
173             if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second || curOldParam == curNewParam) {
174                 //given the same type and either the same position or the same name, it's (probably) the same argument
175                 foundAt = curOldParam;
176 
177                 if (newSignature.parameters[curNewParam].second != m_oldSignature.parameters[curOldParam].second || curOldParam != curNewParam) {
178                     changed = true;  //Either the name changed at this position, or position of this name has changed
179                 }
180                 if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second) {
181                     break;  //Found an argument with the same name and type, no need to look further
182                 }
183                 //else: position/type match, but name match will trump, allowing: (int i=0, int j=1) => (int j=1, int i=0)
184             }
185         }
186 
187         if (foundAt < 0) {
188             changed = true;
189         }
190         oldPositions[curNewParam] = foundAt;
191     }
192 
193     if (newSignature.parameters.size() != m_oldSignature.parameters.size()) {
194         changed = true;
195     }
196     if (newSignature.isConst != m_oldSignature.isConst) {
197         changed = true;
198     }
199     if (newSignature.returnType != m_oldSignature.returnType) {
200         changed = true;
201     }
202     return changed;
203 }
204 
setDefaultParams(Signature & newSignature,const QList<int> & oldPositions) const205 void AdaptSignatureAssistant::setDefaultParams(Signature& newSignature, const QList<int>& oldPositions) const
206 {
207     bool hadDefaultParam = false;
208     for (int i = 0; i < newSignature.defaultParams.size(); ++i) {
209         const auto oldPos = oldPositions[i];
210         if (oldPos == -1) {
211             // default-initialize new argument if we encountered a previous default param
212             if (hadDefaultParam) {
213                 newSignature.defaultParams[i] = QStringLiteral("{} /* TODO */");
214             }
215         } else {
216             newSignature.defaultParams[i] = m_oldSignature.defaultParams[oldPos];
217             hadDefaultParam = hadDefaultParam || !newSignature.defaultParams[i].isEmpty();
218         }
219     }
220 }
221 
getRenameActions(const Signature & newSignature,const QList<int> & oldPositions) const222 QList<RenameAction*> AdaptSignatureAssistant::getRenameActions(const Signature &newSignature, const QList<int> &oldPositions) const
223 {
224     ENSURE_CHAIN_READ_LOCKED
225     QList<RenameAction*> renameActions;
226     if (!m_otherSideContext) {
227         return renameActions;
228     }
229     const auto oldDeclarations = m_otherSideContext->localDeclarations();
230     for (int i = newSignature.parameters.size() - 1; i >= 0; --i) {
231         if (oldPositions[i] == -1) {
232             continue;  //new parameter
233         }
234         Declaration *renamedDecl = oldDeclarations.value(oldPositions[i]);
235         if (!renamedDecl)
236             continue;
237         if (newSignature.parameters[i].second != m_oldSignature.parameters[oldPositions[i]].second) {
238             const auto uses = renamedDecl->uses();
239             if (!uses.isEmpty()) {
240                 renameActions << new RenameAction(renamedDecl->identifier(), newSignature.parameters[i].second,
241                                                   RevisionedFileRanges::convert(uses));
242             }
243         }
244     }
245 
246     return renameActions;
247 }
248 
updateReady(const KDevelop::IndexedString & document,const KDevelop::ReferencedTopDUContext & top)249 void AdaptSignatureAssistant::updateReady(const KDevelop::IndexedString& document, const KDevelop::ReferencedTopDUContext& top)
250 {
251     if (!top || !m_document || document.toUrl() != m_document->url() || top->url() != IndexedString(m_document->url())) {
252         return;
253     }
254     clearActions();
255 
256     DUChainReadLocker lock;
257 
258     Declaration *functionDecl = getDeclarationAtCursor(m_lastEditPosition, m_document->url());
259     if (!functionDecl || functionDecl->identifier() != m_declarationName) {
260         clangDebug() << "No function found at" << m_document->url() << m_lastEditPosition;
261         return;
262     }
263     DUContext *functionCtxt = DUChainUtils::functionContext(functionDecl);
264     if (!functionCtxt) {
265         clangDebug() << "No function context found for" << functionDecl->toString();
266         return;
267     }
268 #if 0 // TODO: Port
269     if (QtFunctionDeclaration * classFun = dynamic_cast<QtFunctionDeclaration*>(functionDecl)) {
270         if (classFun->isSignal()) {
271             // do not offer to change signature of a signal, as the implementation will be generated by moc
272             return;
273         }
274     }
275 #endif
276 
277     //ParseJob having finished, get the signature that was modified
278     Signature newSignature = getDeclarationSignature(functionDecl, functionCtxt, false);
279 
280     //Check for changes between m_oldSignature and newSignature, use oldPositions to store old<->new param index mapping
281     QList<int> oldPositions;
282     if (!getSignatureChanges(newSignature, oldPositions)) {
283         reset();
284         clangDebug() << "no changes to signature";
285         return; //No changes to signature
286     }
287     QList<RenameAction*> renameActions;
288     if (m_editingDefinition) {
289         setDefaultParams(newSignature, oldPositions); //restore default parameters before updating the declarations
290     } else {
291         renameActions = getRenameActions(newSignature, oldPositions);  //rename as needed when updating the definition
292     }
293     IAssistantAction::Ptr action(new AdaptSignatureAction(m_otherSideId, m_otherSideTopContext,
294                                                           m_oldSignature, newSignature,
295                                                           m_editingDefinition, renameActions));
296     connect(action.data(), &IAssistantAction::executed,
297             this, &AdaptSignatureAssistant::reset);
298     addAction(action);
299     emit actionsChanged();
300 }
301 
displayRange() const302 KTextEditor::Range AdaptSignatureAssistant::displayRange() const
303 {
304     if (!m_document) {
305         return {};
306     }
307 
308     auto s = m_lastEditPosition;
309     KTextEditor::Range ran = {s.line(), 0, s.line(), m_document->lineLength(s.line())};
310     return ran;
311 }
312 
313 #include "moc_adaptsignatureassistant.cpp"
314