https://github.com/unterumarmung updated https://github.com/llvm/llvm-project/pull/180404
>From 5e6ce37d89246794a27b634fc24fcace5dac818f Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 28 Feb 2026 21:55:34 +0300 Subject: [PATCH 1/4] [clang-tidy][NFC] Add getCommentsInRange utility Introduce getCommentsInRange in LexerUtils and refactor getTrailingCommentsInRange to reuse a shared comment collector with mode-based behavior. Add unit tests for all-comments behavior and preserve existing trailing-comment semantics. --- .../clang-tidy/utils/LexerUtils.cpp | 27 +++- .../clang-tidy/utils/LexerUtils.h | 5 + .../unittests/clang-tidy/LexerUtilsTest.cpp | 119 ++++++++++++++++++ 3 files changed, 147 insertions(+), 4 deletions(-) diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp index a9a8c7bbf4c89..4c36d4cacd1ec 100644 --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -100,9 +100,14 @@ bool rangeContainsExpansionsOrDirectives(SourceRange Range, return false; } -std::vector<CommentToken> -getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, - const LangOptions &LangOpts) { +namespace { +enum class CommentCollectionMode { AllComments, TrailingComments }; +} // namespace + +static std::vector<CommentToken> +collectCommentsInRange(CharSourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts, + CommentCollectionMode Mode) { std::vector<CommentToken> Comments; if (Range.isInvalid()) return Comments; @@ -149,7 +154,7 @@ getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, Tok.getLocation(), StringRef(Buffer.begin() + CommentLoc.second, Tok.getLength()), }); - } else { + } else if (Mode == CommentCollectionMode::TrailingComments) { // Clear comments found before the different token, e.g. comma. Callers // use this to retrieve only the contiguous comment block that directly // precedes a token of interest. @@ -160,6 +165,20 @@ getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, return Comments; } +std::vector<CommentToken> getCommentsInRange(CharSourceRange Range, + const SourceManager &SM, + const LangOptions &LangOpts) { + return collectCommentsInRange(Range, SM, LangOpts, + CommentCollectionMode::AllComments); +} + +std::vector<CommentToken> +getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts) { + return collectCommentsInRange(Range, SM, LangOpts, + CommentCollectionMode::TrailingComments); +} + std::optional<Token> getQualifyingToken(tok::TokenKind TK, CharSourceRange Range, const ASTContext &Context, diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h index 38123ae14cff7..681fc6194d45c 100644 --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -120,6 +120,11 @@ struct CommentToken { StringRef Text; }; +/// Returns all comment tokens found in the given range. +std::vector<CommentToken> getCommentsInRange(CharSourceRange Range, + const SourceManager &SM, + const LangOptions &LangOpts); + /// Returns comment tokens found in the given range. If a non-comment token is /// encountered, clears previously collected comments and continues. std::vector<CommentToken> diff --git a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp index 438a78b4694ee..0e0c3d60dedfe 100644 --- a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp @@ -43,6 +43,125 @@ static CharSourceRange rangeFromAnnotations(const llvm::Annotations &A, namespace { +TEST(LexerUtilsTest, GetCommentsInRangeAdjacentComments) { + llvm::Annotations Code(R"cpp( +void f() { + $range[[/*first*/ /*second*/]] + int x = 0; +} +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange Range = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const std::vector<utils::lexer::CommentToken> Comments = + utils::lexer::getCommentsInRange(Range, SM, LangOpts); + ASSERT_EQ(2u, Comments.size()); + EXPECT_EQ("/*first*/", Comments[0].Text); + EXPECT_EQ("/*second*/", Comments[1].Text); + const StringRef CodeText = Code.code(); + const size_t FirstOffset = CodeText.find("/*first*/"); + ASSERT_NE(StringRef::npos, FirstOffset); + const size_t SecondOffset = CodeText.find("/*second*/"); + ASSERT_NE(StringRef::npos, SecondOffset); + EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc)); + EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc)); +} + +TEST(LexerUtilsTest, GetCommentsInRangeKeepsCommentsAcrossTokens) { + llvm::Annotations Code(R"cpp( +void f() { + int x = ($range[[/*first*/ 0, /*second*/]] 1); +} +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange Range = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const std::vector<utils::lexer::CommentToken> Comments = + utils::lexer::getCommentsInRange(Range, SM, LangOpts); + ASSERT_EQ(2u, Comments.size()); + EXPECT_EQ("/*first*/", Comments[0].Text); + EXPECT_EQ("/*second*/", Comments[1].Text); + const StringRef CodeText = Code.code(); + const size_t FirstOffset = CodeText.find("/*first*/"); + ASSERT_NE(StringRef::npos, FirstOffset); + const size_t SecondOffset = CodeText.find("/*second*/"); + ASSERT_NE(StringRef::npos, SecondOffset); + EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc)); + EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc)); +} + +TEST(LexerUtilsTest, GetCommentsInRangeLineComments) { + llvm::Annotations Code(R"cpp( +void f() { + $range[[// first + // second + ]] + int x = 0; +} +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange Range = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const std::vector<utils::lexer::CommentToken> Comments = + utils::lexer::getCommentsInRange(Range, SM, LangOpts); + ASSERT_EQ(2u, Comments.size()); + EXPECT_EQ("// first", Comments[0].Text); + EXPECT_EQ("// second", Comments[1].Text); + const StringRef CodeText = Code.code(); + const size_t FirstOffset = CodeText.find("// first"); + ASSERT_NE(StringRef::npos, FirstOffset); + const size_t SecondOffset = CodeText.find("// second"); + ASSERT_NE(StringRef::npos, SecondOffset); + EXPECT_EQ(FirstOffset, SM.getFileOffset(Comments[0].Loc)); + EXPECT_EQ(SecondOffset, SM.getFileOffset(Comments[1].Loc)); +} + +TEST(LexerUtilsTest, GetCommentsInRangeNoComments) { + llvm::Annotations Code(R"cpp( +void f() { + int x = $range[[0 + 1]]; +} +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange Range = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const std::vector<utils::lexer::CommentToken> Comments = + utils::lexer::getCommentsInRange(Range, SM, LangOpts); + EXPECT_TRUE(Comments.empty()); +} + +TEST(LexerUtilsTest, GetCommentsInRangeInvalidRange) { + std::unique_ptr<ASTUnit> AST = buildAST("int value = 0;"); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const std::vector<utils::lexer::CommentToken> Comments = + utils::lexer::getCommentsInRange(CharSourceRange(), SM, LangOpts); + EXPECT_TRUE(Comments.empty()); +} + TEST(LexerUtilsTest, GetTrailingCommentsInRangeAdjacentComments) { llvm::Annotations Code(R"cpp( void f() { >From 36cd9ae4b1eed157b68dd9ce326b6fd7f48167f7 Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sat, 28 Feb 2026 22:08:22 +0300 Subject: [PATCH 2/4] [clang-tidy][NFC] Add findTokenInRange and reuse it in ExplicitConstructorCheck --- .../google/ExplicitConstructorCheck.cpp | 41 +--- .../clang-tidy/utils/LexerUtils.cpp | 53 +++++ .../clang-tidy/utils/LexerUtils.h | 8 + .../unittests/clang-tidy/LexerUtilsTest.cpp | 206 ++++++++++++++++++ 4 files changed, 274 insertions(+), 34 deletions(-) diff --git a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp index ac604b7b9f1b4..ba3af5762f27e 100644 --- a/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp +++ b/clang-tools-extra/clang-tidy/google/ExplicitConstructorCheck.cpp @@ -7,10 +7,10 @@ //===----------------------------------------------------------------------===// #include "ExplicitConstructorCheck.h" +#include "../utils/LexerUtils.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/ASTMatchers/ASTMatchers.h" -#include "clang/Lex/Lexer.h" using namespace clang::ast_matchers; @@ -31,32 +31,6 @@ void ExplicitConstructorCheck::registerMatchers(MatchFinder *Finder) { this); } -// Looks for the token matching the predicate and returns the range of the found -// token including trailing whitespace. -static SourceRange findToken(const SourceManager &Sources, - const LangOptions &LangOpts, - SourceLocation StartLoc, SourceLocation EndLoc, - bool (*Pred)(const Token &)) { - if (StartLoc.isMacroID() || EndLoc.isMacroID()) - return {}; - const FileID File = Sources.getFileID(Sources.getSpellingLoc(StartLoc)); - const StringRef Buf = Sources.getBufferData(File); - const char *StartChar = Sources.getCharacterData(StartLoc); - Lexer Lex(StartLoc, LangOpts, StartChar, StartChar, Buf.end()); - Lex.SetCommentRetentionState(true); - Token Tok; - do { - Lex.LexFromRawLexer(Tok); - if (Pred(Tok)) { - Token NextTok; - Lex.LexFromRawLexer(NextTok); - return {Tok.getLocation(), NextTok.getLocation()}; - } - } while (Tok.isNot(tok::eof) && Tok.getLocation() < EndLoc); - - return {}; -} - static bool declIsStdInitializerList(const NamedDecl *D) { // First use the fast getName() method to avoid unnecessary calls to the // slow getQualifiedNameAsString(). @@ -113,9 +87,10 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { return Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == "explicit"; }; - const SourceRange ExplicitTokenRange = - findToken(*Result.SourceManager, getLangOpts(), - Ctor->getOuterLocStart(), Ctor->getEndLoc(), IsKwExplicit); + const CharSourceRange ConstructorRange = CharSourceRange::getTokenRange( + Ctor->getOuterLocStart(), Ctor->getEndLoc()); + const CharSourceRange ExplicitTokenRange = utils::lexer::findTokenInRange( + ConstructorRange, *Result.SourceManager, getLangOpts(), IsKwExplicit); StringRef ConstructorDescription; if (Ctor->isMoveConstructor()) ConstructorDescription = "move"; @@ -127,10 +102,8 @@ void ExplicitConstructorCheck::check(const MatchFinder::MatchResult &Result) { auto Diag = diag(Ctor->getLocation(), "%0 constructor should not be declared explicit") << ConstructorDescription; - if (ExplicitTokenRange.isValid()) { - Diag << FixItHint::CreateRemoval( - CharSourceRange::getCharRange(ExplicitTokenRange)); - } + if (ExplicitTokenRange.isValid()) + Diag << FixItHint::CreateRemoval(ExplicitTokenRange); return; } diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp index 4c36d4cacd1ec..75842542b63a1 100644 --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.cpp @@ -179,6 +179,59 @@ getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, CommentCollectionMode::TrailingComments); } +CharSourceRange findTokenInRange(CharSourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts, + llvm::function_ref<bool(const Token &)> Pred) { + if (Range.isInvalid()) + return {}; + + // Normalize to a file-based char range so raw lexing can operate on one + // contiguous buffer and reject unmappable (e.g. macro) ranges. + const CharSourceRange FileRange = + Lexer::makeFileCharRange(Range, SM, LangOpts); + if (FileRange.isInvalid()) + return {}; + + const auto [BeginFID, BeginOffset] = + SM.getDecomposedLoc(FileRange.getBegin()); + const auto [EndFID, EndOffset] = SM.getDecomposedLoc(FileRange.getEnd()); + if (BeginFID != EndFID || BeginOffset > EndOffset) + return {}; + + bool Invalid = false; + const StringRef Buffer = SM.getBufferData(BeginFID, &Invalid); + if (Invalid) + return {}; + + const char *LexStart = Buffer.data() + BeginOffset; + // Re-lex raw tokens in the bounded file buffer while preserving comments so + // callers can match tokens regardless of interleaved comments. + Lexer TheLexer(SM.getLocForStartOfFile(BeginFID), LangOpts, Buffer.begin(), + LexStart, Buffer.end()); + TheLexer.SetCommentRetentionState(true); + + while (true) { + Token Tok; + if (TheLexer.LexFromRawLexer(Tok)) + return {}; + + if (Tok.is(tok::eof) || Tok.getLocation() == FileRange.getEnd() || + SM.isBeforeInTranslationUnit(FileRange.getEnd(), Tok.getLocation())) + return {}; + + if (!Pred(Tok)) + continue; + + Token NextTok; + if (TheLexer.LexFromRawLexer(NextTok)) + return {}; + // Return a char range ending at the next token start so trailing trivia of + // the matched token is included (useful for fix-it removals). + return CharSourceRange::getCharRange(Tok.getLocation(), + NextTok.getLocation()); + } +} + std::optional<Token> getQualifyingToken(tok::TokenKind TK, CharSourceRange Range, const ASTContext &Context, diff --git a/clang-tools-extra/clang-tidy/utils/LexerUtils.h b/clang-tools-extra/clang-tidy/utils/LexerUtils.h index 681fc6194d45c..f89ca919ac95a 100644 --- a/clang-tools-extra/clang-tidy/utils/LexerUtils.h +++ b/clang-tools-extra/clang-tidy/utils/LexerUtils.h @@ -12,6 +12,7 @@ #include "clang/AST/ASTContext.h" #include "clang/Basic/TokenKinds.h" #include "clang/Lex/Lexer.h" +#include "llvm/ADT/STLFunctionalExtras.h" #include <optional> #include <utility> #include <vector> @@ -131,6 +132,13 @@ std::vector<CommentToken> getTrailingCommentsInRange(CharSourceRange Range, const SourceManager &SM, const LangOptions &LangOpts); +/// Returns the first token in \p Range matching \p Pred. +/// The returned char range starts at the matched token and ends at the start +/// of the next token. Returns invalid range if no token matches. +CharSourceRange findTokenInRange(CharSourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts, + llvm::function_ref<bool(const Token &)> Pred); + /// Assuming that ``Range`` spans a CVR-qualified type, returns the /// token in ``Range`` that is responsible for the qualification. ``Range`` /// must be valid with respect to ``SM``. Returns ``std::nullopt`` if no diff --git a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp index 0e0c3d60dedfe..9360e6fe9b14f 100644 --- a/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp +++ b/clang-tools-extra/unittests/clang-tidy/LexerUtilsTest.cpp @@ -8,6 +8,7 @@ #include "../clang-tidy/utils/LexerUtils.h" +#include "clang/AST/DeclCXX.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/SourceManager.h" #include "clang/Frontend/ASTUnit.h" @@ -41,8 +42,213 @@ static CharSourceRange rangeFromAnnotations(const llvm::Annotations &A, return CharSourceRange::getCharRange(Begin, End); } +static bool isRawIdentifierNamed(const Token &Tok, StringRef Name) { + return Tok.is(tok::raw_identifier) && Tok.getRawIdentifier() == Name; +} + +static const CXXConstructorDecl * +findFirstNonImplicitConstructor(const ASTContext &Context) { + for (const Decl *D : Context.getTranslationUnitDecl()->decls()) { + const auto *RD = dyn_cast<CXXRecordDecl>(D); + if (!RD) + continue; + for (const CXXConstructorDecl *Ctor : RD->ctors()) + if (!Ctor->isImplicit()) + return Ctor; + } + return nullptr; +} + namespace { +TEST(LexerUtilsTest, FindTokenInRangeFindsMatch) { + llvm::Annotations Code(R"cpp( +struct S { + $range[[explicit ]] S(int); +}; +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange SearchRange = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, + [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); }); + ASSERT_TRUE(MatchedRange.isValid()); + + const StringRef CodeText = Code.code(); + const size_t ExplicitOffset = CodeText.find("explicit"); + ASSERT_NE(StringRef::npos, ExplicitOffset); + const size_t ConstructorOffset = CodeText.find("S(int)"); + ASSERT_NE(StringRef::npos, ConstructorOffset); + EXPECT_EQ(ExplicitOffset, SM.getFileOffset(MatchedRange.getBegin())); + EXPECT_EQ(ConstructorOffset, SM.getFileOffset(MatchedRange.getEnd())); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenNotFound) { + llvm::Annotations Code(R"cpp( +struct S { + $range[[int x = 0;]] + S(int); +}; +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange SearchRange = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, + [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeDoesNotMatchTokenAtEndBoundary) { + llvm::Annotations Code(R"cpp( +struct S { + $range[[int x = 0; ]]explicit S(int); +}; +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange SearchRange = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, + [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenPredicateNeverMatches) { + llvm::Annotations Code(R"cpp( +struct S { + $range[[explicit ]] S(int); +}; +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange SearchRange = + rangeFromAnnotations(Code, SM, SM.getMainFileID(), "range"); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, [](const Token &) { return false; }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForInvalidRange) { + std::unique_ptr<ASTUnit> AST = buildAST("struct S { explicit S(int); };"); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + CharSourceRange(), SM, LangOpts, [](const Token &) { return true; }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForReversedOffsets) { + llvm::Annotations Code(R"cpp( +struct S { + $a^explicit S(int);$b^ +}; +)cpp"); + std::unique_ptr<ASTUnit> AST = buildAST(Code.code()); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const SourceLocation MainFileStart = + SM.getLocForStartOfFile(SM.getMainFileID()); + const SourceLocation Begin = MainFileStart.getLocWithOffset(Code.point("b")); + const SourceLocation End = MainFileStart.getLocWithOffset(Code.point("a")); + ASSERT_TRUE(SM.isBeforeInTranslationUnit(End, Begin)); + + const CharSourceRange ReversedRange = + CharSourceRange::getCharRange(Begin, End); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + ReversedRange, SM, LangOpts, + [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidWhenFileRangeIsInvalid) { + llvm::Annotations Code(R"cpp( +#include "header.h" +int $begin^main_var = 0; +)cpp"); + const FileContentMappings Mappings = { + {"header.h", "int header_var = 0;\n"}, + }; + std::unique_ptr<ASTUnit> AST = buildAST(Code.code(), Mappings); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const SourceLocation MainFileStart = + SM.getLocForStartOfFile(SM.getMainFileID()); + const SourceLocation Begin = + MainFileStart.getLocWithOffset(Code.point("begin")); + ASSERT_TRUE(Begin.isFileID()); + + auto HeaderFile = AST->getFileManager().getOptionalFileRef("header.h"); + ASSERT_TRUE(HeaderFile.has_value()); + const FileID HeaderFID = SM.translateFile(*HeaderFile); + ASSERT_TRUE(HeaderFID.isValid()); + const SourceLocation HeaderBegin = SM.getLocForStartOfFile(HeaderFID); + ASSERT_TRUE(HeaderBegin.isFileID()); + + const CharSourceRange SearchRange = + CharSourceRange::getCharRange(Begin, HeaderBegin); + const CharSourceRange FileRange = + Lexer::makeFileCharRange(SearchRange, SM, LangOpts); + EXPECT_TRUE(FileRange.isInvalid()); + + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, [](const Token &) { return true; }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + +TEST(LexerUtilsTest, FindTokenInRangeReturnsInvalidForMacroRange) { + std::unique_ptr<ASTUnit> AST = buildAST(R"cpp( +#define EXPLICIT explicit +struct S { + EXPLICIT S(int); +}; +)cpp"); + ASSERT_TRUE(AST); + const ASTContext &Context = AST->getASTContext(); + const SourceManager &SM = Context.getSourceManager(); + const LangOptions &LangOpts = Context.getLangOpts(); + + const CXXConstructorDecl *Ctor = findFirstNonImplicitConstructor(Context); + ASSERT_NE(nullptr, Ctor); + ASSERT_TRUE(Ctor->getOuterLocStart().isMacroID()); + + const CharSourceRange SearchRange = CharSourceRange::getTokenRange( + Ctor->getOuterLocStart(), Ctor->getEndLoc()); + const CharSourceRange MatchedRange = utils::lexer::findTokenInRange( + SearchRange, SM, LangOpts, + [](const Token &Tok) { return isRawIdentifierNamed(Tok, "explicit"); }); + EXPECT_TRUE(MatchedRange.isInvalid()); +} + TEST(LexerUtilsTest, GetCommentsInRangeAdjacentComments) { llvm::Annotations Code(R"cpp( void f() { >From d67e1a1d24adc7a6af8775bd74c9ce0357a4b2fa Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sun, 8 Feb 2026 14:16:43 +0300 Subject: [PATCH 3/4] [clang-tidy] Add redundant qualified alias check Introduce `readability-redundant-qualified-alias` to flag identity type aliases that repeat a qualified name and suggest using-declarations when safe. The check is conservative: it skips macros, elaborated keywords, dependent types, and templates, and it suppresses fix-its when comments appear between the alias name and '='. `OnlyNamespaceScope` controls whether local/class scopes are included (default false). --- .../clang-tidy/readability/CMakeLists.txt | 1 + .../readability/ReadabilityTidyModule.cpp | 3 + .../RedundantQualifiedAliasCheck.cpp | 240 ++++++++++++++++++ .../RedundantQualifiedAliasCheck.h | 37 +++ clang-tools-extra/docs/ReleaseNotes.rst | 6 + .../docs/clang-tidy/checks/list.rst | 1 + .../readability/redundant-qualified-alias.rst | 30 +++ .../readability/redundant-qualified-alias.cpp | 201 +++++++++++++++ 8 files changed, 519 insertions(+) create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp create mode 100644 clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h create mode 100644 clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst create mode 100644 clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt index f1f3cde32feff..686e7c19d650b 100644 --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -47,6 +47,7 @@ add_clang_library(clangTidyReadabilityModule STATIC RedundantMemberInitCheck.cpp RedundantParenthesesCheck.cpp RedundantPreprocessorCheck.cpp + RedundantQualifiedAliasCheck.cpp RedundantSmartptrGetCheck.cpp RedundantStringCStrCheck.cpp RedundantStringInitCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp index c582dc98eac6b..8e9e00b23c84a 100644 --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -49,6 +49,7 @@ #include "RedundantMemberInitCheck.h" #include "RedundantParenthesesCheck.h" #include "RedundantPreprocessorCheck.h" +#include "RedundantQualifiedAliasCheck.h" #include "RedundantSmartptrGetCheck.h" #include "RedundantStringCStrCheck.h" #include "RedundantStringInitCheck.h" @@ -148,6 +149,8 @@ class ReadabilityModule : public ClangTidyModule { "readability-redundant-parentheses"); CheckFactories.registerCheck<RedundantPreprocessorCheck>( "readability-redundant-preprocessor"); + CheckFactories.registerCheck<RedundantQualifiedAliasCheck>( + "readability-redundant-qualified-alias"); CheckFactories.registerCheck<RedundantTypenameCheck>( "readability-redundant-typename"); CheckFactories.registerCheck<ReferenceToConstructedTemporaryCheck>( diff --git a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp new file mode 100644 index 0000000000000..ea02dbc634658 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp @@ -0,0 +1,240 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "RedundantQualifiedAliasCheck.h" +#include "../utils/LexerUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Stmt.h" +#include "clang/AST/TypeLoc.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "clang/Basic/SourceManager.h" +#include <cassert> +#include <optional> + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +struct NominalTypeLocInfo { + TypeLoc Loc; + bool HasQualifier = false; +}; + +} // namespace + +static bool hasMacroInRange(SourceRange Range, const SourceManager &SM, + const LangOptions &LangOpts) { + if (Range.isInvalid()) + return true; + return utils::lexer::rangeContainsExpansionsOrDirectives(Range, SM, LangOpts); +} + +static std::optional<NominalTypeLocInfo> peelToNominalTypeLoc(TypeLoc TL) { + if (TL.isNull()) + return std::nullopt; + + if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>()) { + // Avoid rewriting aliases that use an elaborated keyword + // (class/struct/enum). + if (TypedefTL.getElaboratedKeywordLoc().isValid()) + return std::nullopt; + const bool HasQualifier = + static_cast<bool>(TypedefTL.getQualifierLoc().getNestedNameSpecifier()); + return NominalTypeLocInfo{TypedefTL, HasQualifier}; + } + + if (const auto TagTL = TL.getAs<TagTypeLoc>()) { + // Avoid rewriting aliases that use an elaborated keyword + // (class/struct/enum). + if (TagTL.getElaboratedKeywordLoc().isValid()) + return std::nullopt; + const bool HasQualifier = + static_cast<bool>(TagTL.getQualifierLoc().getNestedNameSpecifier()); + return NominalTypeLocInfo{TagTL, HasQualifier}; + } + + return std::nullopt; +} + +static const NamedDecl *getNamedDeclFromNominalTypeLoc(TypeLoc TL) { + if (const auto TypedefTL = TL.getAs<TypedefTypeLoc>()) + return TypedefTL.getTypePtr()->getDecl(); + if (const auto TagTL = TL.getAs<TagTypeLoc>()) + return TagTL.getDecl(); + return nullptr; +} + +static bool hasSameUnqualifiedName(const TypeAliasDecl *Alias, + const NamedDecl *Target) { + return Alias->getName() == Target->getName(); +} + +static bool isNamespaceLikeDeclContext(const DeclContext *DC) { + return isa<TranslationUnitDecl>(DC) || isa<NamespaceDecl>(DC); +} + +static bool canUseUsingDeclarationForTarget(const TypeAliasDecl *Alias, + const NamedDecl *Target) { + const DeclContext *AliasContext = Alias->getDeclContext()->getRedeclContext(); + const DeclContext *TargetContext = + Target->getDeclContext()->getRedeclContext(); + + const auto *AliasRecord = dyn_cast<CXXRecordDecl>(AliasContext); + if (!AliasRecord) + return isNamespaceLikeDeclContext(TargetContext); + + const auto *TargetRecord = dyn_cast<CXXRecordDecl>(TargetContext); + return TargetRecord && AliasRecord->isDerivedFrom(TargetRecord); +} + +static bool hasTrailingSyntaxAfterRhsType(TypeLoc TL, const SourceManager &SM, + const LangOptions &LangOpts) { + const SourceLocation TypeEndLoc = TL.getEndLoc(); + if (TypeEndLoc.isInvalid()) + return true; + if (TypeEndLoc.isMacroID()) + return true; + const std::optional<Token> NextToken = + utils::lexer::findNextTokenSkippingComments(TypeEndLoc, SM, LangOpts); + return !NextToken || NextToken->isNot(tok::semi); +} + +namespace { + +AST_MATCHER(TypeAliasDecl, isAliasTemplate) { + return Node.getDescribedAliasTemplate() != nullptr; +} + +AST_MATCHER(TypeAliasDecl, hasAliasAttributes) { + if (Node.hasAttrs()) + return true; + const TypeSourceInfo *TSI = Node.getTypeSourceInfo(); + if (!TSI) + return false; + for (TypeLoc CurTL = TSI->getTypeLoc(); !CurTL.isNull(); + CurTL = CurTL.getNextTypeLoc()) + if (CurTL.getAs<AttributedTypeLoc>()) + return true; + return false; +} + +AST_MATCHER(TypeLoc, isNonDependentTypeLoc) { + return !Node.getType().isNull() && !Node.getType()->isDependentType(); +} + +AST_MATCHER(TypeLoc, isMacroFreeTypeLoc) { + const ASTContext &Context = Finder->getASTContext(); + return !hasMacroInRange(Node.getSourceRange(), Context.getSourceManager(), + Context.getLangOpts()); +} + +AST_MATCHER(TypeLoc, hasNoTrailingSyntaxAfterTypeLoc) { + const ASTContext &Context = Finder->getASTContext(); + return !hasTrailingSyntaxAfterRhsType(Node, Context.getSourceManager(), + Context.getLangOpts()); +} + +AST_MATCHER(TypeAliasDecl, hasUsingDeclarationEquivalentTarget) { + const TypeSourceInfo *TSI = Node.getTypeSourceInfo(); + if (!TSI) + return false; + const std::optional<NominalTypeLocInfo> NominalInfo = + peelToNominalTypeLoc(TSI->getTypeLoc()); + if (!NominalInfo || !NominalInfo->HasQualifier) + return false; + const NamedDecl *Target = getNamedDeclFromNominalTypeLoc(NominalInfo->Loc); + return Target && hasSameUnqualifiedName(&Node, Target) && + canUseUsingDeclarationForTarget(&Node, Target); +} + +} // namespace + +RedundantQualifiedAliasCheck::RedundantQualifiedAliasCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + OnlyNamespaceScope(Options.get("OnlyNamespaceScope", false)) {} + +void RedundantQualifiedAliasCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "OnlyNamespaceScope", OnlyNamespaceScope); +} + +void RedundantQualifiedAliasCheck::registerMatchers(MatchFinder *Finder) { + const auto ControlFlowInitStatementMatcher = stmt( + anyOf(mapAnyOf(ifStmt, switchStmt, cxxForRangeStmt) + .with(hasInitStatement(stmt(equalsBoundNode("initDeclStmt")))), + forStmt(hasLoopInit(stmt(equalsBoundNode("initDeclStmt")))))); + + const auto AliasPreconditions = + allOf(unless(isAliasTemplate()), unless(isImplicit()), + unless(hasAliasAttributes())); + const auto InControlFlowInit = + allOf(hasParent(declStmt().bind("initDeclStmt")), + hasAncestor(ControlFlowInitStatementMatcher)); + const auto RewriteableTypeLoc = + typeLoc(allOf(isNonDependentTypeLoc(), isMacroFreeTypeLoc(), + hasNoTrailingSyntaxAfterTypeLoc())) + .bind("loc"); + + const auto RedundantQualifiedAliasMatcher = typeAliasDecl( + AliasPreconditions, unless(InControlFlowInit), + hasUsingDeclarationEquivalentTarget(), hasTypeLoc(RewriteableTypeLoc)); + + if (OnlyNamespaceScope) { + Finder->addMatcher(typeAliasDecl(RedundantQualifiedAliasMatcher, + hasDeclContext(anyOf(translationUnitDecl(), + namespaceDecl()))) + .bind("alias"), + this); + return; + } + Finder->addMatcher(RedundantQualifiedAliasMatcher.bind("alias"), this); +} + +void RedundantQualifiedAliasCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Alias = Result.Nodes.getNodeAs<TypeAliasDecl>("alias"); + assert(Alias && "matcher must bind alias"); + const auto *WrittenTLNode = Result.Nodes.getNodeAs<TypeLoc>("loc"); + assert(WrittenTLNode && "matcher must bind loc"); + const TypeLoc WrittenTL = *WrittenTLNode; + + if (Alias->getLocation().isMacroID()) + return; + + const SourceManager &SM = *Result.SourceManager; + const LangOptions &LangOpts = getLangOpts(); + + const SourceLocation AliasLoc = Alias->getLocation(); + const SourceLocation RhsBeginLoc = WrittenTL.getBeginLoc(); + const CharSourceRange EqualRange = utils::lexer::findTokenInRange( + CharSourceRange::getCharRange(AliasLoc, RhsBeginLoc), SM, LangOpts, + [](const Token &Tok) { return Tok.is(tok::equal); }); + if (EqualRange.isInvalid()) + return; + + auto Diag = diag(Alias->getLocation(), + "type alias is redundant; use a using-declaration instead"); + + if (!utils::lexer::getCommentsInRange( + CharSourceRange::getCharRange(AliasLoc, EqualRange.getBegin()), SM, + LangOpts) + .empty()) { + // Suppress fix-it: avoid deleting comments between alias name and '='. + return; + } + + Diag << FixItHint::CreateRemoval(Alias->getLocation()) + << FixItHint::CreateRemoval(EqualRange.getBegin()); +} + +} // namespace clang::tidy::readability diff --git a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h new file mode 100644 index 0000000000000..1084112dd13a7 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.h @@ -0,0 +1,37 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang::tidy::readability { + +/// Finds identity type aliases to qualified names that can be expressed as +/// using-declarations. +/// +/// For the user-facing documentation see: +/// https://clang.llvm.org/extra/clang-tidy/checks/readability/redundant-qualified-alias.html +class RedundantQualifiedAliasCheck : public ClangTidyCheck { +public: + RedundantQualifiedAliasCheck(StringRef Name, ClangTidyContext *Context); + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus11; + } + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const bool OnlyNamespaceScope; +}; + +} // namespace clang::tidy::readability + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_REDUNDANTQUALIFIEDALIASCHECK_H diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst index 6bdc0ae7bdcc8..db6dda3c6645e 100644 --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -142,6 +142,12 @@ New checks Finds and removes redundant conversions from ``std::[w|u8|u16|u32]string_view`` to ``std::[...]string`` in call expressions expecting ``std::[...]string_view``. +- New :doc:`readability-redundant-qualified-alias + <clang-tidy/checks/readability/redundant-qualified-alias>` check. + + Finds redundant identity type aliases that re-expose a qualified name and can + be replaced with a ``using`` declaration. + - New :doc:`readability-trailing-comma <clang-tidy/checks/readability/trailing-comma>` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst index c475870ed7b31..ce1ef9e445877 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -415,6 +415,7 @@ Clang-Tidy Checks :doc:`readability-redundant-member-init <readability/redundant-member-init>`, "Yes" :doc:`readability-redundant-parentheses <readability/redundant-parentheses>`, "Yes" :doc:`readability-redundant-preprocessor <readability/redundant-preprocessor>`, + :doc:`readability-redundant-qualified-alias <readability/redundant-qualified-alias>`, "Yes" :doc:`readability-redundant-smartptr-get <readability/redundant-smartptr-get>`, "Yes" :doc:`readability-redundant-string-cstr <readability/redundant-string-cstr>`, "Yes" :doc:`readability-redundant-string-init <readability/redundant-string-init>`, "Yes" diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst new file mode 100644 index 0000000000000..c85a2b45b387c --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst @@ -0,0 +1,30 @@ +.. title:: clang-tidy - readability-redundant-qualified-alias + +readability-redundant-qualified-alias +===================================== + +Finds redundant identity type aliases that re-expose a qualified name and can +be replaced with a ``using`` declaration. + +.. code-block:: c++ + + using CommentToken = clang::tidy::utils::lexer::CommentToken; + + // becomes + + using clang::tidy::utils::lexer::CommentToken; + +The check is conservative and only warns when the alias name exactly matches +the unqualified name of a non-dependent, non-specialized named type written +with a qualifier. It skips alias templates, dependent forms, elaborated +keywords (``class``, ``struct``, ``enum``, ``typename``), and cases involving +macros or comments between the alias name and ``=``. + +Options +------- + +.. option:: OnlyNamespaceScope + + When `true`, only consider aliases declared in a namespace or the + translation unit. When `false`, also consider aliases declared inside + classes, functions, and lambdas. Default is `false`. diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp new file mode 100644 index 0000000000000..692c3038b2276 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp @@ -0,0 +1,201 @@ +// RUN: %check_clang_tidy -std=c++11-or-later %s readability-redundant-qualified-alias %t +// RUN: %check_clang_tidy -check-suffix=NS -std=c++11-or-later %s readability-redundant-qualified-alias %t -- \ +// RUN: -config='{CheckOptions: { readability-redundant-qualified-alias.OnlyNamespaceScope: true }}' +// RUN: %check_clang_tidy -check-suffixes=,CXX23 -std=c++23-or-later %s readability-redundant-qualified-alias %t +// RUN: %check_clang_tidy -check-suffixes=NS,NS-CXX23 -std=c++23-or-later %s readability-redundant-qualified-alias %t -- \ +// RUN: -config='{CheckOptions: { readability-redundant-qualified-alias.OnlyNamespaceScope: true }}' + +namespace n1 { +struct Foo {}; +struct Bar {}; +struct Attr {}; +enum PlainEnum { V0 }; +enum class ScopedEnum { V1 }; +struct Commented {}; +struct AfterType {}; +struct Elab {}; +struct MacroEq {}; +struct MacroType {}; +struct PtrType {}; +struct LocalType {}; +} // namespace n1 + +namespace n2 { +namespace n3 { +struct Deep {}; +} // namespace n3 +} // namespace n2 + +namespace td { +typedef n1::Foo TypedefFoo; +} // namespace td + +struct GlobalType {}; +struct Outer { + struct Inner {}; +}; + +using Foo = n1::Foo; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using n1::Foo; + +using Bar = ::n1::Bar; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using ::n1::Bar; + +using Attr = n1::Attr __attribute__((aligned(8))); +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +using AliasDeprecated [[deprecated("alias attr")]] = n1::Foo; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +using Deep = n2::n3::Deep; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using n2::n3::Deep; + +using TypedefFoo = td::TypedefFoo; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using td::TypedefFoo; + +using GlobalType = ::GlobalType; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using ::GlobalType; + +using PlainEnum = n1::PlainEnum; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using n1::PlainEnum; + +using ScopedEnum = n1::ScopedEnum; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using n1::ScopedEnum; + +using Inner = Outer::Inner; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +using Builtin = int; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +using PtrType = n1::PtrType *; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +namespace templ { +template <typename T> +struct Vec {}; +} // namespace templ + +using Vec = templ::Vec<int>; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +namespace templ_alias { +template <typename T> +using Foo = n1::Foo; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead +} // namespace templ_alias + +template <typename T> +struct Dependent { + using X = typename T::X; + // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead +}; + +using Elab = class n1::Elab; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +using Commented /*comment*/ = n1::Commented; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using Commented /*comment*/ = n1::Commented; + +using AfterType = n1::AfterType /*rhs-comment*/; +// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] +// CHECK-FIXES: using n1::AfterType /*rhs-comment*/; + +#define DECL_END ; +using MacroDeclEnd = n1::MacroType DECL_END +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +#define ALIAS MacroType +using ALIAS = n1::MacroType; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +#define RHS n1::MacroType +using MacroType = RHS; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +#define EQ = +using MacroEq EQ n1::MacroEq; +// CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead +// CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + +struct Base { + using T = n1::Foo; +}; + +struct Derived : Base { + using T = Base::T; + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] + // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-FIXES: using Base::T; +}; + +struct ClassScopeNamespaceAlias { + using Foo = n1::Foo; + // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead +}; + +void local_scope() { + using LocalType = n1::LocalType; + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] + // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-FIXES: using n1::LocalType; +} + +#if __cplusplus >= 202302L +void cxx23_init_statement_scope(bool Cond) { + if (using Foo = n1::Foo; Cond) { + } + // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + + switch (using Bar = ::n1::Bar; 0) { + default: + break; + } + // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + + for (using Deep = n2::n3::Deep; Cond;) { + Cond = false; + } + // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + + int Values[] = {0}; + for (using GlobalType = ::GlobalType; int V : Values) { + (void)V; + } + // CHECK-MESSAGES-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead + // CHECK-MESSAGES-NS-CXX23-NOT: warning: type alias is redundant; use a using-declaration instead +} +#endif >From 44fe66a4791afd8bae1c978c02de7aa9682fcdac Mon Sep 17 00:00:00 2001 From: Daniil Dudkin <[email protected]> Date: Sun, 1 Mar 2026 16:44:06 +0300 Subject: [PATCH 4/4] Fix review comments --- .../readability/RedundantQualifiedAliasCheck.cpp | 8 -------- .../checks/readability/redundant-qualified-alias.rst | 2 +- .../checkers/readability/redundant-qualified-alias.cpp | 6 ++++-- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp index ea02dbc634658..2d570b6f5610d 100644 --- a/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp +++ b/clang-tools-extra/clang-tidy/readability/RedundantQualifiedAliasCheck.cpp @@ -225,14 +225,6 @@ void RedundantQualifiedAliasCheck::check( auto Diag = diag(Alias->getLocation(), "type alias is redundant; use a using-declaration instead"); - if (!utils::lexer::getCommentsInRange( - CharSourceRange::getCharRange(AliasLoc, EqualRange.getBegin()), SM, - LangOpts) - .empty()) { - // Suppress fix-it: avoid deleting comments between alias name and '='. - return; - } - Diag << FixItHint::CreateRemoval(Alias->getLocation()) << FixItHint::CreateRemoval(EqualRange.getBegin()); } diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst index c85a2b45b387c..399f5aae4828f 100644 --- a/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/readability/redundant-qualified-alias.rst @@ -18,7 +18,7 @@ The check is conservative and only warns when the alias name exactly matches the unqualified name of a non-dependent, non-specialized named type written with a qualifier. It skips alias templates, dependent forms, elaborated keywords (``class``, ``struct``, ``enum``, ``typename``), and cases involving -macros or comments between the alias name and ``=``. +macros. Options ------- diff --git a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp index 692c3038b2276..6edcfe323743d 100644 --- a/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp +++ b/clang-tools-extra/test/clang-tidy/checkers/readability/redundant-qualified-alias.cpp @@ -49,9 +49,11 @@ using Attr = n1::Attr __attribute__((aligned(8))); // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead -using AliasDeprecated [[deprecated("alias attr")]] = n1::Foo; +namespace alias_attr { +using Foo [[deprecated("alias attr")]] = n1::Foo; // CHECK-MESSAGES-NOT: warning: type alias is redundant; use a using-declaration instead // CHECK-MESSAGES-NS-NOT: warning: type alias is redundant; use a using-declaration instead +} // namespace alias_attr using Deep = n2::n3::Deep; // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] @@ -120,7 +122,7 @@ using Elab = class n1::Elab; using Commented /*comment*/ = n1::Commented; // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] // CHECK-MESSAGES-NS: :[[@LINE-2]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] -// CHECK-FIXES: using Commented /*comment*/ = n1::Commented; +// CHECK-FIXES: using{{[ ]+}}/*comment*/{{[ ]+}}n1::Commented; using AfterType = n1::AfterType /*rhs-comment*/; // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: type alias is redundant; use a using-declaration instead [readability-redundant-qualified-alias] _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
