Source file src/cmd/compile/internal/types2/scope_test.go

     1  // Copyright 2026 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package types2
     6  
     7  import (
     8  	"fmt"
     9  	"slices"
    10  	"testing"
    11  )
    12  
    13  // TestScopeObjects tests that Scope.Objects yields elements in sorted name order, handles empty scopes, and supports early break.
    14  func TestScopeObjects(t *testing.T) {
    15  	s := NewScope(nil, nopos, nopos, "test")
    16  
    17  	// Empty scope
    18  	var gotNames []string
    19  	for obj := range s.Objects() {
    20  		gotNames = append(gotNames, obj.Name())
    21  	}
    22  	if len(gotNames) != 0 {
    23  		t.Errorf("empty scope: got %v, want empty", gotNames)
    24  	}
    25  
    26  	// Insert objects out of order
    27  	names := []string{"e", "c", "a", "b", "d"}
    28  	for _, name := range names {
    29  		v := NewVar(nopos, nil, name, Typ[Int])
    30  		if alt := s.Insert(v); alt != nil {
    31  			t.Fatalf("Insert(%s) failed", name)
    32  		}
    33  	}
    34  
    35  	wantNames := []string{"a", "b", "c", "d", "e"}
    36  	if !slices.Equal(s.Names(), wantNames) {
    37  		t.Errorf("Names() = %v, want %v", s.Names(), wantNames)
    38  	}
    39  
    40  	// Scope.Objects yields elements in sorted name order
    41  	gotNames = nil
    42  	for obj := range s.Objects() {
    43  		gotNames = append(gotNames, obj.Name())
    44  		if obj != s.Lookup(obj.Name()) {
    45  			t.Errorf("Objects() yielded %v, want Lookup result %v", obj, s.Lookup(obj.Name()))
    46  		}
    47  	}
    48  	if !slices.Equal(gotNames, wantNames) {
    49  		t.Errorf("Objects() yielded %v, want %v", gotNames, wantNames)
    50  	}
    51  
    52  	// Break early from iteration
    53  	count := 0
    54  	for range s.Objects() {
    55  		count++
    56  		if count == 2 {
    57  			break
    58  		}
    59  	}
    60  	if count != 2 {
    61  		t.Errorf("early break: iterated %d times, want 2", count)
    62  	}
    63  }
    64  
    65  // TestScopeMutationReflectsChanges tests that Scope.Insert clears the name cache so subsequent calls reflect mutations.
    66  func TestScopeMutationReflectsChanges(t *testing.T) {
    67  	s := NewScope(nil, nopos, nopos, "test")
    68  
    69  	v1 := NewVar(nopos, nil, "b", Typ[Int])
    70  	s.Insert(v1)
    71  
    72  	// Prime cache
    73  	if want := []string{"b"}; !slices.Equal(s.Names(), want) {
    74  		t.Fatalf("Names() = %v, want %v", s.Names(), want)
    75  	}
    76  
    77  	// Insert before existing name
    78  	v0 := NewVar(nopos, nil, "a", Typ[Int])
    79  	s.Insert(v0)
    80  
    81  	// Names() must reflect new object
    82  	wantNames := []string{"a", "b"}
    83  	if !slices.Equal(s.Names(), wantNames) {
    84  		t.Errorf("after inserting 'a': Names() = %v, want %v", s.Names(), wantNames)
    85  	}
    86  
    87  	// Objects() must reflect new object
    88  	var gotNames []string
    89  	for obj := range s.Objects() {
    90  		gotNames = append(gotNames, obj.Name())
    91  	}
    92  	if !slices.Equal(gotNames, wantNames) {
    93  		t.Errorf("after inserting 'a': Objects() = %v, want %v", gotNames, wantNames)
    94  	}
    95  
    96  	// Insert after existing names
    97  	v2 := NewVar(nopos, nil, "c", Typ[Int])
    98  	s.Insert(v2)
    99  
   100  	wantNames = []string{"a", "b", "c"}
   101  	if !slices.Equal(s.Names(), wantNames) {
   102  		t.Errorf("after inserting 'c': Names() = %v, want %v", s.Names(), wantNames)
   103  	}
   104  	gotNames = nil
   105  	for obj := range s.Objects() {
   106  		gotNames = append(gotNames, obj.Name())
   107  	}
   108  	if !slices.Equal(gotNames, wantNames) {
   109  		t.Errorf("after inserting 'c': Objects() = %v, want %v", gotNames, wantNames)
   110  	}
   111  
   112  	// Duplicate insert should not mutate or break cache
   113  	dup := NewVar(nopos, nil, "b", Typ[String])
   114  	if alt := s.Insert(dup); alt != v1 {
   115  		t.Errorf("Insert duplicate: got %v, want %v", alt, v1)
   116  	}
   117  	if !slices.Equal(s.Names(), wantNames) {
   118  		t.Errorf("after duplicate Insert: Names() = %v, want %v", s.Names(), wantNames)
   119  	}
   120  }
   121  
   122  // TestScopeNoAllocations tests that repeated calls to Scope.Names
   123  // and Scope.Objects do not allocate once cached.
   124  func TestScopeNoAllocations(t *testing.T) {
   125  	s := NewScope(nil, nopos, nopos, "test")
   126  	for i := range 10 {
   127  		s.Insert(NewVar(nopos, nil, fmt.Sprintf("v%d", i), Typ[Int]))
   128  	}
   129  
   130  	// Prime the cache.
   131  	_ = s.Names()
   132  
   133  	// Scope.Names
   134  	namesAllocs := testing.AllocsPerRun(100, func() { _ = s.Names() })
   135  	if namesAllocs > 0 {
   136  		t.Errorf("repeated s.Names() allocated %f times, want 0", namesAllocs)
   137  	}
   138  
   139  	// Scope.Objects allocates the iterator closure, and nothing else,
   140  	// on top of what Names allocates.
   141  	if allocs := testing.AllocsPerRun(100, func() {
   142  		s.Objects()(func(Object) bool { return true })
   143  	}); allocs > namesAllocs+1 {
   144  		t.Errorf("repeated s.Objects() iteration allocated %f times, want at most %f", allocs, namesAllocs+1)
   145  	}
   146  }
   147  

View as plain text