diff --git a/share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h b/share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h index 74085598589..84016ea9d02 100644 --- a/share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h +++ b/share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h @@ -23,11 +23,17 @@ ** ****************************************************************************/ +#ifndef WRAPPED_QOBJECT_DEFS_H +#define WRAPPED_QOBJECT_DEFS_H + // Include qobjectdefs.h from Qt ... #include_next +#include + #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wmacro-redefined" +#pragma clang diagnostic ignored "-Wgnu-string-literal-operator-template" // ...and redefine macros for tagging signals/slots #ifdef signals @@ -54,4 +60,15 @@ # define Q_SLOT __attribute__((annotate("qt_slot"))) #endif +template +using QPropertyMagicString = std::integer_sequence; + +template +constexpr QPropertyMagicString operator""_qpropstr() { return { }; } + +// Create unique AST node for the property. +#define Q_PROPERTY(arg) void QPropertyMagicFunction(decltype(#arg ## _qpropstr)); + #pragma clang diagnostic pop + +#endif // WRAPPED_QOBJECT_DEFS_H diff --git a/src/libs/clangsupport/clangsupport_global.h b/src/libs/clangsupport/clangsupport_global.h index df34e2963eb..94c5c7c4dc3 100644 --- a/src/libs/clangsupport/clangsupport_global.h +++ b/src/libs/clangsupport/clangsupport_global.h @@ -95,7 +95,8 @@ enum class HighlightingType : quint8 Enum, Union, TypeAlias, - Typedef + Typedef, + QtProperty }; enum class StorageClass : quint8 diff --git a/src/libs/clangsupport/tokeninfocontainer.cpp b/src/libs/clangsupport/tokeninfocontainer.cpp index c14a2a22443..0e67db41a5b 100644 --- a/src/libs/clangsupport/tokeninfocontainer.cpp +++ b/src/libs/clangsupport/tokeninfocontainer.cpp @@ -59,6 +59,7 @@ static const char *highlightingTypeToCStringLiteral(HighlightingType type) RETURN_TEXT_FOR_CASE(Union); RETURN_TEXT_FOR_CASE(TypeAlias); RETURN_TEXT_FOR_CASE(Typedef); + RETURN_TEXT_FOR_CASE(QtProperty); default: return "UnhandledHighlightingType"; } } @@ -79,7 +80,6 @@ QDebug operator<<(QDebug debug, const ExtraInfo &extraInfo) << extraInfo.definition << ", " << extraInfo.signal << ", " << extraInfo.slot << ", " - << extraInfo.property << ")"; return debug; } diff --git a/src/libs/clangsupport/tokeninfocontainer.h b/src/libs/clangsupport/tokeninfocontainer.h index 69047763e2f..555facd9b8f 100644 --- a/src/libs/clangsupport/tokeninfocontainer.h +++ b/src/libs/clangsupport/tokeninfocontainer.h @@ -56,13 +56,12 @@ struct ExtraInfo , definition(false) , signal(false) , slot(false) - , property(false) { } ExtraInfo(Utf8String token, Utf8String typeSpelling, Utf8String resultTypeSpelling, Utf8String semanticParentTypeSpelling, AccessSpecifier accessSpecifier, StorageClass storageClass, bool isIdentifier, bool isInclusion, - bool isDeclaration, bool isDefinition, bool isSignal, bool isSlot, bool isProperty) + bool isDeclaration, bool isDefinition, bool isSignal, bool isSlot) : token(token) , typeSpelling(typeSpelling) , resultTypeSpelling(resultTypeSpelling) @@ -75,7 +74,6 @@ struct ExtraInfo , definition(isDefinition) , signal(isSignal) , slot(isSlot) - , property(isProperty) { } Utf8String token; @@ -90,7 +88,6 @@ struct ExtraInfo bool definition : 1; bool signal : 1; bool slot : 1; - bool property : 1; }; inline QDataStream &operator<<(QDataStream &out, const ExtraInfo &extraInfo); @@ -215,7 +212,6 @@ inline QDataStream &operator<<(QDataStream &out, const ExtraInfo &extraInfo) out << extraInfo.definition; out << extraInfo.signal; out << extraInfo.slot; - out << extraInfo.property; return out; } @@ -234,7 +230,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo) bool isDefinition; bool isSignal; bool isSlot; - bool isProperty; in >> accessSpecifier; in >> storageClass; @@ -244,7 +239,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo) in >> isDefinition; in >> isSignal; in >> isSlot; - in >> isProperty; extraInfo.accessSpecifier = static_cast(accessSpecifier); extraInfo.storageClass = static_cast(storageClass); @@ -254,7 +248,6 @@ inline QDataStream &operator>>(QDataStream &in, ExtraInfo &extraInfo) extraInfo.definition = isDefinition; extraInfo.signal = isSignal; extraInfo.slot = isSlot; - extraInfo.property = isProperty; return in; } @@ -271,8 +264,7 @@ inline bool operator==(const ExtraInfo &first, const ExtraInfo &second) && first.declaration == second.declaration && first.definition == second.definition && first.signal == second.signal - && first.slot == second.slot - && first.property == second.property; + && first.slot == second.slot; } inline QDataStream &operator<<(QDataStream &out, HighlightingType highlightingType) diff --git a/src/plugins/clangcodemodel/clanghighlightingresultreporter.cpp b/src/plugins/clangcodemodel/clanghighlightingresultreporter.cpp index 66a45c1c2d9..29d06ff2be2 100644 --- a/src/plugins/clangcodemodel/clanghighlightingresultreporter.cpp +++ b/src/plugins/clangcodemodel/clanghighlightingresultreporter.cpp @@ -50,6 +50,7 @@ TextEditor::TextStyle toTextStyle(ClangBackEnd::HighlightingType type) case HighlightingType::LocalVariable: return TextEditor::C_LOCAL; case HighlightingType::Field: + case HighlightingType::QtProperty: return TextEditor::C_FIELD; case HighlightingType::GlobalVariable: return TextEditor::C_GLOBAL; diff --git a/src/tools/clangbackend/source/fulltokeninfo.cpp b/src/tools/clangbackend/source/fulltokeninfo.cpp index b9e4b2f5a7b..78321d703a0 100644 --- a/src/tools/clangbackend/source/fulltokeninfo.cpp +++ b/src/tools/clangbackend/source/fulltokeninfo.cpp @@ -26,6 +26,9 @@ #include "clangstring.h" #include "cursor.h" #include "fulltokeninfo.h" +#include "sourcerange.h" + +#include namespace ClangBackEnd { @@ -72,6 +75,73 @@ void FullTokenInfo::updateTypeSpelling(const Cursor &cursor, bool functionLike) + (hasSpaceAfterReturnType ? 1 : 0)); } +static Utf8String propertyParentSpelling(CXTranslationUnit cxTranslationUnit, + const Utf8String &filePath, + uint line, uint column) +{ + // Q_PROPERTY expands into QPropertyMagicFunction which can be found as a child of + // the containing class. + Cursor tuCursor = clang_getTranslationUnitCursor(cxTranslationUnit); + Utf8String parentSpelling; + tuCursor.visit([&filePath, line, column, &parentSpelling](CXCursor cxCursor, CXCursor parent) { + const CXCursorKind kind = clang_getCursorKind(cxCursor); + if (kind == CXCursor_Namespace || kind == CXCursor_StructDecl + || kind == CXCursor_ClassDecl || kind == CXCursor_CXXMethod) { + Cursor cursor(cxCursor); + const SourceRange range = cursor.sourceRange(); + if (range.start().filePath() != filePath) + return CXChildVisit_Continue; + if (range.contains(line, column)) { + if (kind == CXCursor_Namespace || kind == CXCursor_StructDecl + || kind == CXCursor_ClassDecl) { + return CXChildVisit_Recurse; + } + // CXCursor_CXXMethod case. This is Q_PROPERTY_MAGIC_FUNCTION + parentSpelling = Cursor(parent).type().spelling(); + return CXChildVisit_Break; + } + } + return CXChildVisit_Continue; + }); + return parentSpelling; +} + +static Utf8String getPropertyType(const char *const lineContents, uint propertyPosition) +{ + const char *typeStart = std::strstr(lineContents, "Q_PROPERTY") + 10; + typeStart += std::strspn(typeStart, "( \t\n\r"); + if (typeStart - lineContents >= propertyPosition) + return Utf8String(); + auto typeEnd = std::find_if(std::reverse_iterator(lineContents + propertyPosition), + std::reverse_iterator(typeStart), + Utils::unequalTo(' ')); + + return Utf8String(typeStart, static_cast(&(*typeEnd) + 1 - typeStart)); +} + +void FullTokenInfo::updatePropertyData() +{ + CXSourceLocation cxLocation(clang_getTokenLocation(m_cxTranslationUnit, *m_cxToken)); + const SourceLocation location(m_cxTranslationUnit, cxLocation); + m_extraInfo.semanticParentTypeSpelling = propertyParentSpelling(m_cxTranslationUnit, + location.filePath(), + line(), + column()); + m_extraInfo.declaration = true; + m_extraInfo.definition = true; +#if defined(CINDEX_VERSION_HAS_GETFILECONTENTS_BACKPORTED) || CINDEX_VERSION_MINOR >= 47 + // Extract property type from the source code + CXFile cxFile; + uint offset; + clang_getFileLocation(cxLocation, &cxFile, nullptr, nullptr, &offset); + const uint propertyPosition = column() - 1; + const char *const contents = clang_getFileContents(m_cxTranslationUnit, cxFile, nullptr); + const char *const lineContents = &contents[offset - propertyPosition]; + + m_extraInfo.typeSpelling = getPropertyType(lineContents, propertyPosition); +#endif +} + void FullTokenInfo::identifierKind(const Cursor &cursor, Recursion recursion) { updateTypeSpelling(cursor); @@ -79,6 +149,8 @@ void FullTokenInfo::identifierKind(const Cursor &cursor, Recursion recursion) TokenInfo::identifierKind(cursor, recursion); m_extraInfo.identifier = (cursor.kind() != CXCursor_PreprocessingDirective); + if (types().mainHighlightingType == HighlightingType::QtProperty) + updatePropertyData(); } void FullTokenInfo::referencedTypeKind(const Cursor &cursor) @@ -138,8 +210,6 @@ void FullTokenInfo::memberReferenceKind(const Cursor &cursor) void FullTokenInfo::evaluate() { - TokenInfo::evaluate(); - m_extraInfo.token = ClangString(clang_getTokenSpelling(m_cxTranslationUnit, *m_cxToken)); auto cxTokenKind = clang_getTokenKind(*m_cxToken); @@ -148,6 +218,8 @@ void FullTokenInfo::evaluate() m_extraInfo.definition = m_originalCursor.isDefinition(); } m_extraInfo.includeDirectivePath = (m_originalCursor.kind() == CXCursor_InclusionDirective); + + TokenInfo::evaluate(); } } // namespace ClangBackEnd diff --git a/src/tools/clangbackend/source/fulltokeninfo.h b/src/tools/clangbackend/source/fulltokeninfo.h index 8c4aabf7370..3389958bda9 100644 --- a/src/tools/clangbackend/source/fulltokeninfo.h +++ b/src/tools/clangbackend/source/fulltokeninfo.h @@ -48,6 +48,7 @@ protected: void memberReferenceKind(const Cursor &cursor) override; private: void updateTypeSpelling(const Cursor &cursor, bool functionLike = false); + void updatePropertyData(); ExtraInfo m_extraInfo; }; diff --git a/src/tools/clangbackend/source/sourcelocation.h b/src/tools/clangbackend/source/sourcelocation.h index bf8962161f0..ac8b881294f 100644 --- a/src/tools/clangbackend/source/sourcelocation.h +++ b/src/tools/clangbackend/source/sourcelocation.h @@ -40,6 +40,7 @@ class SourceLocation friend class SourceRange; friend class TranslationUnit; friend class Cursor; + friend class FullTokenInfo; friend bool operator==(const SourceLocation &first, const SourceLocation &second); public: diff --git a/src/tools/clangbackend/source/sourcerange.cpp b/src/tools/clangbackend/source/sourcerange.cpp index ff1a4753918..e3a7da64458 100644 --- a/src/tools/clangbackend/source/sourcerange.cpp +++ b/src/tools/clangbackend/source/sourcerange.cpp @@ -69,10 +69,13 @@ bool SourceRange::contains(unsigned line, unsigned column) const const SourceLocation start_ = start(); const SourceLocation end_ = end(); - return start_.line() <= line - && start_.column() <= column - && line <= end_.line() - && column <= end_.column(); + if (line < start_.line() || line > end_.line()) + return false; + if (line == start_.line() && column < start_.column()) + return false; + if (line == end_.line() && column > end_.column()) + return false; + return true; } SourceRangeContainer SourceRange::toSourceRangeContainer() const diff --git a/src/tools/clangbackend/source/tokeninfo.cpp b/src/tools/clangbackend/source/tokeninfo.cpp index 36ee9a2a33b..98358d03412 100644 --- a/src/tools/clangbackend/source/tokeninfo.cpp +++ b/src/tools/clangbackend/source/tokeninfo.cpp @@ -32,7 +32,7 @@ #include -#include +#include namespace ClangBackEnd { @@ -421,6 +421,9 @@ void TokenInfo::identifierKind(const Cursor &cursor, Recursion recursion) case CXCursor_LabelStmt: m_types.mainHighlightingType = HighlightingType::Label; break; + case CXCursor_InvalidFile: + invalidFileKind(); + break; default: break; } @@ -466,10 +469,15 @@ HighlightingType TokenInfo::punctuationKind(const Cursor &cursor) HighlightingType highlightingType = HighlightingType::Invalid; switch (cursor.kind()) { - case CXCursor_DeclRefExpr: highlightingType = operatorKind(cursor); break; + case CXCursor_DeclRefExpr: + highlightingType = operatorKind(cursor); + break; case CXCursor_Constructor: - case CXCursor_CallExpr: collectOutputArguments(cursor); break; - default: break; + case CXCursor_CallExpr: + collectOutputArguments(cursor); + break; + default: + break; } if (isOutputArgument()) @@ -478,6 +486,75 @@ HighlightingType TokenInfo::punctuationKind(const Cursor &cursor) return highlightingType; } +enum class PropertyPart +{ + None, + Type, + Property, + Keyword, + FunctionOrPrimitiveType +}; + +static PropertyPart propertyPart(CXTranslationUnit tu, CXToken *token) +{ + static constexpr const char *propertyKeywords[] + = {"READ", "WRITE", "MEMBER", "RESET", "NOTIFY", "REVISION", "DESIGNABLE", + "SCRIPTABLE", "STORED", "USER", "CONSTANT", "FINAL" + }; + CXSourceLocation location = clang_getTokenLocation(tu, *token); + + // If current token is inside Q_PROPERTY then the cursor from token's position will be + // the whole Q_PROPERTY macro cursor. + Cursor possibleQPropertyCursor = clang_getCursor(tu, location); + if (!(possibleQPropertyCursor.spelling() == "Q_PROPERTY")) + return PropertyPart::None; + + const ClangString currentToken = clang_getTokenSpelling(tu, *token); + if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), currentToken) + != std::end(propertyKeywords)) { + return PropertyPart::Keyword; + } + + const ClangString nextToken = clang_getTokenSpelling(tu, *(token + 1)); + const ClangString previousToken = clang_getTokenSpelling(tu, *(token - 1)); + if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), nextToken) + != std::end(propertyKeywords)) { + if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), previousToken) + == std::end(propertyKeywords)) { + return PropertyPart::Property; + } + + return PropertyPart::FunctionOrPrimitiveType; + } + + if (std::find(std::begin(propertyKeywords), std::end(propertyKeywords), previousToken) + != std::end(propertyKeywords)) { + return PropertyPart::FunctionOrPrimitiveType; + } + return PropertyPart::Type; +} + +void TokenInfo::invalidFileKind() +{ + const PropertyPart propPart = propertyPart(m_cxTranslationUnit, m_cxToken); + + switch (propPart) { + case PropertyPart::None: + case PropertyPart::Keyword: + m_types.mainHighlightingType = HighlightingType::Invalid; + return; + case PropertyPart::Property: + m_types.mainHighlightingType = HighlightingType::QtProperty; + return; + case PropertyPart::Type: + m_types.mainHighlightingType = HighlightingType::Type; + return; + case PropertyPart::FunctionOrPrimitiveType: + m_types.mainHighlightingType = HighlightingType::Function; + return; + } +} + static HighlightingType highlightingTypeForKeyword(CXTranslationUnit cxTranslationUnit, CXToken *cxToken, const Cursor &cursor) diff --git a/src/tools/clangbackend/source/tokeninfo.h b/src/tools/clangbackend/source/tokeninfo.h index dc532740087..7447c61c934 100644 --- a/src/tools/clangbackend/source/tokeninfo.h +++ b/src/tools/clangbackend/source/tokeninfo.h @@ -94,6 +94,7 @@ protected: virtual void memberReferenceKind(const Cursor &cursor); virtual HighlightingType punctuationKind(const Cursor &cursor); virtual void typeKind(const Cursor &cursor); + virtual void invalidFileKind(); Cursor m_originalCursor; CXToken *m_cxToken; diff --git a/tests/unit/unittest/clangcodemodelserver-test.cpp b/tests/unit/unittest/clangcodemodelserver-test.cpp index 15082d2c779..3bf01577083 100644 --- a/tests/unit/unittest/clangcodemodelserver-test.cpp +++ b/tests/unit/unittest/clangcodemodelserver-test.cpp @@ -680,7 +680,7 @@ void ClangCodeModelServer::expectDocumentAnnotationsChangedForFileBWithSpecificH AccessSpecifier::Invalid, StorageClass::None, true, false, true, true, - false, false, false}); + false, false}); EXPECT_CALL(mockClangCodeModelClient, documentAnnotationsChanged( diff --git a/tests/unit/unittest/data/highlightingmarks.cpp b/tests/unit/unittest/data/highlightingmarks.cpp index 9f9a42f278b..5d0d9b82763 100644 --- a/tests/unit/unittest/data/highlightingmarks.cpp +++ b/tests/unit/unittest/data/highlightingmarks.cpp @@ -592,3 +592,10 @@ class WithVirtualFunctionDefined { namespace NFoo { namespace NBar { namespace NTest { class NamespaceTypeSpelling; } } } Undeclared u; + +#include "../../../../share/qtcreator/cplusplus/wrappedQtHeaders/QtCore/qobjectdefs.h" + +class Property { + Q_PROPERTY(const volatile unsigned long long * prop READ getProp WRITE setProp NOTIFY propChanged) + Q_PROPERTY(const QString str READ getStr) +}; diff --git a/tests/unit/unittest/gtest-creator-printing.cpp b/tests/unit/unittest/gtest-creator-printing.cpp index ba942ad2060..c5979e4fba5 100644 --- a/tests/unit/unittest/gtest-creator-printing.cpp +++ b/tests/unit/unittest/gtest-creator-printing.cpp @@ -500,6 +500,7 @@ static const char *highlightingTypeToCStringLiteral(HighlightingType type) RETURN_TEXT_FOR_CASE(Union); RETURN_TEXT_FOR_CASE(TypeAlias); RETURN_TEXT_FOR_CASE(Typedef); + RETURN_TEXT_FOR_CASE(QtProperty); } return ""; @@ -538,8 +539,7 @@ std::ostream &operator<<(std::ostream &os, const ExtraInfo &extraInfo) << extraInfo.declaration << ", " << extraInfo.definition << ", " << extraInfo.signal << ", " - << extraInfo.slot << ", " - << extraInfo.property + << extraInfo.slot << ")"; return os; } diff --git a/tests/unit/unittest/tokenprocessor-test.cpp b/tests/unit/unittest/tokenprocessor-test.cpp index 6aa03a5742d..13c07a21f55 100644 --- a/tests/unit/unittest/tokenprocessor-test.cpp +++ b/tests/unit/unittest/tokenprocessor-test.cpp @@ -1257,18 +1257,53 @@ TEST_F(TokenProcessor, NamespaceTypeSpelling) TEST_F(TokenProcessor, DISABLED_WITHOUT_INVALIDDECL_PATCH(TypeNameOfInvalidDeclarationIsInvalid)) { - const auto infos = translationUnit.tokenInfosInRange(sourceRange(592, 14)); + const auto infos = translationUnit.tokenInfosInRange(sourceRange(594, 14)); ASSERT_THAT(infos[0], HasOnlyType(HighlightingType::Invalid)); } TEST_F(TokenProcessor, DISABLED_WITHOUT_INVALIDDECL_PATCH(VariableNameOfInvalidDeclarationIsInvalid)) { - const auto infos = translationUnit.tokenInfosInRange(sourceRange(592, 14)); + const auto infos = translationUnit.tokenInfosInRange(sourceRange(594, 14)); ASSERT_THAT(infos[1], HasOnlyType(HighlightingType::Invalid)); } +TEST_F(TokenProcessor, QtPropertyName) +{ + const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103)); + + ASSERT_THAT(infos[8], HasOnlyType(HighlightingType::QtProperty)); +} + +TEST_F(TokenProcessor, QtPropertyFunction) +{ + const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103)); + + ASSERT_THAT(infos[10], HasOnlyType(HighlightingType::Function)); +} + +TEST_F(TokenProcessor, QtPropertyInternalKeyword) +{ + const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103)); + + ASSERT_THAT(infos[9], HasOnlyType(HighlightingType::Invalid)); +} + +TEST_F(TokenProcessor, QtPropertyLastToken) +{ + const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(599, 103)); + + ASSERT_THAT(infos[14], HasOnlyType(HighlightingType::Function)); +} + +TEST_F(TokenProcessor, QtPropertyType) +{ + const auto infos = translationUnit.fullTokenInfosInRange(sourceRange(600, 46)); + + ASSERT_THAT(infos[3], HasOnlyType(HighlightingType::Type)); +} + Data *TokenProcessor::d; void TokenProcessor::SetUpTestCase()