r361152 - [LibTooling] Add RangeSelector library for defining source ranges based on bound AST nodes.
Nico Weber via cfe-commits
cfe-commits at lists.llvm.org
Tue May 21 07:15:41 PDT 2019
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 <yitzhakm at 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 <thakis at 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 at lists.llvm.org>
>> *Date: *Mon, May 20, 2019 at 9:12 AM
>> *To: * <cfe-commits at 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 at lists.llvm.org
>>> https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
>>>
>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20190521/257b305b/attachment-0001.html>
More information about the cfe-commits
mailing list