use duck_diagnostic::*;
#[derive(Debug, Clone, Copy)]
enum TinyC {
UnterminatedString,
TypeMismatch,
UnusedVariable,
PreferConst,
DeprecatedApi,
UnreachableCode,
CrossFileMismatch,
Ice,
}
impl DiagnosticCode for TinyC {
fn code(&self) -> &str {
match self {
Self::UnterminatedString => "E0001",
Self::TypeMismatch => "E0201",
Self::UnusedVariable => "W0001",
Self::PreferConst => "L0001",
Self::DeprecatedApi => "W0042",
Self::UnreachableCode => "W0099",
Self::CrossFileMismatch => "E0301",
Self::Ice => "ICE0001",
}
}
fn severity(&self) -> Severity {
match self {
Self::UnterminatedString | Self::TypeMismatch | Self::CrossFileMismatch => Severity::Error,
Self::Ice => Severity::Bug,
_ => Severity::Warning,
}
}
fn url(&self) -> Option<&'static str> {
match self {
Self::UnterminatedString => Some("https://tinyc.example/E0001"),
Self::TypeMismatch => Some("https://tinyc.example/E0201"),
Self::PreferConst => Some("https://tinyc.example/L0001"),
_ => None,
}
}
}
fn main() {
let source = r#"fn greet(name: string) {
let msg = "hello, " + name
setTimeout(function () {
console.log(msg);
}, 100)
let result = a + b * "five"
let count = 1
return msg
unreachable_code_here()
let _temp = old_api(42)
}"#;
let cache = SourceCache::new(source);
let mut engine = DiagnosticEngine::<TinyC>::new();
engine.emit(
Diagnostic::new(TinyC::UnterminatedString, "unterminated string literal")
.with_label(
Label::primary(Span::new("tinyc/main.tc", 2, 13, 9), Some("string opens here".into()))
.with_note("string literals cannot span multiple lines"),
)
.with_label(Label::secondary(
Span::new("tinyc/main.tc", 2, 29, 1),
Some("expected `\"` before end of line".into()),
))
.with_help("close the string with a matching `\"` or use `\"\"\"...\"\"\"` for multi-line")
.with_suggestion(
Suggestion::new(Span::new("tinyc/main.tc", 2, 29, 0), "\"")
.with_message("close the string here")
.with_applicability(Applicability::MachineApplicable),
),
);
engine.emit(
Diagnostic::new(TinyC::TypeMismatch, "mismatched types in arithmetic")
.with_label(Label::primary(
Span::new("tinyc/main.tc", 6, 16, 1),
Some("this is a `number`".into()),
))
.with_label(Label::primary(
Span::new("tinyc/main.tc", 6, 24, 6),
Some("this is a `string`".into()),
))
.with_note("operator `*` is not defined for `number` and `string`")
.with_help("convert one side: `a + b * \"five\".parse::<number>()?`"),
);
engine.emit(
Diagnostic::new(TinyC::PreferConst, "`count` is never reassigned — prefer `const`")
.with_label(Label::primary(Span::new("tinyc/main.tc", 7, 3, 3), Some("declared here".into())))
.with_suggestion(
Suggestion::new(Span::new("tinyc/main.tc", 7, 3, 3), "const")
.with_message("replace `let` with `const`")
.with_applicability(Applicability::MachineApplicable),
),
);
engine.emit(
Diagnostic::new(TinyC::DeprecatedApi, "`old_api` is deprecated since v2.0.0")
.with_label(
Label::primary(Span::new("tinyc/main.tc", 10, 15, 7), Some("called here".into()))
.with_note("use `new_api(value, options)` instead"),
)
.with_help("the deprecation will become a hard error in v3.0.0"),
);
engine.emit(
Diagnostic::new(TinyC::UnusedVariable, "unused variable `msg`")
.with_label(Label::primary(
Span::new("tinyc/main.tc", 2, 7, 3),
Some("declared but never used after this scope".into()),
))
.with_help("prefix with `_` to silence: `_msg`"),
);
engine.emit(
Diagnostic::new(TinyC::UnreachableCode, "unreachable code")
.with_label(Label::primary(
Span::new("tinyc/main.tc", 9, 3, 24),
Some("this expression is never evaluated".into()),
))
.with_label(Label::secondary(
Span::new("tinyc/main.tc", 8, 3, 10),
Some("any code following this `return` is dead".into()),
)),
);
engine.emit(
Diagnostic::new(TinyC::CrossFileMismatch, "function signature mismatch across files")
.with_label(Label::primary(
Span::new("tinyc/main.tc", 3, 3, 10),
Some("called with 1 argument here".into()),
))
.with_label(Label::secondary(
Span::new("tinyc/runtime.tc", 1, 1, 30),
Some("but defined to take 2 arguments".into()),
))
.with_help("update either the call site or the declaration to match"),
);
engine.emit(
Diagnostic::new(TinyC::Ice, "compiler hit `unreachable!()` while lowering AST")
.with_label(Label::primary(
Span::new("tinyc/main.tc", 11, 1, 1),
Some("triggered while lowering this token".into()),
))
.with_note("this is a bug in the compiler, not your code")
.with_help("please file a bug report at https://tinyc.example/issues with the source above"),
);
for d in engine.iter() {
let f = DiagnosticFormatter::with_cache(d, &cache);
print!("{}", f.format());
}
println!();
println!("{}", engine_summary(&engine));
}
fn engine_summary<C: DiagnosticCode>(e: &DiagnosticEngine<C>) -> String {
let mut parts = Vec::new();
if e.bug_count() > 0 {
parts.push(format!("{} internal-error(s)", e.bug_count()));
}
if e.error_count() > 0 {
parts.push(format!("{} error(s)", e.error_count()));
}
if e.warning_count() > 0 {
parts.push(format!("{} warning(s)", e.warning_count()));
}
format!("== summary: {} ==", parts.join(", "))
}