forked from qt-creator/qt-creator
Clang: Add semantic C++ operator-token styling
We used to style overloaded operators in the same way as C++'s built-in operators. There was no way to tell if a + token would call a operator+() function or not. Now, if an operator is overloaded (redefined), we give it the "Overloaded Operator"-mixin so users can style it differently. Note: Calls to overloaded 'new' and 'delete' are not highlighted by "Overloaded Operator". This is because clang today always maps these to CXCursor_CXXNewExpr and CXCursor_CXXDeleteExpr with cursor.spelling == "" (empty string). So there is no (?) quick way for us to tell if a new/delete-token was overloaded or not. After follow-ups, follow symbol will work for operator overload usages in current translation unit. Commit is appended by Ivan Donchevskii. Task-number: QTCREATORBUG-19659 Change-Id: I157855d482a61ad2059642a1ee982089fcb7d312 Reviewed-by: Ivan Donchevskii <ivan.donchevskii@qt.io>
This commit is contained in:
@@ -91,6 +91,11 @@ public:
|
||||
return !isNull() && std::strlen(cString()) > 0;
|
||||
}
|
||||
|
||||
bool startsWith(const char* str) const
|
||||
{
|
||||
return std::strncmp(cString(), str, strlen(str)) == 0;
|
||||
}
|
||||
|
||||
friend bool operator==(const ClangString &first, const ClangString &second)
|
||||
{
|
||||
return std::strcmp(first.cString(), second.cString()) == 0;
|
||||
@@ -107,6 +112,7 @@ public:
|
||||
{
|
||||
return second == first;
|
||||
}
|
||||
|
||||
template<typename Type,
|
||||
typename = typename std::enable_if<std::is_pointer<Type>::value>::type
|
||||
>
|
||||
@@ -123,6 +129,39 @@ public:
|
||||
return second == first;
|
||||
}
|
||||
|
||||
friend bool operator!=(const ClangString &first, const ClangString &second)
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
template<std::size_t Size>
|
||||
friend bool operator!=(const ClangString &first, const char(&second)[Size])
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
template<std::size_t Size>
|
||||
friend bool operator!=(const char(&first)[Size], const ClangString &second)
|
||||
{
|
||||
return second != first;
|
||||
}
|
||||
|
||||
template<typename Type,
|
||||
typename = typename std::enable_if<std::is_pointer<Type>::value>::type
|
||||
>
|
||||
friend bool operator!=(const ClangString &first, Type second)
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
template<typename Type,
|
||||
typename = typename std::enable_if<std::is_pointer<Type>::value>::type
|
||||
>
|
||||
friend bool operator!=(Type first, const ClangString &second)
|
||||
{
|
||||
return !(first == second);
|
||||
}
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &os, const ClangString &string)
|
||||
{
|
||||
return os << string.cString();
|
||||
|
||||
@@ -216,16 +216,16 @@ void FullTokenInfo::memberReferenceKind(const Cursor &cursor)
|
||||
}
|
||||
}
|
||||
|
||||
void FullTokenInfo::keywordKind(const Cursor &cursor)
|
||||
void FullTokenInfo::keywordKind()
|
||||
{
|
||||
TokenInfo::keywordKind(cursor);
|
||||
TokenInfo::keywordKind();
|
||||
|
||||
CXCursorKind cursorKind = cursor.kind();
|
||||
CXCursorKind cursorKind = m_originalCursor.kind();
|
||||
bool anonymous = false;
|
||||
if (clang_Cursor_isAnonymous(cursor.cx())) {
|
||||
if (clang_Cursor_isAnonymous(m_originalCursor.cx())) {
|
||||
anonymous = true;
|
||||
} else {
|
||||
const Utf8String type = fullyQualifiedType(cursor);
|
||||
const Utf8String type = fullyQualifiedType(m_originalCursor);
|
||||
if (type.endsWith(Utf8StringLiteral(")"))
|
||||
&& static_cast<const QByteArray &>(type).indexOf("(anonymous") >= 0) {
|
||||
anonymous = true;
|
||||
@@ -242,11 +242,33 @@ void FullTokenInfo::keywordKind(const Cursor &cursor)
|
||||
m_types.mixinHighlightingTypes.push_back(HighlightingType::Namespace);
|
||||
m_extraInfo.declaration = m_extraInfo.definition = true;
|
||||
m_extraInfo.token = Utf8StringLiteral("anonymous");
|
||||
updateTypeSpelling(cursor);
|
||||
m_extraInfo.cursorRange = cursor.sourceRange();
|
||||
updateTypeSpelling(m_originalCursor);
|
||||
m_extraInfo.cursorRange = m_originalCursor.sourceRange();
|
||||
}
|
||||
}
|
||||
|
||||
void FullTokenInfo::overloadedOperatorKind()
|
||||
{
|
||||
TokenInfo::overloadedOperatorKind();
|
||||
|
||||
if (m_types.mixinHighlightingTypes.front() != HighlightingType::OverloadedOperator)
|
||||
return;
|
||||
|
||||
// Overloaded operator
|
||||
m_extraInfo.identifier = true;
|
||||
if (!m_originalCursor.isDeclaration())
|
||||
return;
|
||||
|
||||
// Overloaded operator declaration
|
||||
m_extraInfo.declaration = true;
|
||||
m_extraInfo.definition = m_originalCursor.isDefinition();
|
||||
|
||||
updateTypeSpelling(m_originalCursor, true);
|
||||
m_extraInfo.cursorRange = m_originalCursor.sourceRange();
|
||||
m_extraInfo.accessSpecifier = m_originalCursor.accessSpecifier();
|
||||
m_extraInfo.storageClass = m_originalCursor.storageClass();
|
||||
}
|
||||
|
||||
void FullTokenInfo::evaluate()
|
||||
{
|
||||
m_extraInfo.token = ClangString(clang_getTokenSpelling(m_cxTranslationUnit, *m_cxToken));
|
||||
|
||||
@@ -48,7 +48,8 @@ protected:
|
||||
void variableKind(const Cursor &cursor) override;
|
||||
void fieldKind(const Cursor &cursor) override;
|
||||
void memberReferenceKind(const Cursor &cursor) override;
|
||||
void keywordKind(const Cursor &cursor) override;
|
||||
void keywordKind() override;
|
||||
void overloadedOperatorKind() override;
|
||||
private:
|
||||
void updateTypeSpelling(const Cursor &cursor, bool functionLike = false);
|
||||
void updatePropertyData();
|
||||
|
||||
@@ -98,9 +98,7 @@ TokenInfo::operator TokenInfoContainer() const
|
||||
return TokenInfoContainer(m_line, m_column, m_length, m_types);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool isFinalFunction(const Cursor &cursor)
|
||||
static bool isFinalFunction(const Cursor &cursor)
|
||||
{
|
||||
auto referencedCursor = cursor.referenced();
|
||||
if (referencedCursor.hasFinalFunctionAttribute())
|
||||
@@ -109,7 +107,7 @@ bool isFinalFunction(const Cursor &cursor)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isFunctionInFinalClass(const Cursor &cursor)
|
||||
static bool isFunctionInFinalClass(const Cursor &cursor)
|
||||
{
|
||||
auto functionBase = cursor.functionBaseDeclaration();
|
||||
if (functionBase.isValid() && functionBase.hasFinalClassAttribute())
|
||||
@@ -117,7 +115,6 @@ bool isFunctionInFinalClass(const Cursor &cursor)
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void TokenInfo::memberReferenceKind(const Cursor &cursor)
|
||||
{
|
||||
@@ -196,12 +193,11 @@ bool TokenInfo::isVirtualMethodDeclarationOrDefinition(const Cursor &cursor) con
|
||||
&& (m_originalCursor.isDeclaration() || m_originalCursor.isDefinition());
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool isNotFinalFunction(const Cursor &cursor)
|
||||
static bool isNotFinalFunction(const Cursor &cursor)
|
||||
{
|
||||
return !cursor.hasFinalFunctionAttribute();
|
||||
}
|
||||
}
|
||||
|
||||
bool TokenInfo::isRealDynamicCall(const Cursor &cursor) const
|
||||
{
|
||||
return m_originalCursor.isDynamicCall() && isNotFinalFunction(cursor);
|
||||
@@ -246,9 +242,7 @@ void TokenInfo::collectOutputArguments(const Cursor &cursor)
|
||||
filterOutPreviousOutputArguments();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
uint getEnd(CXSourceRange cxSourceRange)
|
||||
static uint getEnd(CXSourceRange cxSourceRange)
|
||||
{
|
||||
CXSourceLocation startSourceLocation = clang_getRangeEnd(cxSourceRange);
|
||||
|
||||
@@ -258,7 +252,6 @@ uint getEnd(CXSourceRange cxSourceRange)
|
||||
|
||||
return endOffset;
|
||||
}
|
||||
}
|
||||
|
||||
void TokenInfo::filterOutPreviousOutputArguments()
|
||||
{
|
||||
@@ -456,8 +449,7 @@ void TokenInfo::identifierKind(const Cursor &cursor, Recursion recursion)
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
HighlightingType literalKind(const Cursor &cursor)
|
||||
static HighlightingType literalKind(const Cursor &cursor)
|
||||
{
|
||||
switch (cursor.kind()) {
|
||||
case CXCursor_CharacterLiteral:
|
||||
@@ -476,32 +468,86 @@ HighlightingType literalKind(const Cursor &cursor)
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
bool hasOperatorName(const char *operatorString)
|
||||
static bool isTokenPartOfOperator(const Cursor &declarationCursor, CXToken *token)
|
||||
{
|
||||
return std::strncmp(operatorString, "operator", 8) == 0;
|
||||
Q_ASSERT(declarationCursor.isDeclaration());
|
||||
const CXTranslationUnit cxTranslationUnit = declarationCursor.cxTranslationUnit();
|
||||
const ClangString tokenName = clang_getTokenSpelling(cxTranslationUnit, *token);
|
||||
if (tokenName == "operator")
|
||||
return true;
|
||||
|
||||
if (tokenName == "(") {
|
||||
// Valid operator declarations have at least one token after '(' so
|
||||
// it's safe to proceed to token + 1 without extra checks.
|
||||
const ClangString nextToken = clang_getTokenSpelling(cxTranslationUnit, *(token + 1));
|
||||
if (nextToken != ")") {
|
||||
// Argument lists' parentheses are not operator tokens.
|
||||
// This '('-token opens a (non-empty) argument list.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// It's safe to evaluate the preceding token because we will at least have
|
||||
// the 'operator'-keyword's token to the left.
|
||||
CXToken *prevToken = token - 1;
|
||||
if (clang_getTokenKind(*prevToken) == CXToken_Punctuation) {
|
||||
if (tokenName == "(") {
|
||||
// In an operator declaration, when a '(' follows another punctuation
|
||||
// then this '(' opens an argument list. Ex: operator*()|operator()().
|
||||
return false;
|
||||
}
|
||||
|
||||
// This token is preceded by another punctuation token so this token
|
||||
// could be the second token of a two-tokened operator such as
|
||||
// operator+=|-=|*=|/=|<<|==|<=|++ or the third token of operator
|
||||
// new[]|delete[]. We decrement one more time to hit one of the keywords:
|
||||
// "operator" / "delete" / "new".
|
||||
--prevToken;
|
||||
}
|
||||
|
||||
const ClangString precedingKeyword =
|
||||
clang_getTokenSpelling(cxTranslationUnit, *prevToken);
|
||||
|
||||
return precedingKeyword == "operator" ||
|
||||
precedingKeyword == "new" ||
|
||||
precedingKeyword == "delete";
|
||||
}
|
||||
|
||||
HighlightingType operatorKind(const Cursor &cursor)
|
||||
void TokenInfo::overloadedOperatorKind()
|
||||
{
|
||||
if (hasOperatorName(cursor.spelling().cString()))
|
||||
return HighlightingType::Operator;
|
||||
else
|
||||
return HighlightingType::Invalid;
|
||||
bool inOperatorDeclaration = m_originalCursor.isDeclaration();
|
||||
Cursor declarationCursor =
|
||||
inOperatorDeclaration ? m_originalCursor :
|
||||
m_originalCursor.referenced();
|
||||
if (!declarationCursor.displayName().startsWith("operator"))
|
||||
return;
|
||||
|
||||
if (inOperatorDeclaration && !isTokenPartOfOperator(declarationCursor, m_cxToken))
|
||||
return;
|
||||
|
||||
if (m_types.mainHighlightingType == HighlightingType::Invalid)
|
||||
m_types.mainHighlightingType = HighlightingType::Operator;
|
||||
m_types.mixinHighlightingTypes.push_back(HighlightingType::OverloadedOperator);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
HighlightingType TokenInfo::punctuationKind(const Cursor &cursor)
|
||||
void TokenInfo::punctuationOrOperatorKind()
|
||||
{
|
||||
HighlightingType highlightingType = HighlightingType::Invalid;
|
||||
|
||||
switch (cursor.kind()) {
|
||||
auto kind = m_originalCursor.kind();
|
||||
switch (kind) {
|
||||
case CXCursor_CallExpr:
|
||||
collectOutputArguments(m_originalCursor);
|
||||
Q_FALLTHROUGH();
|
||||
case CXCursor_FunctionDecl:
|
||||
case CXCursor_CXXMethod:
|
||||
case CXCursor_DeclRefExpr:
|
||||
highlightingType = operatorKind(cursor);
|
||||
// TODO(QTCREATORBUG-19948): Mark calls to overloaded new and delete.
|
||||
// Today we can't because libclang sets these cursors' spelling to "".
|
||||
// case CXCursor_CXXNewExpr:
|
||||
// case CXCursor_CXXDeleteExpr:
|
||||
overloadedOperatorKind();
|
||||
break;
|
||||
case CXCursor_Constructor:
|
||||
case CXCursor_CallExpr:
|
||||
collectOutputArguments(cursor);
|
||||
collectOutputArguments(m_originalCursor);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -509,8 +555,6 @@ HighlightingType TokenInfo::punctuationKind(const Cursor &cursor)
|
||||
|
||||
if (isOutputArgument())
|
||||
m_types.mixinHighlightingTypes.push_back(HighlightingType::OutputArgument);
|
||||
|
||||
return highlightingType;
|
||||
}
|
||||
|
||||
enum class PropertyPart
|
||||
@@ -582,17 +626,20 @@ void TokenInfo::invalidFileKind()
|
||||
}
|
||||
}
|
||||
|
||||
static HighlightingType highlightingTypeForKeyword(CXTranslationUnit cxTranslationUnit,
|
||||
CXToken *cxToken,
|
||||
const Cursor &cursor)
|
||||
void TokenInfo::keywordKind()
|
||||
{
|
||||
switch (cursor.kind()) {
|
||||
case CXCursor_PreprocessingDirective: return HighlightingType::Preprocessor;
|
||||
case CXCursor_InclusionDirective: return HighlightingType::StringLiteral;
|
||||
default: break;
|
||||
switch (m_originalCursor.kind()) {
|
||||
case CXCursor_PreprocessingDirective:
|
||||
m_types.mainHighlightingType = HighlightingType::Preprocessor;
|
||||
return;
|
||||
case CXCursor_InclusionDirective:
|
||||
m_types.mainHighlightingType = HighlightingType::StringLiteral;
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
const ClangString spelling = clang_getTokenSpelling(cxTranslationUnit, *cxToken);
|
||||
const ClangString spelling = clang_getTokenSpelling(m_cxTranslationUnit, *m_cxToken);
|
||||
if (spelling == "bool"
|
||||
|| spelling == "char"
|
||||
|| spelling == "char16_t"
|
||||
@@ -606,17 +653,14 @@ static HighlightingType highlightingTypeForKeyword(CXTranslationUnit cxTranslati
|
||||
|| spelling == "unsigned"
|
||||
|| spelling == "void"
|
||||
|| spelling == "wchar_t") {
|
||||
return HighlightingType::PrimitiveType;
|
||||
m_types.mainHighlightingType = HighlightingType::PrimitiveType;
|
||||
return;
|
||||
}
|
||||
|
||||
return HighlightingType::Keyword;
|
||||
}
|
||||
m_types.mainHighlightingType = HighlightingType::Keyword;
|
||||
|
||||
void TokenInfo::keywordKind(const Cursor &cursor)
|
||||
{
|
||||
m_types.mainHighlightingType = highlightingTypeForKeyword(m_cxTranslationUnit,
|
||||
m_cxToken,
|
||||
cursor);
|
||||
if (spelling == "new" || spelling == "delete" || spelling == "operator")
|
||||
overloadedOperatorKind();
|
||||
}
|
||||
|
||||
void TokenInfo::evaluate()
|
||||
@@ -627,10 +671,10 @@ void TokenInfo::evaluate()
|
||||
|
||||
switch (cxTokenKind) {
|
||||
case CXToken_Keyword:
|
||||
keywordKind(m_originalCursor);
|
||||
keywordKind();
|
||||
break;
|
||||
case CXToken_Punctuation:
|
||||
m_types.mainHighlightingType = punctuationKind(m_originalCursor);
|
||||
punctuationOrOperatorKind();
|
||||
break;
|
||||
case CXToken_Identifier:
|
||||
identifierKind(m_originalCursor, Recursion::FirstPass);
|
||||
|
||||
@@ -93,10 +93,11 @@ protected:
|
||||
virtual void fieldKind(const Cursor &cursor);
|
||||
virtual void functionKind(const Cursor &cursor, Recursion recursion);
|
||||
virtual void memberReferenceKind(const Cursor &cursor);
|
||||
virtual HighlightingType punctuationKind(const Cursor &cursor);
|
||||
virtual void typeKind(const Cursor &cursor);
|
||||
virtual void keywordKind(const Cursor &cursor);
|
||||
virtual void keywordKind();
|
||||
virtual void invalidFileKind();
|
||||
virtual void overloadedOperatorKind();
|
||||
virtual void punctuationOrOperatorKind();
|
||||
|
||||
Cursor m_originalCursor;
|
||||
CXToken *m_cxToken = nullptr;
|
||||
|
||||
Reference in New Issue
Block a user