Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 32 additions & 3 deletions clang-tools-extra/clang-tidy/readability/TrailingCommaCheck.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,36 @@ static bool isPreprocessorDirectiveToken(const Token &Tok,
SM.getExpansionLineNumber(Tok.getLocation());
}

// Returns the last non-comment token before the closing brace of Enum.
// Lexer::findPreviousToken() can't be used for this: it starts lexing at the
// beginning of a line, which may be inside a multi-line block comment.
static std::optional<Token> findLastEnumToken(const EnumDecl *Enum,
const SourceManager &SM,
const LangOptions &LangOpts) {
const auto [LBraceFile, LBraceOffset] =
SM.getDecomposedExpansionLoc(Enum->getBraceRange().getBegin());
const auto [RBraceFile, RBraceOffset] =
SM.getDecomposedExpansionLoc(Enum->getBraceRange().getEnd());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this also work for macro generated enums ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Macro handling is unchanged by this PR:

  • An enum whose enum keyword or closing brace comes from a macro expansion is still skipped by the existing unless(isMacro()) matcher, so this function never sees it.
  • If only the opening brace comes from a macro, or macros are used between the braces, the function takes the expansion locations of the braces and lexes the spelled tokens between them. That gives the same last token as findPreviousToken did.

To check, I compared this branch against a build of the same main commit without the patch, on a file of macro cases (enum from a function-like macro, enum inside a macro argument, enumerators wrapped in a macro, last enumerator from a macro, an X-macro list, opening or closing brace from a macro), with both Append and Remove as the multi-line policy. The diagnostics are identical except for the cases with a multi-line block comment before the closing brace, which is what this PR fixes.
I added tests for four of these cases in 0ed8801.
Three existing false positives showed up in that comparison. They are the same before and after, so I left them out of this PR: a macro that expands to enumerators with a trailing comma (#define ITEMS A, B,), an #undef X directly before the closing brace, and an empty macro after the trailing comma (B, EMPTY).

bool Invalid = false;
const StringRef Buffer = SM.getBufferData(RBraceFile, &Invalid);
if (Invalid)
return std::nullopt;
// The braces are in different files if the enum's body is split across an
// #include. Then lex the closing brace's file from its beginning.
const bool SameFile = LBraceFile == RBraceFile;
Lexer RawLexer(SM.getLocForStartOfFile(RBraceFile), LangOpts, Buffer.begin(),
Buffer.data() + (SameFile ? LBraceOffset : 0), Buffer.end());
Token Tok;
// Skip the opening brace.
if (SameFile && RawLexer.LexFromRawLexer(Tok))
return std::nullopt;
std::optional<Token> LastTok;
while (!RawLexer.LexFromRawLexer(Tok) &&
SM.getFileOffset(Tok.getLocation()) < RBraceOffset)
LastTok = Tok;
return LastTok;
}

namespace {

AST_POLYMORPHIC_MATCHER(isMacro,
Expand Down Expand Up @@ -122,9 +152,8 @@ void TrailingCommaCheck::checkEnumDecl(const EnumDecl *Enum,
if (Policy == CommaPolicyKind::Ignore)
return;

const std::optional<Token> LastTok = Lexer::findPreviousToken(
Enum->getBraceRange().getEnd(), *Result.SourceManager, getLangOpts(),
/*IncludeComments=*/false);
const std::optional<Token> LastTok =
findLastEnumToken(Enum, *Result.SourceManager, getLangOpts());
if (!LastTok)
return;

Expand Down
3 changes: 3 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ infrastructure are described first, followed by tool-specific sections.
- Fixed a false positive on empty brace initializers of types with default
member initializers.

- Fixed wrong diagnostics and fix-its when an enum's last enumerator is
followed by a block comment that spans multiple lines.

- Improved {doc}`readability-use-std-min-max
<clang-tidy/checks/readability/use-std-min-max>` check by fixing spurious
trailing semicolons and lost comments when the `if` body has no braces.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
enum SplitEnum {
SplitA,
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ enum E3 {
};
enum Empty {};

// A multi-line block comment before '}' is skipped.
enum MultiLineComment {
MLC_A, /* comment that
spans multiple lines */
};
// CHECK-MESSAGES: :[[@LINE-3]]:8: warning: enum should not have a trailing comma
// CHECK-FIXES: enum MultiLineComment {
// CHECK-FIXES-NEXT: MLC_A /* comment that
// CHECK-FIXES-NEXT: spans multiple lines */
// CHECK-FIXES-NEXT: };

void f() {
// Multi-line init lists with trailing commas - should warn to remove
int a[] = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// RUN: %check_clang_tidy -std=c++98-or-later %s readability-trailing-comma %t -- \
// RUN: -- -I %S/Inputs/trailing-comma

// The enum's opening brace is in the header and its closing brace is here.
#include "split-enum.h"
SplitB
};
// CHECK-MESSAGES: :[[@LINE-2]]:9: warning: enum should have a trailing comma
// CHECK-FIXES: SplitB,
// CHECK-FIXES-NEXT: };
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,65 @@ enum NullDirectiveAfter {
#
};

// A multi-line block comment before '}' is skipped.
enum MultiLineCommentAfterComma {
MLC_A, /* comment that
spans multiple lines */
};

enum MultiLineCommentNoComma {
MLC_B /* comment that
spans multiple lines */
};
// CHECK-MESSAGES: :[[@LINE-3]]:8: warning: enum should have a trailing comma
// CHECK-FIXES: enum MultiLineCommentNoComma {
// CHECK-FIXES-NEXT: MLC_B, /* comment that
// CHECK-FIXES-NEXT: spans multiple lines */
// CHECK-FIXES-NEXT: };

// Enumerators from macros are checked like other enumerators.
#define ENUMERATOR(n) n
enum MacroEnumerator {
ENUMERATOR(ME_A),
ENUMERATOR(ME_B)
};
// CHECK-MESSAGES: :[[@LINE-2]]:19: warning: enum should have a trailing comma
// CHECK-FIXES: enum MacroEnumerator {
// CHECK-FIXES-NEXT: ENUMERATOR(ME_A),
// CHECK-FIXES-NEXT: ENUMERATOR(ME_B),
// CHECK-FIXES-NEXT: };

enum MacroEnumeratorMultiLineComment {
ENUMERATOR(MEC_A),
ENUMERATOR(MEC_B) /* comment that
spans multiple lines */
};
// CHECK-MESSAGES: :[[@LINE-3]]:20: warning: enum should have a trailing comma
// CHECK-FIXES: enum MacroEnumeratorMultiLineComment {
// CHECK-FIXES-NEXT: ENUMERATOR(MEC_A),
// CHECK-FIXES-NEXT: ENUMERATOR(MEC_B), /* comment that
// CHECK-FIXES-NEXT: spans multiple lines */
// CHECK-FIXES-NEXT: };

// The opening brace can come from a macro.
#define LBRACE {
enum MacroOpeningBrace LBRACE
MOB_A,
MOB_B
};
// CHECK-MESSAGES: :[[@LINE-2]]:8: warning: enum should have a trailing comma
// CHECK-FIXES: enum MacroOpeningBrace LBRACE
// CHECK-FIXES-NEXT: MOB_A,
// CHECK-FIXES-NEXT: MOB_B,
// CHECK-FIXES-NEXT: };

// An enum whose closing brace comes from a macro is ignored.
#define RBRACE }
enum MacroClosingBrace {
MCB_A,
MCB_B
RBRACE;

// Macros are ignored
#define ENUM(n, a, b) enum n { a, b }
#define INIT {1, 2}
Expand Down
Loading