goscript 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +1 -1
  2. package/cmd/goscript/cmd_compile.go +1 -1
  3. package/compiler/analysis.go +73 -131
  4. package/compiler/analysis_test.go +220 -0
  5. package/compiler/assignment.go +37 -43
  6. package/compiler/builtin_test.go +102 -0
  7. package/compiler/compiler.go +79 -14
  8. package/compiler/composite-lit.go +108 -43
  9. package/compiler/config.go +7 -3
  10. package/compiler/config_test.go +6 -33
  11. package/compiler/expr-selector.go +66 -41
  12. package/compiler/expr-star.go +57 -65
  13. package/compiler/expr-type.go +1 -1
  14. package/compiler/expr-value.go +1 -1
  15. package/compiler/expr.go +79 -18
  16. package/compiler/primitive.go +11 -10
  17. package/compiler/spec-struct.go +3 -3
  18. package/compiler/spec-value.go +75 -29
  19. package/compiler/spec.go +9 -3
  20. package/compiler/stmt-assign.go +36 -2
  21. package/compiler/stmt-for.go +11 -0
  22. package/compiler/stmt-range.go +110 -0
  23. package/compiler/stmt.go +52 -0
  24. package/compiler/type.go +36 -11
  25. package/dist/gs/builtin/builtin.js +37 -0
  26. package/dist/gs/builtin/builtin.js.map +1 -0
  27. package/dist/gs/builtin/channel.js +471 -0
  28. package/dist/gs/builtin/channel.js.map +1 -0
  29. package/dist/gs/builtin/defer.js +54 -0
  30. package/dist/gs/builtin/defer.js.map +1 -0
  31. package/dist/gs/builtin/io.js +15 -0
  32. package/dist/gs/builtin/io.js.map +1 -0
  33. package/dist/gs/builtin/map.js +44 -0
  34. package/dist/gs/builtin/map.js.map +1 -0
  35. package/dist/gs/builtin/slice.js +799 -0
  36. package/dist/gs/builtin/slice.js.map +1 -0
  37. package/dist/gs/builtin/type.js +745 -0
  38. package/dist/gs/builtin/type.js.map +1 -0
  39. package/dist/gs/builtin/varRef.js +14 -0
  40. package/dist/gs/builtin/varRef.js.map +1 -0
  41. package/dist/gs/context/context.js +55 -0
  42. package/dist/gs/context/context.js.map +1 -0
  43. package/dist/gs/context/index.js +2 -0
  44. package/dist/gs/context/index.js.map +1 -0
  45. package/dist/gs/runtime/index.js +2 -0
  46. package/dist/gs/runtime/index.js.map +1 -0
  47. package/dist/gs/runtime/runtime.js +158 -0
  48. package/dist/gs/runtime/runtime.js.map +1 -0
  49. package/dist/gs/time/index.js +2 -0
  50. package/dist/gs/time/index.js.map +1 -0
  51. package/dist/gs/time/time.js +115 -0
  52. package/dist/gs/time/time.js.map +1 -0
  53. package/package.json +3 -2
package/README.md CHANGED
@@ -81,7 +81,7 @@ func main() {
81
81
 
82
82
  // Configure the compiler
83
83
  conf := &compiler.Config{
84
- OutputPathRoot: "./ts_output", // Directory for generated TypeScript files
84
+ OutputPath: "./ts_output", // Directory for generated TypeScript files
85
85
  }
86
86
  if err := conf.Validate(); err != nil {
87
87
  log.Fatalf("invalid compiler config: %v", err)
@@ -44,7 +44,7 @@ var CompileCommands = []*cli.Command{{
44
44
  &cli.StringFlag{
45
45
  Name: "output",
46
46
  Usage: "the output typescript path to use",
47
- Destination: &cliCompilerConfig.OutputPathRoot,
47
+ Destination: &cliCompilerConfig.OutputPath,
48
48
  Value: "./output",
49
49
  EnvVars: []string{"GOSCRIPT_OUTPUT"},
50
50
  },
@@ -145,13 +145,14 @@ func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
145
145
  return nodeInfo.InAsyncContext
146
146
  }
147
147
 
148
- // NeedsBoxed returns whether the given object needs to be boxed.
149
- // According to the new logic, a variable needs boxing if its address is taken
150
- // and assigned to another variable (i.e., it appears as a destination with AddressOfAssignment).
151
- func (a *Analysis) NeedsBoxed(obj types.Object) bool {
148
+ // NeedsVarRef returns whether the given object needs to be variable referenced.
149
+ // This is true when the object's address is taken (e.g., &myVar) in the analyzed code.
150
+ // Variables that have their address taken must be wrapped in VarRef to maintain identity.
151
+ func (a *Analysis) NeedsVarRef(obj types.Object) bool {
152
152
  if obj == nil {
153
153
  return false
154
154
  }
155
+
155
156
  usageInfo, exists := a.VariableUsage[obj]
156
157
  if !exists {
157
158
  return false
@@ -165,156 +166,97 @@ func (a *Analysis) NeedsBoxed(obj types.Object) bool {
165
166
  return false
166
167
  }
167
168
 
168
- // NeedsBoxedAccess returns whether accessing the given object requires '.value' access in TypeScript.
169
- // This function is critical for correctly handling pointer dereferencing by determining when
170
- // a variable is boxed and needs .value to access its content.
171
- //
172
- // Two distinct cases determine when a variable needs .value access:
173
- //
174
- // 1. The variable itself is boxed (its address is taken)
175
- // Example: let x = $.box(10) => x.value
169
+ // NeedsVarRefAccess returns whether accessing the given object requires '.value' access in TypeScript.
170
+ // This is more nuanced than NeedsVarRef and considers both direct variable references and
171
+ // pointers that may point to variable-referenced values.
176
172
  //
177
- // 2. For pointer variables: it points to a boxed struct variable rather than a direct struct literal
178
- // Example: let ptrToVal = val (where val is boxed) => ptrToVal.value
179
- // vs. let ptr = new MyStruct() => ptr (no .value needed)
180
- //
181
- // This distinction is crucial for avoiding over-dereferencing or under-dereferencing,
182
- // which was the root cause of several bugs in our pointer handling.
183
- func (a *Analysis) NeedsBoxedAccess(obj types.Object) bool {
173
+ // Examples:
174
+ // - Direct variable reference (NeedsVarRef = true):
175
+ // Example: let x = $.varRef(10) => x.value
176
+ // - Pointer pointing to a variable-referenced value:
177
+ // Example: let p: VarRef<number> | null = x => p!.value
178
+ // - Regular pointer (NeedsVarRef = false, but points to variable reference):
179
+ // Example: let q = x => q!.value (where x is VarRef)
180
+ func (a *Analysis) NeedsVarRefAccess(obj types.Object) bool {
184
181
  if obj == nil {
185
182
  return false
186
183
  }
187
184
 
188
- // First, check if the variable itself is boxed - this always requires .value
189
- // A variable is boxed if its address is taken elsewhere in the code
190
- if a.NeedsBoxed(obj) {
185
+ // If the variable itself is variable referenced, it needs .value access
186
+ if a.NeedsVarRef(obj) {
191
187
  return true
192
188
  }
193
189
 
194
- // Check if this is a pointer variable pointing to a boxed struct value
195
- objType := obj.Type()
196
- if ptrType, isPointer := objType.Underlying().(*types.Pointer); isPointer {
197
- // Check if it's a pointer to a struct
198
- if elemType := ptrType.Elem(); elemType != nil {
199
- if _, isStructType := elemType.Underlying().(*types.Struct); isStructType {
200
- // For struct pointers, check if it points to a boxed struct variable
201
- if usageInfo, exists := a.VariableUsage[obj]; exists {
202
- for _, src := range usageInfo.Sources {
203
- // Check if this pointer was assigned the address of another variable
204
- // (e.g., ptr = &someVar) rather than a direct literal (ptr = &Struct{})
205
- if src.Type == AddressOfAssignment && src.Object != nil {
206
- // Bug fix: If the source variable is boxed, the pointer needs .value to access it
207
- // This distinguishes between:
208
- // - ptrToVal := &val (val is boxed, so we need ptrToVal.value)
209
- // - ptr := &MyStruct{} (direct literal, no boxing needed)
210
- return a.NeedsBoxed(src.Object)
211
- }
190
+ // For pointer variables, check if they point to a variable-referenced value
191
+ if ptrType, ok := obj.Type().(*types.Pointer); ok {
192
+ // Check all assignments to this pointer variable
193
+ for varObj, info := range a.VariableUsage {
194
+ if varObj == obj {
195
+ for _, src := range info.Sources {
196
+ if src.Type == AddressOfAssignment && src.Object != nil {
197
+ // This pointer was assigned &someVar, check if someVar is variable referenced
198
+ return a.NeedsVarRef(src.Object)
212
199
  }
213
200
  }
214
201
  }
215
202
  }
203
+
204
+ // Handle direct pointer initialization like: var p *int = &x
205
+ // Check if the pointer type's element type requires variable referencing
206
+ _ = ptrType.Elem()
207
+ // For now, conservatively return false for untracked cases
216
208
  }
217
209
 
218
210
  return false
219
211
  }
220
212
 
221
- // NeedsBoxedDeref determines whether a pointer dereference operation (*ptr) needs
222
- // the .value suffix in TypeScript when used in a direct dereference expression.
213
+ // NeedsVarRefDeref determines whether a pointer dereference operation (*ptr) needs
214
+ // additional .value access beyond the standard !.value pattern.
223
215
  //
224
- // Critical distinction (source of bugs):
216
+ // Standard pattern: ptr!.value (for *int, *string, etc.)
217
+ // Enhanced pattern: ptr.value!.value (when ptr itself is variable referenced)
225
218
  //
226
- // 1. For primitive types and pointers-to-primitive: Need .value
227
- // *p => p!.value
228
- // **p => p!.value!.value
219
+ // This function returns true when the pointer variable itself is variable referenced,
220
+ // meaning we need an extra .value to access the actual pointer before dereferencing.
229
221
  //
230
- // 2. For pointers to structs: No .value needed because structs are references
231
- // *p => p!
232
- // Where p is a pointer to a struct
222
+ // Examples:
223
+ // - ptr := &x (ptr not variable referenced): *ptr => ptr!.value
224
+ // - ptrPtr := &ptr (ptr is variable referenced): *ptr => ptr.value!.value
233
225
  //
234
- // This distinction is essential because in TypeScript:
235
- // - Primitives are stored inside $.Box with a .value property
236
- // - Structs are reference types, so dereferencing just removes the null possibility
237
- func (a *Analysis) NeedsBoxedDeref(ptrType types.Type) bool {
238
- // If we don't have a valid pointer type, default to true (safer)
239
- if ptrType == nil {
240
- return true
241
- }
242
-
243
- // Unwrap the pointer to get the element type
244
- ptrTypeUnwrapped, ok := ptrType.(*types.Pointer)
245
- if !ok {
246
- return true // Not a pointer type, default to true
247
- }
248
-
249
- // Get the underlying element type
250
- elemType := ptrTypeUnwrapped.Elem()
251
- if elemType == nil {
252
- return true
253
- }
254
-
255
- // Check if the element is another pointer - if so, we always need .value
256
- // This fixes the bug with multi-level pointer dereferencing like **p
257
- if _, isPointer := elemType.(*types.Pointer); isPointer {
258
- return true
259
- }
260
-
261
- // Check if the element is a struct (directly or via a named type)
262
- // Bug fix: Struct pointers in TS don't need .value when dereferenced
263
- // because structs are already references in JavaScript/TypeScript
264
- if _, isStruct := elemType.Underlying().(*types.Struct); isStruct {
265
- return false // Pointers to structs don't need .value suffix in direct dereference (*p)
266
- }
267
-
268
- // For all other cases (primitives, pointers-to-pointers, etc.) need .value
269
- // This ensures primitives and nested pointers are correctly dereferenced
270
- return true
226
+ // Args:
227
+ //
228
+ // ptrType: The type of the pointer being dereferenced
229
+ //
230
+ // Returns:
231
+ //
232
+ // true if additional .value access is needed due to the pointer being variable referenced
233
+ func (a *Analysis) NeedsVarRefDeref(ptrType types.Type) bool {
234
+ // For now, return false - this would need more sophisticated analysis
235
+ // to track when pointer variables themselves are variable referenced
236
+ return false
271
237
  }
272
238
 
273
- // NeedsBoxedFieldAccess determines whether a pointer variable needs the .value
274
- // suffix when accessing fields (e.g., ptr.field).
239
+ // NeedsVarRefFieldAccess determines whether a pointer variable needs the .value
240
+ // access when performing field access through the pointer.
275
241
  //
276
- // Bug fix: This function was a major source of issues with struct field access.
277
- // The critical discovery was that field access through a pointer depends not on the
278
- // field itself, but on whether the pointer variable is boxed:
242
+ // In Go, field access through pointers is automatically dereferenced:
279
243
  //
280
- // 1. For normal struct pointers (unboxed): No .value needed
281
- // Example: let ptr = new MyStruct() => ptr.field
282
- // (Common case from &MyStruct{} literals)
244
+ // ptr.Field // equivalent to (*ptr).Field
283
245
  //
284
- // 2. For boxed struct pointers: Need .value to access the pointed-to struct
285
- // Example: let ptrToVal = val (where val is boxed) => ptrToVal.value.field
246
+ // In TypeScript, we need to determine if the pointer is:
247
+ // 1. A simple pointer: ptr.Field (no .value needed)
248
+ // 2. A variable-referenced pointer: ptr.value.Field (needs .value)
286
249
  //
287
- // We ultimately delegated this decision to WriteSelectorExpr which examines
288
- // the actual variable to determine if it's boxed, rather than just the type.
289
- func (a *Analysis) NeedsBoxedFieldAccess(ptrType types.Type) bool {
290
- // If we don't have a valid pointer type, default to false
291
- if ptrType == nil {
292
- return false
293
- }
294
-
295
- // Unwrap the pointer to get the element type
296
- ptrTypeUnwrapped, ok := ptrType.(*types.Pointer)
297
- if !ok {
298
- return false // Not a pointer type, no dereference needed for field access
299
- }
300
-
301
- // Check if the element is a struct (directly or via a named type)
302
- elemType := ptrTypeUnwrapped.Elem()
303
- if elemType == nil {
304
- return false // Not pointing to anything
305
- }
306
-
307
- // For pointers to structs, check if it's a struct type first
308
- _, isStruct := elemType.Underlying().(*types.Struct)
309
- if !isStruct {
310
- return false // Not a pointer to a struct
311
- }
312
-
313
- // The critical decision: We'll determine if .value is needed in the WriteSelectorExpr function
314
- // by checking if the pointer variable itself is boxed.
315
- // This allows us to handle both:
316
- // - ptr := &MyStruct{} (unboxed, direct access)
317
- // - ptrToVal := &val (boxed, needs .value)
250
+ // Args:
251
+ //
252
+ // ptrType: The pointer type being used for field access
253
+ //
254
+ // Returns:
255
+ //
256
+ // true if .value access is needed before field access
257
+ func (a *Analysis) NeedsVarRefFieldAccess(ptrType types.Type) bool {
258
+ // This would require analysis of the specific pointer variable
259
+ // For now, return false as a conservative default
318
260
  return false
319
261
  }
320
262
 
@@ -479,7 +421,7 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
479
421
  if vr, ok := def.(*types.Var); ok {
480
422
  v.currentReceiver = vr
481
423
  // Add the receiver variable to the VariableUsage map
482
- // to ensure it is properly analyzed for boxing
424
+ // to ensure it is properly analyzed for varRefing
483
425
  v.getOrCreateUsageInfo(v.currentReceiver)
484
426
  }
485
427
  }
@@ -609,7 +551,7 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
609
551
  case *ast.UnaryExpr:
610
552
  // We handle address-of (&) within AssignStmt where it's actually used.
611
553
  // Standalone &x doesn't directly assign, but its usage in assignments
612
- // or function calls determines boxing. Assignments are handled below.
554
+ // or function calls determines varRefing. Assignments are handled below.
613
555
  // Function calls like foo(&x) would require different tracking if needed.
614
556
  // For now, we focus on assignments as per the request.
615
557
  return v
@@ -724,7 +666,7 @@ func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
724
666
 
725
667
  // 2. If RHS involved a source variable (rhsSourceObj is not nil),
726
668
  // record that this source variable was used (its destinations).
727
- // This is CRITICAL for boxing analysis (e.g., if &rhsSourceObj was assigned).
669
+ // This is CRITICAL for varRefing analysis (e.g., if &rhsSourceObj was assigned).
728
670
  if rhsSourceObj != nil {
729
671
  sourceUsageInfo := v.getOrCreateUsageInfo(rhsSourceObj)
730
672
  // The 'Object' in DestinationInfo is what/where rhsSourceObj (or its address) was assigned TO.
@@ -842,7 +784,7 @@ func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
842
784
  }
843
785
 
844
786
  // AnalyzeFile analyzes a Go source file AST and populates the Analysis struct with information
845
- // that will be used during code generation to properly handle pointers, variables that need boxing, etc.
787
+ // that will be used during code generation to properly handle pointers, variables that need varRefing, etc.
846
788
  func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap ast.CommentMap) {
847
789
  // Store the comment map in the analysis object
848
790
  analysis.Cmap = cmap
@@ -0,0 +1,220 @@
1
+ package compiler
2
+
3
+ import (
4
+ "go/ast"
5
+ "go/parser"
6
+ "go/token"
7
+ "go/types"
8
+ "testing"
9
+
10
+ "golang.org/x/tools/go/packages"
11
+ )
12
+
13
+ // TestAnalysisVarRefLogic verifies that the analysis correctly identifies
14
+ // which variables need variable references based on actual compliance test cases
15
+ func TestAnalysisVarRefLogic(t *testing.T) {
16
+ tests := []struct {
17
+ name string
18
+ code string
19
+ expected map[string]AnalysisExpectation
20
+ }{
21
+ {
22
+ name: "pointer_range_loop",
23
+ code: `package main
24
+ func main() {
25
+ arr := [3]int{1, 2, 3}
26
+ arrPtr := &arr
27
+ for i, v := range arrPtr {
28
+ println("index:", i, "value:", v)
29
+ }
30
+ }`,
31
+ expected: map[string]AnalysisExpectation{
32
+ "arr": {NeedsVarRef: true, NeedsVarRefAccess: true}, // varrefed because &arr is taken
33
+ "arrPtr": {NeedsVarRef: false, NeedsVarRefAccess: true}, // NOT varrefed, but points to varrefed value
34
+ "i": {NeedsVarRef: false, NeedsVarRefAccess: false}, // regular loop variable
35
+ "v": {NeedsVarRef: false, NeedsVarRefAccess: false}, // regular loop variable
36
+ },
37
+ },
38
+ {
39
+ name: "simple_pointers",
40
+ code: `package main
41
+ type MyStruct struct {
42
+ Val int
43
+ }
44
+ func main() {
45
+ s1 := MyStruct{Val: 1}
46
+ p1 := &s1
47
+ pp1 := &p1
48
+ p4 := &s1
49
+ _ = p4
50
+ _ = pp1
51
+ }`,
52
+ expected: map[string]AnalysisExpectation{
53
+ "s1": {NeedsVarRef: true, NeedsVarRefAccess: true}, // varrefed because &s1 is taken
54
+ "p1": {NeedsVarRef: true, NeedsVarRefAccess: true}, // varrefed because &p1 is taken
55
+ "pp1": {NeedsVarRef: false, NeedsVarRefAccess: true}, // NOT varrefed, points to varrefed value
56
+ "p4": {NeedsVarRef: false, NeedsVarRefAccess: true}, // NOT varrefed, points to varrefed value
57
+ },
58
+ },
59
+ {
60
+ name: "varref_deref_struct",
61
+ code: `package main
62
+ type MyStruct struct {
63
+ MyInt int
64
+ }
65
+ func main() {
66
+ myStruct := &MyStruct{}
67
+ (*myStruct).MyInt = 5
68
+ println((*myStruct).MyInt)
69
+ }`,
70
+ expected: map[string]AnalysisExpectation{
71
+ "myStruct": {NeedsVarRef: false, NeedsVarRefAccess: false}, // NOT varrefed, direct pointer to struct
72
+ },
73
+ },
74
+ {
75
+ name: "pointer_composite_literal_untyped",
76
+ code: `package main
77
+ func main() {
78
+ var ptr *struct{ x int }
79
+ ptr = &struct{ x int }{42}
80
+ println("Pointer value x:", ptr.x)
81
+
82
+ data := []*struct{ x int }{{42}, {43}}
83
+ println("First element x:", data[0].x)
84
+ println("Second element x:", data[1].x)
85
+ }`,
86
+ expected: map[string]AnalysisExpectation{
87
+ "ptr": {NeedsVarRef: false, NeedsVarRefAccess: false}, // Should NOT be varrefed
88
+ "data": {NeedsVarRef: false, NeedsVarRefAccess: false}, // Should NOT be varrefed
89
+ },
90
+ },
91
+ }
92
+
93
+ for _, tt := range tests {
94
+ t.Run(tt.name, func(t *testing.T) {
95
+ analysis, objects := parseAndAnalyze(t, tt.code)
96
+
97
+ for varName, expected := range tt.expected {
98
+ obj, exists := objects[varName]
99
+ if !exists {
100
+ t.Errorf("Variable %q not found in parsed objects", varName)
101
+ continue
102
+ }
103
+
104
+ actualNeedsVarRef := analysis.NeedsVarRef(obj)
105
+ actualNeedsVarRefAccess := analysis.NeedsVarRefAccess(obj)
106
+
107
+ if actualNeedsVarRef != expected.NeedsVarRef {
108
+ t.Errorf("Variable %q: NeedsVarRef = %v, want %v",
109
+ varName, actualNeedsVarRef, expected.NeedsVarRef)
110
+ }
111
+
112
+ if actualNeedsVarRefAccess != expected.NeedsVarRefAccess {
113
+ t.Errorf("Variable %q: NeedsVarRefAccess = %v, want %v",
114
+ varName, actualNeedsVarRefAccess, expected.NeedsVarRefAccess)
115
+ }
116
+
117
+ // Print debug info
118
+ t.Logf("Variable %q: NeedsVarRef=%v, NeedsVarRefAccess=%v",
119
+ varName, actualNeedsVarRef, actualNeedsVarRefAccess)
120
+ }
121
+ })
122
+ }
123
+ }
124
+
125
+ // AnalysisExpectation defines what we expect from the analysis for a variable
126
+ type AnalysisExpectation struct {
127
+ NeedsVarRef bool
128
+ NeedsVarRefAccess bool
129
+ }
130
+
131
+ // parseAndAnalyze parses Go code and runs the analysis, returning the analysis and variable objects
132
+ func parseAndAnalyze(t *testing.T, code string) (*Analysis, map[string]types.Object) {
133
+ // Parse the code
134
+ fset := token.NewFileSet()
135
+ file, err := parser.ParseFile(fset, "test.go", code, parser.ParseComments)
136
+ if err != nil {
137
+ t.Fatalf("Failed to parse code: %v", err)
138
+ }
139
+
140
+ // Create a minimal package for type checking
141
+ pkg := &packages.Package{
142
+ Syntax: []*ast.File{file},
143
+ TypesInfo: &types.Info{
144
+ Types: make(map[ast.Expr]types.TypeAndValue),
145
+ Defs: make(map[*ast.Ident]types.Object),
146
+ Uses: make(map[*ast.Ident]types.Object),
147
+ },
148
+ }
149
+
150
+ // Type check the package
151
+ typeConfig := &types.Config{}
152
+ typePkg, err := typeConfig.Check("main", fset, pkg.Syntax, pkg.TypesInfo)
153
+ if err != nil {
154
+ t.Fatalf("Failed to type check: %v", err)
155
+ }
156
+ pkg.Types = typePkg
157
+
158
+ // Run analysis
159
+ analysis := NewAnalysis()
160
+ cmap := ast.NewCommentMap(fset, file, file.Comments)
161
+ AnalyzeFile(file, pkg, analysis, cmap)
162
+
163
+ // Collect variable objects
164
+ objects := make(map[string]types.Object)
165
+ for ident, obj := range pkg.TypesInfo.Defs {
166
+ if obj != nil && ident.Name != "_" {
167
+ if _, isVar := obj.(*types.Var); isVar {
168
+ objects[ident.Name] = obj
169
+ }
170
+ }
171
+ }
172
+
173
+ return analysis, objects
174
+ }
175
+
176
+ // TestAnalysisDebugInfo prints debug information about variable usage for manual inspection
177
+ func TestAnalysisDebugInfo(t *testing.T) {
178
+ code := `package main
179
+ func main() {
180
+ arr := [3]int{1, 2, 3}
181
+ arrPtr := &arr
182
+ for i, v := range arrPtr {
183
+ println("index:", i, "value:", v)
184
+ }
185
+ }`
186
+
187
+ analysis, objects := parseAndAnalyze(t, code)
188
+
189
+ t.Log("=== Analysis Debug Information ===")
190
+ for varName, obj := range objects {
191
+ needsVarRef := analysis.NeedsVarRef(obj)
192
+ needsVarRefAccess := analysis.NeedsVarRefAccess(obj)
193
+
194
+ t.Logf("Variable: %s", varName)
195
+ t.Logf(" Type: %s", obj.Type())
196
+ t.Logf(" NeedsVarRef: %v", needsVarRef)
197
+ t.Logf(" NeedsVarRefAccess: %v", needsVarRefAccess)
198
+
199
+ // Print usage info if available
200
+ if usage, exists := analysis.VariableUsage[obj]; exists {
201
+ t.Logf(" Sources: %d", len(usage.Sources))
202
+ for i, src := range usage.Sources {
203
+ srcName := "nil"
204
+ if src.Object != nil {
205
+ srcName = src.Object.Name()
206
+ }
207
+ t.Logf(" [%d] %s (type: %v)", i, srcName, src.Type)
208
+ }
209
+ t.Logf(" Destinations: %d", len(usage.Destinations))
210
+ for i, dst := range usage.Destinations {
211
+ dstName := "nil"
212
+ if dst.Object != nil {
213
+ dstName = dst.Object.Name()
214
+ }
215
+ t.Logf(" [%d] %s (type: %v)", i, dstName, dst.Type)
216
+ }
217
+ }
218
+ t.Log("")
219
+ }
220
+ }