Enable SignatureHelp (#187)

This commit is contained in:
ykiko
2025-08-23 20:52:49 +08:00
committed by GitHub
parent c43702048e
commit 52e45e1f26
12 changed files with 281 additions and 72 deletions

View File

@@ -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<clang::TemplateSpecializationType>()) {
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<clang::TypeAliasTemplateDecl>(decl)) {
return decl->getTemplatedDecl();
}
if(llvm::isa<clang::TemplateTemplateParmDecl, clang::BuiltinTemplateDecl>(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<clang::TemplateSpecializationType>()) {
/// 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<clang::TypeAliasTemplateDecl>(decl)) {
/// return decl->getTemplatedDecl();
/// }
///
/// if(llvm::isa<clang::TemplateTemplateParmDecl, clang::BuiltinTemplateDecl>(decl)) {
/// return decl;
/// }
///
/// return instantiated_from(TST->getAsCXXRecordDecl());
///}
return nullptr;
}

View File

@@ -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<clang::GlobalCodeCompletionAllocator>()) {}
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<string>(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<char>').
/// 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<clang::CXXConstructorDecl, clang::CXXDestructorDecl>(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<clang::FunctionProtoType>(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<clang::ClassTemplateDecl>(decl)) {
os << "-> ";
os << cls->getTemplatedDecl()->getKindName();
} else if(auto func = llvm::dyn_cast<clang::FunctionTemplateDecl>(decl)) {
os << "() -> ";
func->getTemplatedDecl()->getReturnType().print(os, policy);
} else if(auto type = llvm::dyn_cast<clang::TypeAliasTemplateDecl>(decl)) {
os << "-> ";
type->getTemplatedDecl()->getUnderlyingType().print(os, policy);
} else if(auto var = llvm::dyn_cast<clang::VarTemplateDecl>(decl)) {
os << "-> ";
var->getTemplatedDecl()->getType().print(os, policy);
} else if(auto tmp = llvm::dyn_cast<clang::TemplateTemplateParmDecl>(decl)) {
os << "-> type";
} else if(auto con = llvm::dyn_cast<clang::ConceptDecl>(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<clang::CXXRecordDecl>(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<clang::GlobalCodeCompletionAllocator> allocator;
proto::SignatureHelp& help;
clang::CodeCompletionTUInfo info;
};
} // namespace
std::vector<SignatureHelpItem> signatureHelp(CompilationParams& params,
const config::SignatureHelpOption& option) {
std::vector<SignatureHelpItem> 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

View File

@@ -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<json::Value> Server::on_hover(proto::HoverParams params) {
});
}
async::Task<json::Value> 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, &params] {
auto help = feature::signature_help(params, {});
return json::serialize(help);
});
}
}
async::Task<json::Value> Server::on_document_symbol(proto::DocumentSymbolParams params) {
auto path = mapping.to_path(params.textDocument.uri);
auto opening_file = opening_files.get_or_add(path);

View File

@@ -53,6 +53,9 @@ async::Task<json::Value> Server::on_initialize(proto::InitializeParams params) {
/// Hover
capabilities.hoverProvider = true;
/// SignatureHelp
capabilities.signatureHelpProvider.triggerCharacters = {"(", ")", "{", "}", "<", ">", ","};
/// DocumentSymbol
capabilities.documentSymbolProvider = {};

View File

@@ -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");