@@ -640,6 +640,60 @@ mod tests {
640640 assert ! ( matches!( rule. replacement, Replacement :: Templates ( _) ) ) ;
641641 }
642642
643+ #[ test]
644+ fn parses_quantified_query_choice_alternatives ( ) {
645+ let pattern = parse_pattern ( quote ! {
646+ [
647+ ( identifier) + @items
648+ ( integer) ?
649+ "," *
650+ ]
651+ } )
652+ . unwrap ( ) ;
653+
654+ let Pattern :: Choice { alternatives } = pattern else {
655+ panic ! ( "expected query choice" ) ;
656+ } ;
657+ assert ! ( matches!(
658+ & alternatives[ 0 ] ,
659+ Pattern :: Capture {
660+ cardinality: Cardinality :: ONE_OR_MORE ,
661+ ..
662+ }
663+ ) ) ;
664+ assert ! ( matches!(
665+ & alternatives[ 1 ] ,
666+ Pattern :: Repeated {
667+ cardinality: Cardinality :: OPTIONAL ,
668+ ..
669+ }
670+ ) ) ;
671+ assert ! ( matches!(
672+ & alternatives[ 2 ] ,
673+ Pattern :: Repeated {
674+ cardinality: Cardinality :: ZERO_OR_MORE ,
675+ ..
676+ }
677+ ) ) ;
678+ }
679+
680+ #[ test]
681+ fn rejects_mixed_capture_modes_in_query_choice ( ) {
682+ for pattern in [
683+ quote ! ( [ ( identifier) @value ( integer) @@value] ) ,
684+ quote ! ( [ ( identifier) @@value ( integer) @value] ) ,
685+ ] {
686+ let result = parse_pattern ( pattern) ;
687+ let Err ( error) = result else {
688+ panic ! ( "expected mixed capture modes to be rejected" ) ;
689+ } ;
690+ assert_eq ! (
691+ error. to_string( ) ,
692+ "capture `value` must consistently use `@` or `@@` across query choice alternatives"
693+ ) ;
694+ }
695+ }
696+
643697 #[ test]
644698 fn rejects_eager_capture_of_rule_root ( ) {
645699 let result = parse_rule ( quote ! {
@@ -656,6 +710,25 @@ mod tests {
656710 ) ;
657711 }
658712
713+ #[ test]
714+ fn rejects_eager_capture_in_rule_root_choice ( ) {
715+ let result = parse_rule ( quote ! {
716+ [
717+ ( identifier) @name
718+ ( integer)
719+ ]
720+ =>
721+ ( identifier "replacement" )
722+ } ) ;
723+ let Err ( error) = result else {
724+ panic ! ( "expected eager capture in rule-root choice to be rejected" ) ;
725+ } ;
726+ assert_eq ! (
727+ error. to_string( ) ,
728+ "a capture of the rule root must use `@@`; the matched root cannot be eagerly translated"
729+ ) ;
730+ }
731+
659732 #[ test]
660733 fn repeated_capture_owns_its_cardinality ( ) {
661734 let pattern = parse_pattern ( quote ! ( ( array ( identifier) * @items) ) ) . unwrap ( ) ;
@@ -798,6 +871,31 @@ mod tests {
798871 ) ) ;
799872 }
800873
874+ #[ test]
875+ fn parses_choice_led_repeated_sequence ( ) {
876+ let pattern = parse_pattern ( quote ! ( ( root ( [ ( identifier) ( integer) ] "," ) * ) ) ) . unwrap ( ) ;
877+ let Pattern :: Node { fields, .. } = pattern else {
878+ panic ! ( "expected root node" ) ;
879+ } ;
880+ let Pattern :: Sequence ( children) = & fields[ 0 ] . 1 else {
881+ panic ! ( "expected child sequence" ) ;
882+ } ;
883+ let Pattern :: Repeated {
884+ pattern : repeated, ..
885+ } = & children[ 0 ]
886+ else {
887+ panic ! ( "expected repetition" ) ;
888+ } ;
889+ assert ! ( matches!(
890+ repeated. as_ref( ) ,
891+ Pattern :: Sequence ( patterns)
892+ if matches!(
893+ patterns. as_slice( ) ,
894+ [ Pattern :: Choice { .. } , Pattern :: Unnamed ( comma) ] if comma == ","
895+ )
896+ ) ) ;
897+ }
898+
801899 #[ test]
802900 fn rejects_unconsumed_node_pattern_tokens ( ) {
803901 for query in [ quote ! ( ( root ( foo . ( bar) @x) ) ) , quote ! ( ( root( bar @ x) ) ) ] {
0 commit comments