#if defined(_MSC_VER) #pragma warning(disable:4996) #endif #include "symbolfinder.h" #include #include #include #include #include #include #include #include #include #include using namespace CPlusPlus; using namespace CppTools; namespace { class FindMatchingDefinition: public SymbolVisitor { Symbol *_declaration; const OperatorNameId *_oper; QList _result; public: FindMatchingDefinition(Symbol *declaration) : _declaration(declaration) , _oper(0) { if (_declaration->name()) _oper = _declaration->name()->asOperatorNameId(); } QList result() const { return _result; } using SymbolVisitor::visit; virtual bool visit(Function *fun) { if (_oper) { if (const Name *name = fun->unqualifiedName()) { if (_oper->isEqualTo(name)) _result.append(fun); } } else if (const Identifier *id = _declaration->identifier()) { if (id->isEqualTo(fun->identifier())) _result.append(fun); } return false; } virtual bool visit(Block *) { return false; } }; } // end of anonymous namespace static const int kMaxCacheSize = 10; SymbolFinder::SymbolFinder() {} // strict means the returned symbol has to match exactly, // including argument count and argument types Symbol *SymbolFinder::findMatchingDefinition(Symbol *declaration, const Snapshot &snapshot, bool strict) { if (!declaration) return 0; QString declFile = QString::fromUtf8(declaration->fileName(), declaration->fileNameLength()); Document::Ptr thisDocument = snapshot.document(declFile); if (! thisDocument) { qWarning() << "undefined document:" << declaration->fileName(); return 0; } Function *declarationTy = declaration->type()->asFunctionType(); if (! declarationTy) { qWarning() << "not a function:" << declaration->fileName() << declaration->line() << declaration->column(); return 0; } foreach (const QString &fileName, fileIterationOrder(declFile, snapshot)) { Document::Ptr doc = snapshot.document(fileName); if (!doc) { clearCache(declFile, fileName); continue; } const Identifier *id = declaration->identifier(); if (id && ! doc->control()->findIdentifier(id->chars(), id->size())) continue; if (!id) { if (!declaration->name()) continue; const OperatorNameId *oper = declaration->name()->asOperatorNameId(); if (!oper) continue; if (!doc->control()->findOperatorNameId(oper->kind())) continue; } FindMatchingDefinition candidates(declaration); candidates.accept(doc->globalNamespace()); const QList result = candidates.result(); if (! result.isEmpty()) { LookupContext context(doc, snapshot); QList viableFunctions; ClassOrNamespace *enclosingType = context.lookupType(declaration); if (! enclosingType) continue; // nothing to do foreach (Function *fun, result) { const QList declarations = context.lookup(fun->name(), fun->enclosingScope()); if (declarations.isEmpty()) continue; const LookupItem best = declarations.first(); if (enclosingType == context.lookupType(best.declaration())) viableFunctions.append(fun); } if (viableFunctions.isEmpty()) continue; else if (! strict && viableFunctions.length() == 1) return viableFunctions.first(); Function *best = 0; foreach (Function *fun, viableFunctions) { if (! (fun->unqualifiedName() && fun->unqualifiedName()->isEqualTo(declaration->unqualifiedName()))) continue; else if (fun->argumentCount() == declarationTy->argumentCount()) { if (! strict && ! best) best = fun; unsigned argc = 0; for (; argc < declarationTy->argumentCount(); ++argc) { Symbol *arg = fun->argumentAt(argc); Symbol *otherArg = declarationTy->argumentAt(argc); if (! arg->type().isEqualTo(otherArg->type())) break; } if (argc == declarationTy->argumentCount()) best = fun; } } if (strict && ! best) continue; if (! best) best = viableFunctions.first(); return best; } } return 0; } Class *SymbolFinder::findMatchingClassDeclaration(Symbol *declaration, const Snapshot &snapshot) { if (! declaration->identifier()) return 0; QString declFile = QString::fromUtf8(declaration->fileName(), declaration->fileNameLength()); foreach (const QString &file, fileIterationOrder(declFile, snapshot)) { Document::Ptr doc = snapshot.document(file); if (!doc) { clearCache(declFile, file); continue; } if (! doc->control()->findIdentifier(declaration->identifier()->chars(), declaration->identifier()->size())) continue; LookupContext context(doc, snapshot); ClassOrNamespace *type = context.lookupType(declaration); if (!type) continue; foreach (Symbol *s, type->symbols()) { if (Class *c = s->asClass()) return c; } } return 0; } void SymbolFinder::findMatchingDeclaration(const LookupContext &context, Function *functionType, QList *typeMatch, QList *argumentCountMatch, QList *nameMatch) { Scope *enclosingScope = functionType->enclosingScope(); while (! (enclosingScope->isNamespace() || enclosingScope->isClass())) enclosingScope = enclosingScope->enclosingScope(); QTC_ASSERT(enclosingScope != 0, return); const Name *functionName = functionType->name(); if (! functionName) return; // anonymous function names are not valid c++ ClassOrNamespace *binding = 0; const QualifiedNameId *qName = functionName->asQualifiedNameId(); if (qName) { if (qName->base()) binding = context.lookupType(qName->base(), enclosingScope); else binding = context.globalNamespace(); functionName = qName->name(); } if (!binding) { // declaration for a global function binding = context.lookupType(enclosingScope); if (!binding) return; } const Identifier *funcId = functionName->identifier(); if (!funcId) // E.g. operator, which we might be able to handle in the future... return; foreach (Symbol *s, binding->symbols()) { Scope *scope = s->asScope(); if (!scope) continue; for (Symbol *s = scope->find(funcId); s; s = s->next()) { if (! s->name()) continue; else if (! funcId->isEqualTo(s->identifier())) continue; else if (! s->type()->isFunctionType()) continue; else if (Declaration *decl = s->asDeclaration()) { if (Function *declFunTy = decl->type()->asFunctionType()) { if (functionType->isEqualTo(declFunTy)) typeMatch->prepend(decl); else if (functionType->argumentCount() == declFunTy->argumentCount()) argumentCountMatch->prepend(decl); else nameMatch->append(decl); } } } } } QList SymbolFinder::findMatchingDeclaration(const LookupContext &context, Function *functionType) { QList result; QList nameMatch, argumentCountMatch, typeMatch; findMatchingDeclaration(context, functionType, &typeMatch, &argumentCountMatch, &nameMatch); result.append(typeMatch); result.append(argumentCountMatch); result.append(nameMatch); return result; } QStringList SymbolFinder::fileIterationOrder(const QString &referenceFile, const Snapshot &snapshot) { if (m_filePriorityCache.contains(referenceFile)) { checkCacheConsistency(referenceFile, snapshot); } else { foreach (Document::Ptr doc, snapshot) insertCache(referenceFile, doc->fileName()); } QStringList files = m_filePriorityCache.value(referenceFile).values(); trackCacheUse(referenceFile); return files; } void SymbolFinder::checkCacheConsistency(const QString &referenceFile, const Snapshot &snapshot) { // We only check for "new" files, which which are in the snapshot but not in the cache. // The counterpart validation for "old" files is done when one tries to access the // corresponding document and notices it's now null. const QSet &meta = m_fileMetaCache.value(referenceFile); foreach (const Document::Ptr &doc, snapshot) { if (!meta.contains(doc->fileName())) insertCache(referenceFile, doc->fileName()); } } void SymbolFinder::clearCache(const QString &referenceFile, const QString &comparingFile) { m_filePriorityCache[referenceFile].remove(computeKey(referenceFile, comparingFile), comparingFile); m_fileMetaCache[referenceFile].remove(comparingFile); } void SymbolFinder::insertCache(const QString &referenceFile, const QString &comparingFile) { // We want an ordering such that the documents with the most common path appear first. m_filePriorityCache[referenceFile].insert(computeKey(referenceFile, comparingFile), comparingFile); m_fileMetaCache[referenceFile].insert(comparingFile); } void SymbolFinder::trackCacheUse(const QString &referenceFile) { if (!m_recent.isEmpty()) { if (m_recent.last() == referenceFile) return; m_recent.removeOne(referenceFile); } m_recent.append(referenceFile); // We don't want this to grow too much. if (m_recent.size() > kMaxCacheSize) { const QString &oldest = m_recent.takeFirst(); m_filePriorityCache.remove(oldest); m_fileMetaCache.remove(oldest); } } int SymbolFinder::computeKey(const QString &referenceFile, const QString &comparingFile) { // As similar the path from the comparing file is to the path from the reference file, // the smaller the key is, which is then used for sorting the map. std::pair r = std::mismatch(referenceFile.begin(), referenceFile.end(), comparingFile.begin()); return referenceFile.length() - (r.first - referenceFile.begin()); }