xref: /llvm-project/flang/lib/Parser/message.cpp (revision 0f973ac783aa100cfbce1cd2c6e8a3a8f648fae7)
1 //===-- lib/Parser/message.cpp --------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "flang/Parser/message.h"
10 #include "flang/Common/idioms.h"
11 #include "flang/Parser/char-set.h"
12 #include "llvm/Support/raw_ostream.h"
13 #include <algorithm>
14 #include <cstdarg>
15 #include <cstddef>
16 #include <cstdio>
17 #include <cstring>
18 #include <string>
19 #include <vector>
20 
21 namespace Fortran::parser {
22 
23 llvm::raw_ostream &operator<<(llvm::raw_ostream &o, const MessageFixedText &t) {
24   std::size_t n{t.text().size()};
25   for (std::size_t j{0}; j < n; ++j) {
26     o << t.text()[j];
27   }
28   return o;
29 }
30 
31 void MessageFormattedText::Format(const MessageFixedText *text, ...) {
32   const char *p{text->text().begin()};
33   std::string asString;
34   if (*text->text().end() != '\0') {
35     // not NUL-terminated
36     asString = text->text().NULTerminatedToString();
37     p = asString.c_str();
38   }
39   va_list ap;
40   va_start(ap, text);
41 #ifdef _MSC_VER
42   // Microsoft has a separate function for "positional arguments", which is
43   // used in some messages.
44   int need{_vsprintf_p(nullptr, 0, p, ap)};
45 #else
46   int need{vsnprintf(nullptr, 0, p, ap)};
47 #endif
48 
49   CHECK(need >= 0);
50   char *buffer{
51       static_cast<char *>(std::malloc(static_cast<std::size_t>(need) + 1))};
52   CHECK(buffer);
53   va_end(ap);
54   va_start(ap, text);
55 #ifdef _MSC_VER
56   // Use positional argument variant of printf.
57   int need2{_vsprintf_p(buffer, need + 1, p, ap)};
58 #else
59   int need2{vsnprintf(buffer, need + 1, p, ap)};
60 #endif
61   CHECK(need2 == need);
62   va_end(ap);
63   string_ = buffer;
64   std::free(buffer);
65   conversions_.clear();
66 }
67 
68 const char *MessageFormattedText::Convert(const std::string &s) {
69   conversions_.emplace_front(s);
70   return conversions_.front().c_str();
71 }
72 
73 const char *MessageFormattedText::Convert(std::string &&s) {
74   conversions_.emplace_front(std::move(s));
75   return conversions_.front().c_str();
76 }
77 
78 const char *MessageFormattedText::Convert(const std::string_view &s) {
79   conversions_.emplace_front(s);
80   return conversions_.front().c_str();
81 }
82 
83 const char *MessageFormattedText::Convert(std::string_view &&s) {
84   conversions_.emplace_front(s);
85   return conversions_.front().c_str();
86 }
87 
88 const char *MessageFormattedText::Convert(CharBlock x) {
89   return Convert(x.ToString());
90 }
91 
92 std::string MessageExpectedText::ToString() const {
93   return common::visit(
94       common::visitors{
95           [](CharBlock cb) {
96             return MessageFormattedText("expected '%s'"_err_en_US, cb)
97                 .MoveString();
98           },
99           [](const SetOfChars &set) {
100             SetOfChars expect{set};
101             if (expect.Has('\n')) {
102               expect = expect.Difference('\n');
103               if (expect.empty()) {
104                 return "expected end of line"_err_en_US.text().ToString();
105               } else {
106                 std::string s{expect.ToString()};
107                 if (s.size() == 1) {
108                   return MessageFormattedText(
109                       "expected end of line or '%s'"_err_en_US, s)
110                       .MoveString();
111                 } else {
112                   return MessageFormattedText(
113                       "expected end of line or one of '%s'"_err_en_US, s)
114                       .MoveString();
115                 }
116               }
117             }
118             std::string s{expect.ToString()};
119             if (s.size() != 1) {
120               return MessageFormattedText("expected one of '%s'"_err_en_US, s)
121                   .MoveString();
122             } else {
123               return MessageFormattedText("expected '%s'"_err_en_US, s)
124                   .MoveString();
125             }
126           },
127       },
128       u_);
129 }
130 
131 bool MessageExpectedText::Merge(const MessageExpectedText &that) {
132   return common::visit(common::visitors{
133                            [](SetOfChars &s1, const SetOfChars &s2) {
134                              s1 = s1.Union(s2);
135                              return true;
136                            },
137                            [](const auto &, const auto &) { return false; },
138                        },
139       u_, that.u_);
140 }
141 
142 bool Message::SortBefore(const Message &that) const {
143   // Messages from prescanning have ProvenanceRange values for their locations,
144   // while messages from later phases have CharBlock values, since the
145   // conversion of cooked source stream locations to provenances is not
146   // free and needs to be deferred, and many messages created during parsing
147   // are speculative.  Messages with ProvenanceRange locations are ordered
148   // before others for sorting.
149   return common::visit(
150       common::visitors{
151           [](CharBlock cb1, CharBlock cb2) {
152             return cb1.begin() < cb2.begin();
153           },
154           [](CharBlock, const ProvenanceRange &) { return false; },
155           [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
156             return pr1.start() < pr2.start();
157           },
158           [](const ProvenanceRange &, CharBlock) { return true; },
159       },
160       location_, that.location_);
161 }
162 
163 bool Message::IsFatal() const {
164   return severity() == Severity::Error || severity() == Severity::Todo;
165 }
166 
167 Severity Message::severity() const {
168   return common::visit(
169       common::visitors{
170           [](const MessageExpectedText &) { return Severity::Error; },
171           [](const MessageFixedText &x) { return x.severity(); },
172           [](const MessageFormattedText &x) { return x.severity(); },
173       },
174       text_);
175 }
176 
177 Message &Message::set_severity(Severity severity) {
178   common::visit(
179       common::visitors{
180           [](const MessageExpectedText &) {},
181           [severity](MessageFixedText &x) { x.set_severity(severity); },
182           [severity](MessageFormattedText &x) { x.set_severity(severity); },
183       },
184       text_);
185   return *this;
186 }
187 
188 std::optional<common::LanguageFeature> Message::languageFeature() const {
189   return languageFeature_;
190 }
191 
192 Message &Message::set_languageFeature(common::LanguageFeature feature) {
193   languageFeature_ = feature;
194   return *this;
195 }
196 
197 std::optional<common::UsageWarning> Message::usageWarning() const {
198   return usageWarning_;
199 }
200 
201 Message &Message::set_usageWarning(common::UsageWarning warning) {
202   usageWarning_ = warning;
203   return *this;
204 }
205 
206 std::string Message::ToString() const {
207   return common::visit(
208       common::visitors{
209           [](const MessageFixedText &t) {
210             return t.text().NULTerminatedToString();
211           },
212           [](const MessageFormattedText &t) { return t.string(); },
213           [](const MessageExpectedText &e) { return e.ToString(); },
214       },
215       text_);
216 }
217 
218 void Message::ResolveProvenances(const AllCookedSources &allCooked) {
219   if (CharBlock * cb{std::get_if<CharBlock>(&location_)}) {
220     if (std::optional<ProvenanceRange> resolved{
221             allCooked.GetProvenanceRange(*cb)}) {
222       location_ = *resolved;
223     }
224   }
225   if (Message * attachment{attachment_.get()}) {
226     attachment->ResolveProvenances(allCooked);
227   }
228 }
229 
230 std::optional<ProvenanceRange> Message::GetProvenanceRange(
231     const AllCookedSources &allCooked) const {
232   return common::visit(
233       common::visitors{
234           [&](CharBlock cb) { return allCooked.GetProvenanceRange(cb); },
235           [](const ProvenanceRange &pr) { return std::make_optional(pr); },
236       },
237       location_);
238 }
239 
240 static std::string Prefix(Severity severity) {
241   switch (severity) {
242   case Severity::Error:
243     return "error: ";
244   case Severity::Warning:
245     return "warning: ";
246   case Severity::Portability:
247     return "portability: ";
248   case Severity::Because:
249     return "because: ";
250   case Severity::Context:
251     return "in the context: ";
252   case Severity::Todo:
253     return "error: not yet implemented: ";
254   case Severity::None:
255     break;
256   }
257   return "";
258 }
259 
260 static llvm::raw_ostream::Colors PrefixColor(Severity severity) {
261   switch (severity) {
262   case Severity::Error:
263   case Severity::Todo:
264     return llvm::raw_ostream::RED;
265   case Severity::Warning:
266   case Severity::Portability:
267     return llvm::raw_ostream::MAGENTA;
268   default:
269     // TODO: Set the color.
270     break;
271   }
272   return llvm::raw_ostream::SAVEDCOLOR;
273 }
274 
275 void Message::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
276     bool echoSourceLine) const {
277   std::optional<ProvenanceRange> provenanceRange{GetProvenanceRange(allCooked)};
278   const AllSources &sources{allCooked.allSources()};
279   sources.EmitMessage(o, provenanceRange, ToString(), Prefix(severity()),
280       PrefixColor(severity()), echoSourceLine);
281   bool isContext{attachmentIsContext_};
282   for (const Message *attachment{attachment_.get()}; attachment;
283        attachment = attachment->attachment_.get()) {
284     Severity severity = isContext ? Severity::Context : attachment->severity();
285     sources.EmitMessage(o, attachment->GetProvenanceRange(allCooked),
286         attachment->ToString(), Prefix(severity), PrefixColor(severity),
287         echoSourceLine);
288   }
289 }
290 
291 // Messages are equal if they're for the same location and text, and the user
292 // visible aspects of their attachments are the same
293 bool Message::operator==(const Message &that) const {
294   if (!AtSameLocation(that) || ToString() != that.ToString() ||
295       severity() != that.severity() ||
296       attachmentIsContext_ != that.attachmentIsContext_) {
297     return false;
298   }
299   const Message *thatAttachment{that.attachment_.get()};
300   for (const Message *attachment{attachment_.get()}; attachment;
301        attachment = attachment->attachment_.get()) {
302     if (!thatAttachment || !attachment->AtSameLocation(*thatAttachment) ||
303         attachment->ToString() != thatAttachment->ToString() ||
304         attachment->severity() != thatAttachment->severity()) {
305       return false;
306     }
307     thatAttachment = thatAttachment->attachment_.get();
308   }
309   return !thatAttachment;
310 }
311 
312 bool Message::Merge(const Message &that) {
313   return AtSameLocation(that) &&
314       (!that.attachment_.get() ||
315           attachment_.get() == that.attachment_.get()) &&
316       common::visit(
317           common::visitors{
318               [](MessageExpectedText &e1, const MessageExpectedText &e2) {
319                 return e1.Merge(e2);
320               },
321               [](const auto &, const auto &) { return false; },
322           },
323           text_, that.text_);
324 }
325 
326 Message &Message::Attach(Message *m) {
327   if (!attachment_) {
328     attachment_ = m;
329   } else {
330     if (attachment_->references() > 1) {
331       // Don't attach to a shared context attachment; copy it first.
332       attachment_ = new Message{*attachment_};
333     }
334     attachment_->Attach(m);
335   }
336   return *this;
337 }
338 
339 Message &Message::Attach(std::unique_ptr<Message> &&m) {
340   return Attach(m.release());
341 }
342 
343 bool Message::AtSameLocation(const Message &that) const {
344   return common::visit(
345       common::visitors{
346           [](CharBlock cb1, CharBlock cb2) {
347             return cb1.begin() == cb2.begin();
348           },
349           [](const ProvenanceRange &pr1, const ProvenanceRange &pr2) {
350             return pr1.start() == pr2.start();
351           },
352           [](const auto &, const auto &) { return false; },
353       },
354       location_, that.location_);
355 }
356 
357 bool Messages::Merge(const Message &msg) {
358   if (msg.IsMergeable()) {
359     for (auto &m : messages_) {
360       if (m.Merge(msg)) {
361         return true;
362       }
363     }
364   }
365   return false;
366 }
367 
368 void Messages::Merge(Messages &&that) {
369   if (messages_.empty()) {
370     *this = std::move(that);
371   } else {
372     while (!that.messages_.empty()) {
373       if (Merge(that.messages_.front())) {
374         that.messages_.pop_front();
375       } else {
376         auto next{that.messages_.begin()};
377         ++next;
378         messages_.splice(
379             messages_.end(), that.messages_, that.messages_.begin(), next);
380       }
381     }
382   }
383 }
384 
385 void Messages::Copy(const Messages &that) {
386   for (const Message &m : that.messages_) {
387     Message copy{m};
388     Say(std::move(copy));
389   }
390 }
391 
392 void Messages::ResolveProvenances(const AllCookedSources &allCooked) {
393   for (Message &m : messages_) {
394     m.ResolveProvenances(allCooked);
395   }
396 }
397 
398 void Messages::Emit(llvm::raw_ostream &o, const AllCookedSources &allCooked,
399     bool echoSourceLines) const {
400   std::vector<const Message *> sorted;
401   for (const auto &msg : messages_) {
402     sorted.push_back(&msg);
403   }
404   std::stable_sort(sorted.begin(), sorted.end(),
405       [](const Message *x, const Message *y) { return x->SortBefore(*y); });
406   const Message *lastMsg{nullptr};
407   for (const Message *msg : sorted) {
408     if (lastMsg && *msg == *lastMsg) {
409       // Don't emit two identical messages for the same location
410       continue;
411     }
412     msg->Emit(o, allCooked, echoSourceLines);
413     lastMsg = msg;
414   }
415 }
416 
417 void Messages::AttachTo(Message &msg, std::optional<Severity> severity) {
418   for (Message &m : messages_) {
419     Message m2{std::move(m)};
420     if (severity) {
421       m2.set_severity(*severity);
422     }
423     msg.Attach(std::move(m2));
424   }
425   messages_.clear();
426 }
427 
428 bool Messages::AnyFatalError() const {
429   for (const auto &msg : messages_) {
430     if (msg.IsFatal()) {
431       return true;
432     }
433   }
434   return false;
435 }
436 } // namespace Fortran::parser
437