Browse Source

Merge the diagnostics library from the toolchain repo. (#212)

Main original commit message:

> Add stubs of a diagnostic emission library. (#13)
>
> This doesn't have much wired up yet, but tries to lay out the most
> primitive API pattern.
Chandler Carruth 5 years ago
parent
commit
b72294aa57

+ 26 - 0
diagnostics/BUILD

@@ -0,0 +1,26 @@
+# Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+# Exceptions. See /LICENSE for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+
+package(default_visibility = ["//visibility:public"])
+
+cc_library(
+    name = "diagnostic_emitter",
+    srcs = ["diagnostic_emitter.cpp"],
+    hdrs = ["diagnostic_emitter.h"],
+    deps = ["@llvm-project//llvm:Support"],
+)
+
+cc_test(
+    name = "diagnostic_emitter_test",
+    srcs = ["diagnostic_emitter_test.cpp"],
+    deps = [
+        ":diagnostic_emitter",
+        "@llvm-project//llvm:Support",
+        "@llvm-project//llvm:gmock",
+        "@llvm-project//llvm:gtest",
+        "@llvm-project//llvm:gtest_main",
+    ],
+)

+ 7 - 0
diagnostics/diagnostic_emitter.cpp

@@ -0,0 +1,7 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "diagnostics/diagnostic_emitter.h"
+
+namespace Carbon {}  // namespace Carbon

+ 79 - 0
diagnostics/diagnostic_emitter.h

@@ -0,0 +1,79 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#ifndef DIAGNOSTICS_DIAGNOSTICEMITTER_H_
+#define DIAGNOSTICS_DIAGNOSTICEMITTER_H_
+
+#include <functional>
+#include <string>
+
+#include "llvm/ADT/Any.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace Carbon {
+
+// An instance of a single error or warning.  Information about the diagnostic
+// can be recorded into it for more complex consumers.
+//
+// TODO: turn this into a much more reasonable API when we add some actual
+// uses of it.
+struct Diagnostic {
+  llvm::StringRef short_name;
+  std::string message;
+};
+
+// Manages the creation of reports, the testing if diagnostics are enabled, and
+// the collection of reports.
+class DiagnosticEmitter {
+ public:
+  using Callback = std::function<void(const Diagnostic&)>;
+
+  explicit DiagnosticEmitter(Callback callback)
+      : callback_(std::move(callback)) {}
+  ~DiagnosticEmitter() {}
+
+  // Emits an error unconditionally.  `F` is guaranteed to be called.
+  template <typename DiagnosticT>
+  void EmitError(
+      llvm::function_ref<void(typename DiagnosticT::Substitutions&)> f) {
+    typename DiagnosticT::Substitutions substitutions;
+    f(substitutions);
+    callback_({.short_name = DiagnosticT::ShortName,
+               .message = DiagnosticT::Format(substitutions)});
+  }
+
+  // Emits a warning if `F` returns true.  `F` may or may not be called if the
+  // warning is disabled.
+  template <typename DiagnosticT>
+  void EmitWarningIf(
+      llvm::function_ref<bool(typename DiagnosticT::Substitutions&)> f) {
+    // TODO(kfm): check if this warning is enabled
+    typename DiagnosticT::Substitutions substitutions;
+    if (f(substitutions)) {
+      callback_({.short_name = DiagnosticT::ShortName,
+                 .message = DiagnosticT::Format(substitutions)});
+    }
+  }
+
+ private:
+  Callback callback_;
+};
+
+inline auto ConsoleDiagnosticEmitter() -> DiagnosticEmitter& {
+  static auto* emitter = new DiagnosticEmitter(
+      [](const Diagnostic& d) { llvm::errs() << d.message << "\n"; });
+  return *emitter;
+}
+
+inline auto NullDiagnosticEmitter() -> DiagnosticEmitter& {
+  static auto* emitter = new DiagnosticEmitter([](const Diagnostic&) {});
+  return *emitter;
+}
+
+}  // namespace Carbon
+
+#endif  // DIAGNOSTICS_DIAGNOSTICEMITTER_H_

+ 83 - 0
diagnostics/diagnostic_emitter_test.cpp

@@ -0,0 +1,83 @@
+// Part of the Carbon Language project, under the Apache License v2.0 with LLVM
+// Exceptions. See /LICENSE for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "diagnostics/diagnostic_emitter.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+
+namespace Carbon {
+namespace {
+
+using namespace ::testing;
+
+struct FakeDiagnostic {
+  static constexpr llvm::StringLiteral ShortName = "fake-diagnostic";
+  // TODO: consider ways to put the Message into `format` to allow dynamic
+  // selection of the message.
+  static constexpr llvm::StringLiteral Message = "{0}";
+
+  struct Substitutions {
+    std::string message;
+  };
+  static auto Format(const Substitutions& substitutions) -> std::string {
+    // Work around a bug in Clang's unused const variable warning by marking it
+    // used here with a no-op.
+    static_cast<void>(ShortName);
+
+    return llvm::formatv(Message.data(), substitutions.message).str();
+  }
+};
+
+TEST(DiagTest, EmitErrors) {
+  std::vector<std::string> reported;
+
+  DiagnosticEmitter emitter([&](const Diagnostic& diagnostic) {
+    EXPECT_THAT(diagnostic.short_name, Eq("fake-diagnostic"));
+    reported.push_back(diagnostic.message);
+  });
+
+  emitter.EmitError<FakeDiagnostic>(
+      [](FakeDiagnostic::Substitutions& diagnostic) {
+        diagnostic.message = "M1";
+      });
+  emitter.EmitError<FakeDiagnostic>(
+      [](FakeDiagnostic::Substitutions& diagnostic) {
+        diagnostic.message = "M2";
+      });
+
+  EXPECT_THAT(reported, ElementsAre("M1", "M2"));
+}
+
+TEST(DiagTest, EmitWarnings) {
+  std::vector<std::string> reported;
+
+  DiagnosticEmitter emitter([&](const Diagnostic& diagnostic) {
+    EXPECT_THAT(diagnostic.short_name, Eq("fake-diagnostic"));
+    reported.push_back(diagnostic.message);
+  });
+
+  emitter.EmitWarningIf<FakeDiagnostic>(
+      [](FakeDiagnostic::Substitutions& diagnostic) {
+        diagnostic.message = "M1";
+        return true;
+      });
+  emitter.EmitWarningIf<FakeDiagnostic>(
+      [](FakeDiagnostic::Substitutions& diagnostic) {
+        diagnostic.message = "M2";
+        return false;
+      });
+  emitter.EmitWarningIf<FakeDiagnostic>(
+      [](FakeDiagnostic::Substitutions& diagnostic) {
+        diagnostic.message = "M3";
+        return true;
+      });
+
+  EXPECT_THAT(reported, ElementsAre("M1", "M3"));
+}
+
+}  // namespace
+}  // namespace Carbon