From 52e45e1f268ef11e9ca6f0cdb7eae44ea0432c33 Mon Sep 17 00:00:00 2001 From: ykiko Date: Sat, 23 Aug 2025 20:52:49 +0800 Subject: [PATCH] Enable `SignatureHelp` (#187) --- include/Feature/SignatureHelp.h | 9 +- include/Protocol/Basic.h | 1 + include/Protocol/Feature/SignatureHelp.h | 43 +++++- include/Protocol/Lifecycle.h | 2 +- include/Server/Server.h | 2 + include/Test/Tester.h | 6 +- src/AST/Utility.cpp | 40 +++-- src/Feature/SignatureHelp.cpp | 186 +++++++++++++++++++---- src/Server/Feature.cpp | 27 ++++ src/Server/Lifecycle.cpp | 3 + src/Server/Server.cpp | 1 + tests/unit/Feature/SignatureHelp.cpp | 33 ++-- 12 files changed, 281 insertions(+), 72 deletions(-) diff --git a/include/Feature/SignatureHelp.h b/include/Feature/SignatureHelp.h index 5019c8a0..b7066d1f 100644 --- a/include/Feature/SignatureHelp.h +++ b/include/Feature/SignatureHelp.h @@ -4,6 +4,7 @@ #include #include "llvm/ADT/StringRef.h" +#include "Protocol/Feature/SignatureHelp.h" namespace clice { @@ -17,12 +18,8 @@ struct SignatureHelpOption {}; namespace feature { -struct SignatureHelpItem {}; - -using SignatureHelpResult = std::vector; - -SignatureHelpResult signatureHelp(CompilationParams& params, - const config::SignatureHelpOption& option); +proto::SignatureHelp signature_help(CompilationParams& params, + const config::SignatureHelpOption& option); } // namespace feature diff --git a/include/Protocol/Basic.h b/include/Protocol/Basic.h index 06072f70..6850f238 100644 --- a/include/Protocol/Basic.h +++ b/include/Protocol/Basic.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include diff --git a/include/Protocol/Feature/SignatureHelp.h b/include/Protocol/Feature/SignatureHelp.h index 846a1a80..633bf4c2 100644 --- a/include/Protocol/Feature/SignatureHelp.h +++ b/include/Protocol/Feature/SignatureHelp.h @@ -4,8 +4,47 @@ namespace clice::proto { -struct SignatureHelpClientCapabilities {}; +struct SignatureHelpClientCapabilities { + /** + * The client supports the `activeParameter` property on + * `SignatureInformation` literal. + * + * @since 3.16.0 + */ +}; -struct SignatureHelpOptions {}; +struct SignatureHelpOptions { + /// The characters that trigger signature help automatically. + array triggerCharacters; + + /// List of characters that re-trigger signature help. + /// + /// These trigger characters are only active when signature help is already + /// showing. All trigger characters are also counted as re-trigger + /// characters. + array retriggerCharacters; +}; + +using SignatureHelpParams = TextDocumentPositionParams; + +struct ParameterInformation { + std::array label; +}; + +struct SignatureInformation { + string label; + + MarkupContent document; + + array parameters; + + uinteger activeParameter; +}; + +struct SignatureHelp { + array signatures; + + uinteger activeSignature; +}; } // namespace clice::proto diff --git a/include/Protocol/Lifecycle.h b/include/Protocol/Lifecycle.h index 905a1273..73b89572 100644 --- a/include/Protocol/Lifecycle.h +++ b/include/Protocol/Lifecycle.h @@ -85,7 +85,7 @@ struct ServerCapabilities { HoverOptions hoverProvider; /// The server provides signature help support. - /// FIXME: SignatureHelpOptions signatureHelpProvider; + SignatureHelpOptions signatureHelpProvider; /// The server provides go to declaration support. /// FIXME: DeclarationOptions declarationProvider; diff --git a/include/Server/Server.h b/include/Server/Server.h index 413e7726..abcaf3cc 100644 --- a/include/Server/Server.h +++ b/include/Server/Server.h @@ -200,6 +200,8 @@ private: async::Task on_hover(proto::HoverParams params); + async::Task on_signature_help(proto::SignatureHelpParams params); + async::Task on_document_symbol(proto::DocumentSymbolParams params); async::Task on_document_link(proto::DocumentLinkParams params); diff --git a/include/Test/Tester.h b/include/Test/Tester.h index a23897d1..3d3b0b97 100644 --- a/include/Test/Tester.h +++ b/include/Test/Tester.h @@ -31,7 +31,7 @@ struct Tester { sources.add_sources(content); } - bool compile(llvm::StringRef standard = "-std=c++20") { + void prepare(llvm::StringRef standard = "-std=c++20") { auto command = std::format("clang++ {} {} -fms-extensions", standard, src_path); database.update_command("fake", src_path, command); @@ -46,6 +46,10 @@ struct Tester { params.add_remapped_file(path, source.content); } } + } + + bool compile(llvm::StringRef standard = "-std=c++20") { + prepare(standard); auto info = clice::compile(params); if(!info) { diff --git a/src/AST/Utility.cpp b/src/AST/Utility.cpp index ed1d3ca3..5ffaa1fb 100644 --- a/src/AST/Utility.cpp +++ b/src/AST/Utility.cpp @@ -281,6 +281,25 @@ const clang::NamedDecl* decl_of_impl(const void* T) { } auto decl_of(clang::QualType type) -> const clang::NamedDecl* { + if(auto TST = type->getAs()) { + auto decl = TST->getTemplateName().getAsTemplateDecl(); + if(type->isDependentType()) { + return decl; + } + + /// For a template specialization type, the template name is possibly a `ClassTemplateDecl` + /// `TypeAliasTemplateDecl` or `TemplateTemplateParmDecl` and `BuiltinTemplateDecl`. + if(llvm::isa(decl)) { + return decl->getTemplatedDecl(); + } + + if(llvm::isa(decl)) { + return decl; + } + + return instantiated_from(TST->getAsCXXRecordDecl()); + } + switch(type->getTypeClass()) { #define ABSTRACT_TYPE(TY, BASE) #define TYPE(TY, BASE) \ @@ -288,27 +307,6 @@ auto decl_of(clang::QualType type) -> const clang::NamedDecl* { #include "clang/AST/TypeNodes.inc" } - /// FIXME: Handle Template Specialization type in the future - /// if(auto TST = type->getAs()) { - /// auto decl = TST->getTemplateName().getAsTemplateDecl(); - /// if(type->isDependentType()) { - /// return decl; - /// } - /// - /// /// For a template specialization type, the template name is possibly a - /// `ClassTemplateDecl` - /// /// `TypeAliasTemplateDecl` or `TemplateTemplateParmDecl` and `BuiltinTemplateDecl`. - /// if(llvm::isa(decl)) { - /// return decl->getTemplatedDecl(); - /// } - /// - /// if(llvm::isa(decl)) { - /// return decl; - /// } - /// - /// return instantiated_from(TST->getAsCXXRecordDecl()); - ///} - return nullptr; } diff --git a/src/Feature/SignatureHelp.cpp b/src/Feature/SignatureHelp.cpp index 63cb792e..b2757147 100644 --- a/src/Feature/SignatureHelp.cpp +++ b/src/Feature/SignatureHelp.cpp @@ -1,57 +1,179 @@ #include "Compiler/Compilation.h" #include "Feature/SignatureHelp.h" +#include "clang/Sema/Sema.h" #include "clang/Sema/CodeCompleteConsumer.h" namespace clice::feature { namespace { -class SignatureHelpCollector final : public clang::CodeCompleteConsumer { +class Collector final : public clang::CodeCompleteConsumer { public: - SignatureHelpCollector(clang::CodeCompleteOptions options) : - clang::CodeCompleteConsumer(options), allocator(new clang::GlobalCodeCompletionAllocator()), - info(allocator) {} + Collector(proto::SignatureHelp& help, clang::CodeCompleteOptions complete_options) : + clang::CodeCompleteConsumer(complete_options), help(help), + info(std::make_shared()) {} void ProcessOverloadCandidates(clang::Sema& sema, - unsigned CurrentArg, + std::uint32_t current_arg, OverloadCandidate* candidates, - unsigned count, - clang::SourceLocation openParLoc, + std::uint32_t candidate_count, + clang::SourceLocation open_paren_loc, bool braced) final { - llvm::outs() << "ProcessOverloadCandidates\n"; - auto range = llvm::make_range(candidates, candidates + count); + help.signatures.reserve(candidate_count); + + // FIXME: How can we determine the "active overload candidate"? + // Right now the overloaded candidates seem to be provided in a "best fit" + // order, so I'm not too worried about this. + help.activeSignature = 0; + + auto range = llvm::make_range(candidates, candidates + candidate_count); + + auto policy = sema.getPrintingPolicy(); + policy.AnonymousTagLocations = false; + policy.SuppressStrongLifetime = true; + policy.SuppressUnwrittenScope = true; + policy.SuppressScope = true; + policy.CleanUglifiedParameters = true; + // Show signatures of constructors as they are declared: + // vector(int n) rather than vector(int n) + // This is less noisy without being less clear, and avoids tricky cases. + policy.SuppressTemplateArgsInCXXConstructors = true; + for(auto& candidate: range) { + /// We want to avoid showing instantiated signatures, because they may be + /// long in some cases (e.g. when 'T' is substituted with 'std::string', we + /// would get 'std::basic_string'). + /// FIXME: In fact, in such case, we may resugar the template arguments. + if(auto func = candidate.getFunction()) { + if(auto pattern = func->getTemplateInstantiationPattern()) { + candidate = OverloadCandidate(pattern); + } + } + + llvm::SmallString<128> buffer; + llvm::raw_svector_ostream os(buffer); + + auto& signature = help.signatures.emplace_back(); + + /// FIXME: Handle explicit this and variadic params... + signature.activeParameter = current_arg; + + auto add_param = [&](auto&& param) { + if(signature.parameters.size() > 0) { + os << ", "; + } + + /// FIXME: Handle param comments in the future. + auto& label = signature.parameters.emplace_back().label; + label[0] = buffer.size(); + param.print(os, policy); + label[1] = buffer.size(); + }; + switch(candidate.getKind()) { - case clang::CodeCompleteConsumer::OverloadCandidate::CK_Function: { - candidate.getFunction()->dump(); - break; - } + case clang::CodeCompleteConsumer::OverloadCandidate::CK_Function: case clang::CodeCompleteConsumer::OverloadCandidate::CK_FunctionTemplate: { - candidate.getFunctionTemplate()->dump(); + auto func = candidate.getFunction(); + func->getDeclName().print(os, policy); + os << "("; + /// FIXME: Handle C++23 explicit object params. + for(auto param: func->parameters()) { + add_param(*param); + } + os << ")"; + + if(!llvm::isa(func)) { + os << " -> "; + func->getReturnType().print(os, policy); + } + break; } + case clang::CodeCompleteConsumer::OverloadCandidate::CK_FunctionType: { - candidate.getFunctionType()->dump(); + auto type = candidate.getFunctionType(); + os << "("; + if(auto proto = llvm::dyn_cast(type)) { + for(auto type: proto->param_types()) { + add_param(type); + } + } + os << ") -> "; + type->getReturnType().print(os, policy); break; } + case clang::CodeCompleteConsumer::OverloadCandidate::CK_FunctionProtoTypeLoc: { - candidate.getFunctionProtoTypeLoc().dump(); + auto loc = candidate.getFunctionProtoTypeLoc(); + os << "("; + for(auto type: loc.getParams()) { + add_param(*type); + } + os << ") -> "; + loc.getTypePtr()->getReturnType().print(os, policy); break; } + case clang::CodeCompleteConsumer::OverloadCandidate::CK_Template: { - candidate.getTemplate()->dump(); + auto decl = candidate.getTemplate(); + /// Add template name first. + decl->getDeclName().print(os, policy); + os << "<"; + for(auto param: *decl->getTemplateParameters()) { + add_param(*param); + } + os << "> "; + + if(auto cls = llvm::dyn_cast(decl)) { + os << "-> "; + os << cls->getTemplatedDecl()->getKindName(); + } else if(auto func = llvm::dyn_cast(decl)) { + os << "() -> "; + func->getTemplatedDecl()->getReturnType().print(os, policy); + } else if(auto type = llvm::dyn_cast(decl)) { + os << "-> "; + type->getTemplatedDecl()->getUnderlyingType().print(os, policy); + } else if(auto var = llvm::dyn_cast(decl)) { + os << "-> "; + var->getTemplatedDecl()->getType().print(os, policy); + } else if(auto tmp = llvm::dyn_cast(decl)) { + os << "-> type"; + } else if(auto con = llvm::dyn_cast(decl)) { + os << "-> concept"; + } else { + std::unreachable(); + } + break; } + case clang::CodeCompleteConsumer::OverloadCandidate::CK_Aggregate: { - candidate.getAggregate()->dump(); + auto cls = candidate.getAggregate(); + cls->getDeclName().print(os, policy); + os << "{"; + + if(auto type = llvm::dyn_cast(cls)) { + for(auto& base: type->bases()) { + add_param(base.getType()); + } + } + + for(auto field: cls->fields()) { + add_param(*field); + } + os << "}"; break; } } + + signature.label = buffer.str(); } + + /// FIXME: Sort the result according the params num and kind ... } clang::CodeCompletionAllocator& getAllocator() final { - return *allocator; + return info.getAllocator(); } clang::CodeCompletionTUInfo& getCodeCompletionTUInfo() final { @@ -59,18 +181,30 @@ public: } private: - std::shared_ptr allocator; + proto::SignatureHelp& help; clang::CodeCompletionTUInfo info; }; } // namespace -std::vector signatureHelp(CompilationParams& params, - const config::SignatureHelpOption& option) { - std::vector items; - auto consumer = new SignatureHelpCollector({}); - if(auto info = complete(params, consumer)) {} - return items; +proto::SignatureHelp signature_help(CompilationParams& params, + const config::SignatureHelpOption& options) { + proto::SignatureHelp help; + + clang::CodeCompleteOptions complete_options; + complete_options.IncludeMacros = false; + complete_options.IncludeCodePatterns = false; + complete_options.IncludeGlobals = false; + complete_options.IncludeNamespaceLevelDecls = false; + complete_options.IncludeBriefComments = false; + complete_options.LoadExternal = true; + complete_options.IncludeFixIts = false; + + auto consumer = new Collector(help, complete_options); + if(auto info = complete(params, consumer)) { + /// FIXME: do something. + } + return help; } } // namespace clice::feature diff --git a/src/Server/Feature.cpp b/src/Server/Feature.cpp index 38fecb99..8db66b83 100644 --- a/src/Server/Feature.cpp +++ b/src/Server/Feature.cpp @@ -3,6 +3,7 @@ #include "Compiler/Compilation.h" #include "Feature/CodeCompletion.h" #include "Feature/Hover.h" +#include "Feature/SignatureHelp.h" #include "Feature/DocumentLink.h" #include "Feature/DocumentSymbol.h" #include "Feature/FoldingRange.h" @@ -59,6 +60,32 @@ async::Task Server::on_hover(proto::HoverParams params) { }); } +async::Task Server::on_signature_help(proto::SignatureHelpParams params) { + auto path = mapping.to_path(params.textDocument.uri); + auto opening_file = opening_files.get_or_add(path); + + if(!opening_file->pch_build_task.empty()) { + co_await opening_file->pch_built_event; + } + + auto& content = opening_file->content; + auto offset = to_offset(kind, content, params.position); + auto& pch = opening_file->pch; + { + /// Set compilation params ... . + CompilationParams params; + params.arguments = database.get_command(path, true).arguments; + params.add_remapped_file(path, content); + params.pch = {pch->path, pch->preamble.size()}; + params.completion = {path, offset}; + + co_return co_await async::submit([kind = this->kind, &content, ¶ms] { + auto help = feature::signature_help(params, {}); + return json::serialize(help); + }); + } +} + async::Task Server::on_document_symbol(proto::DocumentSymbolParams params) { auto path = mapping.to_path(params.textDocument.uri); auto opening_file = opening_files.get_or_add(path); diff --git a/src/Server/Lifecycle.cpp b/src/Server/Lifecycle.cpp index ba8058c3..72dd1623 100644 --- a/src/Server/Lifecycle.cpp +++ b/src/Server/Lifecycle.cpp @@ -53,6 +53,9 @@ async::Task Server::on_initialize(proto::InitializeParams params) { /// Hover capabilities.hoverProvider = true; + /// SignatureHelp + capabilities.signatureHelpProvider.triggerCharacters = {"(", ")", "{", "}", "<", ">", ","}; + /// DocumentSymbol capabilities.documentSymbolProvider = {}; diff --git a/src/Server/Server.cpp b/src/Server/Server.cpp index 20e18b92..07a20d46 100644 --- a/src/Server/Server.cpp +++ b/src/Server/Server.cpp @@ -108,6 +108,7 @@ Server::Server() { register_callback<&Server::on_completion>("textDocument/completion"); register_callback<&Server::on_hover>("textDocument/hover"); + register_callback<&Server::on_signature_help>("textDocument/signatureHelp"); register_callback<&Server::on_document_symbol>("textDocument/documentSymbol"); register_callback<&Server::on_document_link>("textDocument/documentLink"); register_callback<&Server::on_folding_range>("textDocument/foldingRange"); diff --git a/tests/unit/Feature/SignatureHelp.cpp b/tests/unit/Feature/SignatureHelp.cpp index 31edb4ca..d16d19dd 100644 --- a/tests/unit/Feature/SignatureHelp.cpp +++ b/tests/unit/Feature/SignatureHelp.cpp @@ -7,9 +7,20 @@ namespace { suite<"SignatureHelp"> signature_help = [] { Tester tester; + proto::SignatureHelp help; - test("SignatureHelp") = [&] { - const char* code = R"cpp( + auto run = [&](llvm::StringRef code) { + tester.clear(); + tester.add_main("main.cpp", code); + tester.prepare(); + + tester.params.completion = {"main.cpp", tester.nameless_points()[0]}; + + help = feature::signature_help(tester.params, {}); + }; + + test("Simple") = [&] { + run(R"cpp( void foo(); void foo(int x); @@ -17,22 +28,14 @@ void foo(int x); void foo(int x, int y); int main() { - foo(1, 2); + foo($); } -)cpp"; +)cpp"); - CompilationParams params; - params.arguments = {"clang++", "-std=c++20", "main.cpp"}; - params.add_remapped_file("main.cpp", code); - /// params.completion = {"main.cpp", 9, 10}; - - /// config::SignatureHelpOption options = {}; - /// auto result = feature::signatureHelp(params, options); - /// EXPECT - /// foo(int x, int y) - /// foo(int x) - /// foo() + expect(eq(help.signatures.size(), 3)); }; + + /// FIXME: Add more tests. }; } // namespace