#![cfg(test)]
use crate::ast::{Decl, DiagnosticCategory};
use crate::parse::{parse_module, MAX_RECURSION_DEPTH};
const TEST_RECURSION_DEPTH: usize = MAX_RECURSION_DEPTH as usize + 172;
fn diags(src: &str) -> Vec<crate::ast::ParseDiagnostic> {
parse_module(src).diagnostics
}
#[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).into_parts();
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_syntax_is_unsupported() {
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 malformed_expression_is_categorized_malformed() {
let src = "module M where\nf = if x then 1\n";
let (module, ds) = parse_module(src).into_parts();
assert!(
ds.iter()
.any(|d| d.category == DiagnosticCategory::Malformed),
"missing `else` must surface a Malformed diagnostic, got {:?}",
ds.iter()
.map(|d| (d.category, &d.message))
.collect::<Vec<_>>()
);
assert!(
module
.decls
.iter()
.any(|d| matches!(d, Decl::Function(f) if f.name == "f")),
"the malformed body must not drop the surrounding `f` declaration"
);
}
#[test]
fn deep_nesting_emits_recursion_limit_and_does_not_panic() {
let depth = TEST_RECURSION_DEPTH;
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(TEST_RECURSION_DEPTH);
let unnest = ")".repeat(TEST_RECURSION_DEPTH);
let src = format!("module M where\ng = {nest}1{unnest}\nh = {nest}2{unnest}\n");
let diagnostics = diags(&src);
let recursion_limit_spans = diagnostics
.iter()
.filter(|d| d.category == DiagnosticCategory::RecursionLimit)
.map(|d| d.span)
.collect::<Vec<_>>();
let g_start = src.find("g = ").expect("g declaration start");
let h_start = src.find("h = ").expect("h declaration start");
assert!(
recursion_limit_spans
.iter()
.any(|span| span.start >= g_start && span.start < h_start),
"g declaration must report its own recursion-limit, got {recursion_limit_spans:?}"
);
assert!(
recursion_limit_spans
.iter()
.any(|span| span.start >= h_start),
"h declaration must report its own recursion-limit, got {recursion_limit_spans:?}"
);
}
#[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");
}