use super::*;
#[test]
fn integer_literal() {
let p = assert_lossless("var x = 5\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::INTEGER_LIT));
}
#[test]
fn float_literal() {
let p = assert_lossless("var x = 3.14\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::FLOAT_LIT));
}
#[test]
fn boolean_literal_true() {
let p = assert_lossless("var x = true\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::BOOLEAN_LIT));
}
#[test]
fn boolean_literal_false() {
let p = assert_lossless("var x = false\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::BOOLEAN_LIT));
}
#[test]
fn string_literal() {
let p = assert_lossless("var x = \"hello\"\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::STRING_LIT));
}
#[test]
fn string_literal_empty() {
let p = assert_lossless("var x = \"\"\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::STRING_LIT));
}
#[test]
fn string_literal_with_interpolation_nests_an_expression() {
let p = assert_lossless("var x = \"hi {name}\"\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::STRING_LIT));
assert!(has_node_kind(&p.syntax(), SyntaxKind::INTERPOLATION));
let string_lit = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::STRING_LIT)
.expect("STRING_LIT");
assert!(has_node_kind(&string_lit, SyntaxKind::PATH_EXPR));
}
#[test]
fn path_single_segment() {
let p = assert_lossless("var x = y\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::PATH_EXPR));
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::PATH_SEGMENT), 1);
}
#[test]
fn path_two_segments_dot() {
let p = assert_lossless("var x = a.b\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let path_expr = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PATH_EXPR)
.expect("PATH_EXPR");
let path: ast::Path = find_child(&path_expr).expect("PATH");
let segs: Vec<_> = path.segments().map(|t| t.text().to_string()).collect();
assert_eq!(segs, vec!["a".to_string(), "b".to_string()]);
assert!(!path.crosses_module_wall());
}
#[test]
fn path_three_segments_dot() {
let p = assert_lossless("var x = a.b.c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let path_expr = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PATH_EXPR)
.expect("PATH_EXPR");
let path: ast::Path = find_child(&path_expr).expect("PATH");
assert_eq!(path.segments().count(), 3);
}
#[test]
fn path_double_colon_crosses_module_wall() {
let p = assert_lossless("var x = a::b\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let path_expr = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PATH_EXPR)
.expect("PATH_EXPR");
let path: ast::Path = find_child(&path_expr).expect("PATH");
assert!(path.crosses_module_wall());
}
#[test]
fn path_mixed_dot_and_double_colon() {
let p = assert_lossless("var x = a::b.c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let path_expr = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PATH_EXPR)
.expect("PATH_EXPR");
let path: ast::Path = find_child(&path_expr).expect("PATH");
let segs: Vec<_> = path.segments().map(|t| t.text().to_string()).collect();
assert_eq!(
segs,
vec!["a".to_string(), "b".to_string(), "c".to_string()]
);
assert!(path.crosses_module_wall());
}
#[test]
fn prefix_negate_integer() {
let p = assert_lossless("var x = -1\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::PREFIX_EXPR));
}
#[test]
fn prefix_bang_path() {
let p = assert_lossless("var x = !flag\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::PREFIX_EXPR));
}
#[test]
fn prefix_negate_paren() {
let p = assert_lossless("var x = -(a + b)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let prefix = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PREFIX_EXPR)
.expect("PREFIX_EXPR");
assert!(has_node_kind(&prefix, SyntaxKind::PAREN_EXPR));
}
#[test]
fn prefix_double_negate_is_nested_prefix() {
let p = assert_lossless("var x = --y\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::PREFIX_EXPR), 2);
let outer: ast::PrefixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::PrefixExpr::cast)
.expect("outer PREFIX_EXPR");
let operand = outer.operand().expect("operand");
assert_eq!(
operand.kind(),
SyntaxKind::PREFIX_EXPR,
"outer's operand is the inner prefix"
);
}
#[test]
fn prefix_bang_bang_is_nested_prefix() {
let p = assert_lossless("var x = !!flag\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::PREFIX_EXPR), 2);
}
fn infix_op_test(src: &str, op: SyntaxKind) {
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "{src:?} errors: {:?}", p.errors());
let infix: ast::InfixExpr = p
.syntax()
.descendants()
.find_map(ast::InfixExpr::cast)
.expect("INFIX_EXPR should be present");
assert_eq!(
infix.op_token().map(|t| t.kind()),
Some(op),
"{src:?}: unexpected operator token"
);
assert!(infix.lhs().is_some(), "{src:?}: missing lhs");
assert!(infix.rhs().is_some(), "{src:?}: missing rhs");
}
#[test]
fn infix_plus() {
infix_op_test("var x = a + b\n", SyntaxKind::PLUS);
}
#[test]
fn infix_minus() {
infix_op_test("var x = a - b\n", SyntaxKind::MINUS);
}
#[test]
fn infix_star() {
infix_op_test("var x = a * b\n", SyntaxKind::STAR);
}
#[test]
fn infix_slash() {
infix_op_test("var x = a / b\n", SyntaxKind::SLASH);
}
#[test]
fn infix_percent() {
infix_op_test("var x = a % b\n", SyntaxKind::PERCENT);
}
#[test]
fn infix_lt() {
infix_op_test("var x = a < b\n", SyntaxKind::LT);
}
#[test]
fn infix_gt() {
infix_op_test("var x = a > b\n", SyntaxKind::GT);
}
#[test]
fn infix_lte() {
infix_op_test("var x = a <= b\n", SyntaxKind::LT_EQ);
}
#[test]
fn infix_gte() {
infix_op_test("var x = a >= b\n", SyntaxKind::GT_EQ);
}
#[test]
fn infix_eq_eq() {
infix_op_test("var x = a == b\n", SyntaxKind::EQ_EQ);
}
#[test]
fn infix_bang_eq() {
infix_op_test("var x = a != b\n", SyntaxKind::BANG_EQ);
}
#[test]
fn infix_amp_amp() {
infix_op_test("var x = a && b\n", SyntaxKind::AMP_AMP);
}
#[test]
fn infix_pipe_pipe() {
let p = assert_lossless("var x = a || b\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let infix: ast::InfixExpr = p
.syntax()
.descendants()
.find_map(ast::InfixExpr::cast)
.expect("INFIX_EXPR");
assert_eq!(infix.op_token().map(|t| t.kind()), Some(SyntaxKind::PIPE));
assert!(infix.is_double_pipe());
}
#[test]
fn infix_pipe_pipe_paren_rhs_does_not_double_wrap() {
let p = assert_lossless("var x = 0 || (0)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let infix: ast::InfixExpr = p
.syntax()
.descendants()
.find_map(ast::InfixExpr::cast)
.expect("INFIX_EXPR");
let rhs = infix.rhs().expect("rhs");
assert_eq!(rhs.kind(), SyntaxKind::PAREN_EXPR);
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::PAREN_EXPR), 1);
}
#[test]
fn prec_coalesce_over_eq() {
let p = assert_lossless("var x = a or b == c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::KW_OR));
assert_eq!(
outer.lhs().map(|n| n.kind()),
Some(SyntaxKind::PATH_EXPR),
"lhs should be the bare `a`"
);
let rhs = outer.rhs().expect("rhs");
assert_eq!(
rhs.kind(),
SyntaxKind::INFIX_EXPR,
"`b == c` should nest under `or` as its RHS — see the section doc above"
);
let inner = ast::InfixExpr::cast(rhs).expect("inner INFIX_EXPR");
assert_eq!(inner.op_token().map(|t| t.kind()), Some(SyntaxKind::EQ_EQ));
}
#[test]
fn prec_coalesce_over_double_pipe() {
let p = assert_lossless("var x = a || b or c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::KW_OR));
let lhs = outer.lhs().expect("lhs");
assert_eq!(
lhs.kind(),
SyntaxKind::INFIX_EXPR,
"`a || b` should nest under `or` as its LHS — see the section doc above"
);
let inner = ast::InfixExpr::cast(lhs).expect("inner INFIX_EXPR");
assert!(inner.is_double_pipe());
assert_eq!(
outer.rhs().map(|n| n.kind()),
Some(SyntaxKind::PATH_EXPR),
"rhs should be the bare `c`"
);
}
#[test]
fn prec_coalesce_chain_is_left_associative() {
let p = assert_lossless("var x = a or b or c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::KW_OR));
let lhs = outer.lhs().expect("lhs");
assert_eq!(
lhs.kind(),
SyntaxKind::INFIX_EXPR,
"`a or b or c` should parse left-associative as `(a or b) or c` \
(INFIX_EXPR on the LHS)"
);
assert_eq!(
outer.rhs().map(|n| n.kind()),
Some(SyntaxKind::PATH_EXPR),
"rhs should be the bare `c` under left-associative parsing"
);
}
#[test]
fn prec_mul_over_add() {
let p = assert_lossless("var x = 1 + 2 * 3\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::PLUS));
let rhs = outer.rhs().expect("rhs");
assert_eq!(rhs.kind(), SyntaxKind::INFIX_EXPR);
let inner = ast::InfixExpr::cast(rhs).expect("inner INFIX_EXPR");
assert_eq!(inner.op_token().map(|t| t.kind()), Some(SyntaxKind::STAR));
}
#[test]
fn prec_mul_then_add() {
let p = assert_lossless("var x = 1 * 2 + 3\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::PLUS));
let lhs = outer.lhs().expect("lhs");
assert_eq!(lhs.kind(), SyntaxKind::INFIX_EXPR);
}
#[test]
fn prec_and_over_or() {
let p = assert_lossless("var x = a && b || c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert!(outer.is_double_pipe());
let lhs = outer.lhs().expect("lhs");
assert_eq!(lhs.kind(), SyntaxKind::INFIX_EXPR);
let inner = ast::InfixExpr::cast(lhs).expect("inner INFIX_EXPR");
assert_eq!(
inner.op_token().map(|t| t.kind()),
Some(SyntaxKind::AMP_AMP)
);
}
#[test]
fn prec_eq_over_and() {
let p = assert_lossless("var x = a == b && c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(
outer.op_token().map(|t| t.kind()),
Some(SyntaxKind::AMP_AMP)
);
let lhs = outer.lhs().expect("lhs");
let inner = ast::InfixExpr::cast(lhs).expect("inner INFIX_EXPR");
assert_eq!(inner.op_token().map(|t| t.kind()), Some(SyntaxKind::EQ_EQ));
}
#[test]
fn prec_cmp_over_eq() {
let p = assert_lossless("var x = a < b == c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::EQ_EQ));
let lhs = outer.lhs().expect("lhs");
let inner = ast::InfixExpr::cast(lhs).expect("inner INFIX_EXPR");
assert_eq!(inner.op_token().map(|t| t.kind()), Some(SyntaxKind::LT));
}
#[test]
fn prec_add_over_cmp() {
let p = assert_lossless("var x = a + b < c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::LT));
let lhs = outer.lhs().expect("lhs");
let inner = ast::InfixExpr::cast(lhs).expect("inner INFIX_EXPR");
assert_eq!(inner.op_token().map(|t| t.kind()), Some(SyntaxKind::PLUS));
}
#[test]
fn mixed_precedence_three_levels() {
let p = assert_lossless("var x = 1 + 2 * 3 > 4\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let gt: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(gt.op_token().map(|t| t.kind()), Some(SyntaxKind::GT));
let plus = ast::InfixExpr::cast(gt.lhs().expect("lhs")).expect("+ node");
assert_eq!(plus.op_token().map(|t| t.kind()), Some(SyntaxKind::PLUS));
let star = ast::InfixExpr::cast(plus.rhs().expect("rhs")).expect("* node");
assert_eq!(star.op_token().map(|t| t.kind()), Some(SyntaxKind::STAR));
}
#[test]
fn prefix_binds_tighter_than_infix() {
let p = assert_lossless("var x = -a + b\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let plus: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(plus.op_token().map(|t| t.kind()), Some(SyntaxKind::PLUS));
let lhs = plus.lhs().expect("lhs");
assert_eq!(lhs.kind(), SyntaxKind::PREFIX_EXPR);
}
#[test]
fn minus_chain_is_left_associative() {
let p = assert_lossless("var x = a - b - c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(outer.op_token().map(|t| t.kind()), Some(SyntaxKind::MINUS));
let lhs = outer.lhs().expect("lhs");
assert_eq!(
lhs.kind(),
SyntaxKind::INFIX_EXPR,
"`a - b - c` should parse left-associative as `(a - b) - c` \
(INFIX_EXPR on the LHS) — see the section doc above"
);
assert_eq!(
outer.rhs().map(|n| n.kind()),
Some(SyntaxKind::PATH_EXPR),
"rhs should be the bare `c` under left-associative parsing"
);
}
#[test]
fn slash_chain_is_left_associative() {
let p = assert_lossless("var x = a / b / c\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let outer: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
let lhs = outer.lhs().expect("lhs");
assert_eq!(
lhs.kind(),
SyntaxKind::INFIX_EXPR,
"same left-associativity fix as `-`, see `minus_chain_is_left_associative`"
);
}
#[test]
fn paren_simple() {
let p = assert_lossless("var x = (1 + 2)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let paren: ast::ParenExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::ParenExpr::cast)
.expect("PAREN_EXPR");
let inner = paren.inner().expect("inner");
assert_eq!(inner.kind(), SyntaxKind::INFIX_EXPR);
}
#[test]
fn paren_nested() {
let p = assert_lossless("var x = ((a))\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::PAREN_EXPR), 2);
}
#[test]
fn paren_overrides_precedence() {
let p = assert_lossless("var x = (1 + 2) * 3\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let star: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(star.op_token().map(|t| t.kind()), Some(SyntaxKind::STAR));
let lhs = star.lhs().expect("lhs");
assert_eq!(lhs.kind(), SyntaxKind::PAREN_EXPR);
}
#[test]
fn call_zero_args() {
let p = assert_lossless("var x = foo()\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let callee = call.callee().expect("callee");
assert_eq!(
callee
.segments()
.map(|t| t.text().to_string())
.collect::<Vec<_>>(),
vec!["foo".to_string()]
);
let args = call.arg_list().expect("arg list");
assert!(args.is_open());
assert_eq!(args.syntax().children().count(), 0);
}
#[test]
fn call_one_arg() {
let p = assert_lossless("var x = foo(1)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let args = call.arg_list().expect("arg list");
assert_eq!(args.syntax().children().count(), 1);
}
#[test]
fn call_many_args() {
let p = assert_lossless("var x = foo(1, 2, 3)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let args = call.arg_list().expect("arg list");
assert_eq!(args.syntax().children().count(), 3);
}
#[test]
fn call_trailing_comma() {
let p = assert_lossless("var x = foo(1, 2,)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let args = call.arg_list().expect("arg list");
assert_eq!(args.syntax().children().count(), 2);
}
#[test]
fn call_arg_is_an_expression() {
let p = assert_lossless("var x = foo(1 + 2)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let args = call.arg_list().expect("arg list");
let first = args.syntax().children().next().expect("first arg");
assert_eq!(first.kind(), SyntaxKind::INFIX_EXPR);
}
#[test]
fn call_nested() {
let p = assert_lossless("var x = foo(bar(y))\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::CALL_EXPR), 2);
}
#[test]
fn call_dotted_callee() {
let p = assert_lossless("var x = a.b.c(1)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let call: ast::CallExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::CallExpr::cast)
.expect("CALL_EXPR");
let callee = call.callee().expect("callee");
assert_eq!(callee.segments().count(), 3);
}
#[test]
fn call_as_infix_operand() {
let p = assert_lossless("var x = foo(1) + bar(2)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let plus: ast::InfixExpr = find_child(&p.syntax())
.and_then(|vd: ast::VarDecl| vd.value())
.and_then(ast::InfixExpr::cast)
.expect("outer INFIX_EXPR");
assert_eq!(plus.lhs().map(|n| n.kind()), Some(SyntaxKind::CALL_EXPR));
assert_eq!(plus.rhs().map(|n| n.kind()), Some(SyntaxKind::CALL_EXPR));
}
#[test]
fn dotted_without_call_is_path_expr_not_call_expr() {
let p = assert_lossless("var x = a.b\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::PATH_EXPR));
let path_expr = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PATH_EXPR)
.expect("PATH_EXPR");
assert!(!has_node_kind(&path_expr, SyntaxKind::CALL_EXPR));
}
fn lambda_param_names(params: &SyntaxNode) -> Vec<String> {
params
.children()
.filter_map(ast::Param::cast)
.filter_map(|p| p.name_token())
.map(|t| t.text().to_string())
.collect()
}
#[test]
fn lambda_pipe_tokenizes_and_parses() {
let src = "var f = |x, y| x + y\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn lambda_zero_params() {
let p = assert_lossless("var f = || 1\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::LAMBDA_EXPR)
.expect("LAMBDA_EXPR");
let params = lambda
.children()
.find(|n| n.kind() == SyntaxKind::LAMBDA_PARAMS)
.expect("LAMBDA_PARAMS");
assert_eq!(count_node_kind(¶ms, SyntaxKind::PATH), 0);
}
#[test]
fn lambda_one_param() {
let p = assert_lossless("var f = |x| x\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::LAMBDA_EXPR)
.expect("LAMBDA_EXPR");
let params = lambda
.children()
.find(|n| n.kind() == SyntaxKind::LAMBDA_PARAMS)
.expect("LAMBDA_PARAMS");
let idents = lambda_param_names(¶ms);
assert_eq!(idents, vec!["x".to_string()]);
}
#[test]
fn lambda_multiple_params() {
let p = assert_lossless("var f = |x, y, z| x\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::LAMBDA_EXPR)
.expect("LAMBDA_EXPR");
let params = lambda
.children()
.find(|n| n.kind() == SyntaxKind::LAMBDA_PARAMS)
.expect("LAMBDA_PARAMS");
let idents = lambda_param_names(¶ms);
assert_eq!(
idents,
vec!["x".to_string(), "y".to_string(), "z".to_string()]
);
}
#[test]
fn lambda_params_trailing_comma() {
let p = assert_lossless("var f = |x, y,| x\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::LAMBDA_EXPR)
.expect("LAMBDA_EXPR");
let params = lambda
.children()
.find(|n| n.kind() == SyntaxKind::LAMBDA_PARAMS)
.expect("LAMBDA_PARAMS");
let idents = lambda_param_names(¶ms);
assert_eq!(idents, vec!["x".to_string(), "y".to_string()]);
}
#[test]
fn lambda_body_is_a_full_expression() {
let p = assert_lossless("var f = |x| x + 1 * 2\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::LAMBDA_EXPR)
.expect("LAMBDA_EXPR");
assert!(has_node_kind(&lambda, SyntaxKind::INFIX_EXPR));
}
#[test]
fn lambda_nested_in_call_argument() {
let p = assert_lossless("var x = apply(|n| n + 1, 5)\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::LAMBDA_EXPR));
assert!(has_node_kind(&p.syntax(), SyntaxKind::CALL_EXPR));
}
fn lambda_of(p: &Parse) -> ast::LambdaExpr {
p.syntax()
.descendants()
.find_map(ast::LambdaExpr::cast)
.expect("LAMBDA_EXPR")
}
fn lambda_params_of(lambda: &ast::LambdaExpr) -> SyntaxNode {
lambda
.syntax()
.children()
.find(|n| n.kind() == SyntaxKind::LAMBDA_PARAMS)
.expect("LAMBDA_PARAMS")
}
#[test]
fn lambda_param_takes_a_type_annotation() {
let p = assert_lossless("var f = |g: Guest| g\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
let params = lambda_params_of(&lambda);
assert_eq!(lambda_param_names(¶ms), vec!["g".to_string()]);
let param = params.children().find_map(ast::Param::cast).expect("PARAM");
let te = param
.type_annotation()
.expect("annotation")
.type_expr()
.expect("type expr");
let Some(ast::TypeExprKind::Name(n)) = te.kind() else {
unreachable!("expected a nominal type, tree: {:#?}", te.syntax())
};
assert_eq!(n.name(), Some("Guest".to_string()));
}
#[test]
fn lambda_takes_a_colon_return_annotation_before_a_braced_body() {
let p = assert_lossless("var f = |g: Guest|: bool { g }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
let annotation = lambda
.syntax()
.children()
.find_map(ast::TypeAnnotation::cast)
.expect("the lambda's own `: bool` return annotation");
let te = annotation.type_expr().expect("type expr");
let Some(ast::TypeExprKind::Name(n)) = te.kind() else {
unreachable!("expected a nominal type, tree: {:#?}", te.syntax())
};
assert_eq!(n.name(), Some("bool".to_string()));
assert!(
!has_node_kind(lambda.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
"the body brace must not read as a construction literal, tree: {:#?}",
lambda.syntax()
);
assert!(has_node_kind(lambda.syntax(), SyntaxKind::STMT_BLOCK));
}
#[test]
fn lambda_param_takes_a_generic_type_annotation() {
let p = assert_lossless("var f = |y: Option<int>| { y }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
let params = lambda_params_of(&lambda);
let param = params.children().find_map(ast::Param::cast).expect("PARAM");
let te = param
.type_annotation()
.expect("annotation")
.type_expr()
.expect("type expr");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("Option".to_string()));
assert_eq!(g.args().count(), 1);
}
#[test]
fn lambda_return_annotation_takes_a_generic_type() {
let p = assert_lossless("var f = |y: int|: Option<int> { none }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
let annotation = lambda
.syntax()
.children()
.find_map(ast::TypeAnnotation::cast)
.expect("the lambda's own return annotation");
let te = annotation.type_expr().expect("type expr");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("Option".to_string()));
assert_eq!(g.args().count(), 1);
}
#[test]
fn lambda_param_generic_annotation_surface() {
fn type_expr_of(src: &str) -> ast::TypeExpr {
let p = assert_lossless(src);
assert!(
p.errors().is_empty(),
"src={src:?} errors: {:?}",
p.errors()
);
let lambda = lambda_of(&p);
let params = lambda_params_of(&lambda);
let param = params.children().find_map(ast::Param::cast).expect("PARAM");
param
.type_annotation()
.expect("annotation")
.type_expr()
.expect("type expr")
}
let te = type_expr_of("var f = |y: Array<int>| { y }\n");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("Array".to_string()));
assert_eq!(g.args().count(), 1);
let te = type_expr_of("var f = |y: Map<string, int>| { y }\n");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("Map".to_string()));
assert_eq!(g.args().count(), 2);
let te = type_expr_of("var f = |y: Option<Array<int>>| { y }\n");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("Option".to_string()));
let mut args = g.args();
let arg = args.next().expect("Option's single type argument");
assert!(
args.next().is_none(),
"Option<Array<int>> takes exactly one type argument"
);
let Some(ast::TypeExprKind::Generic(inner)) = arg.kind() else {
unreachable!(
"expected the nested arg to be a generic type, tree: {:#?}",
arg.syntax()
)
};
assert_eq!(inner.name(), Some("Array".to_string()));
assert_eq!(inner.args().count(), 1);
let te = type_expr_of("var f = |y: Option| { y }\n");
let Some(ast::TypeExprKind::Name(n)) = te.kind() else {
unreachable!("expected a nominal type, tree: {:#?}", te.syntax())
};
assert_eq!(n.name(), Some("Option".to_string()));
}
#[test]
fn lambda_param_square_bracket_generic_fails_in_lambda_param_fn_param_and_return_position() {
const UNIFIED_MESSAGE: &str = "expected `<` or end of type name, found L_BRACKET";
let lambda = assert_lossless("var f = |y: Option[int]| { y }\n");
assert_eq!(
lambda.errors().first().map(|e| e.message.as_str()),
Some(UNIFIED_MESSAGE),
"errors: {:?}",
lambda.errors()
);
let fn_param = parse("fn f(x: Option[int]) {}\n");
assert_eq!(
fn_param.errors().first().map(|e| e.message.as_str()),
Some(UNIFIED_MESSAGE),
"errors: {:?}",
fn_param.errors()
);
let fn_return = parse("fn f(): Option[int] { none }\n");
assert_eq!(
fn_return.errors().first().map(|e| e.message.as_str()),
Some(UNIFIED_MESSAGE),
"errors: {:?}",
fn_return.errors()
);
let var_decl = parse("var x: Option[int] = none\n");
assert_eq!(
var_decl.errors().first().map(|e| e.message.as_str()),
Some(UNIFIED_MESSAGE),
"errors: {:?}",
var_decl.errors()
);
let const_decl = parse("const MAX: Option[int] = none\n");
assert_eq!(
const_decl.errors().first().map(|e| e.message.as_str()),
Some(UNIFIED_MESSAGE),
"errors: {:?}",
const_decl.errors()
);
}
#[test]
fn lambda_return_annotation_array_literal_body_after_generic_return_type_is_not_a_false_positive() {
let p = assert_lossless("var f = |x: int|: List<int> [1, 2]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
let annotation = lambda
.syntax()
.children()
.find_map(ast::TypeAnnotation::cast)
.expect("the lambda's own return annotation");
let te = annotation.type_expr().expect("type expr");
let Some(ast::TypeExprKind::Generic(g)) = te.kind() else {
unreachable!("expected a generic type, tree: {:#?}", te.syntax())
};
assert_eq!(g.name(), Some("List".to_string()));
let body = lambda.body().expect("lambda body");
assert_eq!(
body.kind(),
SyntaxKind::ARRAY_LITERAL,
"body should be the [1, 2] array literal, not swallowed into the return type; tree: {body:#?}"
);
}
#[test]
fn lambda_return_annotation_square_bracket_mistake_is_a_known_silent_drop_not_a_diagnostic() {
let p = parse("var f = |y: int|: Option[int] { none }\n");
assert!(
p.errors().is_empty(),
"the false-positive fix (issue #2792 review) exempts this position \
entirely, so this known silent drop should produce zero \
diagnostics, not the unified message; errors: {:?}",
p.errors()
);
let lambda = lambda_of(&p);
let body = lambda.body().expect("lambda body");
assert_eq!(
body.kind(),
SyntaxKind::ARRAY_LITERAL,
"the leftover `[int]` is silently read as the body, dropping the \
real ` {{ none }}` body; tree: {body:#?}"
);
}
#[test]
fn zero_arg_lambda_takes_a_return_annotation() {
let p = assert_lossless("var f = ||: int { 1 }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
assert!(lambda_param_names(&lambda_params_of(&lambda)).is_empty());
assert!(
lambda
.syntax()
.children()
.any(|n| n.kind() == SyntaxKind::TYPE_ANNOTATION)
);
}
#[test]
fn unannotated_lambda_has_no_return_annotation() {
let p = assert_lossless("var f = |x| x\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
assert!(
!lambda
.syntax()
.children()
.any(|n| n.kind() == SyntaxKind::TYPE_ANNOTATION)
);
}
#[test]
fn a_lambda_key_in_a_construction_entry_keeps_the_entry_colon() {
let p = assert_lossless("var m = Map { \"k\": |x| x }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lambda = lambda_of(&p);
assert!(
!lambda
.syntax()
.children()
.any(|n| n.kind() == SyntaxKind::TYPE_ANNOTATION)
);
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY));
}
#[test]
fn integer_lit_value_accessor() {
let p = parse("var x = 42\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let lit = ast::IntegerLit::cast(value).expect("INTEGER_LIT");
assert_eq!(lit.value(), Some(42));
}
#[test]
fn float_lit_value_accessor() {
let p = parse("var x = 3.5\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let lit = ast::FloatLit::cast(value).expect("FLOAT_LIT");
assert!((lit.value().expect("float value") - 3.5).abs() < f64::EPSILON);
}
#[test]
fn boolean_lit_value_accessor_true_and_false() {
for (src, expected) in [("var x = true\n", true), ("var x = false\n", false)] {
let p = parse(src);
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let lit = ast::BooleanLit::cast(value).expect("BOOLEAN_LIT");
assert_eq!(lit.value(), Some(expected), "{src:?}");
}
}
#[test]
fn prefix_expr_op_token_and_operand_accessors() {
let p = parse("var x = -a\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let prefix = ast::PrefixExpr::cast(value).expect("PREFIX_EXPR");
assert_eq!(prefix.op_token().map(|t| t.kind()), Some(SyntaxKind::MINUS));
assert_eq!(
prefix.operand().map(|n| n.kind()),
Some(SyntaxKind::PATH_EXPR)
);
}
#[test]
fn call_expr_callee_and_arg_list_accessors_multi_arg() {
let p = parse("const x = compute(a, 1 + 2, foo())\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let const_decl: ast::ConstDecl = find_child(file.syntax()).expect("const decl");
let value = const_decl.value().expect("initializer node");
let call = ast::CallExpr::cast(value).expect("CALL_EXPR");
let callee = call.callee().expect("callee");
assert_eq!(
callee
.segments()
.map(|t| t.text().to_string())
.collect::<Vec<_>>(),
vec!["compute".to_string()]
);
let args = call.arg_list().expect("arg list");
let kinds: Vec<_> = args.syntax().children().map(|n| n.kind()).collect();
assert_eq!(
kinds,
vec![
SyntaxKind::PATH_EXPR,
SyntaxKind::INFIX_EXPR,
SyntaxKind::CALL_EXPR,
]
);
}
#[test]
fn paren_expr_inner_accessor() {
let p = parse("var x = (a)\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let paren = ast::ParenExpr::cast(value).expect("PAREN_EXPR");
assert_eq!(paren.inner().map(|n| n.kind()), Some(SyntaxKind::PATH_EXPR));
}
#[test]
fn path_expr_path_accessor() {
let p = parse("var x = knot.stitch\n");
let file = ast::SourceFile::cast(p.syntax()).expect("SOURCE_FILE");
let var_decl: ast::VarDecl = find_child(file.syntax()).expect("var decl");
let value = var_decl.value().expect("initializer node");
let path_expr = ast::PathExpr::cast(value).expect("PATH_EXPR");
let path = path_expr.path().expect("path");
assert_eq!(
path.segments()
.map(|t| t.text().to_string())
.collect::<Vec<_>>(),
vec!["knot".to_string(), "stitch".to_string()]
);
}
fn assert_infix_has_two_node_children(src: &str) {
let p = parse(src);
assert!(p.errors().is_empty(), "unexpected errors: {:?}", p.errors());
for node in p.syntax().descendants() {
if node.kind() == SyntaxKind::INFIX_EXPR {
let child_count = node.children().count();
assert_eq!(
child_count, 2,
"INFIX_EXPR should have exactly 2 node children, found {child_count} in `{src}`"
);
}
}
}
fn assert_prefix_has_one_node_child(src: &str) {
let p = parse(src);
assert!(p.errors().is_empty(), "unexpected errors: {:?}", p.errors());
for node in p.syntax().descendants() {
if node.kind() == SyntaxKind::PREFIX_EXPR {
let child_count = node.children().count();
assert_eq!(
child_count, 1,
"PREFIX_EXPR should have exactly 1 node child, found {child_count} in `{src}`"
);
}
}
}
#[test]
fn invariant_infix_simple() {
assert_infix_has_two_node_children("var x = a + b\n");
}
#[test]
fn invariant_infix_chained_precedence() {
assert_infix_has_two_node_children("var x = 1 + 2 * 3\n");
}
#[test]
fn invariant_infix_comparison() {
assert_infix_has_two_node_children("var x = a > 5\n");
}
#[test]
fn invariant_infix_double_pipe() {
assert_infix_has_two_node_children("var x = a || b\n");
}
#[test]
fn invariant_prefix_negate() {
assert_prefix_has_one_node_child("var x = -1\n");
}
#[test]
fn invariant_prefix_bang() {
assert_prefix_has_one_node_child("var x = !flag\n");
}
#[test]
fn invariant_call_expr_first_child_is_path() {
for src in [
"var x = foo()\n",
"var x = foo(1, 2)\n",
"var x = foo(bar(y))\n",
] {
let p = parse(src);
assert!(p.errors().is_empty(), "{src:?} errors: {:?}", p.errors());
for node in p.syntax().descendants() {
if node.kind() == SyntaxKind::CALL_EXPR {
let first_child = node
.children()
.next()
.expect("CALL_EXPR should have at least one child");
assert_eq!(
first_child.kind(),
SyntaxKind::PATH,
"CALL_EXPR first child should be PATH in `{src}`"
);
}
}
}
}
#[test]
fn integer_literal_not_float_literal() {
let p = parse("var x = 5\n");
assert!(has_node_kind(&p.syntax(), SyntaxKind::INTEGER_LIT));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::FLOAT_LIT));
}
#[test]
fn float_literal_not_integer_literal() {
let p = parse("var x = 5.0\n");
assert!(has_node_kind(&p.syntax(), SyntaxKind::FLOAT_LIT));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::INTEGER_LIT));
}
#[test]
fn call_not_paren() {
let p = parse("var x = foo(y)\n");
assert!(has_node_kind(&p.syntax(), SyntaxKind::CALL_EXPR));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::PAREN_EXPR));
}
#[test]
fn paren_not_call() {
let p = parse("var x = (1 + 2)\n");
assert!(has_node_kind(&p.syntax(), SyntaxKind::PAREN_EXPR));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::CALL_EXPR));
}
#[test]
fn call_not_lambda() {
let p = parse("var x = foo(1)\n");
assert!(has_node_kind(&p.syntax(), SyntaxKind::CALL_EXPR));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::LAMBDA_EXPR));
}
#[test]
fn error_unterminated_string() {
let src = "var x = \"hello\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for unterminated string"
);
}
#[test]
fn error_missing_rparen_call() {
let src = "var x = foo(1, 2\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for missing `)` in call"
);
}
#[test]
fn error_missing_rparen_paren_expr() {
let src = "var x = (1 + 2\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for missing `)` in paren expression"
);
}
#[test]
fn error_missing_operand_after_infix() {
let src = "var x = 1 +\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected parse error for a dangling infix operator"
);
}
#[test]
fn error_missing_operand_at_eof_no_trailing_newline() {
let src = "var x = 1 +";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn error_empty_parens_has_no_expression() {
let src = "var x = ()\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
let paren = p
.syntax()
.descendants()
.find(|n| n.kind() == SyntaxKind::PAREN_EXPR)
.expect("PAREN_EXPR still opens");
assert_eq!(paren.children().count(), 0);
}
#[test]
fn error_malformed_arg_list_leading_comma() {
let src = "var x = foo(,)\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
assert!(has_node_kind(&p.syntax(), SyntaxKind::ERROR));
}
#[test]
fn error_malformed_arg_list_double_comma() {
let src = "var x = foo(1,,2)\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn error_unclosed_call_at_eof() {
let src = "var x = foo(";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn error_unclosed_lambda_pipe_still_recovers_a_body() {
let src = "var f = |x, y expr\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
assert!(has_node_kind(&p.syntax(), SyntaxKind::LAMBDA_EXPR));
}
#[test]
fn error_unexpected_token_cannot_start_expression() {
let src = "var x = +\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn error_unexpected_token_percent_cannot_start_expression() {
let src = "var x = %5\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn adversarial_deeply_nested_parens_does_not_panic() {
let src = format!("var x = {}1{}\n", "(".repeat(300), ")".repeat(300));
let p = parse(&src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(
!p.errors().is_empty(),
"expected a max-nesting-depth error, not silent success or a panic"
);
}
#[test]
fn adversarial_deeply_nested_calls_does_not_panic() {
let mut src = "var x = ".to_string();
for _ in 0..300 {
src.push_str("foo(");
}
src.push('1');
src.push_str(&")".repeat(300));
src.push('\n');
let p = parse(&src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
let _ = p.errors();
}
#[test]
fn adversarial_long_infix_chain_does_not_panic() {
let mut src = "var x = 1".to_string();
for _ in 0..500 {
src.push_str(" + 1");
}
src.push('\n');
let p = parse(&src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
}
#[test]
fn adversarial_truncated_source_mid_operator() {
let src = "var x = a =";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
}
#[test]
fn adversarial_unicode_in_string_literal() {
let src = "var x = \"héllo wörld 🎉\"\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn adversarial_mixed_garbage_tokens_in_call_args() {
let src = "var x = foo(1, @, )#, 2)\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn construct_literal_empty() {
let p = assert_lossless("var m = Map { }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY));
}
#[test]
fn construct_literal_pair_form_produces_one_entry_per_pair() {
let p = assert_lossless("var m = Map { \"a\": 1, \"b\": 2 }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY), 2);
}
#[test]
fn construct_literal_element_form_produces_one_entry_per_element() {
let p = assert_lossless("var f = Flags { Red, Blue, Green }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY), 3);
}
#[test]
fn construct_literal_field_form_is_the_same_node_shape_as_the_pair_form() {
let field = assert_lossless("var p = Point { x: 1, y: 2 }\n");
let pair = assert_lossless("var m = Map { x: 1, y: 2 }\n");
assert!(field.errors().is_empty(), "errors: {:?}", field.errors());
assert_eq!(
count_node_kind(&field.syntax(), SyntaxKind::CONSTRUCT_ENTRY),
count_node_kind(&pair.syntax(), SyntaxKind::CONSTRUCT_ENTRY),
);
}
#[test]
fn construct_literal_accepts_a_trailing_comma() {
let p = assert_lossless("var m = Map { \"a\": 1, }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY), 1);
}
#[test]
fn construct_literal_entries_may_span_lines() {
let p = assert_lossless("var m = Map {\n \"a\": 1,\n \"b\": 2,\n}\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_ENTRY), 2);
}
#[test]
fn construct_literal_nests() {
let p = assert_lossless("var m = Map { \"p\": Point { x: 1 } }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(
count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
2
);
}
#[test]
fn construct_literal_accepts_a_qualified_type_path() {
let p = assert_lossless("var m = std::map::Map { \"a\": 1 }\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
}
#[test]
fn a_brace_on_the_next_line_is_not_a_construct_literal() {
let p = assert_lossless("var m = Map\n\nflow main() {\n}\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
}
#[test]
fn a_control_flow_head_does_not_swallow_its_body_brace() {
for src in [
"var x = { if ready { 1; } };\n",
"var x = { while ready { 1; } };\n",
"var x = { for k in bag { 1; } };\n",
] {
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "{src}: errors: {:?}", p.errors());
assert!(
!has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
"{src}: head brace must open the body, not a construction literal"
);
assert!(has_node_kind(&p.syntax(), SyntaxKind::STMT_BLOCK));
}
}
#[test]
fn parentheses_restore_the_construct_literal_inside_a_control_flow_head() {
let p = assert_lossless("var x = { if (Point { x: 1 }) == p { 1; } };\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
assert!(has_node_kind(&p.syntax(), SyntaxKind::STMT_BLOCK));
}
#[test]
fn a_content_ground_conditional_head_does_not_swallow_its_arm_brace() {
for src in [
"flow main() {\n {if ready {\n Yes\n }}\n}\n",
"flow main() {\n {match mood {\n calm => Calm\n }}\n}\n",
] {
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "{src}: errors: {:?}", p.errors());
assert!(
!has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
"{src}: head brace must open the arm, not a construction literal"
);
}
}
#[test]
fn a_control_flow_body_may_contain_a_construct_literal() {
let p = assert_lossless("var x = { if ready { let m = Map { \"a\": 1 }; } };\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
}
#[test]
fn construct_literal_in_call_argument_position() {
let p = assert_lossless("var x = size(Map { \"a\": 1 })\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL));
assert!(has_node_kind(&p.syntax(), SyntaxKind::ARG_LIST));
}
#[test]
fn unterminated_construct_literal_never_panics() {
let src = "var m = Map { \"a\": 1\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn garbage_inside_a_construct_literal_never_panics() {
let src = "var m = Map { @@@ }\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn construct_entry_accessors_distinguish_the_two_forms() {
let p = assert_lossless("var m = Map { \"a\": 1 }\n");
let lit = p
.syntax()
.descendants()
.find_map(ast::ConstructLiteral::cast)
.expect("one CONSTRUCT_LITERAL");
assert_eq!(
lit.type_path()
.expect("type path")
.segments()
.map(|t| t.text().to_string())
.collect::<Vec<_>>(),
vec!["Map".to_string()]
);
let entry = lit.entries().next().expect("one entry");
assert!(entry.is_pair());
assert_eq!(entry.key().expect("key").kind(), SyntaxKind::STRING_LIT);
assert_eq!(
entry.value().expect("value").kind(),
SyntaxKind::INTEGER_LIT
);
let p = assert_lossless("var f = Flags { Red }\n");
let lit = p
.syntax()
.descendants()
.find_map(ast::ConstructLiteral::cast)
.expect("one CONSTRUCT_LITERAL");
let entry = lit.entries().next().expect("one entry");
assert!(!entry.is_pair());
assert!(entry.key().is_none());
assert_eq!(entry.value().expect("value").kind(), SyntaxKind::PATH_EXPR);
}
#[test]
fn array_literal_empty() {
let p = assert_lossless("var a = []\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::ARRAY_LITERAL));
}
#[test]
fn array_literal_produces_one_child_per_element() {
let p = assert_lossless("var a = [1, 2, 3]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lit = p
.syntax()
.descendants()
.find_map(ast::ArrayLiteral::cast)
.expect("one ARRAY_LITERAL");
assert_eq!(lit.elements().count(), 3);
}
#[test]
fn array_literal_accepts_a_trailing_comma() {
let p = assert_lossless("var a = [1, 2, ]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lit = p
.syntax()
.descendants()
.find_map(ast::ArrayLiteral::cast)
.expect("one ARRAY_LITERAL");
assert_eq!(lit.elements().count(), 2);
}
#[test]
fn array_literal_elements_may_span_lines() {
let p = assert_lossless("var a = [\n 1,\n 2,\n]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lit = p
.syntax()
.descendants()
.find_map(ast::ArrayLiteral::cast)
.expect("one ARRAY_LITERAL");
assert_eq!(lit.elements().count(), 2);
}
#[test]
fn array_literal_nests() {
let p = assert_lossless("var a = [[1, 2], [3, 4]]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(count_node_kind(&p.syntax(), SyntaxKind::ARRAY_LITERAL), 3);
}
#[test]
fn array_literal_in_call_argument_position() {
let p = assert_lossless("var x = size([1, 2, 3])\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::ARRAY_LITERAL));
assert!(has_node_kind(&p.syntax(), SyntaxKind::ARG_LIST));
}
#[test]
fn array_literal_elements_may_be_construction_literals() {
let p = assert_lossless("var a = [Point { x: 1 }, Point { x: 2 }]\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(
count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
2
);
}
#[test]
fn array_literal_in_a_for_in_head_still_allows_construction_literal_elements() {
let p = assert_lossless("var x = { for q in [Point { x: 1 }] { 1; } };\n");
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(has_node_kind(&p.syntax(), SyntaxKind::ARRAY_LITERAL));
assert_eq!(
count_node_kind(&p.syntax(), SyntaxKind::CONSTRUCT_LITERAL),
1
);
}
#[test]
fn unterminated_array_literal_never_panics() {
let src = "var a = [1, 2\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
#[test]
fn garbage_inside_an_array_literal_never_panics() {
let src = "var a = [ @@@ ]\n";
let p = parse(src);
assert_eq!(src, p.syntax().text().to_string(), "lossless round-trip");
assert!(!p.errors().is_empty());
}
mod proptest_roundtrip {
use super::*;
use proptest::prelude::*;
fn arb_ident() -> impl Strategy<Value = String> {
"[a-z][a-z0-9]{0,5}".prop_filter("not a keyword", |s| {
crate::lexer::classify_keyword(s) == SyntaxKind::IDENT
})
}
fn arb_integer() -> impl Strategy<Value = String> {
(0..10_000i64).prop_map(|n| n.to_string())
}
fn arb_infix_op() -> impl Strategy<Value = &'static str> {
prop_oneof![
Just("+"),
Just("-"),
Just("*"),
Just("/"),
Just("%"),
Just("<"),
Just(">"),
Just("<="),
Just(">="),
Just("=="),
Just("!="),
Just("&&"),
Just("||"),
Just("or"),
]
}
fn arb_expr() -> impl Strategy<Value = String> {
let leaf = prop_oneof![
arb_integer(),
Just("true".to_string()),
Just("false".to_string()),
arb_ident(),
];
leaf.prop_recursive(3, 20, 3, |inner| {
prop_oneof![
inner.clone().prop_map(|e| format!("-{e}")),
inner.clone().prop_map(|e| format!("!{e}")),
inner.clone().prop_map(|e| format!("({e})")),
(inner.clone(), arb_infix_op(), inner.clone())
.prop_map(|(l, op, r)| format!("{l} {op} {r}")),
(arb_ident(), prop::collection::vec(inner, 0..=2))
.prop_map(|(name, args)| format!("{name}({})", args.join(", "))),
]
})
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(256))]
#[test]
fn expr_round_trips_losslessly_and_parses_clean(body in arb_expr()) {
let src = format!("var x = {body}\n");
let p = parse(&src);
prop_assert_eq!(&src, &p.syntax().text().to_string());
prop_assert!(
p.errors().is_empty(),
"well-formed generated expr `{src}` produced errors: {:?}",
p.errors()
);
}
#[test]
fn expr_as_call_argument_round_trips_losslessly(body in arb_expr()) {
let src = format!("var x = wrap({body})\n");
let p = parse(&src);
prop_assert_eq!(&src, &p.syntax().text().to_string());
prop_assert!(
p.errors().is_empty(),
"well-formed generated call-arg `{src}` produced errors: {:?}",
p.errors()
);
}
}
}