How the Swift Compiler Emits Diagnostics, Part 2: Swift's Wrappers of LLVM Abstractions
In the last two articles in this series on Swift compiler development, I wrote about the abstractions provided by LLVM to emit diagnostics. How the Swift Compiler Emits Diagnostics, Part 1 explained how the llvm::SourceMgr class prints diagnostic messages along with lines of source code in an llvm::MemoryBuffer. Then I wrote about the internals of that llvm::MemoryBuffer class in How Swift and Clang Use LLVM to Read Files into Memory.
In this article, I explain how these abstractions come together in the Swift compiler. Specifically, I'll cover:
- How the Swift frontend reads source files in as
llvm::MemoryBufferinstances. - How the Swift frontend registers those buffers with a
swift::SourceManager, a libswiftBasic wrapper aroundllvm::SourceMgr. - How the Swift lexer records
swift::SourceLoclocations as attributes on the tokens it lexes. (swift::SourceLocis defined in libswiftBasic, as a wrapper aroundllvm::SMLoc.) - How the Swift parser instructs
swift::DiagnosticEngineandswift::SourceManagerto print a diagnostic in the event it has encountered a parse error.
It's these mechanisms above that result in the printing of the parser error I wrote about in the last article:
uhoh.swift
1 // uhoh.swift 2 3 print(("Yikes.")
swiftc uhoh.swift uhoh.swift:3:17: error: expected ')' in expression list print(("Yikes.") ^ uhoh.swift:3:6: note: to match this opening '(' print(("Yikes.") ^
Finally, this article will write about how the Swift compiler defines its diagnostics strings – for example, the "expected ')' in expression list" string above – by using macros. This is similar to the way token kinds are defined, as described in my article Getting Started with the Swift Frontend: Lexing & Parsing.
Step 1: Creating an llvm::MemoryBuffer for each input source file
My article An Introduction to the Swift Compiler Driver explained how an invocation of swift is normally split up, by libswiftDriver, into a series of swift -frontend and ld linker invocations. And in Getting Started with the Swift Frontend: Lexing & Parsing, I explained how an invocation of swift -frontend results in libswiftFrontendTool and libswiftFrontend instantiating a swift::CompilerInvocation, parsing command-line arguments via the swift::CompilerInvocation::parseArgs member function, and then using those parsed arguments to instantiate a swift::CompilerInstance. To recap, here's that code again:
swift/lib/FrontendTool/FrontendTool.cpp
1304 int swift::performFrontend(ArrayRef<const char *> Args, 1305 const char *Argv0, void *MainAddr, 1306 FrontendObserver *observer) { .... 1348 std::unique_ptr<CompilerInstance> Instance = 1349 llvm::make_unique<CompilerInstance>(); .... 1371 CompilerInvocation Invocation; .... 1379 // Parse arguments. 1380 if (Invocation.parseArgs(Args, Instance->getDiags(), workingDirectory)) { 1381 return finishDiagProcessing(1); 1382 } .... 1464 if (Instance->setup(Invocation)) { 1465 return finishDiagProcessing(1); 1466 } .... 1542 }
In Getting Started with the Swift Frontend: Lexing & Parsing, I pointed out that the swift::CompilerInstance::setup function instantiated a swift::ASTContext object (a crucially important object that represents the syntax tree of the Swift program being compiled). But it's also in this function that source files are read into llvm::MemoryBuffer objects. The setup function calls through to swift::CompilerInstance::setUpInputs, which calls setUpForInput, and so on, until eventually getInputBufferAndModuleDocBufferIfPresent instantiates an llvm::MemoryBuffer via the llvm::MemoryBuffer::getFileOrSTDIN function (which I explained in detail in the last article):
swift/lib/Frontend/Frontend.cpp
135 bool CompilerInstance::setup(const CompilerInvocation &Invok) { 136 Invocation = Invok; ... 164 return setUpInputs(); 165 } ... 238 bool CompilerInstance::setUpInputs() { ... 243 for (const InputFile &input : 244 Invocation.getFrontendOptions().InputsAndOutputs.getAllInputs()) 245 if (setUpForInput(input)) 246 return true; ... 260 } 261 262 bool CompilerInstance::setUpForInput(const InputFile &input) { 263 bool failed = false; 264 Optional<unsigned> bufferID = getRecordedBufferID(input, failed); 265 if (failed) 266 return true; ... 280 return false; 281 } 282 283 Optional<unsigned> CompilerInstance::getRecordedBufferID(const InputFile &input, 284 bool &failed) { ... 291 std::pair<std::unique_ptr<llvm::MemoryBuffer>, 292 std::unique_ptr<llvm::MemoryBuffer>> 293 buffers = getInputBufferAndModuleDocBufferIfPresent(input); ... 308 // Transfer ownership of the MemoryBuffer to the SourceMgr.309 unsigned bufferID = SourceMgr.addNewSourceBuffer(std::move(buffers.first));... 313 } 314 315 std::pair<std::unique_ptr<llvm::MemoryBuffer>, 316 std::unique_ptr<llvm::MemoryBuffer>> 317 CompilerInstance::getInputBufferAndModuleDocBufferIfPresent( 318 const InputFile &input) { ... 326 using FileOrError = llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>>;327 FileOrError inputFileOrErr = llvm::MemoryBuffer::getFileOrSTDIN(input.file());328 if (!inputFileOrErr) { 329 Diagnostics.diagnose(SourceLoc(), diag::error_open_input_file, input.file(), 330 inputFileOrErr.getError().message()); 331 return std::make_pair(nullptr, nullptr); 332 } ... 342 }
Notice above that, after calling getInputBufferAndModuleDocBufferIfPresent to instantiate an llvm::MemoryBuffer, the swift::CompilerInstance::getRecordedBufferID function adds the buffer to the source manager.
Step 2: Registering the llvm::MemoryBuffer with the swift::SourceManager
The Swift codebase defines a library named libswiftBasic. It's the Swift codebase's equivalent of libLLVMSupport: a grab-bag of helper classes and functions without much in common.
Included in this helper library are the Swift classes swift::SourceManager and swift::SourceLoc, which wrap llvm::SourceMgr and llvm::SMLoc. They even define a lot of the same member functions, and in their implementations simply forward these along to the internal LLVM class – for example, swift::SourceManager::addNewSourceBuffer simply calls through to llvm::SourceMgr::AddNewSourceBuffer:
swift/include/swift/Basic/SourceManager.h
22 namespace swift { 23 24 /// \brief This class manages and owns source buffers. 25 class SourceManager { 26 llvm::SourceMgr LLVMSourceMgr; .. 103 /// Adds a memory buffer to the SourceManager, taking ownership of it.104 unsigned addNewSourceBuffer(std::unique_ptr<llvm::MemoryBuffer> Buffer);... 234 }; 235 236 } // end namespace swift
swift/lib/Basic/SourceLoc.cpp
42 unsigned 43 SourceManager::addNewSourceBuffer(std::unique_ptr<llvm::MemoryBuffer> Buffer) { ..46 auto ID = LLVMSourceMgr.AddNewSourceBuffer(std::move(Buffer), llvm::SMLoc());.. 49 }
libswiftBasic wraps these LLVM abstractions in order to expose only some of their functions to the rest of the Swift codebase, or to add convenience methods. For example
swift::SourceLocis documented as being defined mainly to deter other parts of the Swift codebase from callingllvm::SMLoc::getFromPointer:swift/include/swift/Basic/SourceLoc.h
28 /// SourceLoc in swift is just an SMLoc. We define it as a different type 29 /// (instead of as a typedef) just to remove the "getFromPointer" methods and 30 /// enforce purity in the Swift codebase. 31 class SourceLoc { .. 80 };
So in step one above, after instantiating an llvm::MemoryBuffer in the swift::CompilerInstance::getInputBufferAndModuleDocBufferIfPresent function, the swift::CompilerInstance::getRecordedBufferID function calls swift::SourceManager::addNewSourceBuffer, a trivial wrapper around llvm::SourceMgr::AddNewSourceBuffer.
The
swift::SourceManageris stored as a member variable onswift::CompilerInstance, calledswift::CompilerInstance::SourceMgr– which is confusing, since that's the same spelling as thellvm::SourceMgrclass. But one is an member variable, the other is a class.
swift/lib/Frontend/Frontend.cpp
283 Optional<unsigned> CompilerInstance::getRecordedBufferID(const InputFile &input, 284 bool &failed) { ... 291 std::pair<std::unique_ptr<llvm::MemoryBuffer>, 292 std::unique_ptr<llvm::MemoryBuffer>> 293 buffers = getInputBufferAndModuleDocBufferIfPresent(input); ... 308 // Transfer ownership of the MemoryBuffer to the SourceMgr.309 unsigned bufferID = SourceMgr.addNewSourceBuffer(std::move(buffers.first));... 313 }
I explained in How the Swift Compiler Emits Diagnostics, Part 1: LLVM Abstractions how llvm::SourceMgr prints diagnostics at locations in an llvm::MemoryBuffer. The Swift lexer is responsible for recording those locations, by instantiating swift::SourceLoc objects (wrappers for llvm::SMLoc) as it lexes tokens.
Step 3: Recording the locations of lexed tokens
In Getting Started with the Swift Frontend: Lexing & Parsing I wrote about how libswiftFrontend eventually calls swift::parseIntoSourceFile, which instantiates a swift::Parser. The swift::Parser initializer instantiates a new swift::Lexer, passing in the swift::SourceManager:
swift/lib/Parse/Parser.cpp
329 Parser::Parser(unsigned BufferID, SourceFile &SF, SILParserTUStateBase *SIL, 330 PersistentParserState *PersistentState) 331 : Parser( 332 std::unique_ptr<Lexer>(new Lexer(333 SF.getASTContext().LangOpts, SF.getASTContext().SourceMgr,334 BufferID, &SF.getASTContext().Diags,335 /*InSILMode=*/SIL != nullptr, 336 SF.getASTContext().LangOpts.AttachCommentsToDecls 337 ? CommentRetentionMode::AttachToNextToken 338 : CommentRetentionMode::None, 339 SF.shouldKeepSyntaxInfo() 340 ? TriviaRetentionMode::WithTrivia 341 : TriviaRetentionMode::WithoutTrivia)), 342 SF, SIL, PersistentState) {}
Besides the
swift::SourceManager, theswift::Lexeralso takes aswift::DiagnosticEngineas a parameter to its initializer. This class is used to actually print the diagnostics – I'll cover it more in step 4 of this article.
In that article I also wrote about how the swift::Lexer iterates over each character in its input memory buffer in order to form tokens. For example, if it finds the character 'p' in its buffer, it'll attempt to lex an identifier. To do so, it advances its pointer into the buffer as long as it finds characters that form valid identifiers ('0' through '9', 'a' through 'Z', underscores, etc.). Once it can advance no further, it calls swift::Lexer::formToken in order to reset the current swift::Token with the correct source location.
Here's the code that lexes an identifier and forms a token based on a pointer to its text:
swift/lib/Parse/Lexer.cpp
591 /// lexIdentifier - Match [a-zA-Z_][a-zA-Z_$0-9]* 592 void Lexer::lexIdentifier() { 593 const char *TokStart = CurPtr-1; 594 CurPtr = TokStart; 595 bool didStart = advanceIfValidStartOfIdentifier(CurPtr, BufferEnd); ... 599 // Lex [a-zA-Z_$0-9[[:XID_Continue:]]]* 600 while (advanceIfValidContinuationOfIdentifier(CurPtr, BufferEnd)); 601 602 tok Kind = kindOfIdentifier(StringRef(TokStart, CurPtr-TokStart), InSILMode);603 return formToken(Kind, TokStart);604 }
The swift::Token::getLoc member function can then be used to instantiate an llvm::SMLoc and swift::SourceLoc based on the buffer pointer:
swift/include/swift/Parse/Token.h
34 class Token { ... 221 /// getLoc - Return a source location identifier for the specified 222 /// offset in the current file. 223 SourceLoc getLoc() const { 224 return SourceLoc(llvm::SMLoc::getFromPointer(Text.begin())); 225 } ... 280 };
So that explains how the lexer constructs tokens and their locations, how the frontend instantiates memory buffers for each of the input files, and how those memory buffers are owned and managed by a swift::SourceManager. Finally, the parser calls through to llvm::SourceMgr::PrintMessage in order to actually print the diagnostic – just as my sample program did in How the Swift Compiler Emits Diagnostics, Part 1.
But the Swift compiler uses several layers of indirection to do so: swift::DiagnosticEngine, swift::Diagnostic, swift::InFlightDiagnostic, and more. The next and final step in this article writes about those classes in detail.
Step 4: Printing the diagnostic
When the swift::Parser parses invalid Swift source code and determines it must print a diagnostic, it doesn't call llvm::SourceMgr::PrintMessage directly. As an example, let's take a closer look at the note diagnostic from the beginning of this article:
uhoh.swift:3:6: note: to match this opening '(' print(("Yikes.") ^
This diagnostic is printed because the parser encounters the opening '(' token, and its Parser::parseMatchingToken function kicks off a loop that tries to parse an expression list and a closing ')' token. If a closing ')' token isn't found, it calls Parser::diagnose, passing in two arguments: the location of the '(' token, and the diagnostic to print:
swift/lib/Parse/Parser.cpp
858 /// parseMatchingToken - Parse the specified expected token and return its 859 /// location on success. On failure, emit the specified error diagnostic, and a 860 /// note at the specified note location. 861 bool Parser::parseMatchingToken(tok K, SourceLoc &TokLoc, Diag<> ErrorDiag, 862 SourceLoc OtherLoc) {863 Diag<> OtherNote;864 switch (K) {865 case tok::r_paren: OtherNote = diag::opening_paren; break;866 case tok::r_square: OtherNote = diag::opening_bracket; break; 867 case tok::r_brace: OtherNote = diag::opening_brace; break; 868 default: llvm_unreachable("unknown matching token!"); break; 869 } 870 if (parseToken(K, TokLoc, ErrorDiag)) {871 diagnose(OtherLoc, OtherNote);872 873 TokLoc = PreviousLoc; 874 return true; 875 } 876 877 return false; 878 }
Notice that the note diagnostic text isn't represented with a string, but instead with a type swift::Diag<>, set to the value diag::opening_paren. This value is defined using macros:
swift/include/swift/AST/DiagnosticsParse.def
34 #ifndef NOTE 35 # define NOTE(ID,Options,Text,Signature) \ 36 DIAG(NOTE,ID,Options,Text,Signature) 37 #endif .. 47 NOTE(opening_paren,none, 48 "to match this opening '('", ())
Another file defines the DIAG macro and then includes this file, in order to define a new global variable, named swift::diag::opening_paren:
swift/include/swift/AST/DiagnosticsParse.def
23 namespace swift { 24 namespace diag { 25 // Declare common diagnostics objects with their appropriate types. 26 #define DIAG(KIND,ID,Options,Text,Signature) \ 27 extern detail::DiagWithArguments<void Signature>::type ID; 28 #include "DiagnosticsParse.def" 29 } 30 }
The extern variable opening_paren is an instance of swift::detail::DiagWithArguments<void ()>::type, which is a type alias for swift::Diag<void ()>. The template parameter void () indicates that the diagnostic takes no arguments.
There are other
swift::diagkinds that do take arguments, such asoperator_static_in_protocol, which takes aStringReffor the operator name:swift/include/swift/AST/DiagnosticsParse.def
345 ERROR(operator_static_in_protocol,none, 346 "operator '%0' declared in protocol must be 'static'",347 (StringRef))348In a future article, I'll cover how these arguments are used. For now, I'll focus on
opening_paren, which takes no arguments.
As shown above, Parser::parseMatchingToken calls Parser::diagnose with the location of the opening parenthesis token and the diag::opening_paren. This function instantiates a swift::Diagnostic based on the swift::Diag<>, and calls through to swift::DiagnosticEngine::diagnose (recall above that the swift::Parser was instantiated with an instance of swift::DiagnosticEngine, a member of swift::ASTContext):
swift/include/swift/Parse/Parser.h
545 InFlightDiagnostic diagnose(SourceLoc Loc, Diagnostic Diag) { ... 549 return Diags.diagnose(Loc, Diag); 550 } ... 556 template<typename ...DiagArgTypes, typename ...ArgTypes> 557 InFlightDiagnostic diagnose(SourceLoc Loc, Diag<DiagArgTypes...> DiagID, 558 ArgTypes &&...Args) { 559 return diagnose(Loc, Diagnostic(DiagID, std::forward<ArgTypes>(Args)...)); 560 }
The Swift compiler defines several abstractions around diagnostics, and it can be hard to keep track of them all. So far I've covered swift::Diag<> and swift::detail::DiagnosticWithArguments<>::type, which represent a single unique diagnostic. The code snippet above introduces swift::Diagnostic, swift::DiagnosticEngine, swift::DiagnosticConsumer, swift::DiagnosticInfo, and swift::InFlightDiagnostic. I'll introduce them each before stepping through the code:
swift::Diagnostic: A diagnostic and all of the arguments it requires. For example, this is used to representdiag::opening_parenand its zero arguments. It's also used to representdiag::operator_static_in_protocoland store its oneStringRefargument.swift::DiagnosticEngine: This is an "announcer" class. It stores a list ofswift::DiagnosticConsumer"listeners", which it delegates to in order to print diagnostics text.swift::DiagnosticInfo: Information about the diagnostic that is passed toswift::DiagnosticConsumerisntances.swift::InFlightDiagnostic: This class is, confusingly, called a "diagnostic", but it is actually used to represent state in theswift::DiagnosticEngine. Theswift::DiagnosticEngine::diagnosefunction creates an "active" diagnostic, and returns an instance ofInFlightDiagnostic. When this instance goes out of scope and its destructor~InFlightDiagnosticis called, it callsswift::DiagnosticEngine::flushActiveDiagnostic, which in turn notifies eachswift::DiagnosticConsumerthat it should emit the current active diagnostic.
That's a lot of abstraction, but I found that after I followed the code, things became a little clearer. Recall that the swift::Parser::diagnose function above instantiated a swift::Diagnostic for diag::opening_paren, and then called swift::DiagnosticEngine::diagnose:
swift/include/swift/AST/DiagnosticEngine.h
646 /// \brief Emit an already-constructed diagnostic at the given location. 647 /// 648 /// \param Loc The location to which the diagnostic refers in the source 649 /// code. 650 /// 651 /// \param D The diagnostic. 652 /// 653 /// \returns An in-flight diagnostic, to which additional information can 654 /// be attached. 655 InFlightDiagnostic diagnose(SourceLoc Loc, const Diagnostic &D) { 656 assert(!ActiveDiagnostic && "Already have an active diagnostic");657 ActiveDiagnostic = D;658 ActiveDiagnostic->setLoc(Loc);659 return InFlightDiagnostic(*this);660 }
Here the swift::DiagnosticEngine sets its ActiveDiagnostic to the swift::Diagnostic that it was given. It then instantiates a swift::InFlightDiagnostic and returns it.
Again, the swift::InFlightDiagnostic being returned here isn't actually a "diagnostic." It doesn't hold any information about what text should be printed out or at which location. Its only purpose is to be returned to the caller – the Parser::parseMatchingToken function – in case that caller wants to attach additional fix-its to the swift::DiagnosticEngine::ActiveDiagnostic.
In this case, the Parser::parseMatchingToken function doesn't store the InFlightDiagnostic at all. So it immediately goes out of scope, and its destructor is called:
swift/lib/Parse/Parser.cpp
861 bool Parser::parseMatchingToken(tok K, SourceLoc &TokLoc, Diag<> ErrorDiag, 862 SourceLoc OtherLoc) { ... 870 if (parseToken(K, TokLoc, ErrorDiag)) {871 diagnose(OtherLoc, OtherNote);... 878 }
swift/include/swift/AST/DiagnosticEngine.h
393 ~InFlightDiagnostic() { 394 if (IsActive)395 flush();396 }
The InFlightDiagnostic::flush member function calls through to DiagnosticEngine::flushActiveDiagnostic, which loops over each diagnostic consumer to call DiagnosticConsumer::handleDiagnostic, passing them a reference to the swift::SourceManager, along with a DiagnosticInfo instance:
swift/lib/AST/DiagnosticEngine.cpp
241 void InFlightDiagnostic::flush() { ... 247 Engine->flushActiveDiagnostic(); 248 } ... 682 void DiagnosticEngine::flushActiveDiagnostic() { ... 685 emitDiagnostic(*ActiveDiagnostic); ... 690 } ... 699 void DiagnosticEngine::emitDiagnostic(const Diagnostic &diagnostic) { ... 705 SourceLoc loc = diagnostic.getLoc(); ... 818 // Pass the diagnostic off to the consumer. 819 DiagnosticInfo Info; 820 Info.ID = diagnostic.getID(); 821 Info.Ranges = diagnostic.getRanges(); 822 Info.FixIts = diagnostic.getFixIts(); 823 for (auto &Consumer : Consumers) { 824 Consumer->handleDiagnostic(SourceMgr, loc, toDiagnosticKind(behavior), 825 diagnosticStringFor(Info.ID), 826 diagnostic.getArgs(), Info); 827 } 828 }
The announcer and listener pattern employed by DiagnosticEngine and its DiagnosticConumer instances allow the Swift compiler to handle diagnostics in different ways. For example, when I invoke swiftc -serialize-diagnostics, libswiftFrontendTool registers a swift::SerializedDiagnosticConsumer instance, which writes diagnostics data to a file.
By default, libswiftFrontendTool registers a swift::PrintingDiagnosticConsumer, in the swift::performFrontend function:
swift/lib/FrontendTool/FrontendTool.cpp
1607 int swift::performFrontend(ArrayRef<const char *> Args, 1608 const char *Argv0, void *MainAddr, 1609 FrontendObserver *observer) { .... 1612 PrintingDiagnosticConsumer PDC; .... 1648 std::unique_ptr<CompilerInstance> Instance = 1649 llvm::make_unique<CompilerInstance>(); 1650 Instance->addDiagnosticConsumer(&PDC); .... 1824 }
When DiagnosticEngine::emitDiagnostic invokes the PrintingDiagnosticConsumer::handleDiagnostic function, the llvm::SourceMgr functions described in How the Swift Compiler Emits Diagnostics, Part 1 are called. llvm::SourceMgr::GetMessage is used to get the text to print, and llvm::SourceMgr::PrimtMessage is used to output that text to the console:
swift/lib/FrontendTool/FrontendTool.cpp
66 void PrintingDiagnosticConsumer::handleDiagnostic( 67 SourceManager &SM, SourceLoc Loc, DiagnosticKind Kind, 68 StringRef FormatString, ArrayRef<DiagnosticArgument> FormatArgs, 69 const DiagnosticInfo &Info) { 70 // Determine what kind of diagnostic we're emitting. 71 llvm::SourceMgr::DiagKind SMKind; 72 switch (Kind) { 73 case DiagnosticKind::Error: 74 SMKind = llvm::SourceMgr::DK_Error; 75 break; 76 case DiagnosticKind::Warning: 77 SMKind = llvm::SourceMgr::DK_Warning; 78 break; 79 80 case DiagnosticKind::Note: 81 SMKind = llvm::SourceMgr::DK_Note; 82 break; 83 84 case DiagnosticKind::Remark: 85 SMKind = llvm::SourceMgr::DK_Remark; 86 break; 87 } .. 106 const llvm::SourceMgr &rawSM = SM.getLLVMSourceMgr(); 107 108 // Actually substitute the diagnostic arguments into the diagnostic text. 109 llvm::SmallString<256> Text; 110 { 111 llvm::raw_svector_ostream Out(Text); 112 DiagnosticEngine::formatDiagnosticText(Out, FormatString, FormatArgs); 113 } 114 115 auto Msg = SM.GetMessage(Loc, SMKind, Text, Ranges, FixIts);116 rawSM.PrintMessage(out, Msg);117 }
Summarizing the last three articles
There's a lot of different machinery involved in printing diagnostics, spanning both the Swift and LLVM codebases:
- The Swift frontend uses libLLVMOption to parse command-line arguments. The frontend instantiates an
llvm::MemoryBufferfor each command-line argument that is determined to be an input file path. Anllvm::MemoryBuffereither reads the entire file into memory, or it uses the operating system callmmapto read chunks of it into memory as needed. - The Swift frontend registers each instantiated
llvm::MemoryBufferwith aswift::SourceManager. This is a libswiftBasic wrapper aroundllvm::SourceMgr. libLLVMSupport implementsllvm::SourceMgr, which defines logic to print a line of source code from anllvm::MemoryBuffer, along with carets^or underlines~~~~~at specific locations or ranges, plus some arbitrary text. - The Swift lexer records
swift::SourceLoclocations as attributes on the tokens it lexes.swift::SourceLocis defined in libswiftBasic, as a wrapper aroundllvm::SMLoc. - The Swift parser is instantiated with a
swift::DiagnosticEngine, which in turn holds a reference to theswift::SourceManagerthat owns thellvm::MemoryBufferobjects. When the Swift parser encounters a series of tokens that don't fit Swift's grammar rules, it instructs itsswift::DiagnosticEngineto print a diagnostic. - The
swift::DiagnosticEngineuses an announcer & listener pattern to callhandleDiagnosticon each of itsswift::DiagnosticConsumerinstances, passing them a reference to theswift::SourceManagerand some information about the diagnostic. - The default diagnostic consumer is
swift::PrintingDiagnosticConsumer, which callsllvm::SourceMgr::PrintMessagein order to print the diagnostic text to the console.