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"