diff --git a/clang-tools-extra/clangd/unittests/Annotations.h b/clang-tools-extra/clangd/unittests/Annotations.h --- a/clang-tools-extra/clangd/unittests/Annotations.h +++ b/clang-tools-extra/clangd/unittests/Annotations.h @@ -19,18 +19,25 @@ namespace clangd { /// Same as llvm::Annotations, but adjusts functions to LSP-specific types for -/// positions and ranges. +/// positions and ranges (extending them with the payload that may be contained +/// inside llvm::Annotations' points and ranges. class Annotations : public llvm::Annotations { using Base = llvm::Annotations; public: using llvm::Annotations::Annotations; + struct Position : clangd::Position { + llvm::Optional Payload; + }; Position point(llvm::StringRef Name = "") const; std::vector points(llvm::StringRef Name = "") const; - clangd::Range range(llvm::StringRef Name = "") const; - std::vector ranges(llvm::StringRef Name = "") const; + struct Range : clangd::Range { + llvm::Optional Payload; + }; + Range range(llvm::StringRef Name = "") const; + std::vector ranges(llvm::StringRef Name = "") const; }; } // namespace clangd diff --git a/clang-tools-extra/clangd/unittests/Annotations.cpp b/clang-tools-extra/clangd/unittests/Annotations.cpp --- a/clang-tools-extra/clangd/unittests/Annotations.cpp +++ b/clang-tools-extra/clangd/unittests/Annotations.cpp @@ -12,39 +12,44 @@ namespace clang { namespace clangd { -Position Annotations::point(llvm::StringRef Name) const { - return offsetToPosition(code(), Base::point(Name)); +Annotations::Position Annotations::point(llvm::StringRef Name) const { + auto BasePoint = Base::pointWithPayload(Name); + return {offsetToPosition(code(), BasePoint.Position), BasePoint.Payload}; } -std::vector Annotations::points(llvm::StringRef Name) const { - auto Offsets = Base::points(Name); +std::vector +Annotations::points(llvm::StringRef Name) const { + auto BasePoints = Base::pointsWithPayload(Name); std::vector Ps; - Ps.reserve(Offsets.size()); - for (size_t O : Offsets) - Ps.push_back(offsetToPosition(code(), O)); + Ps.reserve(BasePoints.size()); + for (Point P : BasePoints) + Ps.push_back({offsetToPosition(code(), P.Position), P.Payload}); return Ps; } -static clangd::Range toLSPRange(llvm::StringRef Code, Annotations::Range R) { +static clangd::Range toLSPRange(llvm::StringRef Code, + llvm::Annotations::Range R) { clangd::Range LSPRange; LSPRange.start = offsetToPosition(Code, R.Begin); LSPRange.end = offsetToPosition(Code, R.End); return LSPRange; } -clangd::Range Annotations::range(llvm::StringRef Name) const { - return toLSPRange(code(), Base::range(Name)); +Annotations::Range Annotations::range(llvm::StringRef Name) const { + auto BaseRange = Base::range(Name); + return {toLSPRange(code(), BaseRange), BaseRange.Payload}; } -std::vector Annotations::ranges(llvm::StringRef Name) const { +std::vector +Annotations::ranges(llvm::StringRef Name) const { auto OffsetRanges = Base::ranges(Name); - std::vector Rs; + std::vector Rs; Rs.reserve(OffsetRanges.size()); - for (Annotations::Range R : OffsetRanges) - Rs.push_back(toLSPRange(code(), R)); + for (llvm::Annotations::Range R : OffsetRanges) + Rs.push_back({toLSPRange(code(), R), R.Payload}); return Rs; } diff --git a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.h b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.h --- a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.h +++ b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.h @@ -116,7 +116,8 @@ auto AST = build(A.code()); \ assert(!A.points().empty() || !A.ranges().empty()); \ for (const auto &P : A.points()) \ - EXPECT_EQ(Available, isAvailable(AST, {P, P})) << decorate(A.code(), P); \ + EXPECT_EQ(Available, isAvailable(AST, {P, P, llvm::None})) \ + << decorate(A.code(), P); \ for (const auto &R : A.ranges()) \ EXPECT_EQ(Available, isAvailable(AST, R)) << decorate(A.code(), R); \ } while (0) diff --git a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp --- a/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp +++ b/clang-tools-extra/clangd/unittests/tweaks/TweakTesting.cpp @@ -53,7 +53,7 @@ if (A.points().size() != 0) { assert(A.ranges().size() == 0 && "both a cursor point and a selection range were specified"); - return {A.point(), A.point()}; + return {A.point(), A.point(), llvm::None}; } return A.range(); } diff --git a/llvm/include/llvm/Testing/Support/Annotations.h b/llvm/include/llvm/Testing/Support/Annotations.h --- a/llvm/include/llvm/Testing/Support/Annotations.h +++ b/llvm/include/llvm/Testing/Support/Annotations.h @@ -8,6 +8,7 @@ #ifndef LLVM_TESTING_SUPPORT_ANNOTATIONS_H #define LLVM_TESTING_SUPPORT_ANNOTATIONS_H +#include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" @@ -24,7 +25,9 @@ /// int complete() { x.pri^ } // ^ indicates a point /// void err() { [["hello" == 42]]; } // [[this is a range]] /// $definition^class Foo{}; // points can be named: "definition" +/// $definition(foo)^class Foo{}; // and/or contain a payload: "foo" /// $fail[[static_assert(false, "")]] // ranges can be named too: "fail" +/// $fail(foo)[[]] // and/or contain a payload: "foo" /// )cpp"); /// /// StringRef Code = Example.code(); // annotations stripped. @@ -35,6 +38,9 @@ /// Points/ranges are coordinated into `code()` which is stripped of /// annotations. /// +/// Names consist of only alphanumeric characters or '_'. +/// Payloads can contain any character expect '(' and ')'. +/// /// Ranges may be nested (and points can be inside ranges), but there's no way /// to define general overlapping ranges. /// @@ -52,9 +58,11 @@ struct Range { size_t Begin = 0; size_t End = 0; + llvm::Optional Payload; friend bool operator==(const Range &L, const Range &R) { - return std::tie(L.Begin, L.End) == std::tie(R.Begin, R.End); + return std::tie(L.Begin, L.End, L.Payload) == + std::tie(R.Begin, R.End, R.Payload); } friend bool operator!=(const Range &L, const Range &R) { return !(L == R); } }; @@ -66,16 +74,38 @@ /// All points and ranges are relative to this stripped text. llvm::StringRef code() const { return Code; } + struct Point { + size_t Position; + llvm::Optional Payload; + + friend bool operator==(const Point &L, const Point &R) { + return std::tie(L.Position, L.Payload) == std::tie(R.Position, R.Payload); + } + friend bool operator!=(const Point &L, const Point &R) { return !(L == R); } + }; + /// 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 the point marked by ^ (or $name(payload)^) in the + /// text containing its payload (or llvm::None if there is no payload). + /// Crashes if there isn't exactly one. + Point pointWithPayload(llvm::StringRef Name = "") const; /// Returns the position of all points marked by ^ (or $name^) in the text. /// Order matches the order within the text. std::vector points(llvm::StringRef Name = "") const; + /// Returns the position and payload of all points marked by ^ (or + /// $name(payload)^) in the text. Order matches the order within the text. + std::vector pointsWithPayload(llvm::StringRef Name = "") const; /// Returns the mapping of all names of points marked in the text to their /// position. Unnamed points are mapped to the empty string. The positions are /// sorted. - const llvm::StringMap> &all_points() const; + llvm::StringMap> all_points() const; + /// Returns the mapping of all names of points marked in the text to their + /// position and payload. Unnamed points are mapped to the empty string. The + /// positions are sorted. + const llvm::StringMap> & + all_points_and_payloads() const; /// Returns the location of the range marked by [[ ]] (or $name[[ ]]). /// Crashes if there isn't exactly one. @@ -90,12 +120,14 @@ private: std::string Code; - llvm::StringMap> Points; + llvm::StringMap> Points; llvm::StringMap> Ranges; }; llvm::raw_ostream &operator<<(llvm::raw_ostream &O, const llvm::Annotations::Range &R); +llvm::raw_ostream &operator<<(llvm::raw_ostream &O, + const llvm::Annotations::Point &R); } // namespace llvm diff --git a/llvm/lib/Testing/Support/Annotations.cpp b/llvm/lib/Testing/Support/Annotations.cpp --- a/llvm/lib/Testing/Support/Annotations.cpp +++ b/llvm/lib/Testing/Support/Annotations.cpp @@ -28,27 +28,36 @@ require(Assertion, Msg, Text); }; llvm::Optional Name; - llvm::SmallVector, 8> OpenRanges; + llvm::Optional Payload; + struct OpenRange { + llvm::StringRef Name; + size_t Begin; + llvm::Optional Payload; + }; + llvm::SmallVector OpenRanges; Code.reserve(Text.size()); while (!Text.empty()) { if (Text.consume_front("^")) { - Points[Name.value_or("")].push_back(Code.size()); + Points[Name.value_or("")].push_back({Code.size(), Payload}); Name = llvm::None; + Payload = llvm::None; continue; } if (Text.consume_front("[[")) { - OpenRanges.emplace_back(Name.value_or(""), Code.size()); + OpenRanges.push_back({Name.value_or(""), Code.size(), Payload}); Name = llvm::None; + Payload = 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.Begin = OpenRanges.back().Begin; R.End = Code.size(); - Ranges[OpenRanges.back().first].push_back(R); + R.Payload = OpenRanges.back().Payload; + Ranges[OpenRanges.back().Name].push_back(R); OpenRanges.pop_back(); continue; } @@ -56,6 +65,13 @@ Name = Text.take_while([](char C) { return llvm::isAlnum(C) || C == '_'; }); Text = Text.drop_front(Name->size()); + + if (Text.consume_front("(")) { + Payload = Text.take_while([](char C) { return C != ')'; }); + Require(Text.size() > Payload->size(), "unterminated payload"); + Text = Text.drop_front(Payload->size() + 1); + } + continue; } Code.push_back(Text.front()); @@ -66,6 +82,10 @@ } size_t Annotations::point(llvm::StringRef Name) const { + return pointWithPayload(Name).Position; +} + +Annotations::Point Annotations::pointWithPayload(llvm::StringRef Name) const { auto I = Points.find(Name); require(I != Points.end() && I->getValue().size() == 1, "expected exactly one point", Code); @@ -73,14 +93,34 @@ } std::vector Annotations::points(llvm::StringRef Name) const { + auto Pts = pointsWithPayload(Name); + + std::vector Positions; + Positions.reserve(Pts.size()); + llvm::transform(Pts, std::back_inserter(Positions), + [](const Point &P) { return P.Position; }); + return Positions; +} + +std::vector +Annotations::pointsWithPayload(llvm::StringRef Name) const { auto I = Points.find(Name); if (I == Points.end()) return {}; return {I->getValue().begin(), I->getValue().end()}; } -const llvm::StringMap> & -Annotations::all_points() const { +llvm::StringMap> Annotations::all_points() const { + llvm::StringMap> Result; + for (const auto &Name : Points.keys()) { + auto Pts = points(Name); + Result[Name] = {Pts.begin(), Pts.end()}; + } + return Result; +} + +const llvm::StringMap> & +Annotations::all_points_and_payloads() const { return Points; } @@ -106,5 +146,10 @@ llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O, const llvm::Annotations::Range &R) { - return O << llvm::formatv("[{0}, {1})", R.Begin, R.End); + return O << llvm::formatv("[{0}, {1}) ({2})", R.Begin, R.End, R.Payload); +} + +llvm::raw_ostream &llvm::operator<<(llvm::raw_ostream &O, + const llvm::Annotations::Point &P) { + return O << llvm::formatv("{0} ({1})", P.Position, P.Payload); } diff --git a/llvm/unittests/Support/AnnotationsTest.cpp b/llvm/unittests/Support/AnnotationsTest.cpp --- a/llvm/unittests/Support/AnnotationsTest.cpp +++ b/llvm/unittests/Support/AnnotationsTest.cpp @@ -25,10 +25,21 @@ arg, result_listener); } -llvm::Annotations::Range range(size_t Begin, size_t End) { +llvm::Annotations::Point +point(size_t Position, llvm::Optional Payload = llvm::None) { + llvm::Annotations::Point P; + P.Position = Position; + P.Payload = Payload; + return P; +} + +llvm::Annotations::Range +range(size_t Begin, size_t End, + llvm::Optional Payload = llvm::None) { llvm::Annotations::Range R; R.Begin = Begin; R.End = End; + R.Payload = Payload; return R; } @@ -105,6 +116,32 @@ ElementsAre(range(8, 9), range(7, 10), range(1, 10))); } +TEST(AnnotationsTest, Payload) { + // A single unnamed point or range with empty payload + EXPECT_EQ(llvm::Annotations("a$^b").pointWithPayload(), + point(1u, llvm::None)); + EXPECT_EQ(llvm::Annotations("a$[[b]]cdef").range(), range(1, 2, llvm::None)); + + // A single unnamed point or range with payload. + EXPECT_EQ(llvm::Annotations("a$(foo)^b").pointWithPayload(), + point(1u, {"foo"})); + EXPECT_EQ(llvm::Annotations("a$(foo)[[b]]cdef").range(), + range(1, 2, {"foo"})); + + // A single named point or range with payload + EXPECT_EQ(llvm::Annotations("a$name(foo)^b").pointWithPayload("name"), + point(1u, {"foo"})); + EXPECT_EQ(llvm::Annotations("a$name(foo)[[b]]cdef").range("name"), + range(1, 2, {"foo"})); + + // Multiple named points with payload. + llvm::Annotations Annotated("a$p1(p1)^bcd$p2(p2)^123$p1^345"); + EXPECT_THAT(Annotated.points(), IsEmpty()); + EXPECT_THAT(Annotated.pointsWithPayload("p1"), + ElementsAre(point(1u, {"p1"}), point(7u, llvm::None))); + EXPECT_EQ(Annotated.pointWithPayload("p2"), point(4u, {"p2"})); +} + TEST(AnnotationsTest, Named) { // A single named point or range. EXPECT_EQ(llvm::Annotations("a$foo^b").point("foo"), 1u); @@ -144,6 +181,8 @@ EXPECT_DEATH(llvm::Annotations("ff[[fdfd"), "unmatched \\[\\["); EXPECT_DEATH(llvm::Annotations("ff[[fdjsfjd]]xxx]]"), "unmatched \\]\\]"); EXPECT_DEATH(llvm::Annotations("ff$fdsfd"), "unterminated \\$name"); + EXPECT_DEATH(llvm::Annotations("ff$("), "unterminated payload"); + EXPECT_DEATH(llvm::Annotations("ff$name("), "unterminated payload"); #endif } } // namespace