File indexing completed on 2024-05-19 15:44:49
0001 /* 0002 SPDX-FileCopyrightText: 2014 David Stevens <dgedstevens@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "completionhelper.h" 0008 0009 #include "../duchain/cursorkindtraits.h" 0010 #include "../duchain/parsesession.h" 0011 #include "../duchain/documentfinderhelpers.h" 0012 #include "../duchain/clanghelpers.h" 0013 #include "../util/clangdebug.h" 0014 #include "../util/clangtypes.h" 0015 #include "../util/clangutils.h" 0016 0017 #include <language/duchain/stringhelpers.h> 0018 0019 #include <clang-c/Index.h> 0020 0021 #include <algorithm> 0022 0023 using namespace KDevelop; 0024 0025 namespace { 0026 0027 struct OverrideInfo 0028 { 0029 FunctionOverrideList* functions; 0030 QStringList templateTypes; 0031 QMap<QString, QString> templateTypeMap; 0032 }; 0033 0034 struct ImplementsInfo 0035 { 0036 CXCursor origin; 0037 CXCursor top; 0038 FunctionImplementsList* prototypes; 0039 QVector<CXCursor> originScope; 0040 QVector<CXFile> fileFilter; 0041 int depth; 0042 QString templatePrefix; 0043 }; 0044 0045 QStringList templateParams(CXCursor cursor); 0046 0047 CXChildVisitResult templateParamsHelper(CXCursor cursor, CXCursor /*parent*/, CXClientData data) 0048 { 0049 CXCursorKind kind = clang_getCursorKind(cursor); 0050 auto& params = *static_cast<QStringList*>(data); 0051 if (kind == CXCursor_TemplateTypeParameter) { 0052 auto paramName = ClangString(clang_getCursorSpelling(cursor)).toString(); 0053 auto param = QStringLiteral("typename"); 0054 if (!paramName.isEmpty()) { 0055 param += QLatin1Char(' ') + paramName; 0056 } 0057 params.append(param); 0058 } else if (kind == CXCursor_NonTypeTemplateParameter) { 0059 auto param = ClangString(clang_getTypeSpelling(clang_getCursorType(cursor))).toString(); 0060 auto paramName = ClangString(clang_getCursorSpelling(cursor)).toString(); 0061 if (!paramName.isEmpty()) { 0062 param += QLatin1Char(' ') + paramName; 0063 } 0064 params.append(param); 0065 } else if (kind == CXCursor_TemplateTemplateParameter) { 0066 auto paramName = ClangString(clang_getCursorSpelling(cursor)).toString(); 0067 auto templateTypes = templateParams(cursor); 0068 QString param = QLatin1String("template<") + templateTypes.join(QLatin1String(", ")) + QLatin1String("> class ") + paramName ; 0069 params.append(param); 0070 } 0071 return CXChildVisit_Continue; 0072 } 0073 0074 QStringList templateParams(CXCursor cursor) 0075 { 0076 QStringList types; 0077 clang_visitChildren(cursor, templateParamsHelper, &types); 0078 return types; 0079 } 0080 0081 FuncOverrideInfo processCXXMethod(CXCursor cursor, OverrideInfo* info) 0082 { 0083 FuncParameterList params; 0084 0085 int numArgs = clang_Cursor_getNumArguments(cursor); 0086 params.reserve(numArgs); 0087 for (int i = 0; i < numArgs; i++) { 0088 CXCursor arg = clang_Cursor_getArgument(cursor, i); 0089 QString id = ClangString(clang_getCursorDisplayName(arg)).toString(); 0090 QString type = ClangString(clang_getTypeSpelling(clang_getCursorType(arg))).toString(); 0091 const auto templateTypeIt = info->templateTypeMap.constFind(type); 0092 if (templateTypeIt != info->templateTypeMap.constEnd()) { 0093 type = *templateTypeIt; 0094 } 0095 FuncParameterInfo param; 0096 param.type = type; 0097 param.id = id; 0098 params << param; 0099 } 0100 0101 FuncOverrideInfo fp; 0102 QString retType = ClangString(clang_getTypeSpelling(clang_getCursorResultType(cursor))).toString(); 0103 const auto templateTypeIt = info->templateTypeMap.constFind(retType); 0104 if (templateTypeIt != info->templateTypeMap.constEnd()) { 0105 retType = *templateTypeIt; 0106 } 0107 0108 fp.returnType = retType; 0109 fp.name = ClangString(clang_getCursorSpelling(cursor)).toString(); 0110 fp.params = params; 0111 fp.isPureVirtual = clang_CXXMethod_isPureVirtual(cursor); 0112 fp.isConst = clang_CXXMethod_isConst(cursor); 0113 0114 return fp; 0115 } 0116 0117 CXChildVisitResult baseClassVisitor(CXCursor cursor, CXCursor /*parent*/, CXClientData data); 0118 0119 void processBaseClass(CXCursor cursor, CXCursor parent, FunctionOverrideList* functionList) 0120 { 0121 QStringList concrete; 0122 CXCursor ref = clang_getCursorReferenced(cursor); 0123 0124 if (clang_equalCursors(ref, parent)) { 0125 return; 0126 } 0127 0128 CXCursor isTemplate = clang_getSpecializedCursorTemplate(ref); 0129 if (!clang_Cursor_isNull(isTemplate)) { 0130 concrete = ClangUtils::templateArgumentTypes(ref); 0131 ref = isTemplate; 0132 } 0133 0134 OverrideInfo info{functionList, concrete, {}}; 0135 clang_visitChildren(ref, baseClassVisitor, &info); 0136 } 0137 0138 CXChildVisitResult baseClassVisitor(CXCursor cursor, CXCursor parent, CXClientData data) 0139 { 0140 QString templateParam; 0141 auto* info = static_cast<OverrideInfo*>(data); 0142 0143 switch(clang_getCursorKind(cursor)) { 0144 case CXCursor_TemplateTypeParameter: 0145 templateParam = ClangString(clang_getCursorSpelling(cursor)).toString(); 0146 // TODO: this is probably just a hotfix, find a proper solution to 0147 // https://bugs.kde.org/show_bug.cgi?id=355163 0148 if (info->templateTypes.size() > info->templateTypeMap.size()) { 0149 info->templateTypeMap.insert(templateParam, info->templateTypes.at(info->templateTypeMap.size())); 0150 } 0151 return CXChildVisit_Continue; 0152 case CXCursor_CXXBaseSpecifier: 0153 processBaseClass(cursor, parent, info->functions); 0154 return CXChildVisit_Continue; 0155 case CXCursor_CXXMethod: 0156 if (clang_CXXMethod_isVirtual(cursor)) { 0157 auto methodInfo = processCXXMethod(cursor, info); 0158 const int methodIndex = info->functions->indexOf(methodInfo); 0159 if (methodIndex == -1) { 0160 info->functions->append(methodInfo); 0161 } else { 0162 // update to subclass override 0163 auto& listedMethodInfo = (*info->functions)[methodIndex]; 0164 listedMethodInfo.isPureVirtual = methodInfo.isPureVirtual; 0165 } 0166 } 0167 return CXChildVisit_Continue; 0168 default: 0169 return CXChildVisit_Continue; 0170 } 0171 } 0172 0173 CXChildVisitResult findBaseVisitor(CXCursor cursor, CXCursor parent, CXClientData data) 0174 { 0175 auto cursorKind = clang_getCursorKind(cursor); 0176 if (cursorKind == CXCursor_CXXBaseSpecifier) { 0177 processBaseClass(cursor, parent, static_cast<FunctionOverrideList*>(data)); 0178 } else if (cursorKind == CXCursor_CXXMethod) { 0179 if (!clang_CXXMethod_isVirtual(cursor)) { 0180 return CXChildVisit_Continue; 0181 } 0182 0183 auto info = static_cast<FunctionOverrideList*>(data); 0184 0185 OverrideInfo overrideInfo {info, {}, {}}; 0186 auto methodInfo = processCXXMethod(cursor, &overrideInfo); 0187 // If this method is already implemented, remove it from the list of methods that can be overridden. 0188 // If not implemented, this is a noop 0189 info->removeOne(methodInfo); 0190 } 0191 0192 return CXChildVisit_Continue; 0193 } 0194 0195 // TODO: make sure we only skip this in classes that actually inherit QObject 0196 bool isQtMocFunction(CXCursor cursor) 0197 { 0198 static const QByteArray mocFunctions[] = { 0199 QByteArrayLiteral("metaObject"), 0200 QByteArrayLiteral("qt_metacast"), 0201 QByteArrayLiteral("qt_metacall"), 0202 QByteArrayLiteral("qt_static_metacall"), 0203 QByteArrayLiteral("qt_check_for_QGADGET_macro") 0204 }; 0205 const ClangString function(clang_getCursorSpelling(cursor)); 0206 auto it = std::find(std::begin(mocFunctions), std::end(mocFunctions), function.toByteArray()); 0207 if (it != std::end(mocFunctions)) { 0208 auto range = ClangRange(clang_getCursorExtent(cursor)).toRange(); 0209 // tokenizing the above range fails for some reason, but 0210 // if the function comes from a range that happens to be just as wide 0211 // as the expected Q_OBJECT macro, then we assume this is a moc function 0212 // and skip it. 0213 return range.onSingleLine() && range.columnWidth() == strlen("Q_OBJECT"); 0214 } 0215 return false; 0216 } 0217 0218 CXChildVisitResult declVisitor(CXCursor cursor, CXCursor parent, CXClientData d) 0219 { 0220 CXCursorKind kind = clang_getCursorKind(cursor); 0221 auto* data = static_cast<struct ImplementsInfo*>(d); 0222 0223 auto location = clang_getCursorLocation(cursor); 0224 if (clang_Location_isInSystemHeader(location)) { 0225 // never offer implementation items for system headers 0226 // TODO: also filter out non-system files unrelated to the current file 0227 // e.g. based on the path or similar 0228 return CXChildVisit_Continue; 0229 } 0230 CXFile file = nullptr; 0231 clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); 0232 if (!data->fileFilter.contains(file)) { 0233 return CXChildVisit_Continue; 0234 } 0235 0236 //Recurse into cursors which could contain a function declaration 0237 if (ClangUtils::isScopeKind(kind)) { 0238 0239 //Don't enter a scope that branches from the origin's scope 0240 if (data->depth < data->originScope.count() && 0241 !clang_equalCursors(cursor, data->originScope.at(data->depth))) { 0242 return CXChildVisit_Continue; 0243 } 0244 0245 // we must not declare a function outside of its anonymous namespace, so 0246 // don't recurse into anonymous namespaces if we are not in one already 0247 if (kind == CXCursor_Namespace && !clang_equalCursors(data->origin, cursor) && ClangString(clang_getCursorDisplayName(cursor)).isEmpty()) { 0248 return CXChildVisit_Continue; 0249 } 0250 0251 QString templatePrefix; 0252 if (data->depth >= data->originScope.count()) { 0253 if (kind == CXCursor_ClassTemplate || kind == CXCursor_ClassTemplatePartialSpecialization) { 0254 //If we're at a template, we need to construct the template<typename T1, typename T2> 0255 //which goes at the front of the prototype 0256 const QStringList templateTypes = templateParams(kind == CXCursor_ClassTemplate ? cursor : clang_getSpecializedCursorTemplate(cursor)); 0257 0258 templatePrefix = QLatin1String("template<") + templateTypes.join(QLatin1String(", ")) + QLatin1String("> "); 0259 } 0260 } 0261 0262 ImplementsInfo info{data->origin, data->top, data->prototypes, data->originScope, 0263 data->fileFilter, 0264 data->depth + 1, 0265 data->templatePrefix + templatePrefix}; 0266 clang_visitChildren(cursor, declVisitor, &info); 0267 0268 return CXChildVisit_Continue; 0269 } 0270 0271 if (data->depth < data->originScope.count()) { 0272 return CXChildVisit_Continue; 0273 } 0274 0275 //If the current cursor is not a function or if it is already defined, there's nothing to do here 0276 if (!CursorKindTraits::isFunction(clang_getCursorKind(cursor)) || 0277 !clang_equalCursors(clang_getNullCursor(), clang_getCursorDefinition(cursor))) 0278 { 0279 return CXChildVisit_Continue; 0280 } 0281 0282 // don't try to implement pure virtual functions 0283 if (clang_CXXMethod_isPureVirtual(cursor)) { 0284 return CXChildVisit_Continue; 0285 } 0286 0287 CXCursor origin = data->origin; 0288 0289 //Don't try to redefine class/structure/union members 0290 if (clang_equalCursors(origin, parent) && (clang_getCursorKind(origin) != CXCursor_Namespace 0291 && !clang_equalCursors(origin, data->top))) { 0292 return CXChildVisit_Continue; 0293 } 0294 // skip explicitly defaulted/deleted functions as they don't need a definition 0295 if (ClangUtils::isExplicitlyDefaultedOrDeleted(cursor)) { 0296 return CXChildVisit_Continue; 0297 } 0298 0299 if (isQtMocFunction(cursor) || ClangUtils::specialAttributes(cursor) & FunctionSignalFlag) { 0300 return CXChildVisit_Continue; 0301 } 0302 0303 QString templatePrefix; 0304 if (kind == CXCursor_FunctionTemplate) { 0305 const QStringList templateTypes = templateParams(cursor); 0306 templatePrefix = QLatin1String("template<") + templateTypes.join(QLatin1String(", ")) + QLatin1String("> "); 0307 } 0308 0309 const auto scope = ClangUtils::getScope(cursor, data->origin); 0310 QString signature = ClangUtils::getCursorSignature(cursor, scope); 0311 0312 QString returnType, rest; 0313 if (kind != CXCursor_Constructor && kind != CXCursor_Destructor) { 0314 int spaceIndex = signature.indexOf(QLatin1Char(' ')); 0315 returnType = signature.left(spaceIndex); 0316 rest = signature.mid(spaceIndex + 1); 0317 } else { 0318 rest = signature; 0319 } 0320 0321 //TODO Add support for pure virtual functions 0322 0323 ReferencedTopDUContext top; 0324 { 0325 DUChainReadLocker lock; 0326 top = DUChain::self()->chainForDocument(ClangString(clang_getFileName(file)).toIndexed()); 0327 } 0328 DeclarationPointer declaration = ClangHelpers::findDeclaration(clang_getCursorLocation(cursor), QualifiedIdentifier(), top); 0329 data->prototypes->append(FuncImplementInfo{kind == CXCursor_Constructor, kind == CXCursor_Destructor, 0330 data->templatePrefix + templatePrefix, returnType, rest, declaration}); 0331 0332 return CXChildVisit_Continue; 0333 } 0334 0335 } 0336 0337 bool FuncOverrideInfo::operator==(const FuncOverrideInfo& rhs) const 0338 { 0339 return std::make_tuple(returnType, name, params, isConst) 0340 == std::make_tuple(rhs.returnType, rhs.name, rhs.params, rhs.isConst); 0341 } 0342 0343 CompletionHelper::CompletionHelper() 0344 { 0345 } 0346 0347 void CompletionHelper::computeCompletions(const ParseSession& session, CXFile file, const KTextEditor::Cursor& position) 0348 { 0349 const auto unit = session.unit(); 0350 0351 CXSourceLocation location = clang_getLocation(unit, file, position.line() + 1, position.column() + 1); 0352 0353 if (clang_equalLocations(clang_getNullLocation(), location)) { 0354 clangDebug() << "Completion helper given invalid position " << position 0355 << " in file " << clang_getFileName(file); 0356 return; 0357 } 0358 0359 CXCursor topCursor = clang_getTranslationUnitCursor(unit); 0360 CXCursor currentCursor = clang_getCursor(unit, location); 0361 if (clang_getCursorKind(currentCursor) == CXCursor_NoDeclFound) { 0362 currentCursor = topCursor; 0363 } else if (KTextEditor::Cursor(ClangLocation(clang_getCursorLocation(currentCursor))) >= ClangLocation(location)) { 0364 currentCursor = clang_getCursorLexicalParent(currentCursor); 0365 } 0366 0367 clang_visitChildren(currentCursor, findBaseVisitor, &m_overrides); 0368 0369 if (clang_getCursorKind(currentCursor) == CXCursor_Namespace || 0370 clang_equalCursors(topCursor, currentCursor)) { 0371 0372 QVector<CXCursor> scopes; 0373 if (!clang_equalCursors(topCursor, currentCursor)) { 0374 CXCursor search = currentCursor; 0375 while (!clang_equalCursors(search, topCursor)) { 0376 scopes.append(clang_getCanonicalCursor(search)); 0377 search = clang_getCursorSemanticParent(search); 0378 } 0379 std::reverse(scopes.begin(), scopes.end()); 0380 } 0381 0382 QVector<CXFile> fileFilter; 0383 fileFilter << file; 0384 const auto url = QUrl::fromLocalFile(ClangString(clang_getFileName(file)).toString()).adjusted(QUrl::NormalizePathSegments); 0385 const auto& buddies = DocumentFinderHelpers::potentialBuddies(url); 0386 for (const auto& buddy : buddies) { 0387 auto buddyFile = clang_getFile(unit, qPrintable(buddy.toLocalFile())); 0388 if (buddyFile) { 0389 fileFilter << buddyFile; 0390 } 0391 } 0392 0393 ImplementsInfo info{currentCursor, topCursor, &m_implements, scopes, fileFilter, 0, QString()}; 0394 clang_visitChildren(topCursor, declVisitor, &info); 0395 } 0396 } 0397 0398 FunctionOverrideList CompletionHelper::overrides() const 0399 { 0400 return m_overrides; 0401 } 0402 0403 FunctionImplementsList CompletionHelper::implements() const 0404 { 0405 return m_implements; 0406 }