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
301SMDiagnostic SourceMgr::GetMessage(SMLoc Loc, SourceMgr::DiagKind Kind,
302 const Twine &Msg, ArrayRef<SMRange> Ranges,
303 ArrayRef<SMFixIt> FixIts) const {
304 // First thing to do: find the current buffer containing the specified
305 // location to pull out the source line.
306 SmallVector<std::pair<unsigned, unsigned>, 4> ColRanges;
307 std::pair<unsigned, unsigned> LineAndCol;
308 StringRef BufferID = "<unknown>";
309 StringRef LineStr;
310
311 if (Loc.isValid()) {
312 unsigned CurBuf = FindBufferContainingLoc(Loc);
313 assert(CurBuf && "Invalid or unspecified location!");
314
315 const MemoryBuffer *CurMB = getMemoryBuffer(i: CurBuf);
316 BufferID = CurMB->getBufferIdentifier();
317
318 // Scan backward to find the start of the line.
319 const char *LineStart = Loc.getPointer();
320 const char *BufStart = CurMB->getBufferStart();
321 while (LineStart != BufStart && LineStart[-1] != '\n' &&
322 LineStart[-1] != '\r')
323 --LineStart;
324
325 // Get the end of the line.
326 const char *LineEnd = Loc.getPointer();
327 const char *BufEnd = CurMB->getBufferEnd();
328 while (LineEnd != BufEnd && LineEnd[0] != '\n' && LineEnd[0] != '\r')
329 ++LineEnd;
330 LineStr = StringRef(LineStart, LineEnd - LineStart);
331
332 // Convert any ranges to column ranges that only intersect the line of the
333 // location.
334 for (SMRange R : Ranges) {
335 if (!R.isValid())
336 continue;
337
338 // If the line doesn't contain any part of the range, then ignore it.
339 if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart)
340 continue;
341
342 // Ignore pieces of the range that go onto other lines.
343 if (R.Start.getPointer() < LineStart)
344 R.Start = SMLoc::getFromPointer(Ptr: LineStart);
345 if (R.End.getPointer() > LineEnd)
346 R.End = SMLoc::getFromPointer(Ptr: LineEnd);
347
348 // Translate from SMLoc ranges to column ranges.
349 // FIXME: Handle multibyte characters.
350 ColRanges.push_back(Elt: std::make_pair(x: R.Start.getPointer() - LineStart,
351 y: R.End.getPointer() - LineStart));
352 }
353
354 LineAndCol = getLineAndColumn(Loc, BufferID: CurBuf);
355 }
356
357 return SMDiagnostic(*this, Loc, BufferID, LineAndCol.first,
358 LineAndCol.second - 1, Kind, Msg.str(), LineStr,
359 ColRanges, FixIts);
360}
361
362void SourceMgr::PrintMessage(raw_ostream &OS, const SMDiagnostic &Diagnostic,
363 bool ShowColors) const {
364 // Report the message with the diagnostic handler if present.
365 if (DiagHandler) {
366 DiagHandler(Diagnostic, DiagContext);
367 return;
368 }
369
370 if (Diagnostic.getLoc().isValid()) {
371 unsigned CurBuf = FindBufferContainingLoc(Loc: Diagnostic.getLoc());
372 assert(CurBuf && "Invalid or unspecified location!");
373 PrintIncludeStack(IncludeLoc: getBufferInfo(i: CurBuf).IncludeLoc, OS);
374 }
375
376 Diagnostic.print(ProgName: nullptr, S&: OS, ShowColors);
377}
378
379void SourceMgr::PrintMessage(raw_ostream &OS, SMLoc Loc,
380 SourceMgr::DiagKind Kind, const Twine &Msg,
381 ArrayRef<SMRange> Ranges, ArrayRef<SMFixIt> FixIts,
382 bool ShowColors) const {
383 PrintMessage(OS, Diagnostic: GetMessage(Loc, Kind, Msg, Ranges, FixIts), ShowColors);
384}
385
386void SourceMgr::PrintMessage(SMLoc Loc, SourceMgr::DiagKind Kind,
387 const Twine &Msg, ArrayRef<SMRange> Ranges,
388 ArrayRef<SMFixIt> FixIts, bool ShowColors) const {
389 PrintMessage(OS&: errs(), Loc, Kind, Msg, Ranges, FixIts, ShowColors);
390}
391
392//===----------------------------------------------------------------------===//
393// SMFixIt Implementation
394//===----------------------------------------------------------------------===//
395
396SMFixIt::SMFixIt(SMRange R, const Twine &Replacement)
397 : Range(R), Text(Replacement.str()) {
398 assert(R.isValid());
399}
400
401//===----------------------------------------------------------------------===//
402// SMDiagnostic Implementation
403//===----------------------------------------------------------------------===//
404
405SMDiagnostic::SMDiagnostic(const SourceMgr &sm, SMLoc L, StringRef FN, int Line,
406 int Col, SourceMgr::DiagKind Kind, StringRef Msg,
407 StringRef LineStr,
408 ArrayRef<std::pair<unsigned, unsigned>> Ranges,
409 ArrayRef<SMFixIt> Hints)
410 : SM(&sm), Loc(L), Filename(std::string(FN)), LineNo(Line), ColumnNo(Col),
411 Kind(Kind), Message(Msg), LineContents(LineStr), Ranges(Ranges.vec()),
412 FixIts(Hints) {
413 llvm::sort(C&: FixIts);
414}
415
416static void buildFixItLine(std::string &CaretLine, std::string &FixItLine,
417 ArrayRef<SMFixIt> FixIts,
418 ArrayRef<char> SourceLine) {
419 if (FixIts.empty())
420 return;
421
422 const char *LineStart = SourceLine.begin();
423 const char *LineEnd = SourceLine.end();
424
425 size_t PrevHintEndCol = 0;
426
427 for (const llvm::SMFixIt &Fixit : FixIts) {
428 // If the fixit contains a newline or tab, ignore it.
429 if (Fixit.getText().find_first_of(Chars: "\n\r\t") != StringRef::npos)
430 continue;
431
432 SMRange R = Fixit.getRange();
433
434 // If the line doesn't contain any part of the range, then ignore it.
435 if (R.Start.getPointer() > LineEnd || R.End.getPointer() < LineStart)
436 continue;
437
438 // Translate from SMLoc to column.
439 // Ignore pieces of the range that go onto other lines.
440 // FIXME: Handle multibyte characters in the source line.
441 unsigned FirstCol;
442 if (R.Start.getPointer() < LineStart)
443 FirstCol = 0;
444 else
445 FirstCol = R.Start.getPointer() - LineStart;
446
447 // If we inserted a long previous hint, push this one forwards, and add
448 // an extra space to show that this is not part of the previous
449 // completion. This is sort of the best we can do when two hints appear
450 // to overlap.
451 //
452 // Note that if this hint is located immediately after the previous
453 // hint, no space will be added, since the location is more important.
454 unsigned HintCol = FirstCol;
455 if (HintCol < PrevHintEndCol)
456 HintCol = PrevHintEndCol + 1;
457
458 // FIXME: This assertion is intended to catch unintended use of multibyte
459 // characters in fixits. If we decide to do this, we'll have to track
460 // separate byte widths for the source and fixit lines.
461 assert((size_t)sys::locale::columnWidth(Fixit.getText()) ==
462 Fixit.getText().size());
463
464 // This relies on one byte per column in our fixit hints.
465 unsigned LastColumnModified = HintCol + Fixit.getText().size();
466 if (LastColumnModified > FixItLine.size())
467 FixItLine.resize(n: LastColumnModified, c: ' ');
468
469 llvm::copy(Range: Fixit.getText(), Out: FixItLine.begin() + HintCol);
470
471 PrevHintEndCol = LastColumnModified;
472
473 // For replacements, mark the removal range with '~'.
474 // FIXME: Handle multibyte characters in the source line.
475 unsigned LastCol;
476 if (R.End.getPointer() >= LineEnd)
477 LastCol = LineEnd - LineStart;
478 else
479 LastCol = R.End.getPointer() - LineStart;
480
481 std::fill(first: &CaretLine[FirstCol], last: &CaretLine[LastCol], value: '~');
482 }
483}
484
485static void printSourceLine(raw_ostream &S, StringRef LineContents) {
486 // Print out the source line one character at a time, so we can expand tabs.
487 for (unsigned i = 0, e = LineContents.size(), OutCol = 0; i != e; ++i) {
488 size_t NextTab = LineContents.find(C: '\t', From: i);
489 // If there were no tabs left, print the rest, we are done.
490 if (NextTab == StringRef::npos) {
491 S << LineContents.drop_front(N: i);
492 break;
493 }
494
495 // Otherwise, print from i to NextTab.
496 S << LineContents.slice(Start: i, End: NextTab);
497 OutCol += NextTab - i;
498 i = NextTab;
499
500 // If we have a tab, emit at least one space, then round up to 8 columns.
501 do {
502 S << ' ';
503 ++OutCol;
504 } while ((OutCol % TabStop) != 0);
505 }
506 S << '\n';
507}
508
509static bool isNonASCII(char c) { return c & 0x80; }
510
511void SMDiagnostic::print(const char *ProgName, raw_ostream &OS, bool ShowColors,
512 bool ShowKindLabel, bool ShowLocation) const {
513 ColorMode Mode = ShowColors ? ColorMode::Auto : ColorMode::Disable;
514
515 {
516 WithColor S(OS, raw_ostream::SAVEDCOLOR, true, false, Mode);
517
518 if (ProgName && ProgName[0])
519 S << ProgName << ": ";
520
521 if (ShowLocation && !Filename.empty()) {
522 if (Filename == "-")
523 S << "<stdin>";
524 else
525 S << Filename;
526
527 if (LineNo != -1) {
528 S << ':' << LineNo;
529 if (ColumnNo != -1)
530 S << ':' << (ColumnNo + 1);
531 }
532 S << ": ";
533 }
534 }
535
536 if (ShowKindLabel) {
537 switch (Kind) {
538 case SourceMgr::DK_Error:
539 WithColor::error(OS, Prefix: "", DisableColors: !ShowColors);
540 break;
541 case SourceMgr::DK_Warning:
542 WithColor::warning(OS, Prefix: "", DisableColors: !ShowColors);
543 break;
544 case SourceMgr::DK_Note:
545 WithColor::note(OS, Prefix: "", DisableColors: !ShowColors);
546 break;
547 case SourceMgr::DK_Remark:
548 WithColor::remark(OS, Prefix: "", DisableColors: !ShowColors);
549 break;
550 }
551 }
552
553 WithColor(OS, raw_ostream::SAVEDCOLOR, true, false, Mode) << Message << '\n';
554
555 if (LineNo == -1 || ColumnNo == -1)
556 return;
557
558 // FIXME: If there are multibyte or multi-column characters in the source, all
559 // our ranges will be wrong. To do this properly, we'll need a byte-to-column
560 // map like Clang's TextDiagnostic. For now, we'll just handle tabs by
561 // expanding them later, and bail out rather than show incorrect ranges and
562 // misaligned fixits for any other odd characters.
563 if (any_of(Range: LineContents, P: isNonASCII)) {
564 printSourceLine(S&: OS, LineContents);
565 return;
566 }
567 size_t NumColumns = LineContents.size();
568
569 // Build the line with the caret and ranges.
570 std::string CaretLine(NumColumns + 1, ' ');
571
572 // Expand any ranges.
573 for (const std::pair<unsigned, unsigned> &R : Ranges)
574 std::fill(first: &CaretLine[R.first],
575 last: &CaretLine[std::min(a: (size_t)R.second, b: CaretLine.size())], value: '~');
576
577 // Add any fix-its.
578 // FIXME: Find the beginning of the line properly for multibyte characters.
579 std::string FixItInsertionLine;
580 buildFixItLine(CaretLine, FixItLine&: FixItInsertionLine, FixIts,
581 SourceLine: ArrayRef(Loc.getPointer() - ColumnNo, LineContents.size()));
582
583 // Finally, plop on the caret.
584 if (unsigned(ColumnNo) <= NumColumns)
585 CaretLine[ColumnNo] = '^';
586 else
587 CaretLine[NumColumns] = '^';
588
589 // ... and remove trailing whitespace so the output doesn't wrap for it. We
590 // know that the line isn't completely empty because it has the caret in it at
591 // least.
592 CaretLine.erase(pos: CaretLine.find_last_not_of(c: ' ') + 1);
593
594 printSourceLine(S&: OS, LineContents);
595
596 {
597 ColorMode Mode = ShowColors ? ColorMode::Auto : ColorMode::Disable;
598 WithColor S(OS, raw_ostream::GREEN, true, false, Mode);
599
600 // Print out the caret line, matching tabs in the source line.
601 for (unsigned i = 0, e = CaretLine.size(), OutCol = 0; i != e; ++i) {
602 if (i >= LineContents.size() || LineContents[i] != '\t') {
603 S << CaretLine[i];
604 ++OutCol;
605 continue;
606 }
607
608 // Okay, we have a tab. Insert the appropriate number of characters.
609 do {
610 S << CaretLine[i];
611 ++OutCol;
612 } while ((OutCol % TabStop) != 0);
613 }
614 S << '\n';
615 }
616
617 // Print out the replacement line, matching tabs in the source line.
618 if (FixItInsertionLine.empty())
619 return;
620
621 for (size_t i = 0, e = FixItInsertionLine.size(), OutCol = 0; i < e; ++i) {
622 if (i >= LineContents.size() || LineContents[i] != '\t') {
623 OS << FixItInsertionLine[i];
624 ++OutCol;
625 continue;
626 }
627
628 // Okay, we have a tab. Insert the appropriate number of characters.
629 do {
630 OS << FixItInsertionLine[i];
631 // FIXME: This is trying not to break up replacements, but then to re-sync
632 // with the tabs between replacements. This will fail, though, if two
633 // fix-it replacements are exactly adjacent, or if a fix-it contains a
634 // space. Really we should be precomputing column widths, which we'll
635 // need anyway for multibyte chars.
636 if (FixItInsertionLine[i] != ' ')
637 ++i;
638 ++OutCol;
639 } while (((OutCol % TabStop) != 0) && i != e);
640 }
641 OS << '\n';
642}
643