[tintd] Implement TextDocumentReferencesRequest

Bug: tint:2127
Change-Id: Ic9a979ee7b55afc90cace949b0f3ab2f19a9dafb
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/179403
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
diff --git a/src/tint/lang/wgsl/ls/BUILD.bazel b/src/tint/lang/wgsl/ls/BUILD.bazel
index db8b7b4..3449dc0 100644
--- a/src/tint/lang/wgsl/ls/BUILD.bazel
+++ b/src/tint/lang/wgsl/ls/BUILD.bazel
@@ -43,6 +43,7 @@
     "diagnostics.cc",
     "document.cc",
     "file.cc",
+    "references.cc",
     "serve.cc",
     "server.cc",
     "symbols.cc",
@@ -101,6 +102,7 @@
     "diagnostics_test.cc",
     "helpers_test.cc",
     "helpers_test.h",
+    "references_test.cc",
     "symbols_test.cc",
   ],
   deps = [
diff --git a/src/tint/lang/wgsl/ls/BUILD.cmake b/src/tint/lang/wgsl/ls/BUILD.cmake
index f39c345..8cf5edc 100644
--- a/src/tint/lang/wgsl/ls/BUILD.cmake
+++ b/src/tint/lang/wgsl/ls/BUILD.cmake
@@ -46,6 +46,7 @@
   lang/wgsl/ls/document.cc
   lang/wgsl/ls/file.cc
   lang/wgsl/ls/file.h
+  lang/wgsl/ls/references.cc
   lang/wgsl/ls/serve.cc
   lang/wgsl/ls/serve.h
   lang/wgsl/ls/server.cc
@@ -109,6 +110,7 @@
   lang/wgsl/ls/diagnostics_test.cc
   lang/wgsl/ls/helpers_test.cc
   lang/wgsl/ls/helpers_test.h
+  lang/wgsl/ls/references_test.cc
   lang/wgsl/ls/symbols_test.cc
 )
 
diff --git a/src/tint/lang/wgsl/ls/BUILD.gn b/src/tint/lang/wgsl/ls/BUILD.gn
index ce8653f..e018f9c 100644
--- a/src/tint/lang/wgsl/ls/BUILD.gn
+++ b/src/tint/lang/wgsl/ls/BUILD.gn
@@ -49,6 +49,7 @@
       "document.cc",
       "file.cc",
       "file.h",
+      "references.cc",
       "serve.cc",
       "serve.h",
       "server.cc",
@@ -101,6 +102,7 @@
         "diagnostics_test.cc",
         "helpers_test.cc",
         "helpers_test.h",
+        "references_test.cc",
         "symbols_test.cc",
       ]
       deps = [
diff --git a/src/tint/lang/wgsl/ls/file.cc b/src/tint/lang/wgsl/ls/file.cc
index 39393fc..f586d16 100644
--- a/src/tint/lang/wgsl/ls/file.cc
+++ b/src/tint/lang/wgsl/ls/file.cc
@@ -101,8 +101,10 @@
         }
         for (auto* node : nodes) {
             if (auto* ident = node->As<ast::IdentifierExpression>()) {
-                if (program.Sem().Get<sem::Struct>(node) == s) {
-                    references.push_back(ident->source.range);
+                if (auto* te = program.Sem().Get<sem::TypeExpression>(node)) {
+                    if (te->Type() == s) {
+                        references.push_back(ident->source.range);
+                    }
                 }
             }
         }
diff --git a/src/tint/lang/wgsl/ls/references.cc b/src/tint/lang/wgsl/ls/references.cc
new file mode 100644
index 0000000..9eda4d6
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/references.cc
@@ -0,0 +1,57 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/wgsl/ls/server.h"
+
+#include "src/tint/lang/wgsl/ls/utils.h"
+#include "src/tint/utils/rtti/switch.h"
+
+namespace lsp = langsvr::lsp;
+
+namespace tint::wgsl::ls {
+
+typename lsp::TextDocumentReferencesRequest::ResultType  //
+Server::Handle(const lsp::TextDocumentReferencesRequest& r) {
+    typename lsp::TextDocumentReferencesRequest::SuccessType result = lsp::Null{};
+
+    if (auto file = files_.Get(r.text_document.uri)) {
+        std::vector<lsp::Location> out;
+        for (auto& ref : (*file)->References(Conv(r.position), r.context.include_declaration)) {
+            lsp::Location loc;
+            loc.range = Conv(ref);
+            loc.uri = r.text_document.uri;
+            out.push_back(std::move(loc));
+        }
+        if (!out.empty()) {
+            result = out;
+        }
+    }
+
+    return result;
+}
+
+}  // namespace tint::wgsl::ls
diff --git a/src/tint/lang/wgsl/ls/references_test.cc b/src/tint/lang/wgsl/ls/references_test.cc
new file mode 100644
index 0000000..1053eb2
--- /dev/null
+++ b/src/tint/lang/wgsl/ls/references_test.cc
@@ -0,0 +1,294 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <gtest/gtest.h>
+#include <sstream>
+#include <string_view>
+
+#include "gmock/gmock.h"
+
+#include "langsvr/lsp/lsp.h"
+#include "langsvr/lsp/primitives.h"
+#include "langsvr/lsp/printer.h"
+#include "src/tint/lang/wgsl/ls/helpers_test.h"
+#include "src/tint/utils/text/unicode.h"
+
+namespace tint::wgsl::ls {
+namespace {
+
+namespace lsp = langsvr::lsp;
+
+struct Case {
+    bool include_declaration;
+    std::string_view markup;
+};
+
+std::ostream& operator<<(std::ostream& stream, const Case& c) {
+    return stream << "wgsl: '" << c.markup << "'";
+}
+
+using LsReferencesTest = LsTestWithParam<Case>;
+TEST_P(LsReferencesTest, Symbols) {
+    auto parsed = ParseMarkers(GetParam().markup);
+    ASSERT_EQ(parsed.positions.size(), 1u);
+
+    lsp::TextDocumentReferencesRequest req{};
+    req.text_document.uri = OpenDocument(parsed.clean);
+    req.context.include_declaration = GetParam().include_declaration;
+    req.position = parsed.positions[0];
+
+    for (auto& n : diagnostics_) {
+        for (auto& d : n.diagnostics) {
+            if (d.severity == lsp::DiagnosticSeverity::kError) {
+                FAIL() << "Error: " << d.message << "\nWGSL:\n" << parsed.clean;
+            }
+        }
+    }
+
+    auto future = client_session_.Send(req);
+    ASSERT_EQ(future, langsvr::Success);
+    auto res = future->get();
+    if (parsed.ranges.empty()) {
+        ASSERT_TRUE(res.Is<lsp::Null>());
+    } else {
+        ASSERT_TRUE(res.Is<std::vector<lsp::Location>>());
+        std::vector<lsp::Range> got_ranges;
+        for (auto& location : *res.Get<std::vector<lsp::Location>>()) {
+            EXPECT_EQ(location.uri, req.text_document.uri);
+            got_ranges.push_back(location.range);
+        }
+        EXPECT_THAT(got_ranges, testing::UnorderedElementsAreArray(parsed.ranges));
+    }
+}
+
+// TODO(bclayton): Type aliases.
+INSTANTIATE_TEST_SUITE_P(IncludeDeclaration,
+                         LsReferencesTest,
+                         ::testing::ValuesIn(std::vector<Case>{
+                             {/* include_declaration */ true, R"(
+const「CONST」= 42;
+fn f() { _ =「⧘CONST」; }
+const C =「CONST」;
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+var<private>「VAR」= 42;
+fn f() { _ =「V⧘AR」; }
+fn g() { _ = 「VAR」; }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+override「OVERRIDE」= 42;
+fn f() { _ =「OVERRID⧘E」+「OVERRIDE」; }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+struct「STRUCT」{ i : i32 }
+fn f(s :「⧘STRUCT」) { var v : 「STRUCT」; }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+struct S {「i」: i32 }
+fn f(s : S) { _ = s.「⧘i」; }
+fn g(s : S) { _ = s.「i」; }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f(「p」: i32) { _ =「⧘p」* 「p」; }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    const「i」= 42;
+    _ =「⧘i」*「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    let「i」= 42;
+    _ =「⧘i」+「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    var「i」= 42;
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    var i = 42;
+    {
+        var「i」= 42;
+        「i」=「⧘i」;
+    }
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    var「i」= 42;
+    {
+        var i = 42;
+    }
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+const i = 42;
+fn f() {
+    var「i」= 42;
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+const i = 42;
+fn f(「i」: i32) {
+    _ =「⧘i」*「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn「a」() {}
+fn b() { 「⧘a」(); }
+fn c() { 「a」(); }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn b() { 「a⧘」(); }
+fn「a」() {}
+fn c() { 「a」(); }
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+fn f() {
+    let「i」= 42;
+    _ = (max(「i⧘」, 「i」) * 「i」);
+}
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+const C = m⧘ax(1, 2);
+)"},  // =========================================
+                             {/* include_declaration */ true, R"(
+const C : i⧘32 = 42;
+)"},
+                         }));
+
+INSTANTIATE_TEST_SUITE_P(ExcludeDeclaration,
+                         LsReferencesTest,
+                         ::testing::ValuesIn(std::vector<Case>{
+                             {/* include_declaration */ false, R"(
+const CONST = 42;
+fn f() { _ =「⧘CONST」; }
+const C =「CONST」;
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+var<private> VAR = 42;
+fn f() { _ =「V⧘AR」; }
+fn g() { _ = 「VAR」; }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+override OVERRIDE = 42;
+fn f() { _ =「OVERRID⧘E」+「OVERRIDE」; }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+struct STRUCT { i : i32 }
+fn f(s :「⧘STRUCT」) { var v : 「STRUCT」; }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+struct S { i : i32 }
+fn f(s : S) { _ = s.「⧘i」; }
+fn g(s : S) { _ = s.「i」; }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f( p : i32) { _ =「⧘p」* 「p」; }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    const i = 42;
+    _ =「⧘i」*「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    let i = 42;
+    _ =「⧘i」+「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    var i = 42;
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    var i = 42;
+    {
+        var i = 42;
+        「i」=「⧘i」;
+    }
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    var i = 42;
+    {
+        var i = 42;
+    }
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+const i = 42;
+fn f() {
+    var i = 42;
+    「i」=「⧘i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+const i = 42;
+fn f( i : i32) {
+    _ =「⧘i」*「i」;
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn a() {}
+fn b() { 「⧘a」(); }
+fn c() { 「a」(); }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn b() { 「a⧘」(); }
+fn a() {}
+fn c() { 「a」(); }
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+fn f() {
+    let i = 42;
+    _ = (max(「i⧘」, 「i」) * 「i」);
+}
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+const C = m⧘ax(1, 2);
+)"},  // =========================================
+                             {/* include_declaration */ false, R"(
+const C : i⧘32 = 42;
+)"},
+                         }));
+
+}  // namespace
+}  // namespace tint::wgsl::ls
diff --git a/src/tint/lang/wgsl/ls/server.cc b/src/tint/lang/wgsl/ls/server.cc
index 0e2d9c0..faf5870 100644
--- a/src/tint/lang/wgsl/ls/server.cc
+++ b/src/tint/lang/wgsl/ls/server.cc
@@ -43,6 +43,10 @@
             lsp::DocumentSymbolOptions opts;
             return opts;
         }();
+        result.capabilities.references_provider = [] {
+            lsp::ReferenceOptions opts;
+            return opts;
+        }();
         return result;
     });
 
@@ -61,6 +65,7 @@
     // Request handlers
     session.Register([&](const lsp::TextDocumentDefinitionRequest& r) { return Handle(r); });
     session.Register([&](const lsp::TextDocumentDocumentSymbolRequest& r) { return Handle(r); });
+    session.Register([&](const lsp::TextDocumentReferencesRequest& r) { return Handle(r); });
 }
 
 Server::~Server() = default;
diff --git a/src/tint/lang/wgsl/ls/server.h b/src/tint/lang/wgsl/ls/server.h
index 03dce58..3335c7c 100644
--- a/src/tint/lang/wgsl/ls/server.h
+++ b/src/tint/lang/wgsl/ls/server.h
@@ -63,6 +63,10 @@
     typename langsvr::lsp::TextDocumentDocumentSymbolRequest::ResultType  //
     Handle(const langsvr::lsp::TextDocumentDocumentSymbolRequest& r);
 
+    /// Handler for langsvr::lsp::TextDocumentReferencesRequest
+    typename langsvr::lsp::TextDocumentReferencesRequest::ResultType  //
+    Handle(const langsvr::lsp::TextDocumentReferencesRequest&);
+
     /// Handler for langsvr::lsp::TextDocumentDidOpenNotification
     langsvr::Result<langsvr::SuccessType>  //
     Handle(const langsvr::lsp::TextDocumentDidOpenNotification&);
diff --git a/src/tint/lang/wgsl/ls/symbols_test.cc b/src/tint/lang/wgsl/ls/symbols_test.cc
index 8899adf..33820c9 100644
--- a/src/tint/lang/wgsl/ls/symbols_test.cc
+++ b/src/tint/lang/wgsl/ls/symbols_test.cc
@@ -44,6 +44,10 @@
     const std::vector<lsp::DocumentSymbol> symbols;
 };
 
+std::ostream& operator<<(std::ostream& stream, const Case& c) {
+    return stream << "wgsl: '" << c.wgsl << "'";
+}
+
 struct Symbol : lsp::DocumentSymbol {
     explicit Symbol(std::string_view n) { name = n; }
 
@@ -69,10 +73,6 @@
     }
 };
 
-std::ostream& operator<<(std::ostream& stream, const Case& c) {
-    return stream << "wgsl: '" << c.wgsl << "'";
-}
-
 using LsSymbolsTest = LsTestWithParam<Case>;
 TEST_P(LsSymbolsTest, Symbols) {
     lsp::TextDocumentDocumentSymbolRequest req{};