Welcome to the next lesson in our journey through the Clang frontend. In our previous session, we examined the dynamic collaboration between the Parser and Sema, which work in tandem to construct the Abstract Syntax Tree (AST). A key point we touched upon was that when Sema discovers a semantic issue in the code, it uses the DiagnosticsEngine to communicate this to you, the developer.
Today, we will put that engine under the microscope. This lesson is dedicated to explaining the role and mechanics of Clang's diagnostics subsystem. Understanding how Clang generates its famously precise errors and warnings is a fundamental skill for any contributor. Many "good first issues" involve improving a diagnostic message, adding a helpful note, or fixing a bug where a diagnostic is incorrect or missing entirely. By the end of this lesson, you will understand the entire lifecycle of a diagnostic, from its definition to its final rendering on your screen.
The Anatomy of a World-Class Diagnostic
Clang is renowned for the quality of its diagnostic messages. They are more than just text; they are rich, structured pieces of information designed to help a programmer fix their code as quickly as possible.
Let's begin by formally dissecting a typical Clang diagnostic.
“Clang” CFE Internals Manual — Clang 23.0.0git documentation
The official Clang Internals Manual provides the definitive description of the diagnostics subsystem. We will refer to it throughout this lesson.
Start by reading the introductory section, The Diagnostics Subsystem. Pay close attention to the list of components that make up a diagnostic: the unique ID, the English translation, the SourceLocation, the severity, the arguments, and the source ranges.
As you've just read, a single diagnostic is a bundle of information:
- Unique ID: An internal enum value like
err_typecheck_invalid_operandsthat uniquely identifies the diagnostic. - Severity: A level indicating the seriousness, such as
NOTE,WARNING, orERROR. - Message: A format string in English, like
"invalid operands to binary expression (%0 and %1)". - Source Location: The precise point in the source code where the caret (
^) should be placed. - Arguments: The data used to fill in the format string's placeholders (e.g.,
%0,%1). These can be types, variable names, or other strings from the user's code. - Source Ranges: The squiggly lines (
~) that highlight relevant parts of an expression. - Fix-it Hints: Optional suggestions for how to automatically correct the code.
This structured approach is what separates a simple printf statement from a powerful diagnostic system.
The Lifecycle of a Diagnostic
Now, let's trace the journey of a diagnostic through the compiler. It's a three-stage process: Definition, Emission, and Consumption.
1. Definition: The .td Files
Diagnostics are not defined as string literals directly in the C++ source. Instead, Clang uses its declarative TableGen system. All diagnostic messages, their IDs, and their severities are defined in .td files, primarily found in clang/Basic/.
tblgen processes these files to generate C++ headers containing the unique IDs (as enums) and other metadata, which the compiler then uses. This approach centralizes all diagnostic messages, making them easier to manage, search, and eventually, to translate.
2019 LLVM Developers’ Meeting: S. Haastregt & A. Stulova “An overview of Clang ”
This segment from the "An overview of Clang" talk at the 2019 LLVM Developers' Meeting provides a perfect visual of this definition process.
Watch from this point, where the speaker shows the TableGen definition for the err_unknown_type_name diagnostic. Notice the three key parts: the unique identifier (err_unknown_type_name), the severity (Error), and the format string ("unknown type name '%0'").
The format strings themselves are quite powerful, allowing for pluralization (%s), selection based on an integer argument (%select), and other formatting options to produce grammatically correct and clear messages.
“Clang” CFE Internals Manual — Clang 23.0.0git documentation
Let's return to the Clang Internals Manual for more detail on defining diagnostics.
First, read about the .td files and severities. This explains the different severity levels like ERROR, WARNING, and EXTENSION. Then, quickly review the guidelines for wording and the overview of the format string. This gives you a sense of the best practices for crafting effective messages.
2. Emission: The Diag Helper in C++
Once a diagnostic is defined, it can be issued from anywhere in the Clang C++ code, though it's most common within Sema. This is done using a Diag helper function.
The function takes the source location for the caret and the diagnostic's unique ID. Arguments and source ranges are then streamed to the resulting diagnostic object using the << operator.
2019 LLVM Developers’ Meeting: S. Haastregt & A. Stulova “An overview of Clang ”
The same video segment that showed the .td definition now shows the corresponding C++ code that emits the diagnostic.
Continue watching from where we left off. The speaker shows the Diag call: Diag(Loc, diag::err_unknown_type_name) << ID;. This demonstrates the direct link between the ID defined in the .td file and its usage in C++.
This is the standard pattern you'll see throughout the codebase. The code that detects an issue builds and returns a diagnostic object, populating it with all the necessary context. One of the most powerful pieces of context you can add is a Fix-it Hint.
“Clang” CFE Internals Manual — Clang 23.0.0git documentation
The manual details the C++ side of this process.
Read the section Producing the Diagnostic to see the C++ syntax. Then, read the following section on Fix-It Hints to understand how these automatic code suggestions are created and attached to a diagnostic.
3. Consumption: The DiagnosticConsumer
Emitting a diagnostic doesn't automatically print it to the console. The DiagnosticsEngine forwards the rich, structured diagnostic object to a registered DiagnosticConsumer. This consumer is responsible for interpreting the diagnostic object and rendering it in some form.
This separation of concerns is powerful.
- The default consumer for the
clangcommand-line tool is theTextDiagnosticPrinter, which generates the familiar colored text output. - When you run
clangwith the-verifyflag for testing, aTextDiagnosticBufferis used, which stores diagnostics in memory for later comparison instead of printing them. - IDEs like VSCode or Xcode can provide their own
DiagnosticConsumerto display errors in a UI panel.

The image below shows a web-based diagnostic summary tool. This is a perfect example of a custom DiagnosticConsumer that presents the same underlying diagnostic information in a completely different format.

A Practical Example with LibTooling
While you'll most often interact with diagnostics from within the compiler itself, the system is also exposed via Clang's LibTooling library for building standalone tools. The following article shows how a custom clang-tidy check can issue its own warnings.
Customized C/C++ Tooling with Clang LibTooling - KDAB
This blog post from KDAB provides a concise, real-world example of using the diagnostics engine.
Read the section titled "Analysis, diagnosis and automatic code correction". Focus on the code snippets. First, see how the author gets the DiagnosticsEngine and issues a warning. Then, see how they build upon this by creating and attaching a FixitHint to provide an automated fix.
This example reinforces the core concepts: you get a handle to the DiagnosticsEngine, create a diagnostic with an ID and a message, and then report it for a specific source location, optionally adding fix-its.
Testing Diagnostics with -verify
Your work as a contributor isn't finished when you've implemented a diagnostic. You must also add a test to ensure it behaves correctly and doesn't regress in the future. Clang has a specialized testing mode, invoked with the -verify flag, for exactly this purpose.
You write special comments in a test source file indicating where you expect diagnostics to appear. The verifier then compares the actual diagnostics produced by Clang against your expectations.
2019 LLVM Developers’ Meeting: S. Haastregt & A. Stulova “An overview of Clang ”
The "An overview of Clang" talk provides an excellent demonstration of this workflow.
Watch the final segment on testing, from testing Diagnostics. The speaker walks through a clear example of a test file with a typo, shows the output of the verifier when the test fails, and then shows how to fix the test by adding the correct // expected-error directive.
This -verify mechanism is the standard way to write regression tests for any change involving diagnostics in Clang. The Clang Internals Manual provides a full reference on the syntax, including how to specify line numbers, match partial text with regexes, and expect a certain number of diagnostics. You can refer back to the section on
Conclusion
You now have a complete picture of Clang's diagnostics subsystem, a cornerstone of its user-friendly design and a frequent target for contributions. You've seen that diagnostics are not simple strings but rich data structures that travel through a well-defined lifecycle.
Key Takeaways:
- Diagnostics are structured objects containing an ID, severity, message, location, arguments, source ranges, and optional fix-it hints.
- Lifecycle:
- Define: Diagnostics are declared in
.tdfiles, from whichtblgengenerates C++ definitions. - Emit: Code in the compiler (usually Sema) detects a problem and issues the diagnostic using the
Diag()helper function. - Consume: A
DiagnosticConsumer(likeTextDiagnosticPrinter) receives the diagnostic object and renders it to the user.
- Define: Diagnostics are declared in
- The separation of emission and consumption allows for diverse outputs, from command-line text to interactive UIs.
- Fix-it hints provide powerful, actionable suggestions for correcting code.
- Any change to diagnostics must be accompanied by a test case using the
-verifysystem.
In our next lesson, we will transition from theory to practice. You will set up a debug build of Clang and use a debugger (GDB or LLDB) to step through the compilation of a simple C++ program. This will allow you to see the AST nodes, Parser functions, and Sema actions you've been learning about in action, solidifying your mental model of the Clang frontend.