Index: cfe/trunk/unittests/Sema/CMakeLists.txt =================================================================== --- cfe/trunk/unittests/Sema/CMakeLists.txt +++ cfe/trunk/unittests/Sema/CMakeLists.txt @@ -16,4 +16,5 @@ clangSema clangSerialization clangTooling + LLVMTestingSupport ) Index: cfe/trunk/unittests/Sema/CodeCompleteTest.cpp =================================================================== --- cfe/trunk/unittests/Sema/CodeCompleteTest.cpp +++ cfe/trunk/unittests/Sema/CodeCompleteTest.cpp @@ -13,6 +13,7 @@ #include "clang/Sema/Sema.h" #include "clang/Sema/SemaDiagnostic.h" #include "clang/Tooling/Tooling.h" +#include "llvm/Testing/Support/Annotations.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include @@ -107,41 +108,18 @@ return ResultCtx; } -struct ParsedAnnotations { - std::vector Points; - std::string Code; -}; - -ParsedAnnotations parseAnnotations(StringRef AnnotatedCode) { - ParsedAnnotations R; - while (!AnnotatedCode.empty()) { - size_t NextPoint = AnnotatedCode.find('^'); - if (NextPoint == StringRef::npos) { - R.Code += AnnotatedCode; - AnnotatedCode = ""; - break; - } - R.Code += AnnotatedCode.substr(0, NextPoint); - R.Points.push_back(R.Code.size()); - - AnnotatedCode = AnnotatedCode.substr(NextPoint + 1); - } - return R; -} - CompletionContext runCodeCompleteOnCode(StringRef AnnotatedCode) { - ParsedAnnotations P = parseAnnotations(AnnotatedCode); - assert(P.Points.size() == 1 && "expected exactly one annotation point"); - return runCompletion(P.Code, P.Points.front()); + llvm::Annotations A(AnnotatedCode); + return runCompletion(A.code(), A.point()); } std::vector collectPreferredTypes(StringRef AnnotatedCode, std::string *PtrDiffType = nullptr) { - ParsedAnnotations P = parseAnnotations(AnnotatedCode); + llvm::Annotations A(AnnotatedCode); std::vector Types; - for (size_t Point : P.Points) { - auto Results = runCompletion(P.Code, Point); + for (size_t Point : A.points()) { + auto Results = runCompletion(A.code(), Point); if (PtrDiffType) { assert(PtrDiffType->empty() || *PtrDiffType == Results.PtrDiffType); *PtrDiffType = Results.PtrDiffType; Index: clang-tools-extra/trunk/unittests/clangd/Annotations.h =================================================================== --- clang-tools-extra/trunk/unittests/clangd/Annotations.h +++ clang-tools-extra/trunk/unittests/clangd/Annotations.h @@ -5,64 +5,32 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// -// Annotations lets you mark points and ranges inside source code, for tests: -// -// Annotations Example(R"cpp( -// int complete() { x.pri^ } // ^ indicates a point -// void err() { [["hello" == 42]]; } // [[this is a range]] -// $definition^class Foo{}; // points can be named: "definition" -// $fail[[static_assert(false, "")]] // ranges can be named too: "fail" -// )cpp"); -// -// StringRef Code = Example.code(); // annotations stripped. -// std::vector PP = Example.points(); // all unnamed points -// Position P = Example.point(); // there must be exactly one -// Range R = Example.range("fail"); // find named ranges -// -// Points/ranges are coordinates into `code()` which is stripped of annotations. -// -// Ranges may be nested (and points can be inside ranges), but there's no way -// to define general overlapping ranges. -// +// A clangd-specific version of llvm/Testing/Support/Annotations.h, replaces +// offsets and offset-based ranges with types from the LSP protocol. //===---------------------------------------------------------------------===// #ifndef LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H #define LLVM_CLANG_TOOLS_EXTRA_UNITTESTS_CLANGD_ANNOTATIONS_H #include "Protocol.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" -#include "llvm/ADT/StringRef.h" +#include "llvm/Testing/Support/Annotations.h" namespace clang { namespace clangd { -class Annotations { -public: - // Parses the annotations from Text. Crashes if it's malformed. - Annotations(llvm::StringRef Text); +/// Same as llvm::Annotations, but adjusts functions to LSP-specific types for +/// positions and ranges. +class Annotations : public llvm::Annotations { + using Base = llvm::Annotations; - // The input text with all annotations stripped. - // All points and ranges are relative to this stripped text. - llvm::StringRef code() const { return Code; } +public: + using llvm::Annotations::Annotations; - // Returns the position of the point marked by ^ (or $name^) in the text. - // Crashes if there isn't exactly one. Position point(llvm::StringRef Name = "") const; - // Returns the position of all points marked by ^ (or $name^) in the text. std::vector points(llvm::StringRef Name = "") const; - // Returns the location of the range marked by [[ ]] (or $name[[ ]]). - // Crashes if there isn't exactly one. - Range range(llvm::StringRef Name = "") const; - // Returns the location of all ranges marked by [[ ]] (or $name[[ ]]). - std::vector ranges(llvm::StringRef Name = "") const; - -private: - std::string Code; - llvm::StringMap> Points; - llvm::StringMap> Ranges; + clangd::Range range(llvm::StringRef Name = "") const; + std::vector ranges(llvm::StringRef Name = "") const; }; } // namespace clangd Index: clang-tools-extra/trunk/unittests/clangd/Annotations.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clangd/Annotations.cpp +++ clang-tools-extra/trunk/unittests/clangd/Annotations.cpp @@ -12,74 +12,41 @@ namespace clang { namespace clangd { -// Crash if the assertion fails, printing the message and testcase. -// More elegant error handling isn't needed for unit tests. -static void require(bool Assertion, const char *Msg, llvm::StringRef Code) { - if (!Assertion) { - llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n"; - llvm_unreachable("Annotated testcase assertion failed!"); - } +Position Annotations::point(llvm::StringRef Name) const { + return offsetToPosition(code(), Base::point(Name)); } -Annotations::Annotations(llvm::StringRef Text) { - auto Here = [this] { return offsetToPosition(Code, Code.size()); }; - auto Require = [Text](bool Assertion, const char *Msg) { - require(Assertion, Msg, Text); - }; - llvm::Optional Name; - llvm::SmallVector, 8> OpenRanges; - - Code.reserve(Text.size()); - while (!Text.empty()) { - if (Text.consume_front("^")) { - Points[Name.getValueOr("")].push_back(Here()); - Name = None; - continue; - } - if (Text.consume_front("[[")) { - OpenRanges.emplace_back(Name.getValueOr(""), Here()); - Name = None; - continue; - } - Require(!Name, "$name should be followed by ^ or [["); - if (Text.consume_front("]]")) { - Require(!OpenRanges.empty(), "unmatched ]]"); - Ranges[OpenRanges.back().first].push_back( - {OpenRanges.back().second, Here()}); - OpenRanges.pop_back(); - continue; - } - if (Text.consume_front("$")) { - Name = Text.take_while(llvm::isAlnum); - Text = Text.drop_front(Name->size()); - continue; - } - Code.push_back(Text.front()); - Text = Text.drop_front(); - } - Require(!Name, "unterminated $name"); - Require(OpenRanges.empty(), "unmatched [["); -} +std::vector Annotations::points(llvm::StringRef Name) const { + auto Offsets = Base::points(Name); -Position Annotations::point(llvm::StringRef Name) const { - auto I = Points.find(Name); - require(I != Points.end() && I->getValue().size() == 1, - "expected exactly one point", Code); - return I->getValue()[0]; + std::vector Ps; + Ps.reserve(Offsets.size()); + for (size_t O : Offsets) + Ps.push_back(offsetToPosition(code(), O)); + + return Ps; } -std::vector Annotations::points(llvm::StringRef Name) const { - auto P = Points.lookup(Name); - return {P.begin(), P.end()}; + +static clangd::Range toLSPRange(llvm::StringRef Code, Annotations::Range R) { + clangd::Range LSPRange; + LSPRange.start = offsetToPosition(Code, R.Begin); + LSPRange.end = offsetToPosition(Code, R.End); + return LSPRange; } -Range Annotations::range(llvm::StringRef Name) const { - auto I = Ranges.find(Name); - require(I != Ranges.end() && I->getValue().size() == 1, - "expected exactly one range", Code); - return I->getValue()[0]; + +clangd::Range Annotations::range(llvm::StringRef Name) const { + return toLSPRange(code(), Base::range(Name)); } -std::vector Annotations::ranges(llvm::StringRef Name) const { - auto R = Ranges.lookup(Name); - return {R.begin(), R.end()}; + +std::vector Annotations::ranges(llvm::StringRef Name) const { + auto OffsetRanges = Base::ranges(Name); + + std::vector Rs; + Rs.reserve(OffsetRanges.size()); + for (Annotations::Range R : OffsetRanges) + Rs.push_back(toLSPRange(code(), R)); + + return Rs; } } // namespace clangd Index: llvm/trunk/include/llvm/Testing/Support/Annotations.h =================================================================== --- llvm/trunk/include/llvm/Testing/Support/Annotations.h +++ llvm/trunk/include/llvm/Testing/Support/Annotations.h @@ -0,0 +1,90 @@ +//===--- Annotations.h - Annotated source code for tests ---------*- 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 +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_TESTING_SUPPORT_ANNOTATIONS_H +#define LLVM_TESTING_SUPPORT_ANNOTATIONS_H + +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include +#include + +namespace llvm { + +/// Annotations lets you mark points and ranges inside source code, for tests: +/// +/// Annotations Example(R"cpp( +/// int complete() { x.pri^ } // ^ indicates a point +/// void err() { [["hello" == 42]]; } // [[this is a range]] +/// $definition^class Foo{}; // points can be named: "definition" +/// $fail[[static_assert(false, "")]] // ranges can be named too: "fail" +/// )cpp"); +/// +/// StringRef Code = Example.code(); // annotations stripped. +/// std::vector PP = Example.points(); // all unnamed points +/// size_t P = Example.point(); // there must be exactly one +/// llvm::Range R = Example.range("fail"); // find named ranges +/// +/// Points/ranges are coordinated into `code()` which is stripped of +/// annotations. +/// +/// Ranges may be nested (and points can be inside ranges), but there's no way +/// to define general overlapping ranges. +/// +/// FIXME: the choice of the marking syntax makes it impossible to represent +/// some of the C++ and Objective C constructs (including common ones +/// like C++ attributes). We can fix this by: +/// 1. introducing an escaping mechanism for the special characters, +/// 2. making characters for marking points and ranges configurable, +/// 3. changing the syntax to something less commonly used, +/// 4. ... +class Annotations { +public: + /// Two offsets pointing to a continuous substring. End is not included, i.e. + /// represents a half-open range. + struct Range { + size_t Begin = 0; + size_t End = 0; + + friend bool operator==(const Range &L, const Range &R) { + return std::tie(L.Begin, L.End) == std::tie(R.Begin, R.End); + } + friend bool operator!=(const Range &L, const Range &R) { return !(L == R); } + }; + + /// Parses the annotations from Text. Crashes if it's malformed. + Annotations(llvm::StringRef Text); + + /// The input text with all annotations stripped. + /// All points and ranges are relative to this stripped text. + llvm::StringRef code() const { return Code; } + + /// Returns the position of the point marked by ^ (or $name^) in the text. + /// Crashes if there isn't exactly one. + size_t point(llvm::StringRef Name = "") const; + /// Returns the position of all points marked by ^ (or $name^) in the text. + std::vector points(llvm::StringRef Name = "") const; + + /// Returns the location of the range marked by [[ ]] (or $name[[ ]]). + /// Crashes if there isn't exactly one. + Range range(llvm::StringRef Name = "") const; + /// Returns the location of all ranges marked by [[ ]] (or $name[[ ]]). + std::vector ranges(llvm::StringRef Name = "") const; + +private: + std::string Code; + llvm::StringMap> Points; + llvm::StringMap> Ranges; +}; + +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const llvm::Annotations::Range &R); + +} // namespace llvm + +#endif Index: llvm/trunk/lib/Testing/Support/Annotations.cpp =================================================================== --- llvm/trunk/lib/Testing/Support/Annotations.cpp +++ llvm/trunk/lib/Testing/Support/Annotations.cpp @@ -0,0 +1,95 @@ +//===--- Annotations.cpp - Annotated source code for unit tests --*- 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 "llvm/Testing/Support/Annotations.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +// Crash if the assertion fails, printing the message and testcase. +// More elegant error handling isn't needed for unit tests. +static void require(bool Assertion, const char *Msg, llvm::StringRef Code) { + if (!Assertion) { + llvm::errs() << "Annotated testcase: " << Msg << "\n" << Code << "\n"; + llvm_unreachable("Annotated testcase assertion failed!"); + } +} + +Annotations::Annotations(llvm::StringRef Text) { + auto Require = [Text](bool Assertion, const char *Msg) { + require(Assertion, Msg, Text); + }; + llvm::Optional Name; + llvm::SmallVector, 8> OpenRanges; + + Code.reserve(Text.size()); + while (!Text.empty()) { + if (Text.consume_front("^")) { + Points[Name.getValueOr("")].push_back(Code.size()); + Name = llvm::None; + continue; + } + if (Text.consume_front("[[")) { + OpenRanges.emplace_back(Name.getValueOr(""), Code.size()); + Name = llvm::None; + continue; + } + Require(!Name, "$name should be followed by ^ or [["); + if (Text.consume_front("]]")) { + Require(!OpenRanges.empty(), "unmatched ]]"); + Range R; + R.Begin = OpenRanges.back().second; + R.End = Code.size(); + Ranges[OpenRanges.back().first].push_back(R); + OpenRanges.pop_back(); + continue; + } + if (Text.consume_front("$")) { + Name = Text.take_while(llvm::isAlnum); + Text = Text.drop_front(Name->size()); + continue; + } + Code.push_back(Text.front()); + Text = Text.drop_front(); + } + Require(!Name, "unterminated $name"); + Require(OpenRanges.empty(), "unmatched [["); +} + +size_t Annotations::point(llvm::StringRef Name) const { + auto I = Points.find(Name); + require(I != Points.end() && I->getValue().size() == 1, + "expected exactly one point", Code); + return I->getValue()[0]; +} + +std::vector Annotations::points(llvm::StringRef Name) const { + auto P = Points.lookup(Name); + return {P.begin(), P.end()}; +} + +Annotations::Range Annotations::range(llvm::StringRef Name) const { + auto I = Ranges.find(Name); + require(I != Ranges.end() && I->getValue().size() == 1, + "expected exactly one range", Code); + return I->getValue()[0]; +} + +std::vector +Annotations::ranges(llvm::StringRef Name) const { + auto R = Ranges.lookup(Name); + return {R.begin(), R.end()}; +} + +llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O, + const llvm::Annotations::Range &R) { + return O << llvm::formatv("[{0}, {1})", R.Begin, R.End); +} Index: llvm/trunk/lib/Testing/Support/CMakeLists.txt =================================================================== --- llvm/trunk/lib/Testing/Support/CMakeLists.txt +++ llvm/trunk/lib/Testing/Support/CMakeLists.txt @@ -2,6 +2,7 @@ add_definitions(-DGTEST_HAS_TR1_TUPLE=0) add_llvm_library(LLVMTestingSupport + Annotations.cpp Error.cpp SupportHelpers.cpp Index: llvm/trunk/unittests/Support/AnnotationsTest.cpp =================================================================== --- llvm/trunk/unittests/Support/AnnotationsTest.cpp +++ llvm/trunk/unittests/Support/AnnotationsTest.cpp @@ -0,0 +1,112 @@ +//===----- unittests/AnnotationsTest.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 "llvm/Testing/Support/Annotations.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::ElementsAre; +using ::testing::IsEmpty; + +namespace { +llvm::Annotations::Range range(size_t Begin, size_t End) { + llvm::Annotations::Range R; + R.Begin = Begin; + R.End = End; + return R; +} + +TEST(AnnotationsTest, CleanedCode) { + EXPECT_EQ(llvm::Annotations("foo^bar$nnn[[baz$^[[qux]]]]").code(), + "foobarbazqux"); +} + +TEST(AnnotationsTest, Points) { + // A single point. + EXPECT_EQ(llvm::Annotations("^ab").point(), 0u); + EXPECT_EQ(llvm::Annotations("a^b").point(), 1u); + EXPECT_EQ(llvm::Annotations("ab^").point(), 2u); + + // Multiple points. + EXPECT_THAT(llvm::Annotations("^a^bc^d^").points(), + ElementsAre(0u, 1u, 3u, 4u)); + + // No points. + EXPECT_THAT(llvm::Annotations("ab[[cd]]").points(), IsEmpty()); + + // Consecutive points. + EXPECT_THAT(llvm::Annotations("ab^^^cd").points(), ElementsAre(2u, 2u, 2u)); +} + +TEST(AnnotationsTest, Ranges) { + // A single range. + EXPECT_EQ(llvm::Annotations("[[a]]bc").range(), range(0, 1)); + EXPECT_EQ(llvm::Annotations("a[[bc]]d").range(), range(1, 3)); + EXPECT_EQ(llvm::Annotations("ab[[cd]]").range(), range(2, 4)); + + // Empty range. + EXPECT_EQ(llvm::Annotations("[[]]ab").range(), range(0, 0)); + EXPECT_EQ(llvm::Annotations("a[[]]b").range(), range(1, 1)); + EXPECT_EQ(llvm::Annotations("ab[[]]").range(), range(2, 2)); + + // Multiple ranges. + EXPECT_THAT(llvm::Annotations("[[a]][[b]]cd[[ef]]ef").ranges(), + ElementsAre(range(0, 1), range(1, 2), range(4, 6))); + + // No ranges. + EXPECT_THAT(llvm::Annotations("ab^c^defef").ranges(), IsEmpty()); +} + +TEST(AnnotationsTest, Nested) { + llvm::Annotations Annotated("a[[f^oo^bar[[b[[a]]z]]]]bcdef"); + EXPECT_THAT(Annotated.points(), ElementsAre(2u, 4u)); + EXPECT_THAT(Annotated.ranges(), + ElementsAre(range(8, 9), range(7, 10), range(1, 10))); +} + +TEST(AnnotationsTest, Named) { + // A single named point or range. + EXPECT_EQ(llvm::Annotations("a$foo^b").point("foo"), 1u); + EXPECT_EQ(llvm::Annotations("a$foo[[b]]cdef").range("foo"), range(1, 2)); + + // Empty names should also work. + EXPECT_EQ(llvm::Annotations("a$^b").point(""), 1u); + EXPECT_EQ(llvm::Annotations("a$[[b]]cdef").range(""), range(1, 2)); + + // Multiple named points. + llvm::Annotations Annotated("a$p1^bcd$p2^123$p1^345"); + EXPECT_THAT(Annotated.points(), IsEmpty()); + EXPECT_THAT(Annotated.points("p1"), ElementsAre(1u, 7u)); + EXPECT_EQ(Annotated.point("p2"), 4u); +} + +TEST(AnnotationsTest, Errors) { + // Annotations use llvm_unreachable, it will only crash in debug mode. +#ifndef NDEBUG + // point() and range() crash on zero or multiple ranges. + EXPECT_DEATH(llvm::Annotations("ab[[c]]def").point(), + "expected exactly one point"); + EXPECT_DEATH(llvm::Annotations("a^b^cdef").point(), + "expected exactly one point"); + + EXPECT_DEATH(llvm::Annotations("a^bcdef").range(), + "expected exactly one range"); + EXPECT_DEATH(llvm::Annotations("a[[b]]c[[d]]ef").range(), + "expected exactly one range"); + + EXPECT_DEATH(llvm::Annotations("$foo^a$foo^a").point("foo"), + "expected exactly one point"); + EXPECT_DEATH(llvm::Annotations("$foo[[a]]bc$foo[[a]]").range("foo"), + "expected exactly one range"); + + // Parsing failures. + EXPECT_DEATH(llvm::Annotations("ff[[fdfd"), "unmatched \\[\\["); + EXPECT_DEATH(llvm::Annotations("ff[[fdjsfjd]]xxx]]"), "unmatched ]]"); + EXPECT_DEATH(llvm::Annotations("ff$fdsfd"), "unterminated \\$name"); +#endif +} +} // namespace Index: llvm/trunk/unittests/Support/CMakeLists.txt =================================================================== --- llvm/trunk/unittests/Support/CMakeLists.txt +++ llvm/trunk/unittests/Support/CMakeLists.txt @@ -5,6 +5,7 @@ add_llvm_unittest(SupportTests AlignOfTest.cpp AllocatorTest.cpp + AnnotationsTest.cpp ARMAttributeParser.cpp ArrayRecyclerTest.cpp BinaryStreamTest.cpp