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.
- package/README.md +1 -1
- package/cmd/goscript/cmd_compile.go +1 -1
- package/compiler/analysis.go +73 -131
- package/compiler/analysis_test.go +220 -0
- package/compiler/assignment.go +37 -43
- package/compiler/builtin_test.go +102 -0
- package/compiler/compiler.go +79 -14
- package/compiler/composite-lit.go +108 -43
- package/compiler/config.go +7 -3
- package/compiler/config_test.go +6 -33
- package/compiler/expr-selector.go +66 -41
- package/compiler/expr-star.go +57 -65
- package/compiler/expr-type.go +1 -1
- package/compiler/expr-value.go +1 -1
- package/compiler/expr.go +79 -18
- package/compiler/primitive.go +11 -10
- package/compiler/spec-struct.go +3 -3
- package/compiler/spec-value.go +75 -29
- package/compiler/spec.go +9 -3
- package/compiler/stmt-assign.go +36 -2
- package/compiler/stmt-for.go +11 -0
- package/compiler/stmt-range.go +110 -0
- package/compiler/stmt.go +52 -0
- package/compiler/type.go +36 -11
- package/dist/gs/builtin/builtin.js +37 -0
- package/dist/gs/builtin/builtin.js.map +1 -0
- package/dist/gs/builtin/channel.js +471 -0
- package/dist/gs/builtin/channel.js.map +1 -0
- package/dist/gs/builtin/defer.js +54 -0
- package/dist/gs/builtin/defer.js.map +1 -0
- package/dist/gs/builtin/io.js +15 -0
- package/dist/gs/builtin/io.js.map +1 -0
- package/dist/gs/builtin/map.js +44 -0
- package/dist/gs/builtin/map.js.map +1 -0
- package/dist/gs/builtin/slice.js +799 -0
- package/dist/gs/builtin/slice.js.map +1 -0
- package/dist/gs/builtin/type.js +745 -0
- package/dist/gs/builtin/type.js.map +1 -0
- package/dist/gs/builtin/varRef.js +14 -0
- package/dist/gs/builtin/varRef.js.map +1 -0
- package/dist/gs/context/context.js +55 -0
- package/dist/gs/context/context.js.map +1 -0
- package/dist/gs/context/index.js +2 -0
- package/dist/gs/context/index.js.map +1 -0
- package/dist/gs/runtime/index.js +2 -0
- package/dist/gs/runtime/index.js.map +1 -0
- package/dist/gs/runtime/runtime.js +158 -0
- package/dist/gs/runtime/runtime.js.map +1 -0
- package/dist/gs/time/index.js +2 -0
- package/dist/gs/time/index.js.map +1 -0
- package/dist/gs/time/time.js +115 -0
- package/dist/gs/time/time.js.map +1 -0
- 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
|
-
|
|
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.
|
|
47
|
+
Destination: &cliCompilerConfig.OutputPath,
|
|
48
48
|
Value: "./output",
|
|
49
49
|
EnvVars: []string{"GOSCRIPT_OUTPUT"},
|
|
50
50
|
},
|
package/compiler/analysis.go
CHANGED
|
@@ -145,13 +145,14 @@ func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
|
|
|
145
145
|
return nodeInfo.InAsyncContext
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
//
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
func (a *Analysis)
|
|
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
|
-
//
|
|
169
|
-
// This
|
|
170
|
-
//
|
|
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
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
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
|
-
//
|
|
189
|
-
|
|
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
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
//
|
|
222
|
-
//
|
|
213
|
+
// NeedsVarRefDeref determines whether a pointer dereference operation (*ptr) needs
|
|
214
|
+
// additional .value access beyond the standard !.value pattern.
|
|
223
215
|
//
|
|
224
|
-
//
|
|
216
|
+
// Standard pattern: ptr!.value (for *int, *string, etc.)
|
|
217
|
+
// Enhanced pattern: ptr.value!.value (when ptr itself is variable referenced)
|
|
225
218
|
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
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
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
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
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
//
|
|
244
|
-
|
|
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
|
-
//
|
|
274
|
-
//
|
|
239
|
+
// NeedsVarRefFieldAccess determines whether a pointer variable needs the .value
|
|
240
|
+
// access when performing field access through the pointer.
|
|
275
241
|
//
|
|
276
|
-
//
|
|
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
|
-
//
|
|
281
|
-
// Example: let ptr = new MyStruct() => ptr.field
|
|
282
|
-
// (Common case from &MyStruct{} literals)
|
|
244
|
+
// ptr.Field // equivalent to (*ptr).Field
|
|
283
245
|
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
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
|
-
//
|
|
288
|
-
//
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
//
|
|
296
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
+
}
|