//===-- include/flang/Parser/message.h --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef FORTRAN_PARSER_MESSAGE_H_
#define FORTRAN_PARSER_MESSAGE_H_
// Defines a representation for sequences of compiler messages.
// Supports nested contextualization.
#include "char-block.h"
#include "char-set.h"
#include "provenance.h"
#include "flang/Common/idioms.h"
#include "flang/Common/reference-counted.h"
#include "flang/Common/restorer.h"
#include <cstddef>
#include <cstring>
#include <forward_list>
#include <list>
#include <optional>
#include <string>
#include <utility>
#include <variant>
namespace Fortran::parser {
// Use "..."_err_en_US, "..."_warn_en_US, "..."_port_en_US, "..."_because_en_US,
// "..."_todo_en_US, and "..."_en_US string literals to define the static text
// and severity of a message or attachment.
enum class Severity {
Error, // fatal error that prevents code and module file generation
Warning, // likely problem
Portability, // nonstandard or obsolete features
Because, // for AttachTo(), explanatory attachment to support another message
Context, // (internal): attachment from SetContext()
Todo, // a feature that's not yet implemented, a fatal error
None // everything else, common for attachments with source locations
};
class MessageFixedText {
public:
constexpr MessageFixedText() {}
constexpr MessageFixedText(
const char str[], std::size_t n, Severity severity = Severity::None)
: text_{str, n}, severity_{severity} {}
constexpr MessageFixedText(const MessageFixedText &) = default;
constexpr MessageFixedText(MessageFixedText &&) = default;
constexpr MessageFixedText &operator=(const MessageFixedText &) = default;
constexpr MessageFixedText &operator=(MessageFixedText &&) = default;
CharBlock text() const { return text_; }
bool empty() const { return text_.empty(); }
Severity severity() const { return severity_; }
MessageFixedText &set_severity(Severity severity) {
severity_ = severity;
return *this;
}
bool IsFatal() const {
return severity_ == Severity::Error || severity_ == Severity::Todo;
}
private:
CharBlock text_;
Severity severity_{Severity::None};
};
inline namespace literals {
constexpr MessageFixedText operator""_err_en_US(
const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::Error};
}
constexpr MessageFixedText operator""_warn_en_US(
const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::Warning};
}
constexpr MessageFixedText operator""_port_en_US(
const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::Portability};
}
constexpr MessageFixedText operator""_because_en_US(
const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::Because};
}
constexpr MessageFixedText operator""_todo_en_US(
const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::Todo};
}
constexpr MessageFixedText operator""_en_US(const char str[], std::size_t n) {
return MessageFixedText{str, n, Severity::None};
}
} // namespace literals
// The construction of a MessageFormattedText uses a MessageFixedText
// as a vsnprintf() formatting string that is applied to the
// following arguments. CharBlock, std::string, and std::string_view
// argument values are also supported; they are automatically converted
// into char pointers that are suitable for '%s' formatting.
class MessageFormattedText {
public:
template <typename... A>
MessageFormattedText(const MessageFixedText &text, A &&...x)
: severity_{text.severity()} {
Format(&text, Convert(std::forward<A>(x))...);
}
MessageFormattedText(const MessageFormattedText &) = default;
MessageFormattedText(MessageFormattedText &&) = default;
MessageFormattedText &operator=(const MessageFormattedText &) = default;
MessageFormattedText &operator=(MessageFormattedText &&) = default;
const std::string &string() const { return string_; }
bool IsFatal() const {
return severity_ == Severity::Error || severity_ == Severity::Todo;
}
Severity severity() const { return severity_; }
MessageFormattedText &set_severity(Severity severity) {
severity_ = severity;
return *this;
}
std::string MoveString() { return std::move(string_); }
bool operator==(const MessageFormattedText &that) const {
return severity_ == that.severity_ && string_ == that.string_;
}
bool operator!=(const MessageFormattedText &that) const {
return !(*this == that);
}
private:
void Format(const MessageFixedText *, ...);
template <typename A> A Convert(const A &x) {
static_assert(!std::is_class_v<std::decay_t<A>>);
return x;
}
template <typename A> common::IfNoLvalue<A, A> Convert(A &&x) {
static_assert(!std::is_class_v<std::decay_t<A>>);
return std::move(x);
}
const char *Convert(const char *s) { return s; }
const char *Convert(char *s) { return s; }
const char *Convert(const std::string &);
const char *Convert(std::string &&);
const char *Convert(const std::string_view &);
const char *Convert(std::string_view &&);
const char *Convert(CharBlock);
std::intmax_t Convert(std::int64_t x) { return x; }
std::uintmax_t Convert(std::uint64_t x) { return x; }
Severity severity_;
std::string string_;
std::forward_list<std::string> conversions_; // preserves created strings
};
// Represents a formatted rendition of "expected '%s'"_err_en_US
// on a constant text or a set of characters.
class MessageExpectedText {
public:
MessageExpectedText(const char *s, std::size_t n) {
if (n == std::string::npos) {
n = std::strlen(s);
}
if (n == 1) {
// Treat a one-character string as a singleton set for better merging.
u_ = SetOfChars{*s};
} else {
u_ = CharBlock{s, n};
}
}
constexpr explicit MessageExpectedText(CharBlock cb) : u_{cb} {}
constexpr explicit MessageExpectedText(char ch) : u_{SetOfChars{ch}} {}
constexpr explicit MessageExpectedText(SetOfChars set) : u_{set} {}
MessageExpectedText(const MessageExpectedText &) = default;
MessageExpectedText(MessageExpectedText &&) = default;
MessageExpectedText &operator=(const MessageExpectedText &) = default;
MessageExpectedText &operator=(MessageExpectedText &&) = default;
std::string ToString() const;
bool Merge(const MessageExpectedText &);
private:
std::variant<CharBlock, SetOfChars> u_;
};
class Message : public common::ReferenceCounted<Message> {
public:
using Reference = common::CountedReference<Message>;
Message(const Message &) = default;
Message(Message &&) = default;
Message &operator=(const Message &) = default;
Message &operator=(Message &&) = default;
Message(ProvenanceRange pr, const MessageFixedText &t)
: location_{pr}, text_{t} {}
Message(ProvenanceRange pr, const MessageFormattedText &s)
: location_{pr}, text_{s} {}
Message(ProvenanceRange pr, MessageFormattedText &&s)
: location_{pr}, text_{std::move(s)} {}
Message(ProvenanceRange pr, const MessageExpectedText &t)
: location_{pr}, text_{t} {}
Message(CharBlock csr, const MessageFixedText &t)
: location_{csr}, text_{t} {}
Message(CharBlock csr, const MessageFormattedText &s)
: location_{csr}, text_{s} {}
Message(CharBlock csr, MessageFormattedText &&s)
: location_{csr}, text_{std::move(s)} {}
Message(CharBlock csr, const MessageExpectedText &t)
: location_{csr}, text_{t} {}
template <typename RANGE, typename A, typename... As>
Message(RANGE r, const MessageFixedText &t, A &&x, As &&...xs)
: location_{r}, text_{MessageFormattedText{
t, std::forward<A>(x), std::forward<As>(xs)...}} {}
Reference attachment() const { return attachment_; }
void SetContext(Message *c) {
attachment_ = c;
attachmentIsContext_ = true;
}
Message &Attach(Message *);
Message &Attach(std::unique_ptr<Message> &&);
template <typename... A> Message &Attach(A &&...args) {
return Attach(new Message{std::forward<A>(args)...}); // reference-counted
}
bool SortBefore(const Message &that) const;
bool IsFatal() const;
Severity severity() const;
Message &set_severity(Severity);
std::string ToString() const;
std::optional<ProvenanceRange> GetProvenanceRange(
const AllCookedSources &) const;
void Emit(llvm::raw_ostream &, const AllCookedSources &,
bool echoSourceLine = true) const;
// If this Message or any of its attachments locates itself via a CharBlock,
// replace its location with the corresponding ProvenanceRange.
void ResolveProvenances(const AllCookedSources &);
bool IsMergeable() const {
return std::holds_alternative<MessageExpectedText>(text_);
}
bool Merge(const Message &);
bool operator==(const Message &that) const;
bool operator!=(const Message &that) const { return !(*this == that); }
private:
bool AtSameLocation(const Message &) const;
std::variant<ProvenanceRange, CharBlock> location_;
std::variant<MessageFixedText, MessageFormattedText, MessageExpectedText>
text_;
bool attachmentIsContext_{false};
Reference attachment_;
};
class Messages {
public:
Messages() {}
Messages(Messages &&that) : messages_{std::move(that.messages_)} {}
Messages &operator=(Messages &&that) {
messages_ = std::move(that.messages_);
return *this;
}
std::list<Message> &messages() { return messages_; }
bool empty() const { return messages_.empty(); }
void clear() { messages_.clear(); }
template <typename... A> Message &Say(A &&...args) {
return messages_.emplace_back(std::forward<A>(args)...);
}
void Annex(Messages &&that) {
messages_.splice(messages_.end(), that.messages_);
}
bool Merge(const Message &);
void Merge(Messages &&);
void Copy(const Messages &);
void ResolveProvenances(const AllCookedSources &);
void Emit(llvm::raw_ostream &, const AllCookedSources &,
bool echoSourceLines = true) const;
void AttachTo(Message &, std::optional<Severity> = std::nullopt);
bool AnyFatalError() const;
private:
std::list<Message> messages_;
};
class ContextualMessages {
public:
ContextualMessages() = default;
ContextualMessages(CharBlock at, Messages *m) : at_{at}, messages_{m} {}
explicit ContextualMessages(Messages *m) : messages_{m} {}
ContextualMessages(const ContextualMessages &that)
: at_{that.at_}, messages_{that.messages_} {}
CharBlock at() const { return at_; }
Messages *messages() const { return messages_; }
Message::Reference contextMessage() const { return contextMessage_; }
bool empty() const { return !messages_ || messages_->empty(); }
// Set CharBlock for messages; restore when the returned value is deleted
common::Restorer<CharBlock> SetLocation(CharBlock at) {
if (at.empty()) {
at = at_;
}
return common::ScopedSet(at_, std::move(at));
}
common::Restorer<Message::Reference> SetContext(Message *m) {
if (!m) {
m = contextMessage_.get();
}
return common::ScopedSet(contextMessage_, m);
}
// Diverts messages to another buffer; restored when the returned
// value is deleted.
common::Restorer<Messages *> SetMessages(Messages &buffer) {
return common::ScopedSet(messages_, &buffer);
}
// Discard future messages until the returned value is deleted.
common::Restorer<Messages *> DiscardMessages() {
return common::ScopedSet(messages_, nullptr);
}
template <typename... A> Message *Say(CharBlock at, A &&...args) {
if (messages_ != nullptr) {
auto &msg{messages_->Say(at, std::forward<A>(args)...)};
if (contextMessage_) {
msg.SetContext(contextMessage_.get());
}
return &msg;
} else {
return nullptr;
}
}
template <typename... A>
Message *Say(std::optional<CharBlock> at, A &&...args) {
return Say(at.value_or(at_), std::forward<A>(args)...);
}
template <typename... A> Message *Say(A &&...args) {
return Say(at_, std::forward<A>(args)...);
}
Message *Say(Message &&msg) {
if (messages_ != nullptr) {
if (contextMessage_) {
msg.SetContext(contextMessage_.get());
}
return &messages_->Say(std::move(msg));
} else {
return nullptr;
}
}
private:
CharBlock at_;
Messages *messages_{nullptr};
Message::Reference contextMessage_;
};
} // namespace Fortran::parser
#endif // FORTRAN_PARSER_MESSAGE_H_