@@ -260,6 +260,12 @@ impl<'a, 'd> InferenceVisitor<'a, 'd> {
260260 }
261261
262262 /// Captured expression: wraps inner's flow into a field.
263+ ///
264+ /// Scope creation rules:
265+ /// - Sequences `{...} @x` and alternations `[...] @x` create new scopes.
266+ /// Inner fields become the captured type's fields.
267+ /// - Other expressions (named nodes, refs) don't create scopes.
268+ /// Inner fields bubble up alongside the capture field.
263269 fn infer_captured_expr ( & mut self , cap : & CapturedExpr ) -> TermInfo {
264270 let Some ( name_tok) = cap. name ( ) else {
265271 // Recover gracefully
@@ -284,17 +290,79 @@ impl<'a, 'd> InferenceVisitor<'a, 'd> {
284290 // Determine how inner flow relates to capture (e.g., ? makes field optional)
285291 let ( inner_info, is_optional) = self . resolve_capture_inner ( & inner) ;
286292
287- let captured_type = self . determine_captured_type ( & inner, & inner_info, annotation_type) ;
288- let field_info = if is_optional {
289- FieldInfo :: optional ( captured_type)
293+ // Determine if we need to merge bubbling fields with the capture.
294+ // Only applies when inner has Bubble flow AND doesn't create a scope boundary.
295+ // Sequences and alternations create scopes; named nodes/refs don't.
296+ let should_merge_fields =
297+ matches ! ( & inner_info. flow, TypeFlow :: Bubble ( _) ) && !Self :: inner_creates_scope ( & inner) ;
298+
299+ if should_merge_fields {
300+ // Named node/ref/etc with bubbling fields: capture adds a field,
301+ // inner fields bubble up alongside.
302+ let captured_type = self . determine_non_scope_captured_type ( & inner, annotation_type) ;
303+ let field_info = if is_optional {
304+ FieldInfo :: optional ( captured_type)
305+ } else {
306+ FieldInfo :: required ( captured_type)
307+ } ;
308+
309+ // Merge capture field with inner's bubbling fields
310+ let TypeFlow :: Bubble ( type_id) = & inner_info. flow else {
311+ unreachable ! ( )
312+ } ;
313+ let mut fields = self
314+ . ctx
315+ . get_struct_fields ( * type_id)
316+ . cloned ( )
317+ . unwrap_or_default ( ) ;
318+ fields. insert ( capture_name, field_info) ;
319+
320+ TermInfo :: new (
321+ inner_info. arity ,
322+ TypeFlow :: Bubble ( self . ctx . intern_struct ( fields) ) ,
323+ )
290324 } else {
291- FieldInfo :: required ( captured_type)
292- } ;
325+ // All other cases: scope-creating captures, scalar flows, void flows.
326+ // Inner becomes the captured type (if applicable).
327+ let captured_type = self . determine_captured_type ( & inner, & inner_info, annotation_type) ;
328+ let field_info = if is_optional {
329+ FieldInfo :: optional ( captured_type)
330+ } else {
331+ FieldInfo :: required ( captured_type)
332+ } ;
333+
334+ TermInfo :: new (
335+ inner_info. arity ,
336+ TypeFlow :: Bubble ( self . ctx . intern_single_field ( capture_name, field_info) ) ,
337+ )
338+ }
339+ }
293340
294- TermInfo :: new (
295- inner_info. arity ,
296- TypeFlow :: Bubble ( self . ctx . intern_single_field ( capture_name, field_info) ) ,
297- )
341+ /// Determines if an expression creates a scope boundary when captured.
342+ fn inner_creates_scope ( inner : & Expr ) -> bool {
343+ match inner {
344+ Expr :: SeqExpr ( _) | Expr :: AltExpr ( _) => true ,
345+ Expr :: QuantifiedExpr ( q) => {
346+ // Look through quantifier to the actual expression
347+ q. inner ( )
348+ . map ( |i| Self :: inner_creates_scope ( & i) )
349+ . unwrap_or ( false )
350+ }
351+ _ => false ,
352+ }
353+ }
354+
355+ /// Determines captured type for non-scope-creating expressions.
356+ fn determine_non_scope_captured_type (
357+ & mut self ,
358+ inner : & Expr ,
359+ annotation : Option < TypeId > ,
360+ ) -> TypeId {
361+ if let Some ( ref_type) = self . get_recursive_ref_type ( inner) {
362+ annotation. unwrap_or ( ref_type)
363+ } else {
364+ annotation. unwrap_or ( TYPE_NODE )
365+ }
298366 }
299367
300368 /// Resolves explicit type annotation like `@foo: string`.
0 commit comments