use super::*;
use proptest::prelude::*;
use super::super::MAX_DEPTH;
#[test]
fn annotation_line_parses() {
let src = "fn heal(hp) >{\n @[effects(pure, silent, reads(gold, hp))]\n var x = hp\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
fn nested_annotation_src(depth: usize) -> String {
format!("@[a{}{}]\n", "(a".repeat(depth), ")".repeat(depth))
}
fn walk_nested_chain(line: &crate::ast::AnnotationLine, depth: usize) -> String {
let args = line.args().expect("top-level ANNOTATION_ARGS");
let mut arg = args.args().next().expect("first ANNOTATION_ARG");
for _ in 1..depth {
let nested = arg.nested_args().expect("expected nested ANNOTATION_ARGS");
arg = nested.args().next().expect("nested ANNOTATION_ARG");
}
assert!(
arg.nested_args().is_none(),
"innermost arg at depth {depth} must not have further nested args"
);
arg.name_token()
.expect("leaf arg has an IDENT")
.text()
.to_string()
}
fn annotation_line_node(p: &Parse) -> crate::ast::AnnotationLine {
find_child::<crate::ast::AnnotationLine>(&p.syntax())
.or_else(|| {
p.syntax()
.descendants()
.find_map(crate::ast::AnnotationLine::cast)
})
.expect("ANNOTATION_LINE")
}
#[test]
fn annotation_line_bare_name_no_parens() {
let src = "@[local]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(line.name_token().unwrap().text(), "local");
assert!(line.args().is_none(), "no `(` means no ANNOTATION_ARGS");
}
#[test]
fn annotation_args_empty_parens() {
let src = "@[name()]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
assert_eq!(args.args().count(), 0);
}
#[test]
fn annotation_arg_bare_ident() {
let src = "@[name(pure)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "pure");
assert!(arg.nested_args().is_none());
}
#[test]
fn annotation_arg_spec_effects_fixture() {
let src = "@[effects(reads(gold, hp), pure)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(line.name_token().unwrap().text(), "effects");
let args = line.args().expect("ANNOTATION_ARGS");
let top: Vec<_> = args.args().collect();
assert_eq!(top.len(), 2);
assert_eq!(top[0].name_token().unwrap().text(), "reads");
let reads_args = top[0].nested_args().expect("`reads(...)` nests");
let reads_names: Vec<_> = reads_args
.args()
.map(|a| a.name_token().unwrap().text().to_string())
.collect();
assert_eq!(reads_names, vec!["gold".to_string(), "hp".to_string()]);
assert_eq!(top[1].name_token().unwrap().text(), "pure");
assert!(top[1].nested_args().is_none());
}
#[test]
fn annotation_arg_nested_one_level() {
let src = nested_annotation_src(1);
let p = assert_lossless(&src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(walk_nested_chain(&line, 1), "a");
}
#[test]
fn annotation_arg_nested_two_levels() {
let src = nested_annotation_src(2);
let p = assert_lossless(&src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(walk_nested_chain(&line, 2), "a");
}
#[test]
fn annotation_arg_nested_three_levels() {
let src = nested_annotation_src(3);
let p = assert_lossless(&src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(walk_nested_chain(&line, 3), "a");
}
#[test]
fn annotation_arg_nested_deep_but_within_limit() {
let depth = 64;
assert!(u32::try_from(depth).unwrap_or(u32::MAX) < MAX_DEPTH);
let src = nested_annotation_src(depth);
let p = assert_lossless(&src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(walk_nested_chain(&line, depth), "a");
}
#[test]
fn annotation_arg_integer_literal() {
let src = "@[maxlen(80)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
let lit = find_child::<crate::ast::IntegerLit>(arg.syntax()).expect("INTEGER_LIT");
assert_eq!(lit.value(), Some(80));
}
#[test]
fn annotation_arg_float_literal() {
let src = "@[note(2.5)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
let lit = find_child::<crate::ast::FloatLit>(arg.syntax()).expect("FLOAT_LIT");
assert!((lit.value().expect("float value") - 2.5).abs() < f64::EPSILON * 10.0);
}
#[test]
fn annotation_arg_string_literal() {
let src = "@[note(\"hi there\")]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
let lit = find_child::<crate::ast::StringLit>(arg.syntax()).expect("STRING_LIT");
assert!(lit.syntax().text().to_string().contains("hi there"));
}
#[test]
fn annotation_arg_key_value_string() {
let src = "@[element(args = \"^(?<chan>[A-Z]+): (?<text>.+)$\")]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(line.name_token().unwrap().text(), "element");
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "args");
assert!(arg.nested_args().is_none());
let value = arg.eq_value().expect("eq_value");
assert!(
value
.syntax()
.text()
.to_string()
.contains("(?<chan>[A-Z]+)")
);
}
#[test]
fn annotation_arg_key_value_integer() {
let src = "@[convention(order = 30)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(line.name_token().unwrap().text(), "convention");
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "order");
assert!(
arg.eq_value().is_none(),
"must not be read as a string value"
);
let int_lit = arg.eq_int_value().expect("eq_int_value");
assert_eq!(int_lit.value(), Some(30));
}
#[test]
fn annotation_arg_key_value_negative_integer_is_a_parse_error() {
let src = "@[convention(order = -5)]\n";
let p = assert_lossless(src);
assert!(
!p.errors().is_empty(),
"expected a parse error for `order = -5`"
);
}
#[test]
fn annotation_arg_key_value_ident() {
let src = "@[convention(attach = Cue)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "attach");
assert!(
arg.eq_value().is_none(),
"must not be read as a string value"
);
assert!(
arg.eq_int_value().is_none(),
"must not be read as an integer value"
);
let ident = arg.eq_ident_value().expect("eq_ident_value");
assert_eq!(ident.text(), "Cue");
}
#[test]
fn annotation_args_multiple_key_value_pairs() {
let src = "@[style(chan = \"channel\", line = \"radio\")]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let top: Vec<_> = args.args().collect();
assert_eq!(top.len(), 2);
assert_eq!(top[0].name_token().unwrap().text(), "chan");
assert!(
top[0]
.eq_value()
.unwrap()
.syntax()
.text()
.to_string()
.contains("channel")
);
assert_eq!(top[1].name_token().unwrap().text(), "line");
assert!(
top[1]
.eq_value()
.unwrap()
.syntax()
.text()
.to_string()
.contains("radio")
);
}
#[test]
fn annotation_arg_key_value_non_string_rhs_has_no_eq_value() {
let src = "@[style(chan = 3.5)]\n";
let p = assert_lossless(src);
let messages: Vec<_> = p.errors().iter().map(|e| e.message.as_str()).collect();
assert_eq!(
messages,
vec![
"expected R_PAREN, found FLOAT",
"expected R_BRACKET, found FLOAT",
"unexpected text after `]` on an annotation line",
],
"errors: {messages:?}"
);
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "chan");
assert!(arg.eq_value().is_none());
assert!(arg.eq_int_value().is_none());
assert!(arg.eq_ident_value().is_none());
}
#[test]
fn annotation_arg_key_value_rejects_interpolation() {
let src = "@[element(args = \"^{chan}(?<chan>\\w+)$\")]\n";
let p = assert_lossless(src);
assert!(
!p.errors().is_empty(),
"a `{{…}}` interpolation in an annotation value must be a parse error, not silently dropped"
);
}
#[test]
fn annotation_arg_unquoted_module_path() {
let src = "@[was(story::old::path)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
assert_eq!(line.name_token().unwrap().text(), "was");
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert!(arg.name_token().is_none(), "path arg has no bare IDENT");
let path = arg.path().expect("ANNOTATION_ARG carries a PATH");
let segments: Vec<_> = path.segments().map(|t| t.text().to_string()).collect();
assert_eq!(segments, vec!["story", "old", "path"]);
assert!(
path.crosses_module_wall(),
"`::`-separated, not `.`-separated"
);
}
#[test]
fn annotation_arg_single_segment_stays_bare_ident() {
let src = "@[was(story)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let arg = args.args().next().expect("ANNOTATION_ARG");
assert_eq!(arg.name_token().unwrap().text(), "story");
assert!(arg.path().is_none());
}
#[test]
fn annotation_args_multiple_unquoted_paths() {
let src = "@[rename(story::old::a, story::old::b)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let paths: Vec<Vec<String>> = args
.args()
.map(|a| {
a.path()
.expect("ANNOTATION_ARG carries a PATH")
.segments()
.map(|t| t.text().to_string())
.collect()
})
.collect();
assert_eq!(
paths,
vec![
vec!["story".to_string(), "old".to_string(), "a".to_string()],
vec!["story".to_string(), "old".to_string(), "b".to_string()],
]
);
}
#[test]
fn annotation_args_trailing_comma_allowed() {
let src = "@[name(a, b,)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let names: Vec<_> = args
.args()
.map(|a| a.name_token().unwrap().text().to_string())
.collect();
assert_eq!(names, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn annotation_args_multiline_list() {
let src = "@[name(\n a,\n b\n)]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
assert_eq!(args.args().count(), 2);
}
#[test]
fn multiple_annotation_lines_stacked_before_declaration() {
let src = "@[local]\n@[effects(pure)]\nflow f() {\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let lines: Vec<_> = p
.syntax()
.children()
.filter(|n| n.kind() == SyntaxKind::ANNOTATION_LINE)
.collect();
assert_eq!(lines.len(), 2);
let first = crate::ast::AnnotationLine::cast(lines[0].clone()).expect("AnnotationLine");
let second = crate::ast::AnnotationLine::cast(lines[1].clone()).expect("AnnotationLine");
assert_eq!(first.name_token().unwrap().text(), "local");
assert_eq!(second.name_token().unwrap().text(), "effects");
assert!(has_node_kind(&p.syntax(), SyntaxKind::FLOW_DECL));
}
#[test]
fn annotation_line_unclosed_bracket_no_newline_records_error() {
let src = "@[name";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1);
assert!(p.errors()[0].message.contains("R_BRACKET"));
}
#[test]
fn annotation_line_unclosed_bracket_before_newline_records_error() {
let src = "@[name(a, b)\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1);
assert!(p.errors()[0].message.contains("R_BRACKET"));
assert!(p.errors()[0].message.contains("NEWLINE"));
}
#[test]
fn annotation_args_missing_closing_paren_before_bracket() {
let src = "@[name(a]\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1);
assert!(p.errors()[0].message.contains("R_PAREN"));
assert!(has_node_kind(&p.syntax(), SyntaxKind::ANNOTATION_LINE));
}
#[test]
fn annotation_args_mismatched_nested_parens_recovers_inner() {
let src = "@[effects(reads(gold, hp)]\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1, "errors: {:?}", p.errors());
assert!(p.errors()[0].message.contains("R_PAREN"));
let line = annotation_line_node(&p);
let args = line.args().expect("outer ANNOTATION_ARGS");
let reads = args.args().next().expect("`reads` ANNOTATION_ARG");
assert_eq!(reads.name_token().unwrap().text(), "reads");
let nested = reads.nested_args().expect("inner `(gold, hp)` still nests");
let names: Vec<_> = nested
.args()
.map(|a| a.name_token().unwrap().text().to_string())
.collect();
assert_eq!(names, vec!["gold".to_string(), "hp".to_string()]);
}
#[test]
fn annotation_args_unexpected_token_recovers_as_error_node() {
let src = "@[name(, a)]\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1, "errors: {:?}", p.errors());
assert!(
p.errors()[0]
.message
.contains("unexpected token in annotation arguments")
);
assert!(has_node_kind(&p.syntax(), SyntaxKind::ERROR));
let line = annotation_line_node(&p);
let args = line.args().expect("ANNOTATION_ARGS");
let names: Vec<_> = args
.args()
.map(|a| a.name_token().unwrap().text().to_string())
.collect();
assert_eq!(names, vec!["a".to_string()]);
}
#[test]
fn annotation_line_trailing_text_after_close_records_error() {
let src = "@[name] extra text\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 1, "errors: {:?}", p.errors());
assert!(p.errors()[0].message.contains("unexpected text after"));
}
#[test]
fn annotation_line_trailing_whitespace_only_no_error() {
let src = "@[name] \n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn annotation_line_excess_closers_recovers_without_panic() {
let src = "@[name)))))]\n";
let p = assert_lossless(src);
assert!(!p.errors().is_empty());
assert!(p.errors().iter().any(|e| e.message.contains("R_BRACKET")));
}
#[test]
fn lone_at_outside_bracket_is_plain_text_not_error_token() {
let src = "@ name\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::ANNOTATION_LINE));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::CUE));
assert!(!has_token_kind(&p.syntax(), SyntaxKind::ERROR_TOKEN));
let at_token = p
.syntax()
.descendants_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.find(|t| t.kind() == SyntaxKind::AT)
.expect("AT token");
assert_eq!(at_token.text(), "@");
assert_eq!(text_run_concat(&p.syntax()), "@ name");
}
#[test]
fn lone_at_inside_flow_body_is_plain_text() {
let src = "flow f() {\n meet me @ dawn\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::ANNOTATION_LINE));
assert!(!has_node_kind(&p.syntax(), SyntaxKind::CUE));
assert!(!has_token_kind(&p.syntax(), SyntaxKind::ERROR_TOKEN));
let at_token = p
.syntax()
.descendants_with_tokens()
.filter_map(rowan::NodeOrToken::into_token)
.find(|t| t.kind() == SyntaxKind::AT)
.expect("AT token");
assert_eq!(at_token.text(), "@");
assert!(text_run_concat(&p.syntax()).contains("@ dawn"));
}
#[test]
fn annotation_line_in_block_body_before_divert() {
let src = "flow f() {\n @[effects(reads(gold, hp), pure)]\n -> END\n}\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
let flow = find_child::<crate::ast::FlowDecl>(&p.syntax()).expect("FlowDecl");
let body = expect_prose_body(flow.body());
let items: Vec<_> = body.items().collect();
let annotation_idx = items
.iter()
.position(|n| n.kind() == SyntaxKind::ANNOTATION_LINE)
.expect("ANNOTATION_LINE is a direct child of the block");
let divert_idx = items
.iter()
.position(|n| n.kind() == SyntaxKind::DIVERT_STMT)
.expect("DIVERT_STMT is a direct child of the block");
assert!(
annotation_idx < divert_idx,
"annotation must precede the statement it annotates"
);
let line = crate::ast::AnnotationLine::cast(items[annotation_idx].clone())
.expect("AnnotationLine cast");
assert_eq!(line.name_token().unwrap().text(), "effects");
let args = line.args().expect("ANNOTATION_ARGS");
let top: Vec<_> = args.args().collect();
assert_eq!(top.len(), 2);
assert_eq!(top[0].name_token().unwrap().text(), "reads");
assert_eq!(top[1].name_token().unwrap().text(), "pure");
}
#[test]
fn annotation_line_in_single_line_block_eats_closing_brace() {
let src = "flow f() { @[local] }\n";
let p = assert_lossless(src);
assert_eq!(p.errors().len(), 2, "errors: {:?}", p.errors());
assert!(
p.errors()[0].message.contains("unexpected text after"),
"errors: {:?}",
p.errors()
);
assert!(
p.errors()[1].message.contains("R_BRACE"),
"errors: {:?}",
p.errors()
);
let flow = find_child::<crate::ast::FlowDecl>(&p.syntax()).expect("FlowDecl");
let body = expect_prose_body(flow.body());
let annotation_node = body
.items()
.find(|n| n.kind() == SyntaxKind::ANNOTATION_LINE)
.expect("ANNOTATION_LINE is still a direct child of the block");
assert!(annotation_node.text().to_string().contains('}'));
}
#[test]
fn at_l_bracket_only_the_adjacent_pair_opens_annotation() {
let src = "@ [name]\n";
let p = assert_lossless(src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert!(!has_node_kind(&p.syntax(), SyntaxKind::ANNOTATION_LINE));
}
#[test]
fn annotation_args_extreme_nesting_hits_depth_limit_without_panic() {
let depth = usize::try_from(MAX_DEPTH).unwrap_or(256) + 64;
let src = nested_annotation_src(depth);
let p = assert_lossless(&src);
assert!(
p.errors()
.iter()
.any(|e| e.message.contains("maximum nesting depth exceeded")),
"errors: {:?}",
p.errors()
);
}
#[test]
fn annotation_args_many_unmatched_openers_stays_linear_without_panic() {
let src = format!("@[name{}]\n", "(".repeat(2000));
let p = assert_lossless(&src);
assert!(!p.errors().is_empty());
}
#[test]
fn many_stacked_annotation_lines_no_error() {
let count = 200;
let mut src = String::new();
for i in 0..count {
use std::fmt::Write as _;
let _ = writeln!(src, "@[a{i}]");
}
let p = assert_lossless(&src);
assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
assert_eq!(
count_node_kind(&p.syntax(), SyntaxKind::ANNOTATION_LINE),
count
);
}
const NUM_CASES: u32 = 200;
const KEYWORDS: &[&str] = &[
"pub", "flow", "fn", "var", "const", "let", "flags", "struct", "extern", "import", "use",
"module", "return", "ref", "if", "match", "else", "while", "for", "in", "until", "break",
"continue", "as", "or", "true", "false", "END", "DONE",
];
fn arb_ident() -> impl Strategy<Value = String> {
"[a-z][a-z0-9_]{0,5}".prop_filter("must not be a keyword", |s| !KEYWORDS.contains(&s.as_str()))
}
fn arb_nested_arg() -> impl Strategy<Value = String> {
let leaf = arb_ident();
leaf.prop_recursive(6, 40, 3, |inner| {
(arb_ident(), prop::collection::vec(inner, 1..=3))
.prop_map(|(name, children)| format!("{name}({})", children.join(", ")))
})
}
fn arb_annotation_line_with_nested_args() -> impl Strategy<Value = String> {
prop::collection::vec(arb_nested_arg(), 1..=3)
.prop_map(|args| format!("@[effects({})]\n", args.join(", ")))
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(NUM_CASES))]
#[test]
fn arb_nested_annotation_args_round_trip_clean(src in arb_annotation_line_with_nested_args()) {
let p = parse(&src);
prop_assert_eq!(p.syntax().text().to_string(), src);
prop_assert!(p.errors().is_empty(), "errors: {:?}", p.errors());
}
#[test]
fn arb_nested_depth_straddling_max_depth_never_panics(depth in 1usize..320) {
let src = nested_annotation_src(depth);
let p = parse(&src);
prop_assert_eq!(p.syntax().text().to_string(), src);
if u32::try_from(depth).unwrap_or(u32::MAX) < MAX_DEPTH {
prop_assert!(p.errors().is_empty(), "depth {depth} errors: {:?}", p.errors());
}
}
#[test]
fn arb_annotation_line_garbage_never_panics(body in "[ -~]{0,40}") {
let src = format!("@[{body}\n");
let p = parse(&src);
prop_assert_eq!(p.syntax().text().to_string(), src);
}
}