#![cfg(test)]
#![allow(clippy::unwrap_used)]
use crate::ir::*;
use crate::parser::parse_daml_with_diagnostics;
use std::fmt::Write as _;
use std::path::Path;
fn parse(source: &str) -> DamlModule {
parse_daml_with_diagnostics(source, Path::new("hostile.daml")).module
}
fn single_var(exprs: &[Expr], expected: &str) -> bool {
matches!(exprs, [Expr::Var { name, .. }] if name == expected)
}
#[test]
fn keywords_in_comments_create_no_structure() {
let m = parse(
"module M where\n\
-- template Fake with choice Evil : () controller attacker\n\
{- template Hidden\n\
with\n\
x : Party\n\
-}\n\
f = 1\n",
);
assert!(m.templates.is_empty());
assert_eq!(m.functions.len(), 1);
}
#[test]
fn keywords_in_strings_create_no_structure() {
let m = parse(
"module M where\n\
f = \"template Fake with x : Party where signatory attacker\"\n\
g = \"exercise cid Evil\"\n",
);
assert!(m.templates.is_empty());
for func in &m.functions {
assert!(
!func
.body
.iter()
.any(|s| matches!(s, Statement::Exercise { .. })),
"string literal must not become an Exercise"
);
}
}
#[test]
fn nested_block_comments_with_fake_terminators() {
let m = parse(
"module M where\n\
{- outer {- inner -} still outer, f_in_comment = 1 -}\n\
real = 2\n",
);
assert_eq!(m.functions.len(), 1);
assert_eq!(m.functions[0].name, "real");
}
#[test]
fn tabs_mixed_with_spaces() {
let m = parse(
"module M where\n\
template T\n\
\twith\n\
\t\tx : Party\n\
\twhere\n\
\t\tsignatory x\n",
);
assert_eq!(m.templates.len(), 1);
assert_eq!(m.templates[0].fields.len(), 1);
assert!(single_var(&m.templates[0].signatory_exprs, "x"));
}
#[test]
fn unicode_identifiers() {
let m = parse(
"module M where\n\
template Vertrag\n with\n eigentümer : Party\n größe : Decimal\n where\n signatory eigentümer\n",
);
assert_eq!(m.templates[0].fields.len(), 2);
assert_eq!(m.templates[0].fields[0].name, "eigentümer");
}
#[test]
fn ten_thousand_line_file_parses_quickly() {
let mut src = String::from("module Big where\n\n");
for i in 0..1000 {
write!(
src,
"template T{i}\n with\n owner : Party\n amount : Decimal\n where\n signatory owner\n ensure amount > 0.0\n\n choice C{i} : ()\n controller owner\n do\n pure ()\n\n"
)
.expect("writing to a String cannot fail");
}
assert!(src.lines().count() > 10_000);
let start = std::time::Instant::now();
let result = parse_daml_with_diagnostics(&src, Path::new("big.daml"));
assert!(result.diagnostics.is_empty());
let m = result.module;
assert_eq!(m.templates.len(), 1000);
assert!(
start.elapsed().as_secs() < 5,
"10k-line file took {:?}",
start.elapsed()
);
}
#[test]
fn deeply_nested_parens_no_stack_overflow() {
let mut src = String::from("module M where\nf = ");
src.push_str(&"(".repeat(5000));
src.push('1');
src.push_str(&")".repeat(5000));
src.push('\n');
let _ = parse(&src); }
#[test]
fn deeply_nested_patterns_no_stack_overflow() {
let mut src = String::from("module M where\nf ");
src.push_str(&"(Just ".repeat(2000));
src.push('x');
src.push_str(&")".repeat(2000));
src.push_str(" = 1\n");
let _ = parse(&src);
}
#[test]
fn unterminated_everything_no_hang() {
for hostile in [
"module M where\nf = \"never closed\ng = 2\n",
"module M where\n{- never closed",
"module M where\nf = (((((\n",
"module M where\ntemplate T\n with\n",
"module M where\nf = do\n",
"module M where\nf = let x = \n",
"template",
"",
"\n\n\n",
"-- only a comment\n",
"\u{FEFF}module M where\nf = 1\n", ] {
let _ = parse(hostile); }
}
#[test]
fn crlf_line_endings() {
let m = parse("module M where\r\n\r\ntemplate T\r\n with\r\n x : Party\r\n where\r\n signatory x\r\n");
assert_eq!(m.templates.len(), 1);
assert_eq!(m.templates[0].fields.len(), 1);
}
#[test]
fn string_with_escaped_quotes_and_comment_markers() {
let m = parse("module M where\nf = \"a \\\" -- not a comment {- not a block\"\ng = 2\n");
assert_eq!(m.functions.len(), 2);
}
#[test]
fn operator_that_looks_like_comment() {
let m = parse("module M where\nf = a --> b\ng = c --- this is a comment\n");
assert_eq!(m.functions.len(), 2);
assert!(m.functions[0].body.iter().any(|s| matches!(
s,
Statement::Other {
expr: Expr::BinOp { op, .. },
..
} if op == "-->"
)));
}
#[test]
fn pathological_one_liner_template() {
let m = parse("module M where\ntemplate T with { x : Party } where { signatory x }\n");
assert_eq!(m.templates.len(), 1);
assert!(single_var(&m.templates[0].signatory_exprs, "x"));
}
#[test]
fn comment_between_template_and_fields() {
let m = parse(concat!(
"module M where\n",
"template T\n",
" -- fields below\n",
" with\n",
" -- the owner\n",
" x : Party\n",
" where\n",
" signatory x\n",
));
assert_eq!(m.templates[0].fields.len(), 1);
}
#[test]
fn operator_binding_in_where_no_hang() {
let m = parse(concat!(
"module M where\n",
"f x = implode x\n",
" where\n",
" implode : [Text] -> Text = primitive @\"BEImplodeText\"\n",
" (==) : Text -> Text -> Bool = primitive @\"BEEqual\"\n",
" helper y = y\n",
"g = 2\n",
));
assert!(m.functions.iter().any(|f| f.name == "g"));
}
#[test]
fn cpp_directives_skipped() {
let m = parse(concat!(
"module M where\n",
"#ifdef DAML_BIGNUMERIC\n",
"f = 1\n",
"#endif\n",
"g = 2\n",
));
assert_eq!(m.functions.len(), 2);
}
#[test]
fn empty_with_block_before_controller() {
let m = parse(concat!(
"module M where\n",
"template T\n",
" with\n",
" owner : Party\n",
" where\n",
" signatory owner\n",
" choice F : ()\n",
" with -- superfluous, no fields\n",
" controller owner\n",
" do pure ()\n",
));
let c = &m.templates[0].choices[0];
assert_eq!(c.name, "F");
assert!(single_var(&c.controller_exprs, "owner"));
assert!(c.parameters.is_empty());
}
#[test]
fn sdk_corpus_syntax_gaps() {
let m = parse(concat!(
"module M where\n",
"f (T.isInfixOf \"x\" -> True) = 1\n",
"f (fromAny @T1 -> Some cid) = 2\n",
));
assert_eq!(m.functions.len(), 1, "view-pattern equations merge");
let m = parse(concat!(
"module M where\n",
"applyFilter (filter : Int -> Int -> Bool) (xs : [Int]) : [Int] = xs\n",
));
assert_eq!(m.functions.len(), 1);
assert!(m.functions[0].name == "applyFilter");
let m = parse(concat!(
"module M where\n",
"f = \\case\n",
" x :: _ -> x\n",
" [] -> 0\n",
"g = foldr (\\(a, b) ~(as, bs) -> (a :: as, b :: bs)) ([], [])\n",
));
assert_eq!(m.functions.len(), 2);
let m = parse(concat!(
"module M where\n",
"[] !! _ = error \"index\"\n",
"(x :: _) !! 0 = x\n",
"None <?> s = invalid s\n",
"Some v <?> _ = pure v\n",
"after = 1\n",
));
assert!(m.functions.iter().any(|f| f.name == "after"));
let m = parse(concat!(
"module M where\n",
"f x = case x of\n",
" Left cmd\n",
" | cmd.name == \"Submit\"\n",
" , Some y <- cmd.detail\n",
" -> y\n",
" _ -> 0\n",
));
assert_eq!(m.functions.len(), 1);
let m = parse(concat!(
"module M where\n",
"template S with p : Party where\n",
" signatory p\n",
));
assert_eq!(m.templates.len(), 1);
assert_eq!(m.templates[0].fields.len(), 1);
assert!(single_var(&m.templates[0].signatory_exprs, "p"));
let m = parse(concat!(
"module M where\n",
"template T\n",
" with\n",
" p : Party\n",
" where\n",
" signatory p\n",
" choice Ham : ContractId T with\n",
" controller p\n",
" do pure self\n",
" choice Spam : () with\n",
" extra : Party\n",
" controller p\n",
" do pure ()\n",
));
let names: Vec<&str> = m.templates[0]
.choices
.iter()
.map(|c| c.name.as_str())
.collect();
assert_eq!(names, vec!["Ham", "Spam"]);
assert_eq!(m.templates[0].choices[1].parameters.len(), 1);
}
#[test]
fn huge_single_line() {
let mut src = String::from("module M where\nf = ");
for i in 0..20_000 {
write!(src, "g{i} ").expect("writing to a String cannot fail");
}
src.push('\n');
let _ = parse(&src);
}