tbourvon updated this revision to Diff 140541. tbourvon marked 3 inline comments as done. tbourvon added a comment.
Order and link fixes in the release notes https://reviews.llvm.org/D37014 Files: clang-tidy/readability/CMakeLists.txt clang-tidy/readability/ReadabilityTidyModule.cpp clang-tidy/readability/UnnecessaryIntermediateVarCheck.cpp clang-tidy/readability/UnnecessaryIntermediateVarCheck.h docs/ReleaseNotes.rst docs/clang-tidy/checks/list.rst docs/clang-tidy/checks/readability-unnecessary-intermediate-var.rst test/clang-tidy/readability-unnecessary-intermediate-var.cpp unittests/clang-tidy/ReadabilityModuleTest.cpp
Index: unittests/clang-tidy/ReadabilityModuleTest.cpp =================================================================== --- unittests/clang-tidy/ReadabilityModuleTest.cpp +++ unittests/clang-tidy/ReadabilityModuleTest.cpp @@ -1,6 +1,8 @@ #include "ClangTidyTest.h" #include "readability/BracesAroundStatementsCheck.h" #include "readability/NamespaceCommentCheck.h" +#include "readability/UnnecessaryIntermediateVarCheck.h" +#include "../../../unittests/ASTMatchers/ASTMatchersTest.h" #include "gtest/gtest.h" namespace clang { @@ -500,6 +502,19 @@ runCheckOnCode<BracesAroundStatementsCheck>(Input)); } +TEST(StatementMatcher, HasSuccessor) { + using namespace ast_matchers; + using namespace matchers; + + StatementMatcher DeclHasSuccessorReturnStmt = + declStmt(hasSuccessor(returnStmt())); + + EXPECT_TRUE( + matches("void foo() { int bar; return; }", DeclHasSuccessorReturnStmt)); + EXPECT_TRUE(notMatches("void foo() { int bar; bar++; return; }", + DeclHasSuccessorReturnStmt)); +} + } // namespace test } // namespace tidy } // namespace clang Index: test/clang-tidy/readability-unnecessary-intermediate-var.cpp =================================================================== --- /dev/null +++ test/clang-tidy/readability-unnecessary-intermediate-var.cpp @@ -0,0 +1,245 @@ +// RUN: %check_clang_tidy %s readability-unnecessary-intermediate-var %t + +bool f() { + auto test = 1; // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == 1); + // CHECK-FIXES: {{^}} return (1 == 1);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f2() { + auto test1 = 1; // Test1 + // CHECK-FIXES: {{^}} // Test1{{$}} + auto test2 = 2; // Test2 + // CHECK-FIXES: {{^}} // Test2{{$}} + return (test1 == test2); + // CHECK-FIXES: {{^}} return (1 == 2);{{$}} + // CHECK-MESSAGES: :[[@LINE-6]]:8: warning: unnecessary intermediate variable 'test1' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-5]]:8: warning: and so is 'test2' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-4]]:11: note: because they are only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-9]]:8: note: consider removing both this variable declaration + // CHECK-MESSAGES: :[[@LINE-8]]:8: note: and this one + // CHECK-MESSAGES: :[[@LINE-7]]:11: note: and directly using the variable initialization expressions here +} + +bool f3() { + auto test1 = 1; // Test1 + // CHECK-FIXES: {{^}} // Test1{{$}} + auto test2 = 2; // Test2 + return (test1 == 2); + // CHECK-FIXES: {{^}} return (1 == 2);{{$}} + // CHECK-MESSAGES: :[[@LINE-5]]:8: warning: unnecessary intermediate variable 'test1' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-7]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f4() { + auto test1 = 1; // Test1 + auto test2 = 2; // Test2 + // CHECK-FIXES: {{^}} // Test2{{$}} + return (test2 == 3); + // CHECK-FIXES: {{^}} return (2 == 3);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test2' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f5() { + auto test1 = 1; // Test1 + // CHECK-FIXES: {{^}} // Test1{{$}} + auto test2 = 2; // Test2 + return (2 == test1); + // CHECK-FIXES: {{^}} return (1 == 2);{{$}} + // CHECK-MESSAGES: :[[@LINE-5]]:8: warning: unnecessary intermediate variable 'test1' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:16: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-7]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f6() { + auto test1 = 1; // Test1 + auto test2 = 2; // Test2 + // CHECK-FIXES: {{^}} // Test2{{$}} + return (3 == test2); + // CHECK-FIXES: {{^}} return (2 == 3);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test2' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:16: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +int foo() { return 1; } + +bool f_func() { + auto test = foo(); // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == 1); + // CHECK-FIXES: {{^}} return (foo() == 1);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_lambda() { + auto test = []() { return 1; } (); // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == 1); + // CHECK-FIXES: {{^}} return ([]() { return 1; } () == 1);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +template <typename T> +bool f_template() { + auto test = 1; // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == 1); + // CHECK-FIXES: {{^}} return (1 == 1);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_operator_inversion() { + auto test1 = 1; // Test1 + // CHECK-FIXES: {{^}} // Test1{{$}} + return (2 > test1); + // CHECK-FIXES: {{^}} return (1 < 2);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test1' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:15: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_init2_contains_var1() { + auto test1 = 1; // Test1 + auto test2 = test1; // Test2 + // CHECK-FIXES: {{^}} // Test2{{$}} + return (2 == test2); + // CHECK-FIXES: {{^}} return (test1 == 2);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test2' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:16: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_double_use() { + auto test = 1; + return (test == (test + 1)); +} + +bool f_double_use2() { + auto test1 = 1; + auto test2 = 2; + return (test1 == (test1 + 1)); +} + +bool f_double_use3() { + auto test1 = 1; + auto test2 = 2; + return (test2 == (test2 + 1)); +} + +bool f_double_use4() { + auto test1 = 1; + auto test2 = 2; + return ((test1 + 1) == test1); +} + +bool f_double_use5() { + auto test1 = 1; + auto test2 = 2; + return ((test2 + 1) == test2); +} + +bool f_intermediate_statement() { + auto test = 1; + test = 2; + return (test == 1); +} + +bool f_long_expression() { + auto test = "this is a very very very very very very very very very long expression to test max line length detection"; + return (test == ""); +} + +bool f_expression_at_exact_max_line_length() { + auto test = "this is an expression which gives the maximum line length..."; // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == ""); + // CHECK-FIXES: {{^}} return ("this is an expression which gives the maximum line length..." == "");{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_expression_just_under_max_line_length() { + auto test = "this is an expression just under the maximum line length..."; // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == ""); + // CHECK-FIXES: {{^}} return ("this is an expression just under the maximum line length..." == "");{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +} + +bool f_expression_just_above_max_line_length() { + auto test = "this is an expression just above the maximum line length....."; // Test + return (test == ""); + +} + +bool f_preprocessor_macro1() { + auto test = 1; // Test +#ifdef INTERMITTENT_MACRO + return (test == 1); +#endif +} + +bool f_preprocessor_macro2() { +#ifdef INTERMITTENT_MACRO + auto test = 1; // Test + return (test == 1); +#endif +} + +#define INTERMITTENT_MACRO +bool f_preprocessor_macro3() { +#ifdef INTERMITTENT_MACRO + auto test = 1; // Test + // CHECK-FIXES: {{^}} // Test{{$}} + return (test == 1); + // CHECK-FIXES: {{^}} return (1 == 1);{{$}} + // CHECK-MESSAGES: :[[@LINE-4]]:8: warning: unnecessary intermediate variable 'test' [readability-unnecessary-intermediate-var] + // CHECK-MESSAGES: :[[@LINE-3]]:11: note: because it is only used when returning the result of this comparison + // CHECK-MESSAGES: :[[@LINE-6]]:8: note: consider removing the variable declaration + // CHECK-MESSAGES: :[[@LINE-5]]:11: note: and directly using the variable initialization expression here +#endif +} + +void die(); + +#ifndef NDEBUG +#define assert(cond) void(0) // i.e. it does nothing +#else +#define assert(cond) (cond) ? void(0) : die(); +#endif + +bool some_func(); +bool f_preprocessor_macro4() { + auto test = some_func(); + assert(test); // <- should not be removed regardless of whether NDEBUG is set or not + return test; +} \ No newline at end of file Index: docs/clang-tidy/checks/readability-unnecessary-intermediate-var.rst =================================================================== --- /dev/null +++ docs/clang-tidy/checks/readability-unnecessary-intermediate-var.rst @@ -0,0 +1,20 @@ +.. title:: clang-tidy - readability-unnecessary-intermediate-var + +readability-unnecessary-intermediate-var +======================================== + +Detects unnecessary intermediate variables before ``return` statements returning the +result of a simple comparison. This check also suggests to directly inline the +initializer expression of the variable declaration into the ``return` expression. + +Example: +.. code-block:: c++ + + // the checker detects + + auto test = foo(); + return (test == MY_CONST); + + // and suggests to fix it into + + return (foo() == MY_CONST); Index: docs/clang-tidy/checks/list.rst =================================================================== --- docs/clang-tidy/checks/list.rst +++ docs/clang-tidy/checks/list.rst @@ -227,4 +227,5 @@ readability-static-definition-in-anonymous-namespace readability-string-compare readability-uniqueptr-delete-release + readability-unnecessary-intermediate-var zircon-temporary-objects Index: docs/ReleaseNotes.rst =================================================================== --- docs/ReleaseNotes.rst +++ docs/ReleaseNotes.rst @@ -124,6 +124,14 @@ Warns or suggests alternatives if SIMD intrinsics are used which can be replaced by ``std::experimental::simd`` operations. +- New :doc:`readability-unnecessary-intermediate-var + <clang-tidy/checks/readability-unnecessary-intermediate-var>`_ check + + Detects unnecessary intermediate variables before ``return`` + statements that return the result of a simple comparison. This check also + suggests to directly inline the initializer expression of the variable + declaration into the ``return`` expression. + - New :doc:`zircon-temporary-objects <clang-tidy/checks/zircon-temporary-objects>` check Index: clang-tidy/readability/UnnecessaryIntermediateVarCheck.h =================================================================== --- /dev/null +++ clang-tidy/readability/UnnecessaryIntermediateVarCheck.h @@ -0,0 +1,121 @@ +//===--- UnnecessaryIntermediateVarCheck.h - clang-tidy--------------*- C++ +//-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNNECESSARY_INTERMEDIATE_VAR_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNNECESSARY_INTERMEDIATE_VAR_H + +#include "../ClangTidy.h" +#include "clang/Format/Format.h" +#include "llvm/ADT/SmallSet.h" +#include "llvm/Support/Process.h" +#include "llvm/Support/Signals.h" +#include "clang/Analysis/CFG.h" + +namespace clang { +namespace tidy { + +namespace matchers { + +// Matches the next statement within the parent statement sequence. +AST_MATCHER_P(Stmt, hasSuccessor, ast_matchers::internal::Matcher<Stmt>, + InnerMatcher) { + using namespace ast_matchers; + + // We get the first parent, making sure that we're not in a case statement + // not in a compound statement directly inside a switch, because this causes + // the buildCFG call to crash. + auto Parent = selectFirst<Stmt>( + "parent", + match(stmt(hasAncestor(stmt(unless(caseStmt()), + unless(compoundStmt(hasParent(switchStmt()))), + stmt().bind("parent")))), + Node, Finder->getASTContext())); + + // We build a CFG from the parent statement. + std::unique_ptr<CFG> StatementCFG = + CFG::buildCFG(nullptr, const_cast<Stmt *>(Parent), + &Finder->getASTContext(), CFG::BuildOptions()); + + if (!StatementCFG) + return false; + + // We iterate through all the CFGBlocks, which basically means that we go over + // all the possible branches of the code and therefore cover all statements. + for (const auto &Block : *StatementCFG) { + if (!Block) + continue; + + // We iterate through all the statements of the block. + bool ReturnNextStmt = false; + for (const auto &BlockItem : *Block) { + Optional<CFGStmt> CFGStatement = BlockItem.getAs<CFGStmt>(); + if (!CFGStatement) { + if (ReturnNextStmt) + return false; + + continue; + } + + // If we found the next statement, we apply the inner matcher and return + // the result. + if (ReturnNextStmt) + return InnerMatcher.matches(*CFGStatement->getStmt(), Finder, Builder); + + if (CFGStatement->getStmt() == &Node) + ReturnNextStmt = true; + } + } + + // If we didn't find a successor, we just return false. + return false; +} + +} // namespace matchers + +namespace readability { + +/// This checker detects unnecessary intermediate variables used to store the +/// result of an expression just before using it in a return statement. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-unnecessary-intermediate-var.html +class UnnecessaryIntermediateVarCheck : public ClangTidyCheck { +public: + UnnecessaryIntermediateVarCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + bool checkReplacement(const ClangTidyContext *Context, StringRef Replacement); + void emitMainWarning(const VarDecl *VarDecl1, + const VarDecl *VarDecl2 = nullptr); + void emitUsageInComparisonNote(const DeclRefExpr *VarRef, + const bool IsPlural); + void emitVarDeclRemovalNote(const VarDecl *VarDecl1, + const DeclStmt *DeclStmt1, + const VarDecl *VarDecl2 = nullptr, + const DeclStmt *DeclStmt2 = nullptr); + void emitReturnReplacementNote(const Expr *LHS, + const StringRef LHSReplacement, + const Expr *RHS = nullptr, + const StringRef RHSReplacement = StringRef(), + const BinaryOperator *ReverseBinOp = nullptr); + + unsigned MaximumLineLength; + llvm::SmallSet<const DeclStmt *, 10> CheckedDeclStmt; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_UNNECESSARY_INTERMEDIATE_VAR_H Index: clang-tidy/readability/UnnecessaryIntermediateVarCheck.cpp =================================================================== --- /dev/null +++ clang-tidy/readability/UnnecessaryIntermediateVarCheck.cpp @@ -0,0 +1,434 @@ +//===--- UnnecessaryIntermediateVarCheck.cpp - +// clang-tidy----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "UnnecessaryIntermediateVarCheck.h" +#include "../utils/Matchers.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/FixIt.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +UnnecessaryIntermediateVarCheck::UnnecessaryIntermediateVarCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) { + // FIXME: try to get the max length value from the clang-format configuration + MaximumLineLength = Options.get("MaximumLineLength", 80); +} + +void UnnecessaryIntermediateVarCheck::registerMatchers(MatchFinder *Finder) { + // We match a direct declaration reference expression pointing + // to the variable declaration 1 as LHS. + const auto DirectDeclRefExprLHS1 = + ignoringImplicit(ignoringParenCasts(declRefExpr( + to(equalsBoundNode("varDecl1")), expr().bind("declRefExprLHS1")))); + + // We match a direct declaration reference expression pointing + // to the variable declaration 1 as RHS. + const auto DirectDeclRefExprRHS1 = + ignoringImplicit(ignoringParenCasts(declRefExpr( + to(equalsBoundNode("varDecl1")), expr().bind("declRefExprRHS1")))); + + // We match a declaration reference expression in any descendant + // pointing to variable declaration 1. + const auto NoIndirectDeclRefExpr1 = + unless(hasDescendant(declRefExpr(to(equalsBoundNode("varDecl1"))))); + + // We match a direct declaration reference expression pointing + // to the variable declaration 2 as LHS. + const auto DirectDeclRefExprLHS2 = + ignoringImplicit(ignoringParenCasts(declRefExpr( + to(equalsBoundNode("varDecl2")), expr().bind("declRefExprLHS2")))); + + // We match a direct declaration reference expression pointing + // to the variable declaration 2 as RHS. + const auto DirectDeclRefExprRHS2 = + ignoringImplicit(ignoringParenCasts(declRefExpr( + to(equalsBoundNode("varDecl2")), expr().bind("declRefExprRHS2")))); + + // We match a declaration reference expression in any descendant + // pointing to variable declaration 2. + const auto NoIndirectDeclRefExpr2 = + unless(hasDescendant(declRefExpr(to(equalsBoundNode("varDecl2"))))); + + const auto HasVarDecl1 = + // We match a single declaration which is a variable declaration, + hasSingleDecl(varDecl( + // which has an initializer, + hasInitializer(allOf(NoIndirectDeclRefExpr2, expr().bind("init1"))), + // and which isn't static. + unless(isStaticStorageClass()), decl().bind("varDecl1"))); + + const auto HasVarDecl2 = + // We match a single declaration which is a variable declaration, + hasSingleDecl(varDecl( + // which has an initializer, + hasInitializer(allOf(NoIndirectDeclRefExpr1, expr().bind("init2"))), + // and which isn't static. + unless(isStaticStorageClass()), decl().bind("varDecl2"))); + + const auto ReturnStmt1 = + // We match a return statement, + returnStmt( + stmt().bind("returnStmt1"), + + // which has a return value which is a binary operator, + hasReturnValue(ignoringImplicit(ignoringParenCasts( + binaryOperator(expr().bind("binOp"), + + // which is a comparison operator, + matchers::isComparisonOperator(), + + // which may contain a direct reference to var decl + // 1 on only one side. + anyOf(allOf(hasLHS(DirectDeclRefExprLHS1), + hasRHS(NoIndirectDeclRefExpr1)), + allOf(hasLHS(NoIndirectDeclRefExpr1), + hasRHS(DirectDeclRefExprRHS1)))))))); + + const auto ReturnStmt2 = + // We match a return statement, + returnStmt( + stmt().bind("returnStmt2"), + + // which has a return value which is a binary operator, + hasReturnValue(ignoringImplicit(ignoringParenCasts(binaryOperator( + expr().bind("binOp"), + + // which is a comparison operator, + matchers::isComparisonOperator(), + + // which may contain a direct reference to a var decl on one side, + // as long as there is no indirect reference to the same var decl + // on the other size. + anyOf( + allOf( + hasLHS(DirectDeclRefExprLHS1), + hasRHS(allOf(NoIndirectDeclRefExpr1, + anyOf(DirectDeclRefExprRHS2, anything())))), + + allOf( + hasLHS(DirectDeclRefExprLHS2), + hasRHS(allOf(NoIndirectDeclRefExpr2, + anyOf(DirectDeclRefExprRHS1, anything())))), + + allOf(hasLHS(allOf(NoIndirectDeclRefExpr1, + anyOf(DirectDeclRefExprLHS2, anything()))), + hasRHS(DirectDeclRefExprRHS1)), + + allOf(hasLHS(allOf(NoIndirectDeclRefExpr2, + anyOf(DirectDeclRefExprLHS1, anything()))), + hasRHS(DirectDeclRefExprRHS2)))))))); + + Finder->addMatcher( + // We match a declaration statement, + declStmt( + stmt().bind("declStmt1"), + + // which contains a single variable declaration, + HasVarDecl1, + + // and which has a successor, + matchers::hasSuccessor(anyOf( + // which is another declaration statement, + declStmt(stmt().bind("declStmt2"), + + // which contains a single variable declaration, + HasVarDecl2, + + // and which has a successor which is a return statement + // which may contain var decl 1 or 2. + matchers::hasSuccessor(ReturnStmt2)), + // or which is a return statement only containing var decl 1. + ReturnStmt1))), + this); +} + +void UnnecessaryIntermediateVarCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MaximumLineLength", MaximumLineLength); +} + +void UnnecessaryIntermediateVarCheck::emitMainWarning(const VarDecl *VarDecl1, + const VarDecl *VarDecl2) { + diag(VarDecl1->getLocation(), "unnecessary intermediate variable %0", + DiagnosticIDs::Warning) + << VarDecl1; + + if (VarDecl2) + diag(VarDecl2->getLocation(), "and so is %0", DiagnosticIDs::Warning) + << VarDecl2; +} + +void UnnecessaryIntermediateVarCheck::emitUsageInComparisonNote( + const DeclRefExpr *VarRef, const bool IsPlural) { + diag(VarRef->getLocation(), + "because %select{they are|it is}0 only used when returning the result of this comparison", + DiagnosticIDs::Note) + << (IsPlural ? 0 : 1); +} + +void UnnecessaryIntermediateVarCheck::emitVarDeclRemovalNote( + const VarDecl *VarDecl1, const DeclStmt *DeclStmt1, const VarDecl *VarDecl2, + const DeclStmt *DeclStmt2) { + diag(VarDecl1->getLocation(), "consider removing %select{both this|the}0 variable declaration", + DiagnosticIDs::Note) + << ((VarDecl2 && DeclStmt2) ? 0 : 1) + << FixItHint::CreateRemoval(DeclStmt1->getSourceRange()); + + if (VarDecl2 && DeclStmt2) + diag(VarDecl2->getLocation(), "and this one", DiagnosticIDs::Note) + << FixItHint::CreateRemoval(DeclStmt2->getSourceRange()); +} + +void UnnecessaryIntermediateVarCheck::emitReturnReplacementNote( + const Expr *LHS, const StringRef LHSReplacement, const Expr *RHS, + const StringRef RHSReplacement, const BinaryOperator *BinOpToReverse) { + auto Diag = + diag(LHS->getLocStart(), + "and directly using the variable initialization expression%select{s|}0 here", + DiagnosticIDs::Note) + << ((isa<DeclRefExpr>(LHS) && RHS && isa<DeclRefExpr>(RHS)) ? 0 : 1) + << FixItHint::CreateReplacement(LHS->getSourceRange(), LHSReplacement); + + if (RHS) + Diag << FixItHint::CreateReplacement(RHS->getSourceRange(), RHSReplacement); + + if (BinOpToReverse) { + const StringRef ReversedBinOpText = BinaryOperator::getOpcodeStr( + BinaryOperator::reverseComparisonOp(BinOpToReverse->getOpcode())); + + Diag << FixItHint::CreateReplacement( + SourceRange(BinOpToReverse->getOperatorLoc(), + BinOpToReverse->getOperatorLoc().getLocWithOffset( + BinOpToReverse->getOpcodeStr().size())), + ReversedBinOpText); + } +} + +void UnnecessaryIntermediateVarCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *DeclarationStmt1 = + Result.Nodes.getNodeAs<DeclStmt>("declStmt1"); + const auto *Init1 = Result.Nodes.getNodeAs<Expr>("init1"); + const auto *VariableDecl1 = Result.Nodes.getNodeAs<VarDecl>("varDecl1"); + const auto *VarRefLHS1 = + Result.Nodes.getNodeAs<DeclRefExpr>("declRefExprLHS1"); + const auto *VarRefRHS1 = + Result.Nodes.getNodeAs<DeclRefExpr>("declRefExprRHS1"); + + // If we already checked this declaration in a 2-decl match, skip it. + if (CheckedDeclStmt.count(DeclarationStmt1)) + return; + + const auto *DeclarationStmt2 = + Result.Nodes.getNodeAs<DeclStmt>("declStmt2"); + const auto *Init2 = Result.Nodes.getNodeAs<Expr>("init2"); + const auto *VariableDecl2 = Result.Nodes.getNodeAs<VarDecl>("varDecl2"); + const auto *VarRefLHS2 = + Result.Nodes.getNodeAs<DeclRefExpr>("declRefExprLHS2"); + const auto *VarRefRHS2 = + Result.Nodes.getNodeAs<DeclRefExpr>("declRefExprRHS2"); + + // Add the second declaration to the cache to make sure it doesn't get + // matches individually afterwards. + CheckedDeclStmt.insert(DeclarationStmt2); + + const auto *Return1 = Result.Nodes.getNodeAs<ReturnStmt>("returnStmt1"); + const auto *Return2 = Result.Nodes.getNodeAs<ReturnStmt>("returnStmt2"); + + const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binOp"); + + if (Return1) { + // This is the case where we only have one variable declaration before the + // return statement. + + // First we get the source code of the initializer expression of the + // variable declaration. + auto Init1Text = + clang::tooling::fixit::getText(*Init1, *Result.Context).str(); + if (Init1Text.empty()) + return; + + // If the expression is a binary operator, we wrap it in parentheses to keep + // the same operator precendence. + if (isa<BinaryOperator>(Init1->IgnoreImplicit())) { + Init1Text = "(" + Init1Text + ")"; + } + + // Next we compute the return indentation length and the return length to be + // able to know what length the return statement will have once the fixes + // are applied. + const int ReturnIndentTextLength = + Lexer::getIndentationForLine(Return1->getLocStart(), + Result.Context->getSourceManager()) + .size(); + + const int ReturnTextLength = + clang::tooling::fixit::getText(*Return1, + *Result.Context) + .size(); + + const int NewReturnLength = ReturnIndentTextLength + ReturnTextLength - + VariableDecl1->getName().size() + + Init1Text.size() + + 1; // For the trailing semicolon + + // If the new length is over the statement limit, then folding the + // expression wouldn't really benefit readability. Therefore we abort. + if (NewReturnLength > MaximumLineLength) + return; + + // Otherwise, we're all good and we emit the diagnostics along with the fix + // it hints. + + emitMainWarning(VariableDecl1); + + if (VarRefLHS1) { + emitUsageInComparisonNote(VarRefLHS1, false); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1); + emitReturnReplacementNote(VarRefLHS1, Init1Text); + } else if (VarRefRHS1) { + // If the variable is on the RHS of the comparison, we need to reverse the + // operands of the binary operator to keep the same execution order. + StringRef LHSText = clang::tooling::fixit::getText( + *BinOp->getLHS(), *Result.Context); + if (LHSText.empty()) + return; + + emitUsageInComparisonNote(VarRefRHS1, false); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1); + emitReturnReplacementNote(BinOp->getLHS(), Init1Text, VarRefRHS1, + LHSText, BinOp); + } else + return; + } else if (Return2) { + // This is the case where there are two variable declarations before the + // return statement. + const bool HasVarRef1 = VarRefLHS1 || VarRefRHS1; + const bool HasVarRef2 = VarRefLHS2 || VarRefRHS2; + + // First we get the source code of the initializer expressions of the + // variable declarations. + std::string Init1Text = + clang::tooling::fixit::getText(*Init1, *Result.Context).str(); + if (Init1Text.empty()) + return; + + std::string Init2Text = + clang::tooling::fixit::getText(*Init2, *Result.Context).str(); + if (Init2Text.empty()) + return; + + // If the expressiond are binary operators, we wrap them in parentheses to + // keep the same operator precendence. + if (isa<BinaryOperator>(Init1->IgnoreImplicit())) + Init1Text = "(" + Init1Text + ")"; + + if (isa<BinaryOperator>(Init2->IgnoreImplicit())) + Init2Text = "(" + Init2Text + ")"; + + // Next we compute the return indentation length and the return length to be + // able to know what length the return statement will have once the fixes + // are applied. + const int ReturnIndentTextLength = + Lexer::getIndentationForLine(Return2->getLocStart(), + Result.Context->getSourceManager()) + .size(); + + const int ReturnTextLength = + clang::tooling::fixit::getText(*Return2, + *Result.Context) + .size(); + + auto NewReturnLength = ReturnIndentTextLength + ReturnTextLength + + 1; // For the trailing semicolon + + if (HasVarRef1) { + NewReturnLength -= VariableDecl1->getName().size(); + NewReturnLength += Init1Text.size(); + } + + if (HasVarRef2) { + NewReturnLength -= VariableDecl2->getName().size(); + NewReturnLength += Init2Text.size(); + } + + // If the new length is over the statement limit, then folding the + // expression wouldn't really benefit readability. Therefore we abort. + if (NewReturnLength > MaximumLineLength) + return; + + // Otherwise, we're all good and we emit the diagnostics along with the fix + // it hints. + + if (HasVarRef1 && HasVarRef2) + emitMainWarning(VariableDecl1, VariableDecl2); + else if (HasVarRef1) + emitMainWarning(VariableDecl1); + else if (HasVarRef2) + emitMainWarning(VariableDecl2); + + if (VarRefLHS1 && VarRefRHS2) { + emitUsageInComparisonNote(VarRefLHS1, true); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1, VariableDecl2, + DeclarationStmt2); + emitReturnReplacementNote(VarRefLHS1, Init1Text, VarRefRHS2, Init2Text); + } else if (VarRefLHS2 && VarRefRHS1) { + emitUsageInComparisonNote(VarRefLHS2, true); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1, VariableDecl2, + DeclarationStmt2); + // Here we reverse the operands because we want to keep the same execution + // order. + emitReturnReplacementNote(VarRefLHS2, Init1Text, VarRefRHS1, Init2Text, + BinOp); + } else if (VarRefLHS1 && !VarRefRHS2) { + emitUsageInComparisonNote(VarRefLHS1, false); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1); + emitReturnReplacementNote(VarRefLHS1, Init1Text); + } else if (!VarRefLHS1 && VarRefRHS2) { + // If the variable is on the RHS of the comparison, we need to reverse the + // operands of the binary operator to keep the same execution order. + StringRef LHSText = clang::tooling::fixit::getText( + *BinOp->getLHS(), *Result.Context); + if (LHSText.empty()) + return; + + emitUsageInComparisonNote(VarRefRHS2, false); + emitVarDeclRemovalNote(VariableDecl2, DeclarationStmt2); + emitReturnReplacementNote(BinOp->getLHS(), Init2Text, VarRefRHS2, + LHSText, BinOp); + } else if (VarRefLHS2 && !VarRefRHS1) { + emitUsageInComparisonNote(VarRefLHS2, false); + emitVarDeclRemovalNote(VariableDecl2, DeclarationStmt2); + emitReturnReplacementNote(VarRefLHS2, Init2Text); + } else if (!VarRefLHS2 && VarRefRHS1) { + // If the variable is on the RHS of the comparison, we need to reverse the + // operands of the binary operator to keep the same execution order. + StringRef LHSText = clang::tooling::fixit::getText( + *BinOp->getLHS(), *Result.Context); + if (LHSText.empty()) + return; + + emitUsageInComparisonNote(VarRefRHS1, false); + emitVarDeclRemovalNote(VariableDecl1, DeclarationStmt1); + emitReturnReplacementNote(BinOp->getLHS(), Init1Text, VarRefRHS1, + LHSText, BinOp); + } + } +} + +} // namespace readability +} // namespace tidy +} // namespace clang Index: clang-tidy/readability/ReadabilityTidyModule.cpp =================================================================== --- clang-tidy/readability/ReadabilityTidyModule.cpp +++ clang-tidy/readability/ReadabilityTidyModule.cpp @@ -36,6 +36,7 @@ #include "StaticDefinitionInAnonymousNamespaceCheck.h" #include "StringCompareCheck.h" #include "UniqueptrDeleteReleaseCheck.h" +#include "UnnecessaryIntermediateVarCheck.h" namespace clang { namespace tidy { @@ -96,6 +97,8 @@ "readability-simplify-boolean-expr"); CheckFactories.registerCheck<UniqueptrDeleteReleaseCheck>( "readability-uniqueptr-delete-release"); + CheckFactories.registerCheck<UnnecessaryIntermediateVarCheck>( + "readability-unnecessary-intermediate-var"); } }; Index: clang-tidy/readability/CMakeLists.txt =================================================================== --- clang-tidy/readability/CMakeLists.txt +++ clang-tidy/readability/CMakeLists.txt @@ -29,6 +29,7 @@ StaticDefinitionInAnonymousNamespaceCheck.cpp StringCompareCheck.cpp UniqueptrDeleteReleaseCheck.cpp + UnnecessaryIntermediateVarCheck.cpp LINK_LIBS clangAST
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits