================ @@ -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 '='. ---------------- localspook wrote:
Now that the removal fix-its work token by token, can we just get rid of this logic? The comment shouldn’t get deleted. https://github.com/llvm/llvm-project/pull/180404 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
