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