//===-- lib/Parser/message.cpp --------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "flang/Parser/message.h" #include "flang/Common/idioms.h" #include "flang/Parser/char-set.h" #include "llvm/Support/raw_ostream.h" #include #include #include #include #include #include #include namespace Fortran::parser { llvm::raw_ostream &operator<<(llvm::raw_ostream &o, const MessageFixedText &t) { std::size_t n{t.text().size()}; for (std::size_t j{0}; j < n; ++j) { o << t.text()[j]; } return o; } void MessageFormattedText::Format(const MessageFixedText *text, ...) { const char *p{text->text().begin()}; std::string asString; if (*text->text().end() != '\0') { // not NUL-terminated asString = text->text().NULTerminatedToString(); p = asString.c_str(); } va_list ap; va_start(ap, text); #ifdef _MSC_VER // Microsoft has a separate function for "positional arguments", which is // used in some messages. int need{_vsprintf_p(nullptr, 0, p, ap)}; #else int need{vsnprintf(nullptr, 0, p, ap)}; #endif CHECK(need >= 0); char *buffer{ static_cast(std::malloc(static_cast(need) + 1))}; CHECK(buffer); va_end(ap); va_start(ap, text); #ifdef _MSC_VER // Use positional argument variant of printf. int need2{_vsprintf_p(buffer, need + 1, p, ap)}; #else int need2{vsnprintf(buffer, need + 1, p, ap)}; #endif CHECK(need2 == need); va_end(ap); string_ = buffer; std::free(buffer); conversions_.clear(); } const char *MessageFormattedText::Convert(const std::string &s) { conversions_.emplace_front(s); return conversions_.front().c_str(); } const char *MessageFormattedText::Convert(std::string &&s) { conversions_.emplace_front(std::move(s)); return conversions_.front().c_str(); } const char *MessageFormattedText::Convert(const std::string_view &s) { conversions_.emplace_front(s); return conversions_.front().c_str(); } const char *MessageFormattedText::Convert(std::string_view &&s) { conversions_.emplace_front(s); return conversions_.front().c_str(); } const char *MessageFormattedText::Convert(CharBlock x) { return Convert(x.ToString()); } std::string MessageExpectedText::ToString() const { return common::visit( common::visitors{ [](CharBlock cb) { return MessageFormattedText("expected '%s'"_err_en_US, cb) .MoveString(); }, [](const SetOfChars &set) { SetOfChars expect{set}; if (expect.Has('\n')) { expect = expect.Difference('\n'); if (expect.empty()) { return "expected end of line"_err_en_US.text().ToString(); } else { std::string s{expect.ToString()}; if (s.size() == 1) { return MessageFormattedText( "expected end of line or '%s'"_err_en_US, s) .MoveString(); } else { return MessageFormattedText( "expected end of line or one of '%s'"_err_en_US, s) .MoveString(); } } } std::string s{expect.ToString()}; if (s.size() != 1) { return MessageFormattedText("expected one of '%s'"_err_en_US, s) .MoveString(); } else { return MessageFormattedText("expected '%s'"_err_en_US, s) .MoveString(); } }, }, u_); } bool MessageExpectedText::Merge(const MessageExpectedText &that) { return common::visit(common::visitors{ [](SetOfChars &s1, const SetOfChars &s2) { s1 = s1.Union(s2); return true; }, [](const auto &, const auto &) { return false; }, }, u_, that.u_); } bool Message::SortBefore(const Message &that) const { // Messages from prescanning have ProvenanceRange values for their locations, // while messages from later phases have CharBlock values, since the // conversion of cooked source stream locations to provenances is not // free and needs to be deferred, and many messages created during parsing // are speculative. Messages with ProvenanceRange locations are ordered // before others for sorting. return common::visit( common::visitors{ [](CharBlock cb1, CharBlock cb2) { return cb1.begin() < cb2.begin(); }, [](CharBlock, const ProvenanceRange &) { return false; }, [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { return pr1.start() < pr2.start(); }, [](const ProvenanceRange &, CharBlock) { return true; }, }, location_, that.location_); } bool Message::IsFatal() const { return severity() == Severity::Error || severity() == Severity::Todo; } Severity Message::severity() const { return common::visit( common::visitors{ [](const MessageExpectedText &) { return Severity::Error; }, [](const MessageFixedText &x) { return x.severity(); }, [](const MessageFormattedText &x) { return x.severity(); }, }, text_); } Message &Message::set_severity(Severity severity) { common::visit( common::visitors{ [](const MessageExpectedText &) {}, [severity](MessageFixedText &x) { x.set_severity(severity); }, [severity](MessageFormattedText &x) { x.set_severity(severity); }, }, text_); return *this; } std::string Message::ToString() const { return common::visit( common::visitors{ [](const MessageFixedText &t) { return t.text().NULTerminatedToString(); }, [](const MessageFormattedText &t) { return t.string(); }, [](const MessageExpectedText &e) { return e.ToString(); }, }, text_); } void Message::ResolveProvenances(const AllCookedSources &allCooked) { if (CharBlock * cb{std::get_if(&location_)}) { if (std::optional resolved{ allCooked.GetProvenanceRange(*cb)}) { location_ = *resolved; } } if (Message * attachment{attachment_.get()}) { attachment->ResolveProvenances(allCooked); } } std::optional Message::GetProvenanceRange( const AllCookedSources &allCooked) const { return common::visit( common::visitors{ [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); }, [](const ProvenanceRange &pr) { return std::make_optional(pr); }, }, location_); } static std::string Prefix(Severity severity) { switch (severity) { case Severity::Error: return "error: "; case Severity::Warning: return "warning: "; case Severity::Portability: return "portability: "; case Severity::Because: return "because: "; case Severity::Context: return "in the context: "; case Severity::Todo: return "error: not yet implemented: "; case Severity::None: break; } return ""; } static llvm::raw_ostream::Colors PrefixColor(Severity severity) { switch (severity) { case Severity::Error: case Severity::Todo: return llvm::raw_ostream::RED; case Severity::Warning: case Severity::Portability: return llvm::raw_ostream::MAGENTA; default: // TODO: Set the color. break; } return llvm::raw_ostream::SAVEDCOLOR; } void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, bool echoSourceLine) const { std::optional provenanceRange{GetProvenanceRange(allCooked)}; const AllSources &sources{allCooked.allSources()}; sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()), PrefixColor(severity()), echoSourceLine); bool isContext{attachmentIsContext_}; for (const Message *attachment{attachment_.get()}; attachment; attachment = attachment->attachment_.get()) { Severity severity = isContext ? Severity::Context : attachment->severity(); sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked), attachment->ToString(), Prefix(severity), PrefixColor(severity), echoSourceLine); } } // Messages are equal if they're for the same location and text, and the user // visible aspects of their attachments are the same bool Message::operator==(const Message &that) const { if (!AtSameLocation(that) || ToString() != that.ToString() || severity() != that.severity() || attachmentIsContext_ != that.attachmentIsContext_) { return false; } const Message *thatAttachment{that.attachment_.get()}; for (const Message *attachment{attachment_.get()}; attachment; attachment = attachment->attachment_.get()) { if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) || attachment->ToString() != thatAttachment->ToString() || attachment->severity() != thatAttachment->severity()) { return false; } thatAttachment = thatAttachment->attachment_.get(); } return !thatAttachment; } bool Message::Merge(const Message &that) { return AtSameLocation(that) && (!that.attachment_.get() || attachment_.get() == that.attachment_.get()) && common::visit( common::visitors{ [](MessageExpectedText &e1, const MessageExpectedText &e2) { return e1.Merge(e2); }, [](const auto &, const auto &) { return false; }, }, text_, that.text_); } Message &Message::Attach(Message *m) { if (!attachment_) { attachment_ = m; } else { if (attachment_->references() > 1) { // Don't attach to a shared context attachment; copy it first. attachment_ = new Message{*attachment_}; } attachment_->Attach(m); } return *this; } Message &Message::Attach(std::unique_ptr &&m) { return Attach(m.release()); } bool Message::AtSameLocation(const Message &that) const { return common::visit( common::visitors{ [](CharBlock cb1, CharBlock cb2) { return cb1.begin() == cb2.begin(); }, [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) { return pr1.start() == pr2.start(); }, [](const auto &, const auto &) { return false; }, }, location_, that.location_); } bool Messages::Merge(const Message &msg) { if (msg.IsMergeable()) { for (auto &m : messages_) { if (m.Merge(msg)) { return true; } } } return false; } void Messages::Merge(Messages &&that) { if (messages_.empty()) { *this = std::move(that); } else { while (!that.messages_.empty()) { if (Merge(that.messages_.front())) { that.messages_.pop_front(); } else { auto next{that.messages_.begin()}; ++next; messages_.splice( messages_.end(), that.messages_, that.messages_.begin(), next); } } } } void Messages::Copy(const Messages &that) { for (const Message &m : that.messages_) { Message copy{m}; Say(std::move(copy)); } } void Messages::ResolveProvenances(const AllCookedSources &allCooked) { for (Message &m : messages_) { m.ResolveProvenances(allCooked); } } void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked, bool echoSourceLines) const { std::vector sorted; for (const auto &msg : messages_) { sorted.push_back(&msg); } std::stable_sort(sorted.begin(), sorted.end(), [](const Message *x, const Message *y) { return x->SortBefore(*y); }); const Message *lastMsg{nullptr}; for (const Message *msg : sorted) { if (lastMsg && *msg == *lastMsg) { // Don't emit two identical messages for the same location continue; } msg->Emit(o, allCooked, echoSourceLines); lastMsg = msg; } } void Messages::AttachTo(Message &msg, std::optional severity) { for (Message &m : messages_) { Message m2{std::move(m)}; if (severity) { m2.set_severity(*severity); } msg.Attach(std::move(m2)); } messages_.clear(); } bool Messages::AnyFatalError() const { for (const auto &msg : messages_) { if (msg.IsFatal()) { return true; } } return false; } } // namespace Fortran::parser