blob: 13c9943f52450190e381fda23fd0479abc02beef [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef TOOLS_FIDL_FIDLC_SRC_DIAGNOSTIC_TYPES_H_
#define TOOLS_FIDL_FIDLC_SRC_DIAGNOSTIC_TYPES_H_
#include <zircon/assert.h>
#include <memory>
#include <set>
#include <sstream>
#include <string_view>
#include "tools/fidl/fidlc/src/flat_ast.h"
#include "tools/fidl/fidlc/src/properties.h"
#include "tools/fidl/fidlc/src/source_span.h"
#include "tools/fidl/fidlc/src/token.h"
#include "tools/fidl/fidlc/src/utils.h"
#include "tools/fidl/fidlc/src/versioning_types.h"
namespace fidlc {
using ErrorId = uint32_t;
namespace internal {
std::string Display(char c);
std::string Display(const std::string& s);
std::string Display(std::string_view s);
std::string Display(const std::set<std::string_view>& s);
std::string Display(SourceSpan s);
std::string Display(Token::KindAndSubkind t);
std::string Display(Openness o);
std::string Display(Protocol::Method::Kind k);
std::string Display(const std::vector<std::string_view>& library_name);
std::string Display(const Attribute* a);
std::string Display(const AttributeArg* a);
std::string Display(const Constant* c);
std::string Display(Element::Kind k);
std::string Display(Decl::Kind k);
std::string Display(const Element* e);
std::string Display(const std::vector<const Decl*>& d);
std::string Display(const Type* t);
std::string Display(const Name& n);
std::string Display(const Platform& p);
std::string Display(Version v);
std::string Display(VersionRange r);
std::string Display(const VersionSet& s);
template <typename T, typename = decltype(std::to_string(std::declval<T>()))>
std::string Display(T val) {
return std::to_string(val);
}
// TODO(https://fxbug.dev/42064981): Use std::format when we're on C++20.
template <typename... Args>
std::string FormatDiagnostic(std::string_view msg, const Args&... args) {
std::string displayed_args[] = {Display(args)...};
std::stringstream s;
size_t offset = 0;
for (size_t i = 0; i < msg.size() - 2; i++) {
if (msg[i] == '{' && msg[i + 1] >= '0' && msg[i + 1] <= '9' && msg[i + 2] == '}') {
size_t index = msg[i + 1] - '0';
s << msg.substr(offset, i - offset) << displayed_args[index];
offset = i + 3;
}
}
s << msg.substr(offset);
return s.str();
}
constexpr size_t CountFormatArgs(std::string_view msg) {
size_t count = 0;
for (size_t i = 0; i < msg.size() - 2; i++) {
if (msg[i] == '{' && msg[i + 1] >= '0' && msg[i + 1] <= '9' && msg[i + 2] == '}') {
size_t index = msg[i + 1] - '0';
count = std::max(count, index + 1);
}
}
return count;
}
// No-op non-constexpr function used to produce an error.
inline void IncorrectNumberOfFormatArgs() {}
template <typename... Args>
constexpr void CheckFormatArgs(std::string_view msg) {
static_assert(
(std::is_same_v<Args, std::remove_const_t<std::remove_reference_t<Args>>> && ...),
"remove redundant `const` or `&`; DiagnosticDef args are always passed by const reference");
static_assert(((!std::is_pointer_v<Args> || std::is_const_v<std::remove_pointer_t<Args>>) && ...),
"use a const pointer; DiagnosticDef args should not be mutable pointers");
static_assert(((!std::is_same_v<Args, std::string>) && ...),
"use std::string_view, not std::string");
// We can't static_assert below because the compiler doesn't know msg is
// always constexpr. Instead, we generate a "must be initialized by a constant
// expression" error by calling a non-constexpr function. The error only
// happens if the condition is true. Otherwise, the call gets eliminated.
if (sizeof...(Args) != internal::CountFormatArgs(msg)) {
IncorrectNumberOfFormatArgs();
}
}
} // namespace internal
// A tag that indicates whether a diagnostic definition is an error or warning.
// In the future this could be extended to include hints, suggestions, etc.
enum class DiagnosticKind : uint8_t {
kError,
kWarning,
kRetired,
};
// Extra fields set on some diagnostic definitions.
struct DiagnosticOptions {
// If true, the error message will link to the diagnostic on fuchsia.dev.
bool documented = true;
};
struct DiagnosticDef {
constexpr DiagnosticDef(ErrorId id, DiagnosticKind kind, std::string_view msg,
DiagnosticOptions opts)
: id(id), kind(kind), msg(msg), opts(opts) {}
DiagnosticDef(const DiagnosticDef&) = delete;
// Returns a string of the form "fi-NNNN".
std::string FormatId() const;
ErrorId id;
DiagnosticKind kind;
std::string_view msg;
DiagnosticOptions opts;
};
// The definition of an error. All instances of ErrorDef are in diagnostics.h.
// Template args define format parameters in the error message.
template <ErrorId Id, typename... Args>
struct ErrorDef final : DiagnosticDef {
constexpr explicit ErrorDef(std::string_view msg, DiagnosticOptions opts = {})
: DiagnosticDef(Id, DiagnosticKind::kError, msg, opts) {
internal::CheckFormatArgs<Args...>(msg);
}
};
// The definition of a warning. All instances of WarningDef are in
// diagnostics.h. Template args define format parameters in the warning message.
template <ErrorId Id, typename... Args>
struct WarningDef final : DiagnosticDef {
constexpr explicit WarningDef(std::string_view msg, DiagnosticOptions opts = {})
: DiagnosticDef(Id, DiagnosticKind::kWarning, msg, opts) {
internal::CheckFormatArgs<Args...>(msg);
}
};
// The definition of an obsolete error. These are never displayed to the user -
// they are merely used to retire error numerals from circulation.
template <ErrorId Id>
struct RetiredDef final : DiagnosticDef {
constexpr explicit RetiredDef()
: DiagnosticDef(Id, DiagnosticKind::kRetired, "retired diagnostic", {}) {}
};
// A Diagnostic is the result of instantiating a DiagnosticDef with arguments.
// It stores a formatted std::string where "{}" markers have been replaced by
// arguments. It also stores a SourceSpan indicating where the problem occurred.
struct Diagnostic {
template <typename... Args>
Diagnostic(const DiagnosticDef& def, SourceSpan span, const Args&... args)
: def(def), span(span), msg(internal::FormatDiagnostic(def.msg, args...)) {}
Diagnostic(const Diagnostic&) = delete;
// The factory functions below could be constructors, and std::make_unique
// would work fine. However, template error messages are better with static
// functions because it doesn't have to try every constructor.
template <ErrorId Id, typename... Args>
static std::unique_ptr<Diagnostic> MakeError(const ErrorDef<Id, Args...>& def, SourceSpan span,
const identity_t<Args>&... args) {
return std::make_unique<Diagnostic>(def, span, args...);
}
template <ErrorId Id, typename... Args>
static std::unique_ptr<Diagnostic> MakeWarning(const WarningDef<Id, Args...>& def,
SourceSpan span, const identity_t<Args>&... args) {
return std::make_unique<Diagnostic>(def, span, args...);
}
// Formats the error message to a string.
std::string Format() const;
const DiagnosticDef& def;
const SourceSpan span;
const std::string msg;
};
} // namespace fidlc
#endif // TOOLS_FIDL_FIDLC_SRC_DIAGNOSTIC_TYPES_H_