https://reviews.llvm.org/D62201
On Tue, May 21, 2019 at 10:15 AM Nico Weber <tha...@chromium.org> wrote: > I'm suggesting you make the below change. For testing, I'd just try > building locally. > > $ git diff > diff --git a/clang/unittests/Tooling/CMakeLists.txt > b/clang/unittests/Tooling/CMakeLists.txt > index 8c383be2d74..af8a35d9251 100644 > --- a/clang/unittests/Tooling/CMakeLists.txt > +++ b/clang/unittests/Tooling/CMakeLists.txt > @@ -1,6 +1,7 @@ > set(LLVM_LINK_COMPONENTS > ${LLVM_TARGETS_TO_BUILD} > Support > + TestingSupport > ) > > # By default MSVC has a 2^16 limit on the number of sections in an object > file, > @@ -70,8 +71,6 @@ target_link_libraries(ToolingTests > clangToolingCore > clangToolingInclusions > clangToolingRefactor > - LLVMSupport > - LLVMTestingSupport > ) > > On Mon, May 20, 2019 at 9:49 PM Yitzhak Mandelbaum <yitzh...@google.com> > wrote: > >> Nice, thanks for r361208. As for support -- no, I didn't try that. I >> was following a pattern I saw elsewhere. Do you suggest that I make that >> change? If so, any particular way to test it? >> >> thanks >> >> On Mon, May 20, 2019 at 8:26 PM Nico Weber <tha...@chromium.org> wrote: >> >>> + LLVMSupport >>> + LLVMTestingSupport >>> >>> clang/unittests/Tooling/CMakeLists already has this at the top: >>> set(LLVM_LINK_COMPONENTS >>> ${LLVM_TARGETS_TO_BUILD} >>> Support >>> ) >>> >>> I think we link Support twice now. Did adding TestingSupport up there >>> not work? I'd expect that your addition would break shared library builds, >>> or something like that. >>> >>> (Also, I tried to fix a build error that old gcc versions had with this >>> change in r361208.) >>> >>> *From: *Yitzhak Mandelbaum via cfe-commits <cfe-commits@lists.llvm.org> >>> *Date: *Mon, May 20, 2019 at 9:12 AM >>> *To: * <cfe-commits@lists.llvm.org> >>> >>> Author: ymandel >>>> Date: Mon May 20 06:15:14 2019 >>>> New Revision: 361152 >>>> >>>> URL: http://llvm.org/viewvc/llvm-project?rev=361152&view=rev >>>> Log: >>>> [LibTooling] Add RangeSelector library for defining source ranges based >>>> on bound AST nodes. >>>> >>>> Summary: >>>> >>>> The RangeSelector library defines a combinator language for specifying >>>> source >>>> ranges based on bound ids for AST nodes. The combinator approach >>>> follows the >>>> design of the AST matchers. The RangeSelectors defined here will be >>>> used in >>>> both RewriteRule, for specifying source affected by edit, and in >>>> Stencil for >>>> specifying source to use constructively in a replacement. >>>> >>>> Reviewers: ilya-biryukov >>>> >>>> Subscribers: mgorny, cfe-commits >>>> >>>> Tags: #clang >>>> >>>> Differential Revision: https://reviews.llvm.org/D61774 >>>> >>>> Added: >>>> cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h >>>> cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp >>>> cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp >>>> Modified: >>>> cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt >>>> cfe/trunk/unittests/Tooling/CMakeLists.txt >>>> >>>> Added: cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h >>>> URL: >>>> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h?rev=361152&view=auto >>>> >>>> ============================================================================== >>>> --- cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h (added) >>>> +++ cfe/trunk/include/clang/Tooling/Refactoring/RangeSelector.h Mon May >>>> 20 06:15:14 2019 >>>> @@ -0,0 +1,80 @@ >>>> +//===--- RangeSelector.h - Source-selection library ---------*- C++ >>>> -*-===// >>>> +// >>>> +// 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 >>>> +// >>>> >>>> +//===----------------------------------------------------------------------===// >>>> +/// >>>> +/// \file >>>> +/// Defines a combinator library supporting the definition of >>>> _selectors_, >>>> +/// which select source ranges based on (bound) AST nodes. >>>> +/// >>>> >>>> +//===----------------------------------------------------------------------===// >>>> + >>>> +#ifndef LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ >>>> +#define LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ >>>> + >>>> +#include "clang/ASTMatchers/ASTMatchFinder.h" >>>> +#include "clang/Basic/SourceLocation.h" >>>> +#include "llvm/ADT/StringRef.h" >>>> +#include "llvm/Support/Error.h" >>>> +#include <functional> >>>> + >>>> +namespace clang { >>>> +namespace tooling { >>>> +using RangeSelector = std::function<Expected<CharSourceRange>( >>>> + const ast_matchers::MatchFinder::MatchResult &)>; >>>> + >>>> +inline RangeSelector charRange(CharSourceRange R) { >>>> + return [R](const ast_matchers::MatchFinder::MatchResult &) >>>> + -> Expected<CharSourceRange> { return R; }; >>>> +} >>>> + >>>> +/// Selects from the start of \p Begin and to the end of \p End. >>>> +RangeSelector range(RangeSelector Begin, RangeSelector End); >>>> + >>>> +/// Convenience version of \c range where end-points are bound nodes. >>>> +RangeSelector range(StringRef BeginID, StringRef EndID); >>>> + >>>> +/// Selects a node, including trailing semicolon (for non-expression >>>> +/// statements). \p ID is the node's binding in the match result. >>>> +RangeSelector node(StringRef ID); >>>> + >>>> +/// Selects a node, including trailing semicolon (always). Useful for >>>> selecting >>>> +/// expression statements. \p ID is the node's binding in the match >>>> result. >>>> +RangeSelector statement(StringRef ID); >>>> + >>>> +/// Given a \c MemberExpr, selects the member token. \p ID is the >>>> node's >>>> +/// binding in the match result. >>>> +RangeSelector member(StringRef ID); >>>> + >>>> +/// Given a node with a "name", (like \c NamedDecl, \c DeclRefExpr or >>>> \c >>>> +/// CxxCtorInitializer) selects the name's token. Only selects the >>>> final >>>> +/// identifier of a qualified name, but not any qualifiers or template >>>> +/// arguments. For example, for `::foo::bar::baz` and >>>> `::foo::bar::baz<int>`, >>>> +/// it selects only `baz`. >>>> +/// >>>> +/// \param ID is the node's binding in the match result. >>>> +RangeSelector name(StringRef ID); >>>> + >>>> +// Given a \c CallExpr (bound to \p ID), selects the arguments' source >>>> text (all >>>> +// source between the call's parentheses). >>>> +RangeSelector callArgs(StringRef ID); >>>> + >>>> +// Given a \c CompoundStmt (bound to \p ID), selects the source of the >>>> +// statements (all source between the braces). >>>> +RangeSelector statements(StringRef ID); >>>> + >>>> +// Given a \c InitListExpr (bound to \p ID), selects the range of the >>>> elements >>>> +// (all source between the braces). >>>> +RangeSelector initListElements(StringRef ID); >>>> + >>>> +/// Selects the range from which `S` was expanded (possibly along with >>>> other >>>> +/// source), if `S` is an expansion, and `S` itself, otherwise. >>>> Corresponds to >>>> +/// `SourceManager::getExpansionRange`. >>>> +RangeSelector expansion(RangeSelector S); >>>> +} // namespace tooling >>>> +} // namespace clang >>>> + >>>> +#endif // LLVM_CLANG_TOOLING_REFACTOR_RANGE_SELECTOR_H_ >>>> >>>> Modified: cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt >>>> URL: >>>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff >>>> >>>> ============================================================================== >>>> --- cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt (original) >>>> +++ cfe/trunk/lib/Tooling/Refactoring/CMakeLists.txt Mon May 20 >>>> 06:15:14 2019 >>>> @@ -6,6 +6,7 @@ add_clang_library(clangToolingRefactor >>>> AtomicChange.cpp >>>> Extract/Extract.cpp >>>> Extract/SourceExtraction.cpp >>>> + RangeSelector.cpp >>>> RefactoringActions.cpp >>>> Rename/RenamingAction.cpp >>>> Rename/SymbolOccurrences.cpp >>>> >>>> Added: cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp >>>> URL: >>>> http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp?rev=361152&view=auto >>>> >>>> ============================================================================== >>>> --- cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp (added) >>>> +++ cfe/trunk/lib/Tooling/Refactoring/RangeSelector.cpp Mon May 20 >>>> 06:15:14 2019 >>>> @@ -0,0 +1,264 @@ >>>> +//===--- RangeSelector.cpp - RangeSelector implementations ------*- >>>> C++ -*-===// >>>> +// >>>> +// 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 "clang/Tooling/Refactoring/RangeSelector.h" >>>> +#include "clang/AST/Expr.h" >>>> +#include "clang/ASTMatchers/ASTMatchFinder.h" >>>> +#include "clang/Basic/SourceLocation.h" >>>> +#include "clang/Lex/Lexer.h" >>>> +#include "clang/Tooling/Refactoring/SourceCode.h" >>>> +#include "llvm/ADT/StringRef.h" >>>> +#include "llvm/Support/Errc.h" >>>> +#include "llvm/Support/Error.h" >>>> +#include <string> >>>> +#include <utility> >>>> +#include <vector> >>>> + >>>> +using namespace clang; >>>> +using namespace tooling; >>>> + >>>> +using ast_matchers::MatchFinder; >>>> +using ast_type_traits::ASTNodeKind; >>>> +using ast_type_traits::DynTypedNode; >>>> +using llvm::Error; >>>> +using llvm::StringError; >>>> + >>>> +using MatchResult = MatchFinder::MatchResult; >>>> + >>>> +static Error invalidArgumentError(Twine Message) { >>>> + return llvm::make_error<StringError>(llvm::errc::invalid_argument, >>>> Message); >>>> +} >>>> + >>>> +static Error typeError(StringRef ID, const ASTNodeKind &Kind) { >>>> + return invalidArgumentError("mismatched type (node id=" + ID + >>>> + " kind=" + Kind.asStringRef() + ")"); >>>> +} >>>> + >>>> +static Error typeError(StringRef ID, const ASTNodeKind &Kind, >>>> + Twine ExpectedType) { >>>> + return invalidArgumentError("mismatched type: expected one of " + >>>> + ExpectedType + " (node id=" + ID + >>>> + " kind=" + Kind.asStringRef() + ")"); >>>> +} >>>> + >>>> +static Error missingPropertyError(StringRef ID, Twine Description, >>>> + StringRef Property) { >>>> + return invalidArgumentError(Description + " requires property '" + >>>> Property + >>>> + "' (node id=" + ID + ")"); >>>> +} >>>> + >>>> +static Expected<DynTypedNode> getNode(const ast_matchers::BoundNodes >>>> &Nodes, >>>> + StringRef ID) { >>>> + auto &NodesMap = Nodes.getMap(); >>>> + auto It = NodesMap.find(ID); >>>> + if (It == NodesMap.end()) >>>> + return invalidArgumentError("ID not bound: " + ID); >>>> + return It->second; >>>> +} >>>> + >>>> +// FIXME: handling of macros should be configurable. >>>> +static SourceLocation findPreviousTokenStart(SourceLocation Start, >>>> + const SourceManager &SM, >>>> + const LangOptions >>>> &LangOpts) { >>>> + if (Start.isInvalid() || Start.isMacroID()) >>>> + return SourceLocation(); >>>> + >>>> + SourceLocation BeforeStart = Start.getLocWithOffset(-1); >>>> + if (BeforeStart.isInvalid() || BeforeStart.isMacroID()) >>>> + return SourceLocation(); >>>> + >>>> + return Lexer::GetBeginningOfToken(BeforeStart, SM, LangOpts); >>>> +} >>>> + >>>> +// Finds the start location of the previous token of kind \p TK. >>>> +// FIXME: handling of macros should be configurable. >>>> +static SourceLocation findPreviousTokenKind(SourceLocation Start, >>>> + const SourceManager &SM, >>>> + const LangOptions >>>> &LangOpts, >>>> + tok::TokenKind TK) { >>>> + while (true) { >>>> + SourceLocation L = findPreviousTokenStart(Start, SM, LangOpts); >>>> + if (L.isInvalid() || L.isMacroID()) >>>> + return SourceLocation(); >>>> + >>>> + Token T; >>>> + if (Lexer::getRawToken(L, T, SM, LangOpts, >>>> /*IgnoreWhiteSpace=*/true)) >>>> + return SourceLocation(); >>>> + >>>> + if (T.is(TK)) >>>> + return T.getLocation(); >>>> + >>>> + Start = L; >>>> + } >>>> +} >>>> + >>>> +static SourceLocation findOpenParen(const CallExpr &E, const >>>> SourceManager &SM, >>>> + const LangOptions &LangOpts) { >>>> + SourceLocation EndLoc = >>>> + E.getNumArgs() == 0 ? E.getRParenLoc() : >>>> E.getArg(0)->getBeginLoc(); >>>> + return findPreviousTokenKind(EndLoc, SM, LangOpts, >>>> tok::TokenKind::l_paren); >>>> +} >>>> + >>>> +RangeSelector tooling::node(StringRef ID) { >>>> + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { >>>> + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); >>>> + if (!Node) >>>> + return Node.takeError(); >>>> + return Node->get<Stmt>() != nullptr && Node->get<Expr>() == nullptr >>>> + ? getExtendedRange(*Node, tok::TokenKind::semi, >>>> *Result.Context) >>>> + : >>>> CharSourceRange::getTokenRange(Node->getSourceRange()); >>>> + }; >>>> +} >>>> + >>>> +RangeSelector tooling::statement(StringRef ID) { >>>> + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { >>>> + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); >>>> + if (!Node) >>>> + return Node.takeError(); >>>> + return getExtendedRange(*Node, tok::TokenKind::semi, >>>> *Result.Context); >>>> + }; >>>> +} >>>> + >>>> +RangeSelector tooling::range(RangeSelector Begin, RangeSelector End) { >>>> + return [Begin, End](const MatchResult &Result) -> >>>> Expected<CharSourceRange> { >>>> + Expected<CharSourceRange> BeginRange = Begin(Result); >>>> + if (!BeginRange) >>>> + return BeginRange.takeError(); >>>> + Expected<CharSourceRange> EndRange = End(Result); >>>> + if (!EndRange) >>>> + return EndRange.takeError(); >>>> + SourceLocation B = BeginRange->getBegin(); >>>> + SourceLocation E = EndRange->getEnd(); >>>> + // Note: we are precluding the possibility of sub-token ranges in >>>> the case >>>> + // that EndRange is a token range. >>>> + if (Result.SourceManager->isBeforeInTranslationUnit(E, B)) { >>>> + return invalidArgumentError("Bad range: out of order"); >>>> + } >>>> + return CharSourceRange(SourceRange(B, E), >>>> EndRange->isTokenRange()); >>>> + }; >>>> +} >>>> + >>>> +RangeSelector tooling::range(StringRef BeginID, StringRef EndID) { >>>> + return tooling::range(node(BeginID), node(EndID)); >>>> +} >>>> + >>>> +RangeSelector tooling::member(StringRef ID) { >>>> + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { >>>> + Expected<DynTypedNode> Node = getNode(Result.Nodes, ID); >>>> + if (!Node) >>>> + return Node.takeError(); >>>> + if (auto *M = Node->get<clang::MemberExpr>()) >>>> + return CharSourceRange::getTokenRange( >>>> + M->getMemberNameInfo().getSourceRange()); >>>> + return typeError(ID, Node->getNodeKind(), "MemberExpr"); >>>> + }; >>>> +} >>>> + >>>> +RangeSelector tooling::name(StringRef ID) { >>>> + return [ID](const MatchResult &Result) -> Expected<CharSourceRange> { >>>> + Expected<DynTypedNode> N = getNode(Result.Nodes, ID); >>>> + if (!N) >>>> + return N.takeError(); >>>> + auto &Node = *N; >>>> + if (const auto *D = Node.get<NamedDecl>()) { >>>> + if (!D->getDeclName().isIdentifier()) >>>> + return missingPropertyError(ID, "name", "identifier"); >>>> + SourceLocation L = D->getLocation(); >>>> + auto R = CharSourceRange::getTokenRange(L, L); >>>> + // Verify that the range covers exactly the name. >>>> + // FIXME: extend this code to support cases like `operator +` or >>>> + // `foo<int>` for which this range will be too short. Doing so >>>> will >>>> + // require subcasing `NamedDecl`, because it doesn't provide >>>> virtual >>>> + // access to the \c DeclarationNameInfo. >>>> + if (getText(R, *Result.Context) != D->getName()) >>>> + return CharSourceRange(); >>>> + return R; >>>> + } >>>> + if (const auto *E = Node.get<DeclRefExpr>()) { >>>> + if (!E->getNameInfo().getName().isIdentifier()) >>>> + return missingPropertyError(ID, "name", "identifier"); >>>> + SourceLocation L = E->getLocation(); >>>> + return CharSourceRange::getTokenRange(L, L); >>>> + } >>>> + if (const auto *I = Node.get<CXXCtorInitializer>()) { >>>> + if (!I->isMemberInitializer() && I->isWritten()) >>>> + return missingPropertyError(ID, "name", "explicit member >>>> initializer"); >>>> + SourceLocation L = I->getMemberLocation(); >>>> + return CharSourceRange::getTokenRange(L, L); >>>> + } >>>> + return typeError(ID, Node.getNodeKind(), >>>> + "DeclRefExpr, NamedDecl, CXXCtorInitializer"); >>>> + }; >>>> +} >>>> + >>>> +namespace { >>>> +// Creates a selector from a range-selection function \p Func, which >>>> selects a >>>> +// range that is relative to a bound node id. \c T is the node type >>>> expected by >>>> +// \p Func. >>>> +template <typename T, CharSourceRange (*Func)(const MatchResult &, >>>> const T &)> >>>> +class RelativeSelector { >>>> + std::string ID; >>>> + >>>> +public: >>>> + RelativeSelector(StringRef ID) : ID(ID) {} >>>> + >>>> + Expected<CharSourceRange> operator()(const MatchResult &Result) { >>>> + Expected<DynTypedNode> N = getNode(Result.Nodes, ID); >>>> + if (!N) >>>> + return N.takeError(); >>>> + if (const auto *Arg = N->get<T>()) >>>> + return Func(Result, *Arg); >>>> + return typeError(ID, N->getNodeKind()); >>>> + } >>>> +}; >>>> +} // namespace >>>> + >>>> +// Returns the range of the statements (all source between the braces). >>>> +static CharSourceRange getStatementsRange(const MatchResult &, >>>> + const CompoundStmt &CS) { >>>> + return >>>> CharSourceRange::getCharRange(CS.getLBracLoc().getLocWithOffset(1), >>>> + CS.getRBracLoc()); >>>> +} >>>> + >>>> +RangeSelector tooling::statements(StringRef ID) { >>>> + return RelativeSelector<CompoundStmt, getStatementsRange>(ID); >>>> +} >>>> + >>>> +// Returns the range of the source between the call's parentheses. >>>> +static CharSourceRange getCallArgumentsRange(const MatchResult &Result, >>>> + const CallExpr &CE) { >>>> + return CharSourceRange::getCharRange( >>>> + findOpenParen(CE, *Result.SourceManager, >>>> Result.Context->getLangOpts()) >>>> + .getLocWithOffset(1), >>>> + CE.getRParenLoc()); >>>> +} >>>> + >>>> +RangeSelector tooling::callArgs(StringRef ID) { >>>> + return RelativeSelector<CallExpr, getCallArgumentsRange>(ID); >>>> +} >>>> + >>>> +// Returns the range of the elements of the initializer list. Includes >>>> all >>>> +// source between the braces. >>>> +static CharSourceRange getElementsRange(const MatchResult &, >>>> + const InitListExpr &E) { >>>> + return >>>> CharSourceRange::getCharRange(E.getLBraceLoc().getLocWithOffset(1), >>>> + E.getRBraceLoc()); >>>> +} >>>> + >>>> +RangeSelector tooling::initListElements(StringRef ID) { >>>> + return RelativeSelector<InitListExpr, getElementsRange>(ID); >>>> +} >>>> + >>>> +RangeSelector tooling::expansion(RangeSelector S) { >>>> + return [S](const MatchResult &Result) -> Expected<CharSourceRange> { >>>> + Expected<CharSourceRange> SRange = S(Result); >>>> + if (!SRange) >>>> + return SRange.takeError(); >>>> + return Result.SourceManager->getExpansionRange(*SRange); >>>> + }; >>>> +} >>>> >>>> Modified: cfe/trunk/unittests/Tooling/CMakeLists.txt >>>> URL: >>>> http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/CMakeLists.txt?rev=361152&r1=361151&r2=361152&view=diff >>>> >>>> ============================================================================== >>>> --- cfe/trunk/unittests/Tooling/CMakeLists.txt (original) >>>> +++ cfe/trunk/unittests/Tooling/CMakeLists.txt Mon May 20 06:15:14 2019 >>>> @@ -22,6 +22,7 @@ add_clang_unittest(ToolingTests >>>> LexicallyOrderedRecursiveASTVisitorTest.cpp >>>> LookupTest.cpp >>>> QualTypeNamesTest.cpp >>>> + RangeSelectorTest.cpp >>>> RecursiveASTVisitorTests/Attr.cpp >>>> RecursiveASTVisitorTests/Class.cpp >>>> RecursiveASTVisitorTests/ConstructExpr.cpp >>>> @@ -69,6 +70,8 @@ target_link_libraries(ToolingTests >>>> clangToolingCore >>>> clangToolingInclusions >>>> clangToolingRefactor >>>> + LLVMSupport >>>> + LLVMTestingSupport >>>> ) >>>> >>>> >>>> >>>> Added: cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp >>>> URL: >>>> http://llvm.org/viewvc/llvm-project/cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp?rev=361152&view=auto >>>> >>>> ============================================================================== >>>> --- cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp (added) >>>> +++ cfe/trunk/unittests/Tooling/RangeSelectorTest.cpp Mon May 20 >>>> 06:15:14 2019 >>>> @@ -0,0 +1,496 @@ >>>> +//===- unittest/Tooling/RangeSelectorTest.cpp >>>> -----------------------------===// >>>> +// >>>> +// 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 "clang/Tooling/Refactoring/RangeSelector.h" >>>> +#include "clang/ASTMatchers/ASTMatchers.h" >>>> +#include "clang/Tooling/FixIt.h" >>>> +#include "clang/Tooling/Tooling.h" >>>> +#include "llvm/Support/Error.h" >>>> +#include "llvm/Testing/Support/Error.h" >>>> +#include "gmock/gmock.h" >>>> +#include "gtest/gtest.h" >>>> + >>>> +using namespace clang; >>>> +using namespace tooling; >>>> +using namespace ast_matchers; >>>> + >>>> +namespace { >>>> +using ::testing::AllOf; >>>> +using ::testing::HasSubstr; >>>> +using MatchResult = MatchFinder::MatchResult; >>>> +using ::llvm::Expected; >>>> +using ::llvm::Failed; >>>> +using ::llvm::HasValue; >>>> +using ::llvm::StringError; >>>> + >>>> +struct TestMatch { >>>> + // The AST unit from which `result` is built. We bundle it because >>>> it backs >>>> + // the result. Users are not expected to access it. >>>> + std::unique_ptr<ASTUnit> ASTUnit; >>>> + // The result to use in the test. References `ast_unit`. >>>> + MatchResult Result; >>>> +}; >>>> + >>>> +template <typename M> TestMatch matchCode(StringRef Code, M Matcher) { >>>> + auto ASTUnit = buildASTFromCode(Code); >>>> + assert(ASTUnit != nullptr && "AST construction failed"); >>>> + >>>> + ASTContext &Context = ASTUnit->getASTContext(); >>>> + assert(!Context.getDiagnostics().hasErrorOccurred() && "Compilation >>>> error"); >>>> + >>>> + auto Matches = ast_matchers::match(Matcher, Context); >>>> + // We expect a single, exact match. >>>> + assert(Matches.size() != 0 && "no matches found"); >>>> + assert(Matches.size() == 1 && "too many matches"); >>>> + >>>> + return TestMatch{std::move(ASTUnit), MatchResult(Matches[0], >>>> &Context)}; >>>> +} >>>> + >>>> +// Applies \p Selector to \p Match and, on success, returns the >>>> selected source. >>>> +Expected<StringRef> apply(RangeSelector Selector, const TestMatch >>>> &Match) { >>>> + Expected<CharSourceRange> Range = Selector(Match.Result); >>>> + if (!Range) >>>> + return Range.takeError(); >>>> + return fixit::internal::getText(*Range, *Match.Result.Context); >>>> +} >>>> + >>>> +// Applies \p Selector to a trivial match with only a single bound >>>> node with id >>>> +// "bound_node_id". For use in testing unbound-node errors. >>>> +Expected<CharSourceRange> applyToTrivial(const RangeSelector >>>> &Selector) { >>>> + // We need to bind the result to something, or the match will fail. >>>> Use a >>>> + // binding that is not used in the unbound node tests. >>>> + TestMatch Match = >>>> + matchCode("static int x = 0;", varDecl().bind("bound_node_id")); >>>> + return Selector(Match.Result); >>>> +} >>>> + >>>> +// Matches the message expected for unbound-node failures. >>>> +testing::Matcher<StringError> withUnboundNodeMessage() { >>>> + return testing::Property( >>>> + &StringError::getMessage, >>>> + AllOf(HasSubstr("unbound_id"), HasSubstr("not bound"))); >>>> +} >>>> + >>>> +// Applies \p Selector to code containing assorted node types, where >>>> the match >>>> +// binds each one: a statement ("stmt"), a (non-member) >>>> ctor-initializer >>>> +// ("init"), an expression ("expr") and a (nameless) declaration >>>> ("decl"). Used >>>> +// to test failures caused by applying selectors to nodes of the wrong >>>> type. >>>> +Expected<CharSourceRange> applyToAssorted(RangeSelector Selector) { >>>> + StringRef Code = R"cc( >>>> + struct A {}; >>>> + class F : public A { >>>> + public: >>>> + F(int) {} >>>> + }; >>>> + void g() { F f(1); } >>>> + )cc"; >>>> + >>>> + auto Matcher = >>>> + compoundStmt( >>>> + hasDescendant( >>>> + cxxConstructExpr( >>>> + hasDeclaration( >>>> + >>>> decl(hasDescendant(cxxCtorInitializer(isBaseInitializer()) >>>> + .bind("init"))) >>>> + .bind("decl"))) >>>> + .bind("expr"))) >>>> + .bind("stmt"); >>>> + >>>> + return Selector(matchCode(Code, Matcher).Result); >>>> +} >>>> + >>>> +// Matches the message expected for type-error failures. >>>> +testing::Matcher<StringError> withTypeErrorMessage(StringRef NodeID) { >>>> + return testing::Property( >>>> + &StringError::getMessage, >>>> + AllOf(HasSubstr(NodeID), HasSubstr("mismatched type"))); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, UnboundNode) { >>>> + EXPECT_THAT_EXPECTED(applyToTrivial(node("unbound_id")), >>>> + Failed<StringError>(withUnboundNodeMessage())); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, RangeOp) { >>>> + StringRef Code = R"cc( >>>> + int f(int x, int y, int z) { return 3; } >>>> + int g() { return f(/* comment */ 3, 7 /* comment */, 9); } >>>> + )cc"; >>>> + StringRef Arg0 = "a0"; >>>> + StringRef Arg1 = "a1"; >>>> + StringRef Call = "call"; >>>> + auto Matcher = callExpr(hasArgument(0, expr().bind(Arg0)), >>>> + hasArgument(1, expr().bind(Arg1))) >>>> + .bind(Call); >>>> + TestMatch Match = matchCode(Code, Matcher); >>>> + >>>> + // Node-id specific version: >>>> + EXPECT_THAT_EXPECTED(apply(range(Arg0, Arg1), Match), HasValue("3, >>>> 7")); >>>> + // General version: >>>> + EXPECT_THAT_EXPECTED(apply(range(node(Arg0), node(Arg1)), Match), >>>> + HasValue("3, 7")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NodeOpStatement) { >>>> + StringRef Code = "int f() { return 3; }"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, returnStmt().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("return 3;")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NodeOpExpression) { >>>> + StringRef Code = "int f() { return 3; }"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, expr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(node(ID), Match), HasValue("3")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, StatementOp) { >>>> + StringRef Code = "int f() { return 3; }"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, expr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(statement(ID), Match), HasValue("3;")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, MemberOp) { >>>> + StringRef Code = R"cc( >>>> + struct S { >>>> + int member; >>>> + }; >>>> + int g() { >>>> + S s; >>>> + return s.member; >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, memberExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("member")); >>>> +} >>>> + >>>> +// Tests that member does not select any qualifiers on the member name. >>>> +TEST(RangeSelectorTest, MemberOpQualified) { >>>> + StringRef Code = R"cc( >>>> + struct S { >>>> + int member; >>>> + }; >>>> + struct T : public S { >>>> + int field; >>>> + }; >>>> + int g() { >>>> + T t; >>>> + return t.S::member; >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, memberExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("member")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, MemberOpTemplate) { >>>> + StringRef Code = R"cc( >>>> + struct S { >>>> + template <typename T> T foo(T t); >>>> + }; >>>> + int f(int x) { >>>> + S s; >>>> + return s.template foo<int>(3); >>>> + } >>>> + )cc"; >>>> + >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, memberExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("foo")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, MemberOpOperator) { >>>> + StringRef Code = R"cc( >>>> + struct S { >>>> + int operator*(); >>>> + }; >>>> + int f(int x) { >>>> + S s; >>>> + return s.operator *(); >>>> + } >>>> + )cc"; >>>> + >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, memberExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(member(ID), Match), HasValue("operator >>>> *")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NameOpNamedDecl) { >>>> + StringRef Code = R"cc( >>>> + int myfun() { >>>> + return 3; >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, functionDecl().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(name(ID), Match), HasValue("myfun")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NameOpDeclRef) { >>>> + StringRef Code = R"cc( >>>> + int foo(int x) { >>>> + return x; >>>> + } >>>> + int g(int x) { return foo(x) * x; } >>>> + )cc"; >>>> + StringRef Ref = "ref"; >>>> + TestMatch Match = matchCode(Code, >>>> declRefExpr(to(functionDecl())).bind(Ref)); >>>> + EXPECT_THAT_EXPECTED(apply(name(Ref), Match), HasValue("foo")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NameOpCtorInitializer) { >>>> + StringRef Code = R"cc( >>>> + class C { >>>> + public: >>>> + C() : field(3) {} >>>> + int field; >>>> + }; >>>> + )cc"; >>>> + StringRef Init = "init"; >>>> + TestMatch Match = matchCode(Code, cxxCtorInitializer().bind(Init)); >>>> + EXPECT_THAT_EXPECTED(apply(name(Init), Match), HasValue("field")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NameOpErrors) { >>>> + EXPECT_THAT_EXPECTED(applyToTrivial(name("unbound_id")), >>>> + Failed<StringError>(withUnboundNodeMessage())); >>>> + EXPECT_THAT_EXPECTED(applyToAssorted(name("stmt")), >>>> + >>>> Failed<StringError>(withTypeErrorMessage("stmt"))); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, NameOpDeclRefError) { >>>> + StringRef Code = R"cc( >>>> + struct S { >>>> + int operator*(); >>>> + }; >>>> + int f(int x) { >>>> + S s; >>>> + return *s + x; >>>> + } >>>> + )cc"; >>>> + StringRef Ref = "ref"; >>>> + TestMatch Match = matchCode(Code, >>>> declRefExpr(to(functionDecl())).bind(Ref)); >>>> + EXPECT_THAT_EXPECTED( >>>> + name(Ref)(Match.Result), >>>> + Failed<StringError>(testing::Property( >>>> + &StringError::getMessage, >>>> + AllOf(HasSubstr(Ref), HasSubstr("requires property >>>> 'identifier'"))))); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOp) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(int, int); >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar(3, 4); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("3, 4")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOpNoArgs) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(); >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar(); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOpNoArgsWithComments) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(); >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar(/*empty*/); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), >>>> HasValue("/*empty*/")); >>>> +} >>>> + >>>> +// Tests that arguments are extracted correctly when a temporary (with >>>> parens) >>>> +// is used. >>>> +TEST(RangeSelectorTest, CallArgsOpWithParens) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(int, int) { return 3; } >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return C().bar(3, 4); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = >>>> + matchCode(Code, >>>> callExpr(callee(functionDecl(hasName("bar")))).bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue("3, 4")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOpLeadingComments) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(int, int) { return 3; } >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar(/*leading*/ 3, 4); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), >>>> + HasValue("/*leading*/ 3, 4")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOpTrailingComments) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(int, int) { return 3; } >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar(3 /*trailing*/, 4); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), >>>> + HasValue("3 /*trailing*/, 4")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsOpEolComments) { >>>> + const StringRef Code = R"cc( >>>> + struct C { >>>> + int bar(int, int) { return 3; } >>>> + }; >>>> + int f() { >>>> + C x; >>>> + return x.bar( // Header >>>> + 1, // foo >>>> + 2 // bar >>>> + ); >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, callExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(callArgs(ID), Match), HasValue(R"( // >>>> Header >>>> + 1, // foo >>>> + 2 // bar >>>> + )")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, CallArgsErrors) { >>>> + EXPECT_THAT_EXPECTED(applyToTrivial(callArgs("unbound_id")), >>>> + Failed<StringError>(withUnboundNodeMessage())); >>>> + EXPECT_THAT_EXPECTED(applyToAssorted(callArgs("stmt")), >>>> + >>>> Failed<StringError>(withTypeErrorMessage("stmt"))); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, StatementsOp) { >>>> + StringRef Code = R"cc( >>>> + void g(); >>>> + void f() { /* comment */ g(); /* comment */ g(); /* comment */ } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, compoundStmt().bind(ID)); >>>> + EXPECT_THAT_EXPECTED( >>>> + apply(statements(ID), Match), >>>> + HasValue(" /* comment */ g(); /* comment */ g(); /* comment */ >>>> ")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, StatementsOpEmptyList) { >>>> + StringRef Code = "void f() {}"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, compoundStmt().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(statements(ID), Match), HasValue("")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, StatementsOpErrors) { >>>> + EXPECT_THAT_EXPECTED(applyToTrivial(statements("unbound_id")), >>>> + Failed<StringError>(withUnboundNodeMessage())); >>>> + EXPECT_THAT_EXPECTED(applyToAssorted(statements("decl")), >>>> + >>>> Failed<StringError>(withTypeErrorMessage("decl"))); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, ElementsOp) { >>>> + StringRef Code = R"cc( >>>> + void f() { >>>> + int v[] = {/* comment */ 3, /* comment*/ 4 /* comment */}; >>>> + (void)v; >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, initListExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED( >>>> + apply(initListElements(ID), Match), >>>> + HasValue("/* comment */ 3, /* comment*/ 4 /* comment */")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, ElementsOpEmptyList) { >>>> + StringRef Code = R"cc( >>>> + void f() { >>>> + int v[] = {}; >>>> + (void)v; >>>> + } >>>> + )cc"; >>>> + StringRef ID = "id"; >>>> + TestMatch Match = matchCode(Code, initListExpr().bind(ID)); >>>> + EXPECT_THAT_EXPECTED(apply(initListElements(ID), Match), >>>> HasValue("")); >>>> +} >>>> + >>>> +TEST(RangeSelectorTest, ElementsOpErrors) { >>>> + EXPECT_THAT_EXPECTED(applyToTrivial(initListElements("unbound_id")), >>>> + Failed<StringError>(withUnboundNodeMessage())); >>>> + EXPECT_THAT_EXPECTED(applyToAssorted(initListElements("stmt")), >>>> + >>>> Failed<StringError>(withTypeErrorMessage("stmt"))); >>>> +} >>>> + >>>> +// Tests case where the matched node is the complete expanded text. >>>> +TEST(RangeSelectorTest, ExpansionOp) { >>>> + StringRef Code = R"cc( >>>> +#define BADDECL(E) int bad(int x) { return E; } >>>> + BADDECL(x * x) >>>> + )cc"; >>>> + >>>> + StringRef Fun = "Fun"; >>>> + TestMatch Match = matchCode(Code, >>>> functionDecl(hasName("bad")).bind(Fun)); >>>> + EXPECT_THAT_EXPECTED(apply(expansion(node(Fun)), Match), >>>> + HasValue("BADDECL(x * x)")); >>>> +} >>>> + >>>> +// Tests case where the matched node is (only) part of the expanded >>>> text. >>>> +TEST(RangeSelectorTest, ExpansionOpPartial) { >>>> + StringRef Code = R"cc( >>>> +#define BADDECL(E) int bad(int x) { return E; } >>>> + BADDECL(x * x) >>>> + )cc"; >>>> + >>>> + StringRef Ret = "Ret"; >>>> + TestMatch Match = matchCode(Code, returnStmt().bind(Ret)); >>>> + EXPECT_THAT_EXPECTED(apply(expansion(node(Ret)), Match), >>>> + HasValue("BADDECL(x * x)")); >>>> +} >>>> + >>>> +} // namespace >>>> >>>> >>>> _______________________________________________ >>>> cfe-commits mailing list >>>> cfe-commits@lists.llvm.org >>>> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits >>>> >>>
smime.p7s
Description: S/MIME Cryptographic Signature
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits