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:
Hugo Holgersson
2016-11-05 15:29:10 +01:00
parent bb6eae5c3b
commit 142ae0cdf9
15 changed files with 519 additions and 75 deletions

View File

@@ -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();

View File

@@ -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));

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;