| // 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 <string_view> |
| #include "src/tint/lang/core/constant/scalar.h" |
| #include "src/tint/lang/core/constant/splat.h" |
| #include "src/tint/lang/core/constant/value.h" |
| #include "src/tint/lang/core/type/void.h" |
| #include "src/tint/lang/wgsl/ast/const.h" |
| #include "src/tint/lang/wgsl/ast/identifier.h" |
| #include "src/tint/lang/wgsl/ast/identifier_expression.h" |
| #include "src/tint/lang/wgsl/ast/let.h" |
| #include "src/tint/lang/wgsl/ast/member_accessor_expression.h" |
| #include "src/tint/lang/wgsl/ast/override.h" |
| #include "src/tint/lang/wgsl/ast/templated_identifier.h" |
| #include "src/tint/lang/wgsl/ast/var.h" |
| #include "src/tint/lang/wgsl/ls/server.h" |
| #include "src/tint/lang/wgsl/ls/utils.h" |
| #include "src/tint/lang/wgsl/sem/behavior.h" |
| #include "src/tint/lang/wgsl/sem/builtin_enum_expression.h" |
| #include "src/tint/lang/wgsl/sem/builtin_fn.h" |
| #include "src/tint/lang/wgsl/sem/call.h" |
| #include "src/tint/lang/wgsl/sem/call_target.h" |
| #include "src/tint/lang/wgsl/sem/function.h" |
| #include "src/tint/lang/wgsl/sem/function_expression.h" |
| #include "src/tint/lang/wgsl/sem/load.h" |
| #include "src/tint/lang/wgsl/sem/materialize.h" |
| #include "src/tint/lang/wgsl/sem/member_accessor_expression.h" |
| #include "src/tint/lang/wgsl/sem/struct.h" |
| #include "src/tint/lang/wgsl/sem/type_expression.h" |
| #include "src/tint/lang/wgsl/sem/variable.h" |
| #include "src/tint/utils/rtti/switch.h" |
| #include "src/tint/utils/text/string_stream.h" |
| |
| using namespace tint::core::fluent_types; // NOLINT |
| |
| namespace lsp = langsvr::lsp; |
| |
| namespace tint::wgsl::ls { |
| |
| namespace { |
| |
| /// @returns a lsp::MarkedStringWithLanguage with the content @p wgsl, using the language `wgsl` |
| lsp::MarkedStringWithLanguage WGSL(std::string wgsl) { |
| lsp::MarkedStringWithLanguage str; |
| str.language = "wgsl"; |
| str.value = wgsl; |
| return str; |
| } |
| |
| /// PrintConstant() writes the constant value @p val as a WGSL value to the StringStream @p ss |
| void PrintConstant(const core::constant::Value* val, StringStream& ss) { |
| Switch( |
| val, // |
| [&](const core::constant::Scalar<AInt>* s) { ss << s->value; }, |
| [&](const core::constant::Scalar<AFloat>* s) { ss << s->value; }, |
| [&](const core::constant::Scalar<bool>* s) { ss << s->value; }, |
| [&](const core::constant::Scalar<f16>* s) { ss << s->value << "h"; }, |
| [&](const core::constant::Scalar<f32>* s) { ss << s->value << "f"; }, |
| [&](const core::constant::Scalar<i32>* s) { ss << s->value << "i"; }, |
| [&](const core::constant::Scalar<u32>* s) { ss << s->value << "u"; }, |
| [&](const core::constant::Splat* s) { |
| ss << s->Type()->FriendlyName() << "("; |
| PrintConstant(s->el, ss); |
| ss << ")"; |
| }, |
| [&](const core::constant::Composite* s) { |
| ss << s->Type()->FriendlyName() << "("; |
| for (size_t i = 0, n = s->elements.Length(); i < n; i++) { |
| if (i > 0) { |
| ss << ", "; |
| } |
| PrintConstant(s->elements[i], ss); |
| } |
| ss << ")"; |
| }); |
| } |
| |
| /// Variable() writes the hover information for the variable @p v to @p out |
| void Variable(const sem::Variable* v, std::vector<lsp::MarkedString>& out) { |
| StringStream ss; |
| auto* kind = Switch( |
| v->Declaration(), // |
| [&](const ast::Var*) { return "var"; }, // |
| [&](const ast::Let*) { return "let"; }, // |
| [&](const ast::Const*) { return "const"; }, // |
| [&](const ast::Override*) { return "override"; }); |
| if (kind) { |
| ss << kind << " "; |
| } |
| ss << v->Declaration()->name->symbol.NameView() << " : " |
| << v->Type()->UnwrapRef()->FriendlyName(); |
| |
| if (auto* init = v->Initializer()) { |
| if (auto* val = init->ConstantValue()) { |
| ss << " = "; |
| PrintConstant(val, ss); |
| } |
| } |
| |
| out.push_back(WGSL(ss.str())); |
| } |
| |
| /// Function() writes the hover information for the function @p f to @p out |
| void Function(const sem::Function* f, std::vector<lsp::MarkedString>& out) { |
| StringStream ss; |
| ss << "fn " << f->Declaration()->name->symbol.NameView(); |
| ss << "("; |
| bool first = true; |
| for (auto* param : f->Parameters()) { |
| if (!first) { |
| ss << ", "; |
| } |
| first = false; |
| ss << param->Declaration()->name->symbol.NameView() << " : " |
| << param->Type()->FriendlyName(); |
| } |
| ss << ")"; |
| if (auto* ret = f->ReturnType(); !ret->Is<core::type::Void>()) { |
| ss << " -> " << ret->FriendlyName(); |
| } |
| out.push_back(WGSL(ss.str())); |
| } |
| |
| /// Call() writes the hover information for a call to the function with the name @p name with |
| /// semantic info @p c, to @p out |
| void Call(std::string_view name, const sem::Call* c, std::vector<lsp::MarkedString>& out) { |
| StringStream ss; |
| ss << name << "("; |
| bool first = true; |
| for (auto* param : c->Target()->Parameters()) { |
| if (!first) { |
| ss << ", "; |
| } |
| first = false; |
| if (auto* decl = param->Declaration()) { |
| ss << decl->name->symbol.NameView() << " : "; |
| } else if (auto usage = param->Usage(); usage != core::ParameterUsage::kNone) { |
| ss << param->Usage() << " : "; |
| } |
| ss << param->Type()->FriendlyName(); |
| } |
| ss << ")"; |
| if (auto* ret = c->Target()->ReturnType(); !ret->Is<core::type::Void>()) { |
| ss << " -> " << ret->FriendlyName(); |
| } |
| out.push_back(WGSL(ss.str())); |
| } |
| |
| /// Constant() writes the hover information for the constant value @p val to @p out |
| void Constant(const core::constant::Value* val, std::vector<lsp::MarkedString>& out) { |
| StringStream ss; |
| PrintConstant(val, ss); |
| out.push_back(WGSL(ss.str())); |
| } |
| |
| } // namespace |
| |
| typename lsp::TextDocumentHoverRequest::ResultType // |
| Server::Handle(const lsp::TextDocumentHoverRequest& r) { |
| auto file = files_.Get(r.text_document.uri); |
| if (!file) { |
| return lsp::Null{}; |
| } |
| |
| auto* node = |
| (*file)->NodeAt<CastableBase, File::UnwrapMode::kNoUnwrap>((*file)->Conv(r.position)); |
| if (!node) { |
| return lsp::Null{}; |
| } |
| |
| std::vector<lsp::MarkedString> strings; |
| |
| if (auto* materialize = node->As<sem::Materialize>()) { |
| if (auto* val = materialize->ConstantValue()) { |
| Constant(val, strings); |
| lsp::Hover hover; |
| hover.contents = std::move(strings); |
| hover.range = (*file)->Conv(materialize->Declaration()->source.range); |
| return hover; |
| } |
| } |
| |
| langsvr::Optional<lsp::Range> range; |
| Switch( |
| Unwrap(node), // |
| [&](const sem::VariableUser* user) { |
| Variable(user->Variable(), strings); |
| range = (*file)->Conv(user->Declaration()->source.range); |
| }, |
| [&](const sem::Variable* v) { |
| Variable(v, strings); |
| range = (*file)->Conv(v->Declaration()->name->source.range); |
| }, |
| [&](const sem::Call* c) { |
| Call(c->Declaration()->target->identifier->symbol.NameView(), c, strings); |
| range = (*file)->Conv(c->Declaration()->target->source.range); |
| }, |
| [&](const sem::FunctionExpression* expr) { |
| Function(expr->Function(), strings); |
| range = (*file)->Conv(expr->Declaration()->source.range); |
| }, |
| [&](const sem::BuiltinEnumExpression<wgsl::BuiltinFn>* fn) { |
| if (auto* call = (*file)->NodeAt<sem::Call>((*file)->Conv(r.position))) { |
| Call(str(fn->Value()), call, strings); |
| } else { |
| strings.push_back(WGSL(str(fn->Value()))); |
| } |
| range = (*file)->Conv(fn->Declaration()->source.range); |
| }, |
| [&](const sem::TypeExpression* expr) { |
| Switch( |
| expr->Type(), // |
| [&](const sem::Struct* str) { |
| strings.push_back(WGSL("struct " + str->Name().Name())); |
| }, |
| [&](Default) { strings.push_back(WGSL(expr->Type()->FriendlyName())); }); |
| range = (*file)->Conv(expr->Declaration()->source.range); |
| }, |
| [&](const sem::StructMemberAccess* access) { |
| if (auto* member = access->Member()->As<sem::StructMember>()) { |
| StringStream ss; |
| ss << member->Declaration()->name->symbol.NameView() << " : " |
| << member->Type()->FriendlyName(); |
| strings.push_back(WGSL(ss.str())); |
| range = (*file)->Conv(access->Declaration()->member->source.range); |
| } |
| }, |
| [&](const sem::ValueExpression* expr) { |
| if (auto* val = expr->ConstantValue()) { |
| Constant(val, strings); |
| range = (*file)->Conv(expr->Declaration()->source.range); |
| } |
| }); |
| |
| if (strings.empty()) { |
| return lsp::Null{}; |
| } |
| |
| lsp::Hover hover; |
| hover.contents = std::move(strings); |
| hover.range = std::move(range); |
| return hover; |
| } |
| |
| } // namespace tint::wgsl::ls |