1//===- SourceMgr.cpp - Manager for Simple Source Buffers & Diagnostics ----===//
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// This file implements the SourceMgr class. This class is used as a simple
10// substrate for diagnostics, #include handling, and other low level things for
11// simple parsers.
12//
13//===----------------------------------------------------------------------===//
14
15#include "llvm/Support/SourceMgr.h"
16#include "llvm/ADT/ArrayRef.h"
17#include "llvm/ADT/STLExtras.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/ADT/SmallVector.h"
20#include "llvm/ADT/StringRef.h"
21#include "llvm/ADT/Twine.h"
22#include "llvm/Support/ErrorOr.h"
23#include "llvm/Support/Locale.h"
24#include "llvm/Support/MemoryBuffer.h"
25#include "llvm/Support/Path.h"
26#include "llvm/Support/SMLoc.h"
27#include "llvm/Support/VirtualFileSystem.h"
28#include "llvm/Support/WithColor.h"
29#include "llvm/Support/raw_ostream.h"
30#include <algorithm>
31#include <cassert>
32#include <cstddef>
33#include <limits>
34#include <memory>
35#include <string>
36#include <utility>
37
38using namespace llvm;
39
40static const size_t TabStop = 8;
41
42// Out of line to avoid needing definition of vfs::FileSystem in header.
43SourceMgr::SourceMgr() = default;
44SourceMgr::SourceMgr(IntrusiveRefCntPtr<vfs::FileSystem> FS)
45 : FS(std::move(FS)) {}
46SourceMgr::SourceMgr(SourceMgr &&) = default;
47SourceMgr &SourceMgr::operator=(SourceMgr &&) = default;
48SourceMgr::~SourceMgr() = default;
49
50IntrusiveRefCntPtr<vfs::FileSystem> SourceMgr::getVirtualFileSystem() const {
51 return FS;
52}
53
54void SourceMgr::setVirtualFileSystem(IntrusiveRefCntPtr<vfs::FileSystem> FS) {
55 this->FS = std::move(FS);
56}
57
58unsigned SourceMgr::AddIncludeFile(const std::string &Filename,
59 SMLoc IncludeLoc,
60 std::string &IncludedFile) {
61 ErrorOr<std::unique_ptr<MemoryBuffer>> NewBufOrErr =
62 OpenIncludeFile(Filename, IncludedFile);
63 if (!NewBufOrErr)
64 return 0;
65
66 return AddNewSourceBuffer(F: std::move(*NewBufOrErr), IncludeLoc);
67}
68
69ErrorOr<std::unique_ptr<MemoryBuffer>>
70SourceMgr::OpenIncludeFile(const std::string &Filename,
71 std::string &IncludedFile,
72 bool RequiresNullTerminator) {
73 auto GetFile = [this, RequiresNullTerminator](StringRef Path) {
74 return FS ? FS->getBufferForFile(Name: Path, /*FileSize=*/-1,
75 RequiresNullTerminator)
76 : MemoryBuffer::getFile(Filename: Path, /*IsText=*/false,
77 RequiresNullTerminator);
78 };
79
80 ErrorOr<std::unique_ptr<MemoryBuffer>> NewBufOrErr = GetFile(Filename);
81
82 SmallString<64> Buffer(Filename);
83 // If the file didn't exist directly, see if it's in an include path.
84 for (unsigned i = 0, e = IncludeDirectories.size(); i != e && !NewBufOrErr;
85 ++i) {
86 Buffer = IncludeDirectories[i];
87 sys::path::append(path&: Buffer, a: Filename);
88 NewBufOrErr = GetFile(Buffer);
89 }
90
91 if (NewBufOrErr)
92 IncludedFile = static_cast<std::string>(Buffer);
93
94 return NewBufOrErr;
95}
96
97unsigned SourceMgr::FindBufferContainingLoc(SMLoc Loc) const {
98 for (unsigned i = 0, e = Buffers.size(); i != e; ++i)
99 if (Loc.getPointer() >= Buffers[i].Buffer->getBufferStart() &&
100 // Use <= here so that a pointer to the null at the end of the buffer
101 // is included as part of the buffer.
102 Loc.getPointer() <= Buffers[i].Buffer->getBufferEnd())
103 return i + 1;
104 return 0;
105}
106
107template <typename T>
108static std::vector<T> &GetOrCreateOffsetCache(void *&OffsetCache,
109 MemoryBuffer *Buffer) {
110 if (OffsetCache)
111 return *static_cast<std::vector<T> *>(OffsetCache);
112
113 // Lazily fill in the offset cache.
114 auto *Offsets = new std::vector<T>();
115 size_t Sz = Buffer->getBufferSize();
116 assert(Sz <= std::numeric_limits<T>::max());
117 StringRef S = Buffer->getBuffer();
118 for (size_t N = 0; N < Sz; ++N) {
119 if (S[N] == '\n')
120 Offsets->push_back(static_cast<T>(N));
121 }
122
123 OffsetCache = Offsets;
124 return *Offsets;
125}
126
127template <typename T>
128std::pair<unsigned, unsigned>
129SourceMgr::SrcBuffer::getLineAndColumnSpecialized(const char *Ptr) const {
130 std::vector<T> &Offsets =
131 GetOrCreateOffsetCache<T>(OffsetCache, Buffer.get());
132
133 const char *BufStart = Buffer->getBufferStart();
134 assert(Ptr >= BufStart && Ptr <= Buffer->getBufferEnd());
135 ptrdiff_t PtrDiff = Ptr - BufStart;
136 assert(PtrDiff >= 0 &&
137 static_cast<size_t>(PtrDiff) <= std::numeric_limits<T>::max());
138 T PtrOffset = static_cast<T>(PtrDiff);
139
140 // llvm::lower_bound gives the number of EOL before PtrOffset. Add 1 to get
141 // the line number.
142 auto I = llvm::lower_bound(Offsets, PtrOffset);
143 unsigned LineNo = I - Offsets.begin() + 1;
144
145 // The column number is the distance from the previous EOL (or the start of
146 // the buffer) to the pointer.
147 T LineStartOffs = (I == Offsets.begin()) ? 0 : (I[-1] + 1);
148 unsigned ColNo = PtrOffset - LineStartOffs + 1;
149 return {LineNo, ColNo};
150}
151
152/// Look up a given \p Ptr in the buffer, determining which line and column
153/// it came from.
154LLVM_ABI std::pair<unsigned, unsigned>
155SourceMgr::SrcBuffer::getLineAndColumn(const char *Ptr) const {
156 size_t Sz = Buffer->getBufferSize();
157 if (Sz <= std::numeric_limits<uint8_t>::max())
158 return getLineAndColumnSpecialized<uint8_t>(Ptr);
159 if (Sz <= std::numeric_limits<uint16_t>::max())
160 return getLineAndColumnSpecialized<uint16_t>(Ptr);
161 if (Sz <= std::numeric_limits<uint32_t>::max())
162 return getLineAndColumnSpecialized<uint32_t>(Ptr);
163 return getLineAndColumnSpecialized<uint64_t>(Ptr);
164}
165
166template <typename T>
167const char *SourceMgr::SrcBuffer::getPointerForLineNumberSpecialized(
168 unsigned LineNo) const {
169 std::vector<T> &Offsets =
170 GetOrCreateOffsetCache<T>(OffsetCache, Buffer.get());
171
172 // We start counting line and column numbers from 1.
173 if (LineNo != 0)
174 --LineNo;
175
176 const char *BufStart = Buffer->getBufferStart();
177
178 // The offset cache contains the location of the \n for the specified line,
179 // we want the start of the line. As such, we look for the previous entry.
180 if (LineNo == 0)
181 return BufStart;
182 if (LineNo > Offsets.size())
183 return nullptr;
184 return BufStart + Offsets[LineNo - 1] + 1;
185}
186
187/// Return a pointer to the first character of the specified line number or
188/// null if the line number is invalid.
189const char *
190SourceMgr::SrcBuffer::getPointerForLineNumber(unsigned LineNo) const {
191 size_t Sz = Buffer->getBufferSize();
192 if (Sz <= std::numeric_limits<uint8_t>::max())
193 return getPointerForLineNumberSpecialized<uint8_t>(LineNo);
194 else if (Sz <= std::numeric_limits<uint16_t>::max())
195 return getPointerForLineNumberSpecialized<uint16_t>(LineNo);
196 else if (Sz <= std::numeric_limits<uint32_t>::max())
197 return getPointerForLineNumberSpecialized<uint32_t>(LineNo);
198 else
199 return getPointerForLineNumberSpecialized<uint64_t>(LineNo);
200}
201
202SourceMgr::SrcBuffer::SrcBuffer(SourceMgr::SrcBuffer &&Other)
203 : Buffer(std::move(Other.Buffer)), OffsetCache(Other.OffsetCache),
204 IncludeLoc(Other.IncludeLoc) {
205 Other.OffsetCache = nullptr;
206}
207
208SourceMgr::SrcBuffer::~SrcBuffer() {
209 if (OffsetCache) {
210 size_t Sz = Buffer->getBufferSize();
211 if (Sz <= std::numeric_limits<uint8_t>::max())
212 delete static_cast<std::vector<uint8_t> *>(OffsetCache);
213 else if (Sz <= std::numeric_limits<uint16_t>::max())
214 delete static_cast<std::vector<uint16_t> *>(OffsetCache);
215 else if (Sz <= std::numeric_limits<uint32_t>::max())
216 delete static_cast<std::vector<uint32_t> *>(OffsetCache);
217 else
218 delete static_cast<std::vector<uint64_t> *>(OffsetCache);
219 OffsetCache = nullptr;
220 }
221}
222
223std::pair<unsigned, unsigned>
224SourceMgr::getLineAndColumn(SMLoc Loc, unsigned BufferID) const {
225 if (!BufferID)
226 BufferID = FindBufferContainingLoc(Loc);
227 assert(BufferID && "Invalid location!");
228
229 auto &SB = getBufferInfo(i: BufferID);
230 const char *Ptr = Loc.getPointer();
231
232 return SB.getLineAndColumn(Ptr);
233}
234
235// FIXME: Note that the formatting of source locations is spread between
236// multiple functions, some in SourceMgr and some in SMDiagnostic. A better
237// solution would be a general-purpose source location formatter
238// in one of those two classes, or possibly in SMLoc.
239
240/// Get a string with the source location formatted in the standard
241/// style, but without the line offset. If \p IncludePath is true, the path
242/// is included. If false, only the file name and extension are included.
243std::string SourceMgr::getFormattedLocationNoOffset(SMLoc Loc,
244 bool IncludePath) const {
245 auto BufferID = FindBufferContainingLoc(Loc);
246 assert(BufferID && "Invalid location!");
247 auto FileSpec = getBufferInfo(i: BufferID).Buffer->getBufferIdentifier();
248
249 if (IncludePath) {
250 return FileSpec.str() + ":" + std::to_string(val: FindLineNumber(Loc, BufferID));
251 } else {
252 auto I = FileSpec.find_last_of(Chars: "/\\");
253 I = (I == FileSpec.size()) ? 0 : (I + 1);
254 return FileSpec.substr(Start: I).str() + ":" +
255 std::to_string(val: FindLineNumber(Loc, BufferID));
256 }
257}
258
259/// Given a line and column number in a mapped buffer, turn it into an SMLoc.
260/// This will return a null SMLoc if the line/column location is invalid.
261SMLoc SourceMgr::FindLocForLineAndColumn(unsigned BufferID, unsigned LineNo,
262 unsigned ColNo) {
263 auto &SB = getBufferInfo(i: BufferID);
264 const char *Ptr = SB.getPointerForLineNumber(LineNo);
265 if (!Ptr)
266 return SMLoc();
267
268 // We start counting line and column numbers from 1.
269 if (ColNo != 0)
270 --ColNo;
271
272 // If we have a column number, validate it.
273 if (ColNo) {
274 // Make sure the location is within the current line.
275 if (Ptr + ColNo > SB.Buffer->getBufferEnd())
276 return SMLoc();
277
278 // Make sure there is no newline in the way.
279 if (StringRef(Ptr, ColNo).find_first_of(Chars: "\n\r") != StringRef::npos)
280 return SMLoc();
281
282 Ptr += ColNo;
283 }
284
285 return SMLoc::getFromPointer(Ptr);
286}
287
288void SourceMgr::PrintIncludeStack(SMLoc IncludeLoc, raw_ostream &OS) const {
289 if (IncludeLoc == SMLoc())
290 return; // Top of stack.
291
292 unsigned CurBuf = FindBufferContainingLoc(Loc: IncludeLoc);
293 assert(CurBuf && "Invalid or unspecified location!");
294
295 PrintIncludeStack(IncludeLoc: getBufferInfo(i: CurBuf).IncludeLoc, OS);
296
297 OS << "Included from " << getBufferInfo(i: CurBuf).Buffer->getBufferIdentifier()
298 << ":" << FindLineNumber(Loc: IncludeLoc, BufferID: CurBuf) << ":\n";
299}
300
301void SourceMgr::printIncludeStackForDiagnostic(SMLoc Loc,
302 raw_ostream &OS) const {
303 if (!Loc.isValid())
304 return;
305 unsigned DiagCurBuffer = FindBufferContainingLoc(Loc);
306 if (DiagCurBuffer && DiagCurBuffer != getMainFileID()) {
307 SMLoc ParentIncludeLoc = getParentIncludeLoc(i: DiagCurBuffer);
308 // Ignore macro instantiation buffers to avoid redundant include stacks.
309 PrintIncludeStack(IncludeLoc: ParentIncludeLoc, OS);
310 }
311}
312
313SMDiagnostic SourceMgr::GetMessage(SMLoc Loc, SourceMgr::DiagKind Kind,
314 const Twine &Msg, ArrayRef<SMRange> Ranges,
315 ArrayRef<SMFixIt> FixIts) const {
316 // First thing to do: find the current buffer containing the specified
317 // location to pull out the source line.
318 SmallVector<std::pair<unsigned, unsigned>, 4> ColRanges;
319 std::pair<unsigned, unsigned> LineAndCol;
320 StringRef BufferID = "<unknown>";
321 StringRef LineStr;
322
323 if (Loc.isValid()) {
324 unsigned CurBuf = FindBufferContainingLoc(Loc);
325 assert(CurBuf && "Invalid or unspecified location!");
326
327 const MemoryBuffer *CurMB = getMemoryBuffer(i: CurBuf);
328 BufferID = CurMB->getBufferIdentifier();
329
330 // Scan backward to find the start of the line.
331 const char *LineStart = Loc.getPointer();
332 const char *BufStart = CurMB->getBufferStart();
333 while (LineStart != BufStart && LineStart[-1] != '\n' &&
334 LineStart[-1] != '\r')
335 --LineStart;
336
337 // Get the end of the line.
338 const char *LineEnd = Loc.getPointer();
339 const char *BufEnd = CurMB->getBufferEnd();
340 while (LineEnd != BufEnd && LineEnd[0] != '\n' && LineEnd[0] != '\r')
341 ++LineEnd;
342 LineStr = StringRef(LineStart, LineEnd - LineStart);
343
344 // Convert any ranges to column ranges that only intersect the line of the
345 // location.
346 for (SMRange R : Ranges) {
347 if (!R.isValid())
348 continue;
349
350 // If the line doesn't contain any part of the range, then ignore it.
351 if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart)
352 continue;
353
354 // Ignore pieces of the range that go onto other lines.
355 if (R.Start.getPointer() < LineStart)
356 R.Start = SMLoc::getFromPointer(Ptr: LineStart);
357 if (R.End.getPointer() > LineEnd)
358 R.End = SMLoc::getFromPointer(Ptr: LineEnd);
359
360 // Translate from SMLoc ranges to column ranges.
361 // FIXME: Handle multibyte characters.
362 ColRanges.push_back(Elt: std::make_pair(x: R.Start.getPointer() - LineStart,
363 y: R.End.getPointer() - LineStart));
364 }
365
366 LineAndCol = getLineAndColumn(Loc, BufferID: CurBuf);
367 }
368
369 return SMDiagnostic(*this, Loc, BufferID, LineAndCol.first,
370 LineAndCol.second - 1, Kind, Msg.str(), LineStr,
371 ColRanges, FixIts);
372}
373
374void SourceMgr::PrintMessage(raw_ostream &OS, const SMDiagnostic &Diagnostic,
375 bool ShowColors) const {
376 // Report the message with the diagnostic handler if present.
377 if (DiagHandler) {
378 DiagHandler(Diagnostic, DiagContext);
379 return;
380 }
381
382 if (Diagnostic.getLoc().isValid()) {
383 unsigned CurBuf = FindBufferContainingLoc(Loc: Diagnostic.getLoc());
384 assert(CurBuf && "Invalid or unspecified location!");
385 PrintIncludeStack(IncludeLoc: getBufferInfo(i: CurBuf).IncludeLoc, OS);
386 }
387
388 Diagnostic.print(ProgName: nullptr, S&: OS, ShowColors);
389}
390
391void SourceMgr::PrintMessage(raw_ostream &OS, SMLoc Loc,
392 SourceMgr::DiagKind Kind, const Twine &Msg,
393 ArrayRef<SMRange> Ranges, ArrayRef<SMFixIt> FixIts,
394 bool ShowColors) const {
395 PrintMessage(OS, Diagnostic: GetMessage(Loc, Kind, Msg, Ranges, FixIts), ShowColors);
396}
397
398void SourceMgr::PrintMessage(SMLoc Loc, SourceMgr::DiagKind Kind,
399 const Twine &Msg, ArrayRef<SMRange> Ranges,
400 ArrayRef<SMFixIt> FixIts, bool ShowColors) const {
401 PrintMessage(OS&: errs(), Loc, Kind, Msg, Ranges, FixIts, ShowColors);
402}
403
404//===----------------------------------------------------------------------===//
405// SMFixIt Implementation
406//===----------------------------------------------------------------------===//
407
408SMFixIt::SMFixIt(SMRange R, const Twine &Replacement)
409 : Range(R), Text(Replacement.str()) {
410 assert(R.isValid());
411}
412
413//===----------------------------------------------------------------------===//
414// SMDiagnostic Implementation
415//===----------------------------------------------------------------------===//
416
417SMDiagnostic::SMDiagnostic(const SourceMgr &sm, SMLoc L, StringRef FN, int Line,
418 int Col, SourceMgr::DiagKind Kind, StringRef Msg,
419 StringRef LineStr,
420 ArrayRef<std::pair<unsigned, unsigned>> Ranges,
421 ArrayRef<SMFixIt> Hints)
422 : SM(&sm), Loc(L), Filename(std::string(FN)), LineNo(Line), ColumnNo(Col),
423 Kind(Kind), Message(Msg), LineContents(LineStr), Ranges(Ranges.vec()),
424 FixIts(Hints) {
425 llvm::sort(C&: FixIts);
426}
427
428static void buildFixItLine(std::string &CaretLine, std::string &FixItLine,
429 ArrayRef<SMFixIt> FixIts,
430 ArrayRef<char> SourceLine) {
431 if (FixIts.empty())
432 return;
433
434 const char *LineStart = SourceLine.begin();
435 const char *LineEnd = SourceLine.end();
436
437 size_t PrevHintEndCol = 0;
438
439 for (const llvm::SMFixIt &Fixit : FixIts) {
440 // If the fixit contains a newline or tab, ignore it.
441 if (Fixit.getText().find_first_of(Chars: "\n\r\t") != StringRef::npos)
442 continue;
443
444 SMRange R = Fixit.getRange();
445
446 // If the line doesn't contain any part of the range, then ignore it.
447 if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart)
448 continue;
449
450 // Translate from SMLoc to column.
451 // Ignore pieces of the range that go onto other lines.
452 // FIXME: Handle multibyte characters in the source line.
453 unsigned FirstCol;
454 if (R.Start.getPointer() < LineStart)
455 FirstCol = 0;
456 else
457 FirstCol = R.Start.getPointer() - LineStart;
458
459 // If we inserted a long previous hint, push this one forwards, and add
460 // an extra space to show that this is not part of the previous
461 // completion. This is sort of the best we can do when two hints appear
462 // to overlap.
463 //
464 // Note that if this hint is located immediately after the previous
465 // hint, no space will be added, since the location is more important.
466 unsigned HintCol = FirstCol;
467 if (HintCol < PrevHintEndCol)
468 HintCol = PrevHintEndCol + 1;
469
470 // FIXME: This assertion is intended to catch unintended use of multibyte
471 // characters in fixits. If we decide to do this, we'll have to track
472 // separate byte widths for the source and fixit lines.
473 assert((size_t)sys::locale::columnWidth(Fixit.getText()) ==
474 Fixit.getText().size());
475
476 // This relies on one byte per column in our fixit hints.
477 unsigned LastColumnModified = HintCol + Fixit.getText().size();
478 if (LastColumnModified > FixItLine.size())
479 FixItLine.resize(n: LastColumnModified, c: ' ');
480
481 llvm::copy(Range: Fixit.getText(), Out: FixItLine.begin() + HintCol);
482
483 PrevHintEndCol = LastColumnModified;
484
485 // For replacements, mark the removal range with '~'.
486 // FIXME: Handle multibyte characters in the source line.
487 unsigned LastCol;
488 if (R.End.getPointer() >= LineEnd)
489 LastCol = LineEnd - LineStart;
490 else
491 LastCol = R.End.getPointer() - LineStart;
492
493 std::fill(first: &CaretLine[FirstCol], last: &CaretLine[LastCol], value: '~');
494 }
495}
496
497static void printSourceLine(raw_ostream &S, StringRef LineContents) {
498 // Print out the source line one character at a time, so we can expand tabs.
499 for (unsigned i = 0, e = LineContents.size(), OutCol = 0; i != e; ++i) {
500 size_t NextTab = LineContents.find(C: '\t', From: i);
501 // If there were no tabs left, print the rest, we are done.
502 if (NextTab == StringRef::npos) {
503 S << LineContents.drop_front(N: i);
504 break;
505 }
506
507 // Otherwise, print from i to NextTab.
508 S << LineContents.slice(Start: i, End: NextTab);
509 OutCol += NextTab - i;
510 i = NextTab;
511
512 // If we have a tab, emit at least one space, then round up to 8 columns.
513 do {
514 S << ' ';
515 ++OutCol;
516 } while ((OutCol % TabStop) != 0);
517 }
518 S << '\n';
519}
520
521static bool isNonASCII(char c) { return c & 0x80; }
522
523void SMDiagnostic::print(const char *ProgName, raw_ostream &OS, bool ShowColors,
524 bool ShowKindLabel, bool ShowLocation) const {
525 ColorMode Mode = ShowColors ? ColorMode::Auto : ColorMode::Disable;
526
527 {
528 WithColor S(OS, raw_ostream::SAVEDCOLOR, true, false, Mode);
529
530 if (ProgName && ProgName[0])
531 S << ProgName << ": ";
532
533 if (ShowLocation && !Filename.empty()) {
534 if (Filename == "-")
535 S << "<stdin>";
536 else
537 S << Filename;
538
539 if (LineNo != -1) {
540 S << ':' << LineNo;
541 if (ColumnNo != -1)
542 S << ':' << (ColumnNo + 1);
543 }
544 S << ": ";
545 }
546 }
547
548 if (ShowKindLabel) {
549 switch (Kind) {
550 case SourceMgr::DK_Error:
551 WithColor::error(OS, Prefix: "", DisableColors: !ShowColors);
552 break;
553 case SourceMgr::DK_Warning:
554 WithColor::warning(OS, Prefix: "", DisableColors: !ShowColors);
555 break;
556 case SourceMgr::DK_Note:
557 WithColor::note(OS, Prefix: "", DisableColors: !ShowColors);
558 break;
559 case SourceMgr::DK_Remark:
560 WithColor::remark(OS, Prefix: "", DisableColors: !ShowColors);
561 break;
562 }
563 }
564
565 WithColor(OS, raw_ostream::SAVEDCOLOR, true, false, Mode) << Message << '\n';
566
567 if (LineNo == -1 || ColumnNo == -1)
568 return;
569
570 // FIXME: If there are multibyte or multi-column characters in the source, all
571 // our ranges will be wrong. To do this properly, we'll need a byte-to-column
572 // map like Clang's TextDiagnostic. For now, we'll just handle tabs by
573 // expanding them later, and bail out rather than show incorrect ranges and
574 // misaligned fixits for any other odd characters.
575 if (any_of(Range: LineContents, P: isNonASCII)) {
576 printSourceLine(S&: OS, LineContents);
577 return;
578 }
579 size_t NumColumns = LineContents.size();
580
581 // Build the line with the caret and ranges.
582 std::string CaretLine(NumColumns + 1, ' ');
583
584 // Expand any ranges.
585 for (const std::pair<unsigned, unsigned> &R : Ranges)
586 std::fill(first: &CaretLine[R.first],
587 last: &CaretLine[std::min(a: (size_t)R.second, b: CaretLine.size())], value: '~');
588
589 // Add any fix-its.
590 // FIXME: Find the beginning of the line properly for multibyte characters.
591 std::string FixItInsertionLine;
592 buildFixItLine(CaretLine, FixItLine&: FixItInsertionLine, FixIts,
593 SourceLine: ArrayRef(Loc.getPointer() - ColumnNo, LineContents.size()));
594
595 // Finally, plop on the caret.
596 if (unsigned(ColumnNo) <= NumColumns)
597 CaretLine[ColumnNo] = '^';
598 else
599 CaretLine[NumColumns] = '^';
600
601 // ... and remove trailing whitespace so the output doesn't wrap for it. We
602 // know that the line isn't completely empty because it has the caret in it at
603 // least.
604 CaretLine.erase(pos: CaretLine.find_last_not_of(c: ' ') + 1);
605
606 printSourceLine(S&: OS, LineContents);
607
608 {
609 ColorMode Mode = ShowColors ? ColorMode::Auto : ColorMode::Disable;
610 WithColor S(OS, raw_ostream::GREEN, true, false, Mode);
611
612 // Print out the caret line, matching tabs in the source line.
613 for (unsigned i = 0, e = CaretLine.size(), OutCol = 0; i != e; ++i) {
614 if (i >= LineContents.size() || LineContents[i] != '\t') {
615 S << CaretLine[i];
616 ++OutCol;
617 continue;
618 }
619
620 // Okay, we have a tab. Insert the appropriate number of characters.
621 do {
622 S << CaretLine[i];
623 ++OutCol;
624 } while ((OutCol % TabStop) != 0);
625 }
626 S << '\n';
627 }
628
629 // Print out the replacement line, matching tabs in the source line.
630 if (FixItInsertionLine.empty())
631 return;
632
633 for (size_t i = 0, e = FixItInsertionLine.size(), OutCol = 0; i < e; ++i) {
634 if (i >= LineContents.size() || LineContents[i] != '\t') {
635 OS << FixItInsertionLine[i];
636 ++OutCol;
637 continue;
638 }
639
640 // Okay, we have a tab. Insert the appropriate number of characters.
641 do {
642 OS << FixItInsertionLine[i];
643 // FIXME: This is trying not to break up replacements, but then to re-sync
644 // with the tabs between replacements. This will fail, though, if two
645 // fix-it replacements are exactly adjacent, or if a fix-it contains a
646 // space. Really we should be precomputing column widths, which we'll
647 // need anyway for multibyte chars.
648 if (FixItInsertionLine[i] != ' ')
649 ++i;
650 ++OutCol;
651 } while (((OutCol % TabStop) != 0) && i != e);
652 }
653 OS << '\n';
654}
655