Skip to content

Commit 16b2caf

Browse files
authored
Clean up language server's output handling (#4855)
Handles verbose logging and printing errors as diagnostics to stderr when there's no way to communicate them back on a request.
1 parent 7befe2c commit 16b2caf

13 files changed

+200
-18
lines changed

toolchain/diagnostics/diagnostic_kind.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ CARBON_DIAGNOSTIC_KIND(CompileOutputFileOpenError)
3030
CARBON_DIAGNOSTIC_KIND(FormatMultipleFilesToOneOutput)
3131
CARBON_DIAGNOSTIC_KIND(LanguageServerMissingInputStream)
3232
CARBON_DIAGNOSTIC_KIND(LanguageServerTransportError)
33+
CARBON_DIAGNOSTIC_KIND(LanguageServerUnexpectedReply)
34+
CARBON_DIAGNOSTIC_KIND(LanguageServerUnsupportedNotification)
3335

3436
// ============================================================================
3537
// SourceBuffer diagnostics

toolchain/driver/language_server_subcommand.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ auto LanguageServerSubcommand::Run(DriverEnv& driver_env) -> DriverResult {
2727
return {.success = false};
2828
}
2929

30-
bool success =
31-
LanguageServer::Run(driver_env.input_stream, *driver_env.output_stream,
32-
*driver_env.error_stream, driver_env.consumer);
30+
bool success = LanguageServer::Run(
31+
driver_env.input_stream, *driver_env.output_stream,
32+
*driver_env.error_stream, driver_env.vlog_stream, driver_env.consumer);
3333
return {.success = success};
3434
}
3535

toolchain/language_server/BUILD

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ cc_library(
3131
hdrs = ["context.h"],
3232
deps = [
3333
"//common:map",
34+
"//toolchain/diagnostics:diagnostic_emitter",
3435
],
3536
)
3637

@@ -60,6 +61,8 @@ cc_library(
6061
":handle",
6162
"//common:check",
6263
"//common:map",
64+
"//common:ostream",
65+
"//common:raw_string_ostream",
6366
"@llvm-project//clang-tools-extra/clangd:ClangDaemon",
6467
],
6568
)

toolchain/language_server/context.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,24 @@
88
#include <string>
99

1010
#include "common/map.h"
11+
#include "toolchain/diagnostics/diagnostic_consumer.h"
12+
#include "toolchain/diagnostics/diagnostic_emitter.h"
1113

1214
namespace Carbon::LanguageServer {
1315

1416
// Context for LSP call handling.
1517
class Context {
1618
public:
19+
// `consumer` is required.
20+
explicit Context(DiagnosticConsumer* consumer) : no_loc_emitter_(consumer) {}
21+
22+
auto no_loc_emitter() -> NoLocDiagnosticEmitter& { return no_loc_emitter_; }
23+
1724
auto files() -> Map<std::string, std::string>& { return files_; }
1825

1926
private:
27+
NoLocDiagnosticEmitter no_loc_emitter_;
28+
2029
// Content of files managed by the language client.
2130
Map<std::string, std::string> files_;
2231
};

toolchain/language_server/incoming_messages.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "toolchain/language_server/incoming_messages.h"
66

7+
#include "common/ostream.h"
8+
#include "common/raw_string_ostream.h"
79
#include "toolchain/language_server/handle.h"
810

911
namespace Carbon::LanguageServer {
@@ -83,7 +85,7 @@ auto IncomingMessages::onCall(llvm::StringRef name, llvm::json::Value params,
8385
});
8486
} else {
8587
transport_->reply(id, llvm::make_error<clang::clangd::LSPError>(
86-
llvm::formatv("call `{0}` not found", name),
88+
llvm::formatv("unsupported call `{0}`", name),
8789
clang::clangd::ErrorCode::MethodNotFound));
8890
}
8991

@@ -98,10 +100,32 @@ auto IncomingMessages::onNotify(llvm::StringRef name, llvm::json::Value value)
98100
if (auto result = notification_handlers_.Lookup(name)) {
99101
(result.value())(*context_, std::move(value));
100102
} else {
101-
clang::clangd::log("notification `{0}` not found", name);
103+
CARBON_DIAGNOSTIC(LanguageServerUnsupportedNotification, Warning,
104+
"unsupported notification `{0}`", std::string);
105+
context_->no_loc_emitter().Emit(LanguageServerUnsupportedNotification,
106+
name.str());
102107
}
103108

104109
return true;
105110
}
106111

112+
auto IncomingMessages::onReply(llvm::json::Value id,
113+
llvm::Expected<llvm::json::Value> result)
114+
-> bool {
115+
RawStringOstream id_str;
116+
id_str << id;
117+
RawStringOstream result_str;
118+
if (result) {
119+
result_str << result.get();
120+
} else {
121+
result_str << result.takeError();
122+
}
123+
CARBON_DIAGNOSTIC(LanguageServerUnexpectedReply, Warning,
124+
"unexpected reply to request ID {0}: {1}", std::string,
125+
std::string);
126+
context_->no_loc_emitter().Emit(LanguageServerUnexpectedReply,
127+
id_str.TakeStr(), result_str.TakeStr());
128+
return true;
129+
}
130+
107131
} // namespace Carbon::LanguageServer

toolchain/language_server/incoming_messages.h

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,18 @@ class IncomingMessages : public clang::clangd::Transport::MessageHandler {
2525
explicit IncomingMessages(clang::clangd::Transport* transport,
2626
Context* context);
2727

28-
// Dispatches calls to the appropriate entry in `call_handlers_`.
28+
// Dispatches calls to the appropriate entry in `call_handlers_`. Always
29+
// returns true.
2930
auto onCall(llvm::StringRef name, llvm::json::Value params,
3031
llvm::json::Value id) -> bool override;
3132

3233
// Dispatches notifications to the appropriate entry in
3334
// `notification_handlers_`, except for `exit` which directly returns false.
3435
auto onNotify(llvm::StringRef name, llvm::json::Value value) -> bool override;
3536

36-
// Handles replies.
37-
// TODO: Implement when needed.
37+
// Handles replies. Always returns true.
3838
auto onReply(llvm::json::Value /*id*/,
39-
llvm::Expected<llvm::json::Value> /*result*/) -> bool override {
40-
return true;
41-
}
39+
llvm::Expected<llvm::json::Value> /*result*/) -> bool override;
4240

4341
private:
4442
// These are the signatures expected for handlers.

toolchain/language_server/language_server.cpp

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,47 @@
1515

1616
namespace Carbon::LanguageServer {
1717

18+
// An adapter for clangd's logging that sends most messages to `error_stream`.
19+
// Verbose logging is only printed if `vlog_stream` is provided, and will be
20+
// sent there.
21+
class Logger : public clang::clangd::Logger {
22+
public:
23+
explicit Logger(llvm::raw_ostream* error_stream,
24+
llvm::raw_ostream* vlog_stream)
25+
: error_logger_(*error_stream, clang::clangd::Logger::Info),
26+
vlog_logger_(vlog_stream
27+
? std::make_unique<clang::clangd::StreamLogger>(
28+
*vlog_stream, clang::clangd::Logger::Verbose)
29+
: nullptr) {}
30+
31+
auto log(Level level, const char* format,
32+
const llvm::formatv_object_base& message) -> void override {
33+
if (level != clang::clangd::Logger::Verbose) {
34+
error_logger_.log(level, format, message);
35+
} else if (vlog_logger_) {
36+
vlog_logger_->log(level, format, message);
37+
}
38+
}
39+
40+
private:
41+
clang::clangd::StreamLogger error_logger_;
42+
std::unique_ptr<clang::clangd::StreamLogger> vlog_logger_;
43+
};
44+
1845
auto Run(FILE* input_stream, llvm::raw_ostream& output_stream,
19-
llvm::raw_ostream& error_stream, DiagnosticConsumer& consumer)
20-
-> bool {
21-
// TODO: Consider implementing a custom logger that splits vlog to
22-
// vlog_stream when provided. For now, this disables verbose logging.
23-
clang::clangd::StreamLogger logger(error_stream, clang::clangd::Logger::Info);
46+
llvm::raw_ostream& error_stream, llvm::raw_ostream* vlog_stream,
47+
DiagnosticConsumer& consumer) -> bool {
48+
// The language server internally uses diagnostics for logging issues, but the
49+
// clangd parts have their own logging system. We intercept that here.
50+
Logger logger(&error_stream, vlog_stream);
2451
clang::clangd::LoggingSession logging_session(logger);
2552

2653
// Set up the connection.
2754
std::unique_ptr<clang::clangd::Transport> transport(
2855
clang::clangd::newJSONTransport(input_stream, output_stream,
2956
/*InMirror=*/nullptr,
3057
/*Pretty=*/true));
31-
Context context;
58+
Context context(&consumer);
3259
// TODO: Use error_stream in IncomingMessages to report dropped errors.
3360
IncomingMessages incoming(transport.get(), &context);
3461
OutgoingMessages outgoing(transport.get());

toolchain/language_server/language_server.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ namespace Carbon::LanguageServer {
1313
// Start the language server. input_stream and output_stream are used by LSP;
1414
// error_stream is primarily for errors that don't fit into LSP. Returns true if
1515
// the server cleanly exits.
16+
//
17+
// This is thread-hostile because `clangd::LoggingSession` relies on a global.
1618
auto Run(FILE* input_stream, llvm::raw_ostream& output_stream,
17-
llvm::raw_ostream& error_stream, DiagnosticConsumer& consumer) -> bool;
19+
llvm::raw_ostream& error_stream, llvm::raw_ostream* vlog_stream,
20+
DiagnosticConsumer& consumer) -> bool;
1821

1922
} // namespace Carbon::LanguageServer
2023

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// TIP: To test this file alone, run:
7+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/parse_error.carbon
8+
// TIP: To dump output, run:
9+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/parse_error.carbon
10+
11+
12+
// --- STDIN
13+
[[@LSP-REPLY:1]]
14+
[[@LSP-REPLY:2:"result": 3]]
15+
[[@LSP-NOTIFY:exit]]
16+
17+
// --- AUTOUPDATE-SPLIT
18+
19+
// CHECK:STDERR: warning: unexpected reply to request ID "1": null [LanguageServerUnexpectedReply]
20+
// CHECK:STDERR:
21+
// CHECK:STDERR: warning: unexpected reply to request ID "2": 3 [LanguageServerUnexpectedReply]
22+
// CHECK:STDERR:
23+
// CHECK:STDOUT:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
2+
// Exceptions. See /LICENSE for license information.
3+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4+
//
5+
// AUTOUPDATE
6+
// TIP: To test this file alone, run:
7+
// TIP: bazel test //toolchain/testing:file_test --test_arg=--file_tests=toolchain/language_server/testdata/unexpected_reply.carbon
8+
// TIP: To dump output, run:
9+
// TIP: bazel run //toolchain/testing:file_test -- --dump_output --file_tests=toolchain/language_server/testdata/unexpected_reply.carbon
10+
11+
12+
// --- STDIN
13+
[[@LSP-REPLY:1]]
14+
[[@LSP-REPLY:2:"value": 3]]
15+
[[@LSP-NOTIFY:exit]]
16+
17+
// --- AUTOUPDATE-SPLIT
18+
19+
// CHECK:STDERR: warning: unexpected reply to request ID "1": null [LanguageServerUnexpectedReply]
20+
// CHECK:STDERR:
21+
// CHECK:STDERR: warning: unexpected reply to request ID "2": null [LanguageServerUnexpectedReply]
22+
// CHECK:STDERR:
23+
// CHECK:STDOUT:

0 commit comments

Comments
 (0)