//===----------------------------------------------------------------------===// // // 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 #include using namespace clang::ast_matchers; namespace clang::tidy::readability { namespace { struct TypeLocInfo { 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 getTypeLocInfo(TypeLoc TL) { if (TL.isNull()) return std::nullopt; const auto MakeTypeLocInfo = [](auto TypeTL) { const bool HasQualifier = static_cast(TypeTL.getQualifierLoc().getNestedNameSpecifier()); return TypeLocInfo{TypeTL, HasQualifier}; }; if (const auto TypedefTL = TL.getAs()) return MakeTypeLocInfo(TypedefTL); if (const auto TagTL = TL.getAs()) return MakeTypeLocInfo(TagTL); return std::nullopt; } static const NamedDecl *getNamedDeclFromTypeLoc(TypeLoc TL) { if (const auto TypedefTL = TL.getAs()) return TypedefTL.getDecl(); if (const auto TagTL = TL.getAs()) return TagTL.getDecl(); return nullptr; } static bool hasSameUnqualifiedName(const NamedDecl *LHS, const NamedDecl *RHS) { return LHS->getName() == RHS->getName(); } static bool isNamespaceLikeDeclContext(const DeclContext *DC) { return isa(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(AliasContext); if (!AliasRecord) return isNamespaceLikeDeclContext(TargetContext); const auto *TargetRecord = dyn_cast(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() || TypeEndLoc.isMacroID()) return true; const std::optional NextToken = utils::lexer::findNextTokenSkippingComments(TypeEndLoc, SM, LangOpts); return !NextToken || NextToken->isNot(tok::semi); } namespace { AST_MATCHER(TypeAliasDecl, isAliasTemplate) { return Node.getDescribedAliasTemplate() != nullptr; } AST_MATCHER(NamedDecl, isInMacro) { return Node.getLocation().isMacroID(); } 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()) return true; return false; } AST_MATCHER(TypeLoc, isNonDependentTypeLoc) { return !Node.getType().isNull() && !Node.getType()->isDependentType(); } AST_MATCHER(TypeLoc, isNonElaboratedTypeLoc) { const auto IsNonElaboratedTypeLoc = [](auto TL) { return !TL.isNull() && !TL.getElaboratedKeywordLoc().isValid(); }; return IsNonElaboratedTypeLoc(Node.getAs()) || IsNonElaboratedTypeLoc(Node.getAs()); } 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 TypeInfo = getTypeLocInfo(TSI->getTypeLoc()); if (!TypeInfo || !TypeInfo->HasQualifier) return false; const NamedDecl *Target = getNamedDeclFromTypeLoc(TypeInfo->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(isInMacro()), unless(isAliasTemplate()), unless(hasAliasAttributes())); const auto InControlFlowInit = allOf(hasParent(declStmt().bind("initDeclStmt")), hasAncestor(ControlFlowInitStatementMatcher)); const auto RewriteableTypeLoc = typeLoc(allOf(isNonDependentTypeLoc(), isNonElaboratedTypeLoc(), 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("alias"); assert(Alias && "matcher must bind alias"); const auto *WrittenTLNode = Result.Nodes.getNodeAs("loc"); assert(WrittenTLNode && "matcher must bind loc"); const TypeLoc WrittenTL = *WrittenTLNode; const SourceManager &SM = *Result.SourceManager; const LangOptions &LangOpts = getLangOpts(); const SourceLocation AliasLoc = Alias->getLocation(); const SourceLocation RhsBeginLoc = WrittenTL.getBeginLoc(); const CharSourceRange EqualRange = utils::lexer::findTokenTextInRange( 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"); Diag << FixItHint::CreateRemoval(Alias->getLocation()) << FixItHint::CreateRemoval(EqualRange.getBegin()); } } // namespace clang::tidy::readability