@@ -581,9 +581,9 @@ impl<'a, 'd> InferenceVisitor<'a, 'd> {
581581 let flow = match quantifier {
582582 QuantifierKind :: Optional => self . make_flow_optional ( inner_info. flow ) ,
583583 QuantifierKind :: ZeroOrMore | QuantifierKind :: OneOrMore => {
584- if !is_row_capture {
585- self . check_strict_dimensionality ( quant , & inner_info ) ;
586- }
584+ // Always check multi-element sequences (row capture doesn't help)
585+ // Only skip internal capture check when is_row_capture
586+ self . check_strict_dimensionality ( quant , & inner_info , is_row_capture ) ;
587587 self . make_flow_array ( inner_info. flow , & inner, quantifier. is_non_empty ( ) )
588588 }
589589 } ;
@@ -681,8 +681,46 @@ impl<'a, 'd> InferenceVisitor<'a, 'd> {
681681 }
682682
683683 /// Check strict dimensionality rule for * and + quantifiers.
684- /// Captures inside a quantifier are forbidden unless marked as a row capture.
685- fn check_strict_dimensionality ( & mut self , quant : & QuantifiedExpr , inner_info : & TermInfo ) {
684+ ///
685+ /// Two checks:
686+ /// 1. Multi-element patterns (Arity::Many) without captures can't be scalar arrays
687+ /// (applies regardless of is_row_capture - row capture doesn't help here)
688+ /// 2. Internal captures require a row capture on the quantifier
689+ /// (skipped when is_row_capture=true)
690+ fn check_strict_dimensionality (
691+ & mut self ,
692+ quant : & QuantifiedExpr ,
693+ inner_info : & TermInfo ,
694+ is_row_capture : bool ,
695+ ) {
696+ let op = quant
697+ . operator ( )
698+ . map ( |t| t. text ( ) . to_string ( ) )
699+ . unwrap_or_else ( || "*" . to_string ( ) ) ;
700+
701+ // Check 1: Multi-element patterns without captures can't be scalar arrays
702+ // This check applies even with row capture - you can't meaningfully capture
703+ // multiple nodes per iteration as a scalar
704+ if inner_info. arity == Arity :: Many && inner_info. flow . is_void ( ) {
705+ self . diag
706+ . report (
707+ self . source_id ,
708+ DiagnosticKind :: MultiElementScalarCapture ,
709+ quant. text_range ( ) ,
710+ )
711+ . message ( format ! (
712+ "sequence with `{}` matches multiple nodes but has no internal captures" ,
713+ op
714+ ) )
715+ . emit ( ) ;
716+ return ;
717+ }
718+
719+ // Check 2: Internal captures require row capture (skip if already a row capture)
720+ if is_row_capture {
721+ return ;
722+ }
723+
686724 let TypeFlow :: Bubble ( type_id) = & inner_info. flow else {
687725 return ;
688726 } ;
@@ -695,11 +733,6 @@ impl<'a, 'd> InferenceVisitor<'a, 'd> {
695733 return ;
696734 }
697735
698- let op = quant
699- . operator ( )
700- . map ( |t| t. text ( ) . to_string ( ) )
701- . unwrap_or_else ( || "*" . to_string ( ) ) ;
702-
703736 let capture_names: Vec < _ > = fields
704737 . keys ( )
705738 . map ( |s| format ! ( "`@{}`" , self . interner. resolve( * s) ) )
0 commit comments