From b68a16f1b5d69d63e7adaa67810b99c73bcfb979 Mon Sep 17 00:00:00 2001 From: Roberto Raggi <roberto.raggi@nokia.com> Date: Tue, 25 May 2010 14:53:21 +0200 Subject: [PATCH] Highlight user defined types. --- src/libs/cplusplus/CheckUndefinedSymbols.cpp | 334 ++++++++++++++++++- src/libs/cplusplus/CheckUndefinedSymbols.h | 22 ++ src/libs/cplusplus/FindUsages.cpp | 6 +- src/libs/cplusplus/pp-engine.cpp | 9 +- src/plugins/cppeditor/cppeditor.cpp | 36 +- src/plugins/cppeditor/cppeditor.h | 4 +- src/plugins/cppeditor/cpphighlighter.cpp | 13 +- src/plugins/texteditor/basetexteditor.h | 1 + src/shared/cplusplus/CheckStatement.cpp | 10 +- 9 files changed, 406 insertions(+), 29 deletions(-) diff --git a/src/libs/cplusplus/CheckUndefinedSymbols.cpp b/src/libs/cplusplus/CheckUndefinedSymbols.cpp index eba07d4b882..7de0f7c77da 100644 --- a/src/libs/cplusplus/CheckUndefinedSymbols.cpp +++ b/src/libs/cplusplus/CheckUndefinedSymbols.cpp @@ -36,16 +36,211 @@ #include <TranslationUnit.h> #include <Scope.h> #include <AST.h> +#include <SymbolVisitor.h> #include <QCoreApplication> #include <QDebug> using namespace CPlusPlus; +namespace { + +class CollectTypes: protected SymbolVisitor +{ + Document::Ptr _doc; + Snapshot _snapshot; + QSet<QByteArray> _types; + QList<ScopedSymbol *> _scopes; + QList<NameAST *> _names; + bool _mainDocument; + +public: + CollectTypes(Document::Ptr doc, const Snapshot &snapshot) + : _doc(doc), _snapshot(snapshot), _mainDocument(false) + { + QSet<Namespace *> processed; + process(doc, &processed); + } + + const QSet<QByteArray> &types() const + { + return _types; + } + + const QList<ScopedSymbol *> &scopes() const + { + return _scopes; + } + + static Scope *findScope(unsigned tokenOffset, const QList<ScopedSymbol *> &scopes) + { + for (int i = scopes.size() - 1; i != -1; --i) { + ScopedSymbol *symbol = scopes.at(i); + const unsigned start = symbol->startOffset(); + const unsigned end = symbol->endOffset(); + + if (tokenOffset >= start && tokenOffset < end) + return symbol->members(); + } + + return 0; + } + +protected: + void process(Document::Ptr doc, QSet<Namespace *> *processed) + { + if (! doc) + return; + else if (! processed->contains(doc->globalNamespace())) { + processed->insert(doc->globalNamespace()); + + foreach (const Document::Include &i, doc->includes()) + process(_snapshot.document(i.fileName()), processed); + + _mainDocument = (doc == _doc); // ### improve + accept(doc->globalNamespace()); + } + } + + void addType(const Identifier *id) + { + if (id) + _types.insert(QByteArray::fromRawData(id->chars(), id->size())); + } + + void addType(const Name *name) + { + if (! name) { + return; + + } else if (const QualifiedNameId *q = name->asQualifiedNameId()) { + for (unsigned i = 0; i < q->nameCount(); ++i) + addType(q->nameAt(i)); + + } else if (name->isNameId() || name->isTemplateNameId()) { + addType(name->identifier()); + + } + } + + void addScope(ScopedSymbol *symbol) + { + if (_mainDocument) + _scopes.append(symbol); + } + + // nothing to do + virtual bool visit(UsingNamespaceDirective *) { return true; } + virtual bool visit(UsingDeclaration *) { return true; } + virtual bool visit(Argument *) { return true; } + virtual bool visit(BaseClass *) { return true; } + + virtual bool visit(Function *symbol) + { + addScope(symbol); + return true; + } + + virtual bool visit(Block *symbol) + { + addScope(symbol); + return true; + } + + virtual bool visit(NamespaceAlias *symbol) + { + addType(symbol->name()); + return true; + } + + virtual bool visit(Declaration *symbol) + { + if (symbol->isTypedef()) + addType(symbol->name()); + + return true; + } + + virtual bool visit(TypenameArgument *symbol) + { + addType(symbol->name()); + return true; + } + + virtual bool visit(Enum *symbol) + { + addScope(symbol); + addType(symbol->name()); + return true; + } + + virtual bool visit(Namespace *symbol) + { + addScope(symbol); + addType(symbol->name()); + return true; + } + + virtual bool visit(Class *symbol) + { + addScope(symbol); + addType(symbol->name()); + return true; + } + + virtual bool visit(ForwardClassDeclaration *symbol) + { + addType(symbol->name()); + return true; + } + + // Objective-C + virtual bool visit(ObjCBaseClass *) { return true; } + virtual bool visit(ObjCBaseProtocol *) { return true; } + virtual bool visit(ObjCPropertyDeclaration *) { return true; } + + virtual bool visit(ObjCMethod *symbol) + { + addScope(symbol); + return true; + } + + virtual bool visit(ObjCClass *symbol) + { + addScope(symbol); + addType(symbol->name()); + return true; + } + + virtual bool visit(ObjCForwardClassDeclaration *symbol) + { + addType(symbol->name()); + return true; + } + + virtual bool visit(ObjCProtocol *symbol) + { + addScope(symbol); + addType(symbol->name()); + return true; + } + + virtual bool visit(ObjCForwardProtocolDeclaration *symbol) + { + addType(symbol->name()); + return true; + } +}; + +} // end of anonymous namespace + CheckUndefinedSymbols::CheckUndefinedSymbols(TranslationUnit *unit, const LookupContext &context) : ASTVisitor(unit), _context(context) { _fileName = context.thisDocument()->fileName(); + CollectTypes collectTypes(context.thisDocument(), context.snapshot()); + _potentialTypes = collectTypes.types(); + _scopes = collectTypes.scopes(); } CheckUndefinedSymbols::~CheckUndefinedSymbols() @@ -84,17 +279,19 @@ bool CheckUndefinedSymbols::visit(UsingDirectiveAST *ast) return false; } -bool CheckUndefinedSymbols::visit(SimpleDeclarationAST *ast) +bool CheckUndefinedSymbols::visit(SimpleDeclarationAST *) { return true; } bool CheckUndefinedSymbols::visit(NamedTypeSpecifierAST *ast) { +#if 0 if (ast->name) { unsigned line, column; - getTokenStartPosition(ast->firstToken(), &line, &column); + getTokenStartPosition(ast->name->firstToken(), &line, &column); + // ### use the potential types. Scope *enclosingScope = _context.thisDocument()->scopeAt(line, column); const QList<Symbol *> candidates = _context.lookup(ast->name->name, enclosingScope); @@ -108,8 +305,9 @@ bool CheckUndefinedSymbols::visit(NamedTypeSpecifierAST *ast) if (! ty) warning(ast->name, QCoreApplication::translate("CheckUndefinedSymbols", "Expected a type-name")); } +#endif - return false; + return true; } void CheckUndefinedSymbols::checkNamespace(NameAST *name) @@ -131,3 +329,133 @@ void CheckUndefinedSymbols::checkNamespace(NameAST *name) const unsigned length = tokenAt(name->lastToken() - 1).end() - tokenAt(name->firstToken()).begin(); warning(line, column, QCoreApplication::translate("CheckUndefinedSymbols", "Expected a namespace-name"), length); } + +bool CheckUndefinedSymbols::visit(SimpleNameAST *ast) +{ + if (ast->name) { + const QByteArray id = QByteArray::fromRawData(ast->name->identifier()->chars(), // ### move + ast->name->identifier()->size()); + if (_potentialTypes.contains(id)) { + const unsigned tokenOffset = tokenAt(ast->firstToken()).offset; + Scope *scope = CollectTypes::findScope(tokenOffset, _scopes); // ### move + if (! scope) + scope = _context.thisDocument()->globalSymbols(); + + const QList<Symbol *> candidates = _context.lookup(ast->name, scope); + addTypeUsage(candidates, ast); + } + } + + return true; +} + +bool CheckUndefinedSymbols::visit(TemplateIdAST *ast) +{ + if (ast->name) { + const QByteArray id = QByteArray::fromRawData(ast->name->identifier()->chars(), // ### move + ast->name->identifier()->size()); + if (_potentialTypes.contains(id)) { + Scope *scope = CollectTypes::findScope(tokenAt(ast->firstToken()).offset, _scopes); // ### move + if (! scope) + scope = _context.thisDocument()->globalSymbols(); + + ClassOrNamespace *b = _context.lookupType(ast->name, scope); + addTypeUsage(b, ast); + } + } + + return true; +} + +bool CheckUndefinedSymbols::visit(DestructorNameAST *ast) +{ + if (ast->name) { + const QByteArray id = QByteArray::fromRawData(ast->name->identifier()->chars(), // ### move + ast->name->identifier()->size()); + if (_potentialTypes.contains(id)) { + Scope *scope = CollectTypes::findScope(tokenAt(ast->firstToken()).offset, _scopes); // ### move + if (! scope) + scope = _context.thisDocument()->globalSymbols(); + + ClassOrNamespace *b = _context.lookupType(ast->name, scope); + addTypeUsage(b, ast); + } + } + + return true; +} + +bool CheckUndefinedSymbols::visit(QualifiedNameAST *ast) +{ + if (ast->name) { + Scope *scope = CollectTypes::findScope(tokenAt(ast->firstToken()).offset, _scopes); // ### move + if (! scope) + scope = _context.thisDocument()->globalSymbols(); + + ClassOrNamespace *b = 0; + if (NestedNameSpecifierListAST *it = ast->nested_name_specifier_list) { + NestedNameSpecifierAST *nested_name_specifier = it->value; + NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name; // ### remove shadowing + + const Name *name = class_or_namespace_name->name; + b = _context.lookupType(name, scope); + addTypeUsage(b, class_or_namespace_name); + + for (it = it->next; b && it; it = it->next) { + NestedNameSpecifierAST *nested_name_specifier = it->value; + + if (NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name) { + b = b->findType(class_or_namespace_name->name); + addTypeUsage(b, class_or_namespace_name); + } + } + } + + if (b && ast->unqualified_name) + addTypeUsage(b->find(ast->unqualified_name->name), ast->unqualified_name); + } + + return false; +} + +void CheckUndefinedSymbols::addTypeUsage(ClassOrNamespace *b, NameAST *ast) +{ + if (! b) + return; + + const Token &tok = tokenAt(ast->firstToken()); + if (tok.generated()) + return; + + unsigned line, column; + getTokenStartPosition(ast->firstToken(), &line, &column); + const unsigned length = tok.length(); + Use use(line, column, length); + _typeUsages.append(use); + //qDebug() << "added use" << oo(ast->name) << line << column << length; +} + +void CheckUndefinedSymbols::addTypeUsage(const QList<Symbol *> &candidates, NameAST *ast) +{ + const Token &tok = tokenAt(ast->firstToken()); + if (tok.generated()) + return; + + unsigned line, column; + getTokenStartPosition(ast->firstToken(), &line, &column); + const unsigned length = tok.length(); + + foreach (Symbol *c, candidates) { + if (c->isTypedef() || c->isClass() || c->isEnum() || c->isForwardClassDeclaration() || c->isTypenameArgument()) { + Use use(line, column, length); + _typeUsages.append(use); + //qDebug() << "added use" << oo(ast->name) << line << column << length; + break; + } + } +} + +QList<CheckUndefinedSymbols::Use> CheckUndefinedSymbols::typeUsages() const +{ + return _typeUsages; +} diff --git a/src/libs/cplusplus/CheckUndefinedSymbols.h b/src/libs/cplusplus/CheckUndefinedSymbols.h index 58f0edd1d90..f375d904e8a 100644 --- a/src/libs/cplusplus/CheckUndefinedSymbols.h +++ b/src/libs/cplusplus/CheckUndefinedSymbols.h @@ -33,6 +33,7 @@ #include "CppDocument.h" #include "LookupContext.h" #include <ASTVisitor.h> +#include <QtCore/QSet> namespace CPlusPlus { @@ -44,6 +45,17 @@ public: QList<Document::DiagnosticMessage> operator()(AST *ast); + struct Use { // ### remove me + unsigned line; + unsigned column; + unsigned length; + + Use(unsigned line = 0, unsigned column = 0, unsigned length = 0) + : line(line), column(column), length(length) {} + }; + + QList<Use> typeUsages() const; + protected: using ASTVisitor::visit; @@ -51,15 +63,25 @@ protected: bool warning(AST *ast, const QString &text); void checkNamespace(NameAST *name); + void addTypeUsage(ClassOrNamespace *b, NameAST *ast); + void addTypeUsage(const QList<Symbol *> &candidates, NameAST *ast); virtual bool visit(UsingDirectiveAST *); virtual bool visit(SimpleDeclarationAST *); virtual bool visit(NamedTypeSpecifierAST *); + virtual bool visit(SimpleNameAST *ast); + virtual bool visit(DestructorNameAST *ast); + virtual bool visit(QualifiedNameAST *ast); + virtual bool visit(TemplateIdAST *ast); + private: LookupContext _context; QString _fileName; QList<Document::DiagnosticMessage> _diagnosticMessages; + QSet<QByteArray> _potentialTypes; + QList<ScopedSymbol *> _scopes; + QList<Use> _typeUsages; }; } // end of namespace CPlusPlus diff --git a/src/libs/cplusplus/FindUsages.cpp b/src/libs/cplusplus/FindUsages.cpp index 6c52a02e2a0..003e4df8404 100644 --- a/src/libs/cplusplus/FindUsages.cpp +++ b/src/libs/cplusplus/FindUsages.cpp @@ -128,12 +128,14 @@ void FindUsages::reportResult(unsigned tokenIndex, const QList<Symbol *> &candid void FindUsages::reportResult(unsigned tokenIndex) { - if (_processed.contains(tokenIndex)) + const Token &tk = tokenAt(tokenIndex); + if (tk.generated()) + return; + else if (_processed.contains(tokenIndex)) return; _processed.insert(tokenIndex); - const Token &tk = tokenAt(tokenIndex); const QString lineText = matchingLine(tk); unsigned line, col; diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp index 0282ac65872..a9d605415f9 100644 --- a/src/libs/cplusplus/pp-engine.cpp +++ b/src/libs/cplusplus/pp-engine.cpp @@ -779,12 +779,9 @@ void Preprocessor::preprocess(const QString &fileName, const QByteArray &source, if (! env->isBuiltinMacro(spell)) { Macro *m = env->resolve(spell); if (m && ! m->isFunctionLike()) { - QByteArray expandedDefinition; - expandObjectLikeMacro(identifierToken, spell, m, &expandedDefinition); - if (expandedDefinition.trimmed().isEmpty()) { - out(QByteArray(spell.length(), ' ')); - continue; - } + // expand object-like macros. + processObjectLikeMacro(identifierToken, spell, m); + continue; } } out(spell); diff --git a/src/plugins/cppeditor/cppeditor.cpp b/src/plugins/cppeditor/cppeditor.cpp index adbec6ce8e8..ac8851de7bf 100644 --- a/src/plugins/cppeditor/cppeditor.cpp +++ b/src/plugins/cppeditor/cppeditor.cpp @@ -265,6 +265,10 @@ protected: bool findMemberForToken(unsigned tokenIdx, NameAST *ast) { + const Token &tok = tokenAt(tokenIdx); + if (tok.generated()) + return false; + unsigned line, column; getTokenStartPosition(tokenIdx, &line, &column); @@ -303,6 +307,10 @@ protected: for (TemplateArgumentListAST *arg = ast->template_argument_list; arg; arg = arg->next) accept(arg->value); + const Token &tok = tokenAt(ast->identifier_token); + if (tok.generated()) + return false; + unsigned line, column; getTokenStartPosition(ast->firstToken(), &line, &column); @@ -1781,6 +1789,7 @@ void CPPEditor::setFontSettings(const TextEditor::FontSettings &fs) m_occurrencesUnusedFormat.clearForeground(); m_occurrencesUnusedFormat.setToolTip(tr("Unused variable")); m_occurrenceRenameFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_OCCURRENCES_RENAME)); + m_typeFormat = fs.toTextCharFormat(QLatin1String(TextEditor::Constants::C_TYPE)); // only set the background, we do not want to modify foreground properties set by the syntax highlighter or the link m_occurrencesFormat.clearForeground(); @@ -1889,6 +1898,21 @@ void CPPEditor::updateSemanticInfo(const SemanticInfo &semanticInfo) } setExtraSelections(UndefinedSymbolSelection, undefinedSymbolSelections); + + QList<QTextEdit::ExtraSelection> typeSelections; + foreach (const SemanticInfo::Use &use, semanticInfo.typeUsages) { + QTextCursor cursor(document()); + cursor.setPosition(document()->findBlockByNumber(use.line - 1).position() + use.column - 1); + cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, use.length); + + QTextEdit::ExtraSelection sel; + sel.cursor = cursor; + sel.format = m_typeFormat; + typeSelections.append(sel); + } + + setExtraSelections(TypeSelection, typeSelections); + } setExtraSelections(UnusedSymbolSelection, unusedSelections); @@ -1987,12 +2011,14 @@ SemanticInfo SemanticHighlighter::semanticInfo(const Source &source) Snapshot snapshot; Document::Ptr doc; QList<Document::DiagnosticMessage> diagnosticMessages; + QList<SemanticInfo::Use> typeUsages; if (! source.force && revision == source.revision) { m_mutex.lock(); snapshot = m_lastSemanticInfo.snapshot; // ### TODO: use the new snapshot. doc = m_lastSemanticInfo.doc; diagnosticMessages = m_lastSemanticInfo.diagnosticMessages; + typeUsages = m_lastSemanticInfo.typeUsages; m_mutex.unlock(); } @@ -2003,17 +2029,14 @@ SemanticInfo SemanticHighlighter::semanticInfo(const Source &source) doc = snapshot.documentFromSource(preprocessedCode, source.fileName); doc->check(); - Document::Ptr documentInSnapshot = snapshot.document(source.fileName); - if (! documentInSnapshot) { - // use the newly parsed document. - documentInSnapshot = doc; - } - LookupContext context(doc, snapshot); if (TranslationUnit *unit = doc->translationUnit()) { CheckUndefinedSymbols checkUndefinedSymbols(unit, context); diagnosticMessages = checkUndefinedSymbols(unit->ast()); + typeUsages.clear(); + foreach (const CheckUndefinedSymbols::Use &use, checkUndefinedSymbols.typeUsages()) // ### remove me + typeUsages.append(SemanticInfo::Use(use.line, use.column, use.length)); } } @@ -2035,6 +2058,7 @@ SemanticInfo SemanticHighlighter::semanticInfo(const Source &source) semanticInfo.hasD = useTable.hasD; semanticInfo.forced = source.force; semanticInfo.diagnosticMessages = diagnosticMessages; + semanticInfo.typeUsages = typeUsages; return semanticInfo; } diff --git a/src/plugins/cppeditor/cppeditor.h b/src/plugins/cppeditor/cppeditor.h index fc74e77c394..c6e039ba007 100644 --- a/src/plugins/cppeditor/cppeditor.h +++ b/src/plugins/cppeditor/cppeditor.h @@ -88,7 +88,8 @@ public: bool forced: 1; CPlusPlus::Snapshot snapshot; CPlusPlus::Document::Ptr doc; - LocalUseMap localUses; + LocalUseMap localUses; // ### rename + QList<Use> typeUsages; QList<CPlusPlus::Document::DiagnosticMessage> diagnosticMessages; }; @@ -299,6 +300,7 @@ private: QTextCharFormat m_occurrencesFormat; QTextCharFormat m_occurrencesUnusedFormat; QTextCharFormat m_occurrenceRenameFormat; + QTextCharFormat m_typeFormat; QList<QTextEdit::ExtraSelection> m_renameSelections; int m_currentRenameSelection; diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp index a8a02fd550c..4575fa9ec59 100644 --- a/src/plugins/cppeditor/cpphighlighter.cpp +++ b/src/plugins/cppeditor/cpphighlighter.cpp @@ -360,11 +360,14 @@ void CppHighlighter::highlightWord(QStringRef word, int position, int length) { // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY // but don't highlight words like 'Query' - if (word.length() > 1 - && word.at(0) == QLatin1Char('Q') - && (word.at(1).isUpper() - || word.at(1) == QLatin1Char('_') - || word.at(1) == QLatin1Char('t'))) { + + if (word.length() > 1 && word.at(0) == QLatin1Char('Q')) { + for (int i = 1; i < word.length(); ++i) { + const QChar &ch = word.at(i); + if (! (ch.isUpper() || ch == QLatin1Char('_'))) + return; + } + setFormat(position, length, m_formats[CppTypeFormat]); } } diff --git a/src/plugins/texteditor/basetexteditor.h b/src/plugins/texteditor/basetexteditor.h index 3edf8f7b163..1f2afc97ab6 100644 --- a/src/plugins/texteditor/basetexteditor.h +++ b/src/plugins/texteditor/basetexteditor.h @@ -340,6 +340,7 @@ public: FakeVimSelection, OtherSelection, SnippetPlaceholderSelection, + TypeSelection, NExtraSelectionKinds }; void setExtraSelections(ExtraSelectionKind kind, const QList<QTextEdit::ExtraSelection> &selections); diff --git a/src/shared/cplusplus/CheckStatement.cpp b/src/shared/cplusplus/CheckStatement.cpp index 093ed91605b..c476b2195df 100644 --- a/src/shared/cplusplus/CheckStatement.cpp +++ b/src/shared/cplusplus/CheckStatement.cpp @@ -153,12 +153,10 @@ bool CheckStatement::visit(ExpressionOrDeclarationStatementAST *ast) { // translationUnit()->warning(ast->firstToken(), // "ambiguous expression or declaration statement"); - if (ast->declaration) { - semantic()->check(ast->declaration, _scope); - _exprType = FullySpecifiedType(); - } else { - _exprType = semantic()->check(ast->expression, _scope); - } + + semantic()->check(ast->declaration, _scope); + _exprType = semantic()->check(ast->expression, _scope); + return false; } -- GitLab