use crate::ast::{BinOp, ExprKind, Sigil, StmtKind};
use crate::parse;
fn first_stmt(code: &str) -> StmtKind {
let p = parse(code).expect("parse");
assert!(!p.statements.is_empty(), "{code}");
p.statements[0].kind.clone()
}
fn first_expr_kind(code: &str) -> ExprKind {
let p = parse(code).expect("parse");
let sk = &p.statements[0].kind;
match sk {
StmtKind::Expression(e) => e.kind.clone(),
_ => panic!("expected expression stmt"),
}
}
#[test]
fn shape_local_hash_element() {
let k = first_stmt("local $SIG{__WARN__} = 1;");
assert!(matches!(k, StmtKind::LocalExpr { .. }));
}
#[test]
fn shape_bareword_vs_quoted_in_if() {
let p = parse("if (FOO) { }").expect("parse");
match &p.statements[0].kind {
StmtKind::If { condition, .. } => {
assert!(matches!(condition.kind, ExprKind::Bareword(_)));
}
_ => panic!("expected if"),
}
let p2 = parse("if ('FOO') { }").expect("parse");
match &p2.statements[0].kind {
StmtKind::If { condition, .. } => {
assert!(matches!(condition.kind, ExprKind::String(_)));
}
_ => panic!("expected if"),
}
}
#[test]
fn shape_compound_assign_xor_shift() {
match first_expr_kind("$x ^= 3;") {
ExprKind::CompoundAssign { op, .. } => assert_eq!(op, BinOp::BitXor),
_ => panic!("expected compound assign"),
}
match first_expr_kind("$x <<= 2;") {
ExprKind::CompoundAssign { op, .. } => assert_eq!(op, BinOp::ShiftLeft),
_ => panic!("expected compound assign"),
}
match first_expr_kind("$x >>= 2;") {
ExprKind::CompoundAssign { op, .. } => assert_eq!(op, BinOp::ShiftRight),
_ => panic!("expected compound assign"),
}
}
#[test]
fn shape_percent_scalar_hash_deref() {
let k = first_expr_kind("%$export_cache");
assert!(matches!(
k,
ExprKind::Deref {
kind: Sigil::Hash,
..
}
));
}
#[test]
fn shape_symbolic_array_braces() {
let k = first_expr_kind(r##"@{ "Foo::EXPORT" }"##);
assert!(matches!(
k,
ExprKind::Deref {
kind: Sigil::Array,
..
}
));
let r = first_expr_kind(r##"\@{ "Foo::EXPORT" }"##);
let ExprKind::ScalarRef(inner) = r else {
panic!("expected scalar ref");
};
assert!(matches!(
inner.kind,
ExprKind::Deref {
kind: Sigil::Array,
..
}
));
}
#[test]
fn shape_dynamic_subref_and_typeglob_expr() {
let k = first_expr_kind(r##"\&{"Foo::bar"}"##);
assert!(matches!(k, ExprKind::DynamicSubCodeRef(_)));
let t = first_expr_kind(r##"*{"Foo::bar"}"##);
assert!(matches!(
t,
ExprKind::Deref {
kind: Sigil::Typeglob,
..
}
));
}
#[test]
fn shape_hash_ref_autoquotes_keyword_before_fat_arrow() {
let p = parse("my $h = { pos => 1, bless => 2 };").expect("parse");
let StmtKind::My(decls) = &p.statements[0].kind else {
panic!("expected my, got {:?}", p.statements[0].kind);
};
let Some(init) = &decls[0].initializer else {
panic!("expected initializer");
};
let ExprKind::HashRef(pairs) = &init.kind else {
panic!("expected HashRef, got {:?}", init.kind);
};
assert_eq!(pairs.len(), 2);
assert!(matches!(&pairs[0].0.kind, ExprKind::String(s) if s == "pos"));
assert!(matches!(&pairs[0].1.kind, ExprKind::Integer(1)));
assert!(matches!(&pairs[1].0.kind, ExprKind::String(s) if s == "bless"));
assert!(matches!(&pairs[1].1.kind, ExprKind::Integer(2)));
}
#[test]
fn shape_pos_assign_implicit_underbar() {
let k = first_expr_kind("pos = 3;");
let ExprKind::Assign { target, value } = k else {
panic!("expected Assign, got {k:?}");
};
assert!(matches!(
target.kind,
ExprKind::Pos(Some(ref b)) if matches!(b.kind, ExprKind::ScalarVar(ref s) if s == "_")
));
assert!(matches!(value.kind, ExprKind::Integer(3)));
}
#[test]
fn shape_pos_assign_named_scalar() {
let k = first_expr_kind("pos $x = 5;");
let ExprKind::Assign { target, value } = k else {
panic!("expected Assign, got {k:?}");
};
let ExprKind::Pos(Some(ref subj)) = target.kind else {
panic!("expected Pos lvalue, got {:?}", target.kind);
};
assert!(matches!(subj.kind, ExprKind::ScalarVar(ref s) if s == "x"));
assert!(matches!(value.kind, ExprKind::Integer(5)));
}
#[test]
fn shape_chained_our_arrays_share_initializer() {
let p = parse("our @A = our @B = (1, 2);").expect("parse");
let StmtKind::Our(decls) = &p.statements[0].kind else {
panic!("expected our, got {:?}", p.statements[0].kind);
};
assert_eq!(decls.len(), 2);
assert_eq!(decls[0].name, "A");
assert_eq!(decls[1].name, "B");
assert!(decls[0].initializer.is_some());
assert!(decls[1].initializer.is_some());
}
#[test]
fn shape_hash_ref_return_key_autoquoted() {
let p = parse("my $h = { return => 0 };").expect("parse");
let StmtKind::My(decls) = &p.statements[0].kind else {
panic!("expected my");
};
let Some(init) = &decls[0].initializer else {
panic!("expected initializer");
};
let ExprKind::HashRef(pairs) = &init.kind else {
panic!("expected HashRef, got {:?}", init.kind);
};
assert_eq!(pairs.len(), 1);
assert!(matches!(&pairs[0].0.kind, ExprKind::String(s) if s == "return"));
}
#[test]
fn shape_indirect_scalar_call() {
let k = first_expr_kind("$cr($x);");
let ExprKind::IndirectCall {
target,
args,
ampersand,
pass_caller_arglist,
} = k
else {
panic!("expected IndirectCall");
};
assert!(!ampersand);
assert!(!pass_caller_arglist);
assert!(matches!(target.kind, ExprKind::ScalarVar(ref s) if s == "cr"));
assert_eq!(args.len(), 1);
}
#[test]
fn shape_ampersand_indirect_call() {
let k = first_expr_kind(r##"&$recurse($sym);"##);
let ExprKind::IndirectCall {
target,
args,
ampersand,
pass_caller_arglist,
} = k
else {
panic!("expected IndirectCall");
};
assert!(ampersand);
assert!(!pass_caller_arglist);
assert!(matches!(target.kind, ExprKind::ScalarVar(ref s) if s == "recurse"));
assert_eq!(args.len(), 1);
}
#[test]
fn shape_goto_postfix_if() {
let p = parse("goto &$boots if defined &$boots;").expect("parse");
let StmtKind::If { body, .. } = &p.statements[0].kind else {
panic!("expected if-wrapped goto (XSLoader.pm style)");
};
assert_eq!(body.len(), 1);
assert!(matches!(body[0].kind, StmtKind::Goto { .. }));
}
#[test]
fn shape_statement_label_mixed_case_boot() {
let p = parse("boot: my $xs = 0;").expect("parse");
assert_eq!(p.statements[0].label.as_deref(), Some("boot"));
assert!(matches!(p.statements[0].kind, StmtKind::My(_)));
}
#[test]
fn shape_ampersand_scalar_no_paren_passes_caller_arglist() {
let k = first_expr_kind(r##"&$cb;"##);
let ExprKind::IndirectCall {
target,
args,
ampersand,
pass_caller_arglist,
} = k
else {
panic!("expected IndirectCall");
};
assert!(ampersand);
assert!(pass_caller_arglist);
assert!(args.is_empty());
assert!(matches!(target.kind, ExprKind::ScalarVar(ref s) if s == "cb"));
}
#[test]
fn shape_sort_coderef_paren_list_inside_ternary_then() {
let k = first_expr_kind("1 ? (sort $k (1)) : 0");
let ExprKind::Ternary { then_expr, .. } = k else {
panic!("expected ternary");
};
let ExprKind::SortExpr { cmp, .. } = then_expr.kind else {
panic!("expected sort in then branch");
};
assert!(cmp.is_some(), "sort with coderef comparator must keep cmp");
}
#[test]
fn shape_do_block_postfix_if_and_regex() {
let p = parse("do { print } if /running/ and /service/;").expect("parse");
assert!(
matches!(p.statements[0].kind, StmtKind::If { .. }),
"expected postfix-if wrapping do-block, not bare `if (`"
);
}
#[test]
fn shape_bare_block_postfix_if_and_regex() {
let p = parse("{ print } if /running/ and /service/").expect("parse");
let StmtKind::If { body, .. } = &p.statements[0].kind else {
panic!("expected postfix-if wrapping bare block, not bare `if (`");
};
assert!(
body.len() == 1 && matches!(body[0].kind, StmtKind::Block(_)),
"expected single Block in if-body"
);
}
#[test]
fn shape_do_block_postfix_for() {
assert!(matches!(
first_expr_kind(r#"do { print "x" } for @a;"#),
ExprKind::PostfixForeach { .. }
));
}
#[test]
fn shape_bare_block_postfix_for() {
assert!(matches!(
first_expr_kind(r#"{ print "x" } for @a;"#),
ExprKind::PostfixForeach { .. }
));
}
#[test]
fn shape_pmap_postfix_for() {
match first_expr_kind("pmap { $_ } @x for @y;") {
ExprKind::PostfixForeach { expr, .. } => {
assert!(matches!(expr.kind, ExprKind::PMapExpr { .. }));
}
k => panic!("expected PostfixForeach wrapping pmap, got {k:?}"),
}
}
#[test]
fn shape_if_block() {
assert!(matches!(first_stmt("if (1) { 2; }"), StmtKind::If { .. }));
}
#[test]
fn shape_unless_block() {
assert!(matches!(
first_stmt("unless (0) { 1; }"),
StmtKind::Unless { .. }
));
}
#[test]
fn shape_while_loop() {
assert!(matches!(
first_stmt("while (0) { 1; }"),
StmtKind::While { .. }
));
}
#[test]
fn shape_until_loop() {
assert!(matches!(
first_stmt("until (1) { 1; }"),
StmtKind::Until { .. }
));
}
#[test]
fn shape_for_c_style() {
assert!(matches!(
first_stmt("for (my $i = 0; $i < 1; $i++) { 1; }"),
StmtKind::For { .. }
));
}
#[test]
fn shape_foreach() {
assert!(matches!(
first_stmt("foreach my $x (1, 2) { $x; }"),
StmtKind::Foreach { .. }
));
}
#[test]
fn shape_sub_decl() {
assert!(matches!(
first_stmt("sub foo { 1; }"),
StmtKind::SubDecl { .. }
));
}
#[test]
fn shape_sub_decl_qualified_name() {
match first_stmt("sub B::GV::SAFENAME { 1; }") {
StmtKind::SubDecl { name, .. } => assert_eq!(name, "B::GV::SAFENAME"),
_ => panic!("expected SubDecl"),
}
}
#[test]
fn shape_package() {
assert!(matches!(
first_stmt("package Foo::Bar;"),
StmtKind::Package { .. }
));
}
#[test]
fn shape_use_no() {
assert!(matches!(first_stmt("use strict;"), StmtKind::Use { .. }));
assert!(matches!(
first_stmt("use 5.008;"),
StmtKind::UsePerlVersion { .. }
));
assert!(matches!(
first_stmt("use 5;"),
StmtKind::UsePerlVersion { .. }
));
assert!(matches!(
first_stmt("use overload ();"),
StmtKind::UseOverload { pairs } if pairs.is_empty()
));
assert!(matches!(first_stmt("no warnings;"), StmtKind::No { .. }));
}
#[test]
fn shape_my_our_local() {
assert!(matches!(first_stmt("my $x;"), StmtKind::My(_)));
assert!(matches!(first_stmt("our $y;"), StmtKind::Our(_)));
assert!(matches!(first_stmt("local $z;"), StmtKind::Local(_)));
}
#[test]
fn shape_return_last_next_redo() {
assert!(matches!(first_stmt("return 1;"), StmtKind::Return(_)));
assert!(matches!(first_stmt("last;"), StmtKind::Last(_)));
assert!(matches!(first_stmt("next;"), StmtKind::Next(_)));
assert!(matches!(first_stmt("redo;"), StmtKind::Redo(_)));
}
#[test]
fn shape_begin_end_blocks() {
assert!(matches!(first_stmt("BEGIN { 1; }"), StmtKind::Begin(_)));
assert!(matches!(first_stmt("END { 1; }"), StmtKind::End(_)));
}
#[test]
fn shape_leading_semicolon_is_empty_statement() {
let p = parse(";;").expect("parse");
assert_eq!(p.statements.len(), 2);
assert!(matches!(p.statements[0].kind, StmtKind::Empty));
assert!(matches!(p.statements[1].kind, StmtKind::Empty));
}
#[test]
fn expr_binop_add() {
assert!(matches!(
first_expr_kind("1 + 2;"),
ExprKind::BinOp { op: BinOp::Add, .. }
));
}
#[test]
fn expr_binop_pow() {
assert!(matches!(
first_expr_kind("2 ** 3;"),
ExprKind::BinOp { op: BinOp::Pow, .. }
));
}
#[test]
fn expr_ternary() {
assert!(matches!(
first_expr_kind("1 ? 2 : 3;"),
ExprKind::Ternary { .. }
));
}
#[test]
fn expr_repeat() {
assert!(matches!(
first_expr_kind(r#""a" x 3;"#),
ExprKind::Repeat { .. }
));
}
#[test]
fn expr_range() {
assert!(matches!(first_expr_kind("1..10;"), ExprKind::Range { .. }));
}
#[test]
fn expr_scalar_var() {
assert!(matches!(
first_expr_kind("$foo;"),
ExprKind::ScalarVar(ref s) if s == "foo"
));
}
#[test]
fn expr_array_var() {
assert!(matches!(
first_expr_kind("@arr;"),
ExprKind::ArrayVar(ref s) if s == "arr"
));
}
#[test]
fn expr_hash_var() {
assert!(matches!(
first_expr_kind("%h;"),
ExprKind::HashVar(ref s) if s == "h"
));
}
#[test]
fn expr_array_element() {
assert!(matches!(
first_expr_kind("$a[0];"),
ExprKind::ArrayElement { .. }
));
}
#[test]
fn expr_hash_element() {
assert!(matches!(
first_expr_kind("$h{key};"),
ExprKind::HashElement { .. }
));
}
#[test]
fn expr_length_builtin() {
assert!(matches!(
first_expr_kind("length('ab');"),
ExprKind::Length(_)
));
}
#[test]
fn expr_print_say() {
assert!(matches!(
first_expr_kind("print 1;"),
ExprKind::Print { .. }
));
assert!(matches!(first_expr_kind("say 1;"), ExprKind::Say { .. }));
}
#[test]
fn expr_undef_literal() {
assert!(matches!(first_expr_kind("undef;"), ExprKind::Undef));
}
#[test]
fn expr_integer_float_string() {
assert!(matches!(first_expr_kind("42;"), ExprKind::Integer(42)));
assert!(matches!(first_expr_kind("1.5;"), ExprKind::Float(f) if (f - 1.5).abs() < 1e-9));
assert!(matches!(
first_expr_kind("'hi';"),
ExprKind::String(ref s) if s == "hi"
));
}
#[test]
fn expr_regex_literal_token_form() {
assert!(matches!(
first_expr_kind("m/pattern/;"),
ExprKind::Regex(_, _) | ExprKind::Match { .. }
));
}
#[test]
fn expr_substitution_form() {
assert!(matches!(
first_expr_kind("s/a/b/;"),
ExprKind::Substitution { .. }
));
}
#[test]
fn expr_transliterate_form() {
assert!(matches!(
first_expr_kind("tr/a/b/;"),
ExprKind::Transliterate { .. }
));
}
#[test]
fn expr_map_grep_sort() {
assert!(matches!(
first_expr_kind("map { $_ } (1);"),
ExprKind::MapExpr { .. }
));
assert!(matches!(
first_expr_kind("grep { $_ } (1);"),
ExprKind::GrepExpr { .. }
));
assert!(matches!(
first_expr_kind("grep -e \"x\", (1);"),
ExprKind::GrepExprComma { .. }
));
assert!(matches!(
first_expr_kind("map $_ + 1, (1);"),
ExprKind::MapExprComma { .. }
));
assert!(matches!(
first_expr_kind("sort (1, 2);"),
ExprKind::SortExpr { .. }
));
}
#[test]
fn expr_list_paren_parses() {
parse("(1, 2, 3);").expect("list expr");
}
#[test]
fn stmt_block_bare() {
assert!(matches!(first_stmt("{ 1; }"), StmtKind::Block(_)));
}
#[test]
fn shape_eval_block_stmt() {
assert!(matches!(
first_stmt("eval { 1; };"),
StmtKind::Expression(_)
));
}
#[test]
fn shape_require_do_string() {
assert!(matches!(
first_stmt("require strict;"),
StmtKind::Expression(_)
));
assert!(matches!(
first_stmt("do 'lib.pl';"),
StmtKind::Expression(_)
));
}
#[test]
fn expr_postfix_increment_second_statement() {
let p = parse("my $i = 0; $i++;").expect("parse");
assert!(p.statements.len() >= 2);
match &p.statements[1].kind {
StmtKind::Expression(e) => {
assert!(matches!(e.kind, ExprKind::PostfixOp { .. }));
}
_ => panic!("expected expression statement for postfix"),
}
}
#[test]
fn shape_ssh_parenless_list_call() {
match first_expr_kind("ssh r5, ls;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "ssh");
assert_eq!(args.len(), 2);
}
k => panic!("expected FuncCall for ssh r5, ls, got {k:?}"),
}
}
#[test]
fn pipe_bareword_rhs_becomes_funccall() {
match first_expr_kind("42 |> foo;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "foo");
assert_eq!(args.len(), 1);
assert!(matches!(args[0].kind, ExprKind::Integer(42)));
}
k => panic!("expected FuncCall, got {k:?}"),
}
}
#[test]
fn pipe_prepends_lhs_as_first_arg() {
match first_expr_kind("10 |> foo 20, 30;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "foo");
assert_eq!(args.len(), 3);
assert!(matches!(args[0].kind, ExprKind::Integer(10)));
assert!(matches!(args[1].kind, ExprKind::Integer(20)));
assert!(matches!(args[2].kind, ExprKind::Integer(30)));
}
k => panic!("expected FuncCall, got {k:?}"),
}
}
#[test]
fn pipe_chain_is_left_associative() {
match first_expr_kind("1 |> foo |> bar;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "bar");
assert_eq!(args.len(), 1);
match &args[0].kind {
ExprKind::FuncCall {
name: inner_name,
args: inner_args,
} => {
assert_eq!(inner_name, "foo");
assert_eq!(inner_args.len(), 1);
assert!(matches!(inner_args[0].kind, ExprKind::Integer(1)));
}
k => panic!("expected inner FuncCall, got {k:?}"),
}
}
k => panic!("expected outer FuncCall, got {k:?}"),
}
}
#[test]
fn pipe_into_unary_builtin_replaces_operand() {
match first_expr_kind("\"hello\" |> length;") {
ExprKind::Length(inner) => {
assert!(matches!(inner.kind, ExprKind::String(ref s) if s == "hello"));
}
k => panic!("expected Length, got {k:?}"),
}
match first_expr_kind("\"abc\" |> uc;") {
ExprKind::Uc(inner) => {
assert!(matches!(inner.kind, ExprKind::String(ref s) if s == "abc"));
}
k => panic!("expected Uc, got {k:?}"),
}
}
#[test]
fn pipe_into_map_fills_list_slot() {
match first_expr_kind("(1,2,3) |> map { $_ * 2 };") {
ExprKind::MapExpr { block, list, .. } => {
assert!(!block.is_empty());
assert!(matches!(list.kind, ExprKind::List(ref v) if v.len() == 3));
}
k => panic!("expected MapExpr, got {k:?}"),
}
}
#[test]
fn pipe_into_join_fills_missing_list() {
match first_expr_kind("(1,2) |> join \",\";") {
ExprKind::JoinExpr { separator, list } => {
assert!(matches!(separator.kind, ExprKind::String(ref s) if s == ","));
assert!(matches!(list.kind, ExprKind::List(ref v) if v.len() == 2));
}
k => panic!("expected JoinExpr, got {k:?}"),
}
}
#[test]
fn pipe_chain_parenless_arg_does_not_swallow_outer_pipe() {
match first_expr_kind("qw(a b c d) |> head 2 |> join \"-\";") {
ExprKind::JoinExpr { separator, list } => {
assert!(
matches!(separator.kind, ExprKind::String(ref s) if s == "-"),
"join separator should be \"-\""
);
match &list.kind {
ExprKind::FuncCall {
name: head_name,
args: head_args,
} => {
assert_eq!(head_name, "head");
assert_eq!(
head_args.len(),
2,
"head should receive exactly the piped LIST and the count `2`"
);
assert!(
matches!(head_args[1].kind, ExprKind::Integer(2)),
"head's second arg should be the integer 2, got {:?}",
head_args[1].kind
);
}
k => panic!("expected inner `head` FuncCall, got {k:?}"),
}
}
k => panic!("expected outer JoinExpr, got {k:?}"),
}
}
#[test]
fn pipe_chain_three_parenless_stages_left_associative() {
match first_expr_kind("qw(a b c d e) |> head 3 |> tail 2 |> join \"-\";") {
ExprKind::JoinExpr { list, .. } => match &list.kind {
ExprKind::FuncCall {
name: tail_name,
args: tail_args,
} => {
assert_eq!(tail_name, "tail");
assert_eq!(tail_args.len(), 2);
assert!(matches!(tail_args[1].kind, ExprKind::Integer(2)));
match &tail_args[0].kind {
ExprKind::FuncCall {
name: head_name,
args: head_args,
} => {
assert_eq!(head_name, "head");
assert_eq!(head_args.len(), 2);
assert!(matches!(head_args[1].kind, ExprKind::Integer(3)));
}
k => panic!("expected inner `head` FuncCall, got {k:?}"),
}
}
k => panic!("expected middle `tail` FuncCall, got {k:?}"),
},
k => panic!("expected outer JoinExpr, got {k:?}"),
}
}
#[test]
fn parens_reenable_pipe_forward_inside_parenless_call() {
match first_expr_kind("head((1, 2, 3) |> tail 2);") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "head");
assert_eq!(args.len(), 1);
match &args[0].kind {
ExprKind::FuncCall {
name: tail_name,
args: tail_args,
} => {
assert_eq!(tail_name, "tail");
assert_eq!(tail_args.len(), 2);
assert!(matches!(tail_args[1].kind, ExprKind::Integer(2)));
}
k => panic!("expected inner `tail` FuncCall from pipe, got {k:?}"),
}
}
k => panic!("expected outer `head` FuncCall, got {k:?}"),
}
}
#[test]
fn pipe_chain_map_into_join() {
match first_expr_kind("(1..3) |> map { $_ * $_ } |> join \",\";") {
ExprKind::JoinExpr { list, .. } => match &list.kind {
ExprKind::MapExpr { list: inner, .. } => {
assert!(matches!(inner.kind, ExprKind::Range { .. }));
}
k => panic!("expected inner MapExpr, got {k:?}"),
},
k => panic!("expected outer JoinExpr, got {k:?}"),
}
}
#[test]
fn pipe_into_scalar_becomes_indirect_call() {
match first_expr_kind("10 |> $cr;") {
ExprKind::IndirectCall { target, args, .. } => {
assert!(matches!(target.kind, ExprKind::ScalarVar(ref n) if n == "cr"));
assert_eq!(args.len(), 1);
assert!(matches!(args[0].kind, ExprKind::Integer(10)));
}
k => panic!("expected IndirectCall, got {k:?}"),
}
}
#[test]
fn pipe_binds_looser_than_logical_or() {
match first_expr_kind("0 || 1 |> foo;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "foo");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0].kind,
ExprKind::BinOp {
op: BinOp::LogOr,
..
}
));
}
k => panic!("expected FuncCall wrapping `0 || 1`, got {k:?}"),
}
}
#[test]
fn pipe_binds_looser_than_addition() {
match first_expr_kind("1 + 2 |> foo;") {
ExprKind::FuncCall { name, args } => {
assert_eq!(name, "foo");
assert_eq!(args.len(), 1);
assert!(matches!(
args[0].kind,
ExprKind::BinOp { op: BinOp::Add, .. }
));
}
k => panic!("expected FuncCall wrapping 1+2, got {k:?}"),
}
}
#[test]
fn pipe_rejects_nonsense_rhs() {
assert!(crate::parse("42 |> 1 + 2;").is_err());
assert!(crate::parse("42 |> 99;").is_err());
}