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 }