#![cfg(test)]
use crate::ast::{Decl, DiagnosticCategory};
use crate::parse::parse_module;
fn diags(src: &str) -> Vec<crate::ast::ParseDiagnostic> {
parse_module(src).1
}
#[test]
fn skipped_declaration_does_not_abort_later_template() {
let src = "module M where\n\
%%% junk\n\
template Good\n \
with\n o : Party\n \
where\n signatory o\n";
let (module, ds) = parse_module(src);
assert!(
ds.iter()
.any(|d| d.category == DiagnosticCategory::SkippedDecl),
"expected a skipped-declaration diagnostic, got {:?}",
ds.iter()
.map(|d| (d.category, &d.message))
.collect::<Vec<_>>()
);
assert!(
module
.decls
.iter()
.any(|d| matches!(d, Decl::Template(t) if t.name == "Good")),
"template after the junk decl must still parse"
);
}
#[test]
fn legacy_controller_can_is_unsupported_syntax() {
let src = "module M where\n\
template T\n \
with\n o : Party\n \
where\n signatory o\n \
controller o can\n \
Foo : ()\n do pure ()\n";
let ds = diags(src);
assert!(
ds.iter()
.any(|d| d.category == DiagnosticCategory::UnsupportedSyntax),
"legacy controller-can must be flagged unsupported, got {:?}",
ds.iter()
.map(|d| (d.category, &d.message))
.collect::<Vec<_>>()
);
}
#[test]
fn deep_nesting_emits_recursion_limit_and_does_not_panic() {
let depth = 300;
let src = format!(
"module M where\nf = {}1{}\n",
"(".repeat(depth),
")".repeat(depth)
);
let ds = diags(&src);
assert!(
ds.iter()
.any(|d| d.category == DiagnosticCategory::RecursionLimit),
"deep nesting must report recursion-limit, got categories {:?}",
ds.iter().map(|d| d.category).collect::<Vec<_>>()
);
}
#[test]
fn each_deep_declaration_reports_its_own_recursion_limit() {
let nest = "(".repeat(300);
let unnest = ")".repeat(300);
let src = format!("module M where\ng = {nest}1{unnest}\nh = {nest}2{unnest}\n");
let count = diags(&src)
.iter()
.filter(|d| d.category == DiagnosticCategory::RecursionLimit)
.count();
assert!(
count >= 2,
"each over-deep declaration must report its own recursion-limit; got {count}"
);
}
#[test]
fn lex_error_span_is_tab_correct() {
let src = "module M where\n\tx = \"unterminated\n";
let ds = diags(src);
let lex = ds
.iter()
.find(|d| d.category == DiagnosticCategory::Lex)
.expect("lexical-error diagnostic");
assert_eq!(
&src[lex.span.start..lex.span.start + 1],
"\"",
"lex span must point at the opening quote, not a tab-naive offset"
);
}
#[test]
fn diagnostic_span_pins_the_offending_token() {
let src = "module M where\n%%% junk\n";
let ds = diags(src);
let skipped = ds
.iter()
.find(|d| d.category == DiagnosticCategory::SkippedDecl)
.expect("skipped-declaration diagnostic");
assert!(
skipped.span.end > skipped.span.start,
"span must have a real end: {:?}",
skipped.span
);
assert!(skipped.span.end <= src.len());
assert_eq!(&src[skipped.span.start..skipped.span.end], "%%%");
}
#[test]
fn lexical_error_is_categorized_lex() {
let src = "module M where\nf = \"unterminated\n";
let ds = diags(src);
assert!(
ds.iter().any(|d| d.category == DiagnosticCategory::Lex),
"unterminated string must surface a lexical-error diagnostic, got {:?}",
ds.iter()
.map(|d| (d.category, &d.message))
.collect::<Vec<_>>()
);
}
#[test]
fn category_tags_are_stable_kebab_case() {
assert_eq!(
DiagnosticCategory::SkippedDecl.as_str(),
"skipped-declaration"
);
assert_eq!(DiagnosticCategory::Malformed.as_str(), "malformed");
assert_eq!(
DiagnosticCategory::UnsupportedSyntax.as_str(),
"unsupported-syntax"
);
assert_eq!(
DiagnosticCategory::RecursionLimit.as_str(),
"recursion-limit"
);
assert_eq!(DiagnosticCategory::Lex.as_str(), "lexical-error");
}