context.cpp 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  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. #include "toolchain/language_server/context.h"
  5. #include <memory>
  6. #include <optional>
  7. #include <utility>
  8. #include "common/check.h"
  9. #include "common/raw_string_ostream.h"
  10. #include "llvm/TargetParser/Host.h"
  11. #include "toolchain/base/clang_invocation.h"
  12. #include "toolchain/base/shared_value_stores.h"
  13. #include "toolchain/check/check.h"
  14. #include "toolchain/diagnostics/consumer.h"
  15. #include "toolchain/diagnostics/diagnostic.h"
  16. #include "toolchain/diagnostics/emitter.h"
  17. #include "toolchain/lex/lex.h"
  18. #include "toolchain/lex/tokenized_buffer.h"
  19. #include "toolchain/parse/parse.h"
  20. #include "toolchain/parse/tree_and_subtrees.h"
  21. namespace Carbon::LanguageServer {
  22. namespace {
  23. // A consumer for turning diagnostics into a `textDocument/publishDiagnostics`
  24. // notification.
  25. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_publishDiagnostics
  26. class DiagnosticConsumer : public Diagnostics::Consumer {
  27. public:
  28. // Initializes params with the target file information.
  29. explicit DiagnosticConsumer(Context* context,
  30. const clang::clangd::URIForFile& uri,
  31. std::optional<int64_t> version)
  32. : context_(context), params_{.uri = uri, .version = version} {}
  33. // Turns a diagnostic into an LSP diagnostic.
  34. auto HandleDiagnostic(Diagnostics::Diagnostic diagnostic) -> void override {
  35. const auto& message = diagnostic.messages[0];
  36. if (message.loc.filename != params_.uri.file()) {
  37. // `pushDiagnostic` requires diagnostics to be associated with a location
  38. // in the current file. Suppress diagnostics rooted in other files.
  39. // TODO: Consider if there's a better way to handle this.
  40. RawStringOstream stream;
  41. Diagnostics::StreamConsumer consumer(&stream);
  42. consumer.HandleDiagnostic(diagnostic);
  43. CARBON_DIAGNOSTIC(LanguageServerDiagnosticInWrongFile, Warning,
  44. "dropping diagnostic in {0}:\n{1}", std::string,
  45. std::string);
  46. context_->file_emitter().Emit(
  47. params_.uri.file(), LanguageServerDiagnosticInWrongFile,
  48. message.loc.filename.str(), stream.TakeStr());
  49. return;
  50. }
  51. // Add the main message.
  52. params_.diagnostics.push_back(clang::clangd::Diagnostic{
  53. .range = GetRange(message.loc),
  54. .severity = GetSeverity(diagnostic.level),
  55. .source = "carbon",
  56. .message = message.Format(),
  57. });
  58. // TODO: Figure out constructing URIs for note locations.
  59. }
  60. // Returns the constructed request.
  61. auto params() -> const clang::clangd::PublishDiagnosticsParams& {
  62. return params_;
  63. }
  64. private:
  65. // Returns the LSP range for a diagnostic. Note that Carbon uses 1-based
  66. // numbers while LSP uses 0-based.
  67. auto GetRange(const Diagnostics::Loc& loc) -> clang::clangd::Range {
  68. return {.start = {.line = loc.line_number - 1,
  69. .character = loc.column_number - 1},
  70. .end = {.line = loc.line_number,
  71. .character = loc.column_number + loc.length}};
  72. }
  73. // Converts a diagnostic level to an LSP severity.
  74. auto GetSeverity(Diagnostics::Level level) -> int {
  75. // https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnosticSeverity
  76. enum class DiagnosticSeverity {
  77. Error = 1,
  78. Warning = 2,
  79. Information = 3,
  80. Hint = 4,
  81. };
  82. switch (level) {
  83. case Diagnostics::Level::Error:
  84. return static_cast<int>(DiagnosticSeverity::Error);
  85. case Diagnostics::Level::Warning:
  86. return static_cast<int>(DiagnosticSeverity::Warning);
  87. default:
  88. CARBON_FATAL("Unexpected diagnostic level: {0}", level);
  89. }
  90. }
  91. Context* context_;
  92. clang::clangd::PublishDiagnosticsParams params_;
  93. };
  94. } // namespace
  95. auto Context::File::SetText(Context& context, std::optional<int64_t> version,
  96. llvm::StringRef text) -> void {
  97. // Clear state dependent on the source text.
  98. tree_and_subtrees_.reset();
  99. tree_.reset();
  100. tokens_.reset();
  101. value_stores_.reset();
  102. source_.reset();
  103. // A consumer to gather diagnostics for the file.
  104. DiagnosticConsumer consumer(&context, uri_, version);
  105. // TODO: Make the processing asynchronous, to better handle rapid text
  106. // updates.
  107. CARBON_CHECK(!source_ && !value_stores_ && !tokens_ && !tree_,
  108. "We currently cache everything together");
  109. // TODO: Diagnostics should be passed to the LSP instead of dropped.
  110. std::optional source =
  111. SourceBuffer::MakeFromStringCopy(uri_.file(), text, consumer);
  112. if (!source) {
  113. // Failing here should be rare, but provide stub data for recovery so that
  114. // we can have a simple API.
  115. source = SourceBuffer::MakeFromStringCopy(uri_.file(), "", consumer);
  116. CARBON_CHECK(source, "Making an empty buffer should always succeed");
  117. }
  118. source_ = std::make_unique<SourceBuffer>(std::move(*source));
  119. value_stores_ = std::make_unique<SharedValueStores>();
  120. Lex::LexOptions lex_options;
  121. lex_options.consumer = &consumer;
  122. tokens_ = std::make_unique<Lex::TokenizedBuffer>(
  123. Lex::Lex(*value_stores_, *source_, lex_options));
  124. Parse::ParseOptions parse_options;
  125. parse_options.consumer = &consumer;
  126. parse_options.vlog_stream = context.vlog_stream();
  127. tree_ = std::make_unique<Parse::Tree>(Parse::Parse(*tokens_, parse_options));
  128. tree_and_subtrees_ =
  129. std::make_unique<Parse::TreeAndSubtrees>(*tokens_, *tree_);
  130. SemIR::File sem_ir(tree_.get(), SemIR::CheckIRId(0), tree_->packaging_decl(),
  131. *value_stores_, uri_.file().str());
  132. // TODO: Support cross-file checking when multiple files have edits.
  133. llvm::SmallVector<Check::Unit> units = {{{.consumer = &consumer,
  134. .value_stores = value_stores_.get(),
  135. .timings = nullptr,
  136. .sem_ir = &sem_ir,
  137. .total_ir_count = 1}}};
  138. auto getter = [this]() -> const Parse::TreeAndSubtrees& {
  139. return *tree_and_subtrees_;
  140. };
  141. // TODO: Include any unsaved files as an overlay on the real file system.
  142. llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs =
  143. llvm::vfs::getRealFileSystem();
  144. // TODO: Include the prelude. Make sure `total_ir_count` includes the files.
  145. Check::CheckParseTreesOptions check_options;
  146. check_options.vlog_stream = context.vlog_stream();
  147. auto getters =
  148. Parse::GetTreeAndSubtreesStore::MakeWithExplicitSize(1, getter);
  149. auto clang_invocation =
  150. BuildClangInvocation(consumer, fs, context.installation(),
  151. llvm::sys::getDefaultTargetTriple());
  152. Check::CheckParseTrees(units, getters, fs, check_options,
  153. std::move(clang_invocation));
  154. // Note we need to publish diagnostics even when empty.
  155. // TODO: Consider caching previously published diagnostics and only publishing
  156. // when they change.
  157. context.PublishDiagnostics(consumer.params());
  158. }
  159. auto Context::LookupFile(llvm::StringRef filename) -> File* {
  160. if (!filename.ends_with(".carbon")) {
  161. CARBON_DIAGNOSTIC(LanguageServerFileUnsupported, Warning,
  162. "non-Carbon file requested");
  163. file_emitter_.Emit(filename, LanguageServerFileUnsupported);
  164. return nullptr;
  165. }
  166. if (auto lookup_result = files().Lookup(filename)) {
  167. return &lookup_result.value();
  168. } else {
  169. CARBON_DIAGNOSTIC(LanguageServerFileUnknown, Warning,
  170. "unknown file requested");
  171. file_emitter_.Emit(filename, LanguageServerFileUnknown);
  172. return nullptr;
  173. }
  174. }
  175. } // namespace Carbon::LanguageServer