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