Slang diagnostics are defined in source/slang/slang-diagnostics.lua. This file is processed at build time to generate C++ structs in slang-rich-diagnostics.h and slang-rich-diagnostics.cpp.
err("diagnostic-name", 1234, "message with ~param interpolation",
span { loc = "location:Type", message = "span label" })
warning("diagnostic-name", 5678, "message",
span { loc = "location" })Available severities are: err, warning, internal, fatal, standalone_note
Diagnostic names must be kebab-case: lowercase letters, numbers, and hyphens only. No underscores, no uppercase, no spaces.
Interpolate parameters with ~:
~param- String parameter~param:Type- Typed parameter (Type, Decl, Expr, Stmt, Val, Name, int)
-- Named arguments
span { loc = "location:Type", message = "label text" }Location types:
- Plain
"location"- SourceLoc - Typed
"location:Expr"- extracts loc from Expr*, Decl*, etc.
The string location here refers to what name the member of the generated diagnostic struct will be given
Notes appear after the main diagnostic with additional context:
err("redefinition", 30201, "function '~function' already has a body",
span { loc = "function:Decl", message = "redeclared here" },
note { message = "see previous definition", span { loc = "original:Decl", messages = "this message is attached to the original span" } })Notes require at least one span. First span is primary, additional spans are secondary.
For diagnostics with lists of similar items we can use variadic_span or variadic_note:
err("ambiguous-overload", 39999, "ambiguous call to '~name'",
span { loc = "expr:Expr" },
variadic_note { cpp_name = "Candidate", message = "candidate: ~signature",
span { loc = "candidate:Decl" } })Generates a nested struct and List<Candidate> candidates member.
Omit the span for diagnostics without source locations:
err("cannot-deduce-source-language", 12, "can't deduce language for '~path'")Generated structs live in Slang::Diagnostics namespace:
#include "slang-rich-diagnostics.h"
sink->diagnose(Diagnostics::UnknownProfile{
.profile = profileName,
.location = loc
});
sink->diagnose(Diagnostics::MacroRedefinition{
.name = macroName,
.location = newLoc,
.originalLocation = prevLoc
});The struct's toGenericDiagnostic() method builds the full diagnostic with all spans and notes.
- Add definition to
source/slang/slang-diagnostics.lua - Rebuild (regenerates
.fiddlefiles) - Use the generated struct via
sink->diagnose(Diagnostics::YourDiagnostic{...})
Test files can verify expected diagnostics using inline annotations. The test runner parses machine-readable diagnostic output and matches it against annotations.
//DIAGNOSTIC_TEST:SIMPLE(diag=CHECK):-target spirvOptions:
diag=PREFIX- Use PREFIX for annotation comments (e.g.,CHECK)non-exhaustive- Only check annotated diagnostics (default: all diagnostics must be annotated)
Carets align with source columns on the preceding non-annotation line:
int foo = undefined;
//CHECK: ^^^^^^^^^ undeclared identifier
//CHECK: ^ another error on this lineUse block comments for early columns.
if (x == y);
/*CHECK:
^ don't use if here
^ empty statement
*/Matches anywhere in output:
//CHECK: unused variableAnnotations can match:
- Message text
- Severity:
warning,error - Error code:
E20101 - Combined:
warning E20101
if (some + bad_addition)
//CHECK: ^ warning
//CHECK: ^ E20101
//CHECK: ^ bad_additionDefault (exhaustive): Test fails if any diagnostic lacks an annotation.
Non-exhaustive (non-exhaustive option): Only checks that annotations match; extra diagnostics are ignored.
Use non-exhaustive when:
- Testing specific diagnostics while ignoring cascading errors
- Incremental test development