r361152 - [LibTooling] Add RangeSelector library for defining source ranges based on bound AST nodes.

Yitzhak Mandelbaum via cfe-commits cfe-commits at lists.llvm.org
Tue May 21 08:03:25 PDT 2019


https://reviews.llvm.org/D62201

On Tue, May 21, 2019 at 10:15 AM Nico Weber <thakis at 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 <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/c2080e05/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 4847 bytes
Desc: S/MIME Cryptographic Signature
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20190521/c2080e05/attachment-0001.bin>


More information about the cfe-commits mailing list