@@ -549,20 +549,28 @@ fn test_repeat_loop() {
549549
550550#[ test]
551551fn test_super_assignment_at_file_scope ( ) {
552- // At file scope, `<<-` records in file scope with IS_SUPER_BOUND
552+ // At file scope, `<<-` targets the file scope itself (no parent to
553+ // walk to), so the symbol gets both IS_SUPER_BOUND and IS_BOUND.
553554 let index = index ( "x <<- 1" ) ;
554555 let file = ScopeId :: from ( 0 ) ;
555556
556557 let x = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
557- assert_eq ! ( x. flags( ) , SymbolFlags :: IS_SUPER_BOUND ) ;
558+ assert_eq ! (
559+ x. flags( ) ,
560+ SymbolFlags :: IS_SUPER_BOUND . union ( SymbolFlags :: IS_BOUND )
561+ ) ;
558562
559- assert_eq ! ( index. definitions( file) . len( ) , 1 ) ;
560- let DefinitionKind :: SuperAssignment ( node) =
561- index. definitions ( file) [ DefinitionId :: from ( 0 ) ] . kind ( )
562- else {
563- panic ! ( "expected SuperAssignment" ) ;
564- } ;
565- assert_eq ! ( node. kind( ) , RSyntaxKind :: R_BINARY_EXPRESSION ) ;
563+ // Two definitions: one from the current-scope recording, one from the
564+ // target-scope recording (same scope in this case).
565+ assert_eq ! ( index. definitions( file) . len( ) , 2 ) ;
566+ assert ! ( matches!(
567+ index. definitions( file) [ DefinitionId :: from( 0 ) ] . kind( ) ,
568+ DefinitionKind :: SuperAssignment ( _)
569+ ) ) ;
570+ assert ! ( matches!(
571+ index. definitions( file) [ DefinitionId :: from( 1 ) ] . kind( ) ,
572+ DefinitionKind :: SuperAssignment ( _)
573+ ) ) ;
566574 assert_eq ! ( index. uses( file) . len( ) , 0 ) ;
567575}
568576
@@ -572,9 +580,12 @@ fn test_super_assignment_right_at_file_scope() {
572580 let file = ScopeId :: from ( 0 ) ;
573581
574582 let x = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
575- assert_eq ! ( x. flags( ) , SymbolFlags :: IS_SUPER_BOUND ) ;
583+ assert_eq ! (
584+ x. flags( ) ,
585+ SymbolFlags :: IS_SUPER_BOUND . union ( SymbolFlags :: IS_BOUND )
586+ ) ;
576587
577- assert_eq ! ( index. definitions( file) . len( ) , 1 ) ;
588+ assert_eq ! ( index. definitions( file) . len( ) , 2 ) ;
578589 assert ! ( matches!(
579590 index. definitions( file) [ DefinitionId :: from( 0 ) ] . kind( ) ,
580591 DefinitionKind :: SuperAssignment ( _)
@@ -585,16 +596,23 @@ fn test_super_assignment_right_at_file_scope() {
585596#[ test]
586597fn test_super_assignment_recorded_in_current_scope ( ) {
587598 // `<<-` records the definition in the function scope where it lexically
588- // appears, not in an ancestor scope.
599+ // appears AND an extra definition in the parent scope.
589600 let index = index ( "f <- function() { x <<- 1 }" ) ;
590601 let file = ScopeId :: from ( 0 ) ;
591602 let fun = ScopeId :: from ( 1 ) ;
592603
593- // File scope only has `f`
594- assert ! ( index. symbols( file) . get( "x" ) . is_none( ) ) ;
595- assert_eq ! ( index. definitions( file) . len( ) , 1 ) ;
604+ // File scope has `x` with IS_BOUND (extra definition from `<<-`)
605+ // and `f` with IS_BOUND. The `x <<-` definition is added during
606+ // function body processing, before `f <-`.
607+ let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
608+ assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
609+ assert_eq ! ( index. definitions( file) . len( ) , 2 ) ;
596610 assert ! ( matches!(
597611 index. definitions( file) [ DefinitionId :: from( 0 ) ] . kind( ) ,
612+ DefinitionKind :: SuperAssignment ( _)
613+ ) ) ;
614+ assert ! ( matches!(
615+ index. definitions( file) [ DefinitionId :: from( 1 ) ] . kind( ) ,
598616 DefinitionKind :: Assignment ( _)
599617 ) ) ;
600618
@@ -614,7 +632,8 @@ fn test_super_assignment_right_recorded_in_current_scope() {
614632 let file = ScopeId :: from ( 0 ) ;
615633 let fun = ScopeId :: from ( 1 ) ;
616634
617- assert ! ( index. symbols( file) . get( "x" ) . is_none( ) ) ;
635+ let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
636+ assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
618637
619638 let x = index. symbols ( fun) . get ( "x" ) . unwrap ( ) ;
620639 assert_eq ! ( x. flags( ) , SymbolFlags :: IS_SUPER_BOUND ) ;
@@ -623,12 +642,13 @@ fn test_super_assignment_right_recorded_in_current_scope() {
623642#[ test]
624643fn test_super_assignment_does_not_pollute_ancestor ( ) {
625644 // `x <- 1` is in file scope, `x <<- 2` is in the function. The `<<-`
626- // does NOT add a definition to the file scope.
645+ // adds an extra definition to the file scope in addition to the
646+ // existing `x <- 1` assignment.
627647 let index = index ( "x <- 1\n f <- function() { x <<- 2 }" ) ;
628648 let file = ScopeId :: from ( 0 ) ;
629649 let fun = ScopeId :: from ( 1 ) ;
630650
631- // File scope: `x` has IS_BOUND from the `<-`, `f` has IS_BOUND
651+ // File scope: `x` has IS_BOUND ( from both `<-` and `<<-`) , `f` has IS_BOUND
632652 let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
633653 assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
634654
@@ -637,11 +657,15 @@ fn test_super_assignment_does_not_pollute_ancestor() {
637657 . iter ( )
638658 . filter ( |( _, d) | index. symbols ( file) . symbol ( d. symbol ( ) ) . name ( ) == "x" )
639659 . collect ( ) ;
640- assert_eq ! ( x_file_defs. len( ) , 1 ) ;
660+ assert_eq ! ( x_file_defs. len( ) , 2 ) ;
641661 assert ! ( matches!(
642662 x_file_defs[ 0 ] . 1 . kind( ) ,
643663 DefinitionKind :: Assignment ( _)
644664 ) ) ;
665+ assert ! ( matches!(
666+ x_file_defs[ 1 ] . 1 . kind( ) ,
667+ DefinitionKind :: SuperAssignment ( _)
668+ ) ) ;
645669
646670 // Function scope: `x` has IS_SUPER_BOUND from the `<<-`
647671 let x_fun = index. symbols ( fun) . get ( "x" ) . unwrap ( ) ;
@@ -656,7 +680,8 @@ fn test_super_assignment_does_not_pollute_ancestor() {
656680#[ test]
657681fn test_super_assignment_nested_recorded_in_inner_scope ( ) {
658682 // `x` is bound in both file and outer function. `<<-` in the inner
659- // function records the definition in the inner scope, not in any ancestor.
683+ // function targets the outer function scope (immediate parent), adding
684+ // an extra definition there.
660685 let index = index ( "x <- 0\n f <- function() { x <- 1; g <- function() { x <<- 2 } }" ) ;
661686 let file = ScopeId :: from ( 0 ) ;
662687 let outer = ScopeId :: from ( 1 ) ;
@@ -666,7 +691,8 @@ fn test_super_assignment_nested_recorded_in_inner_scope() {
666691 let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
667692 assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
668693
669- // Outer function: `x` has IS_BOUND (from `<-`), no super-assignment here
694+ // Outer function: `x` has IS_BOUND (from both `x <- 1` and the `<<-`
695+ // extra definition from the inner function)
670696 let x_outer = index. symbols ( outer) . get ( "x" ) . unwrap ( ) ;
671697 assert_eq ! ( x_outer. flags( ) , SymbolFlags :: IS_BOUND ) ;
672698
@@ -675,11 +701,15 @@ fn test_super_assignment_nested_recorded_in_inner_scope() {
675701 . iter ( )
676702 . filter ( |( _, d) | index. symbols ( outer) . symbol ( d. symbol ( ) ) . name ( ) == "x" )
677703 . collect ( ) ;
678- assert_eq ! ( x_outer_defs. len( ) , 1 ) ;
704+ assert_eq ! ( x_outer_defs. len( ) , 2 ) ;
679705 assert ! ( matches!(
680706 x_outer_defs[ 0 ] . 1 . kind( ) ,
681707 DefinitionKind :: Assignment ( _)
682708 ) ) ;
709+ assert ! ( matches!(
710+ x_outer_defs[ 1 ] . 1 . kind( ) ,
711+ DefinitionKind :: SuperAssignment ( _)
712+ ) ) ;
683713
684714 // Inner function: `x` has IS_SUPER_BOUND (from `<<-`)
685715 let x_inner = index. symbols ( inner) . get ( "x" ) . unwrap ( ) ;
@@ -693,8 +723,9 @@ fn test_super_assignment_nested_recorded_in_inner_scope() {
693723
694724#[ test]
695725fn test_super_assignment_coexists_with_use_in_ancestors ( ) {
696- // Outer function uses `x` but doesn't bind it. `<<-` in the inner
697- // function records in the inner scope. Ancestors are unaffected.
726+ // `<<-` in inner function walks up from outer, finds `x` bound in file
727+ // scope (from `x <- 1`), so it targets the file scope -- not the outer
728+ // function where `x` is only used.
698729 let index = index ( "x <- 1\n f <- function() { print(x); g <- function() { x <<- 2 } }" ) ;
699730 let file = ScopeId :: from ( 0 ) ;
700731 let outer = ScopeId :: from ( 1 ) ;
@@ -703,7 +734,8 @@ fn test_super_assignment_coexists_with_use_in_ancestors() {
703734 let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
704735 assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
705736
706- // Outer function has `x` as `IS_USED` only (from `print(x)`)
737+ // Outer function has `x` as IS_USED only (from `print(x)`). The `<<-`
738+ // skips it because `x` is not bound here.
707739 let x_outer = index. symbols ( outer) . get ( "x" ) . unwrap ( ) ;
708740 assert_eq ! ( x_outer. flags( ) , SymbolFlags :: IS_USED ) ;
709741
@@ -714,22 +746,24 @@ fn test_super_assignment_coexists_with_use_in_ancestors() {
714746
715747#[ test]
716748fn test_super_assignment_not_visible_to_resolve_symbol ( ) {
717- // `<<-` does not create a binding visible to `resolve_symbol` (which
718- // checks IS_BOUND, not IS_SUPER_BOUND). Cross-scope effects of `<<-`
719- // are a runtime concern, not statically modelled.
749+ // `<<-` creates an extra definition in the parent scope with IS_BOUND,
750+ // which is visible to `resolve_symbol`.
720751 let index = index ( "f <- function() { x <<- 1 }" ) ;
721752 let file = ScopeId :: from ( 0 ) ;
722753 let fun = ScopeId :: from ( 1 ) ;
723754
724- // File scope has no `x`
725- assert ! ( index. symbols( file) . get( "x" ) . is_none( ) ) ;
755+ // File scope has `x` with IS_BOUND (extra definition from `<<-`)
756+ let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
757+ assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
726758
727759 // Function scope has `x` with IS_SUPER_BOUND
728760 let x = index. symbols ( fun) . get ( "x" ) . unwrap ( ) ;
729761 assert_eq ! ( x. flags( ) , SymbolFlags :: IS_SUPER_BOUND ) ;
730762
731- // resolve_symbol does not find `x` because no scope has IS_BOUND for it
732- assert ! ( index. resolve_symbol( "x" , fun) . is_none( ) ) ;
763+ // `resolve_symbol` finds `x` in the file scope via the extra definition
764+ let resolved = index. resolve_symbol ( "x" , fun) ;
765+ assert ! ( resolved. is_some( ) ) ;
766+ assert_eq ! ( resolved. unwrap( ) . 0 , file) ;
733767}
734768
735769#[ test]
@@ -1122,7 +1156,8 @@ fn test_string_super_assignment() {
11221156 let file = ScopeId :: from ( 0 ) ;
11231157 let fun = ScopeId :: from ( 1 ) ;
11241158
1125- assert ! ( index. symbols( file) . get( "x" ) . is_none( ) ) ;
1159+ let x_file = index. symbols ( file) . get ( "x" ) . unwrap ( ) ;
1160+ assert_eq ! ( x_file. flags( ) , SymbolFlags :: IS_BOUND ) ;
11261161
11271162 let x = index. symbols ( fun) . get ( "x" ) . unwrap ( ) ;
11281163 assert_eq ! ( x. flags( ) , SymbolFlags :: IS_SUPER_BOUND ) ;
0 commit comments