goscript 0.0.13 → 0.0.16
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/LICENSE +2 -1
- package/README.md +163 -45
- package/builtin/builtin.ts +1169 -178
- package/cmd/goscript/cmd_compile.go +2 -2
- package/compiler/analysis.go +726 -0
- package/compiler/compiler.go +5701 -4
- package/compiler/compiler_test.go +104 -0
- package/compiler/config.go +3 -3
- package/compiler/config_test.go +89 -0
- package/compiler/index.ts +1 -1
- package/compiler/output.go +26 -0
- package/compiler/write-type-spec.go +506 -0
- package/compiler/writer.go +11 -0
- package/dist/builtin/builtin.d.ts +204 -67
- package/dist/builtin/builtin.js +846 -144
- package/dist/builtin/builtin.js.map +1 -1
- package/dist/compiler/index.d.ts +1 -1
- package/go.mod +5 -6
- package/go.sum +6 -11
- package/package.json +21 -5
- package/compiler/compile.go +0 -190
- package/compiler/compile_comment.go +0 -41
- package/compiler/compile_decls.go +0 -84
- package/compiler/compile_expr.go +0 -1022
- package/compiler/compile_field.go +0 -110
- package/compiler/compile_spec.go +0 -566
- package/compiler/compile_stmt.go +0 -1616
- package/compiler/context.go +0 -9
- package/compiler/file_compiler.go +0 -80
- package/compiler/output_path.go +0 -31
- package/compiler/pkg_compiler.go +0 -72
- package/compiler/types/tokens.go +0 -66
- package/compiler/types/types.go +0 -46
- package/dist/compliance/tests/array_literal/array_literal.gs.d.ts +0 -1
- package/dist/compliance/tests/array_literal/array_literal.gs.js +0 -15
- package/dist/compliance/tests/array_literal/array_literal.gs.js.map +0 -1
- package/dist/compliance/tests/async_basic/async_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/async_basic/async_basic.gs.js +0 -24
- package/dist/compliance/tests/async_basic/async_basic.gs.js.map +0 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js +0 -82
- package/dist/compliance/tests/async_defer_statement/async_defer_statement.gs.js.map +0 -1
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.d.ts +0 -1
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.js +0 -16
- package/dist/compliance/tests/basic_arithmetic/basic_arithmetic.gs.js.map +0 -1
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.d.ts +0 -1
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.js +0 -14
- package/dist/compliance/tests/boolean_logic/boolean_logic.gs.js.map +0 -1
- package/dist/compliance/tests/channel_basic/channel_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/channel_basic/channel_basic.gs.js +0 -14
- package/dist/compliance/tests/channel_basic/channel_basic.gs.js.map +0 -1
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js +0 -27
- package/dist/compliance/tests/composite_literal_assignment/composite_literal_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/constants/constants.gs.d.ts +0 -1
- package/dist/compliance/tests/constants/constants.gs.js +0 -16
- package/dist/compliance/tests/constants/constants.gs.js.map +0 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.d.ts +0 -1
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js +0 -33
- package/dist/compliance/tests/copy_independence/copy_independence.gs.js.map +0 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js +0 -75
- package/dist/compliance/tests/defer_statement/defer_statement.gs.js.map +0 -1
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js +0 -37
- package/dist/compliance/tests/embedded_interface_assertion/embedded_interface_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.d.ts +0 -1
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js +0 -29
- package/dist/compliance/tests/flag_bitwise_op/flag_bitwise_op.gs.js.map +0 -1
- package/dist/compliance/tests/float64/float64.gs.d.ts +0 -1
- package/dist/compliance/tests/float64/float64.gs.js +0 -24
- package/dist/compliance/tests/float64/float64.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.js +0 -10
- package/dist/compliance/tests/for_loop_basic/for_loop_basic.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.js +0 -11
- package/dist/compliance/tests/for_loop_condition_only/for_loop_condition_only.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_condition_only/main.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_condition_only/main.gs.js +0 -10
- package/dist/compliance/tests/for_loop_condition_only/main.gs.js.map +0 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.d.ts +0 -1
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js +0 -14
- package/dist/compliance/tests/for_loop_infinite/for_loop_infinite.gs.js.map +0 -1
- package/dist/compliance/tests/for_range/for_range.gs.d.ts +0 -1
- package/dist/compliance/tests/for_range/for_range.gs.js +0 -39
- package/dist/compliance/tests/for_range/for_range.gs.js.map +0 -1
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.d.ts +0 -1
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.js +0 -15
- package/dist/compliance/tests/for_range_index_use/for_range_index_use.gs.js.map +0 -1
- package/dist/compliance/tests/func_literal/func_literal.gs.d.ts +0 -1
- package/dist/compliance/tests/func_literal/func_literal.gs.js +0 -10
- package/dist/compliance/tests/func_literal/func_literal.gs.js.map +0 -1
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.d.ts +0 -12
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js +0 -30
- package/dist/compliance/tests/function_call_result_assignment/function_call_result_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/if_statement/if_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/if_statement/if_statement.gs.js +0 -13
- package/dist/compliance/tests/if_statement/if_statement.gs.js.map +0 -1
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js +0 -12
- package/dist/compliance/tests/interface_method_comments/interface_method_comments.gs.js.map +0 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js +0 -34
- package/dist/compliance/tests/interface_multi_param_return/interface_multi_param_return.gs.js.map +0 -1
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js +0 -32
- package/dist/compliance/tests/interface_to_interface_type_assertion/interface_to_interface_type_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js +0 -40
- package/dist/compliance/tests/interface_type_assertion/interface_type_assertion.gs.js.map +0 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.d.ts +0 -1
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js +0 -51
- package/dist/compliance/tests/interface_type_assertion_signature_mismatch/interface_type_assertion_signature_mismatch.gs.js.map +0 -1
- package/dist/compliance/tests/map_support/map_support.gs.d.ts +0 -1
- package/dist/compliance/tests/map_support/map_support.gs.js +0 -88
- package/dist/compliance/tests/map_support/map_support.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js +0 -24
- package/dist/compliance/tests/method_call_on_pointer_receiver/method_call_on_pointer_receiver.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js +0 -33
- package/dist/compliance/tests/method_call_on_pointer_via_value/method_call_on_pointer_via_value.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js +0 -22
- package/dist/compliance/tests/method_call_on_value_receiver/method_call_on_value_receiver.gs.js.map +0 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.d.ts +0 -1
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js +0 -33
- package/dist/compliance/tests/method_call_on_value_via_pointer/method_call_on_value_via_pointer.gs.js.map +0 -1
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.d.ts +0 -1
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.js +0 -17
- package/dist/compliance/tests/multiple_return_values/multiple_return_values.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js +0 -29
- package/dist/compliance/tests/pointer_assignment_no_copy/pointer_assignment_no_copy.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js +0 -27
- package/dist/compliance/tests/pointer_composite_literal_assignment/pointer_composite_literal_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js +0 -22
- package/dist/compliance/tests/pointer_deref_multiassign/pointer_deref_multiassign.gs.js.map +0 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.d.ts +0 -1
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js +0 -20
- package/dist/compliance/tests/pointer_initialization/pointer_initialization.gs.js.map +0 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.d.ts +0 -1
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js +0 -28
- package/dist/compliance/tests/select_receive_on_closed_channel_no_default/select_receive_on_closed_channel_no_default.gs.js.map +0 -1
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.d.ts +0 -1
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js +0 -30
- package/dist/compliance/tests/select_send_on_full_buffered_channel_with_default/select_send_on_full_buffered_channel_with_default.gs.js.map +0 -1
- package/dist/compliance/tests/select_statement/select_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/select_statement/select_statement.gs.js +0 -207
- package/dist/compliance/tests/select_statement/select_statement.gs.js.map +0 -1
- package/dist/compliance/tests/simple/simple.gs.d.ts +0 -1
- package/dist/compliance/tests/simple/simple.gs.js +0 -6
- package/dist/compliance/tests/simple/simple.gs.js.map +0 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.d.ts +0 -1
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js +0 -24
- package/dist/compliance/tests/simple_deref_assignment/simple_deref_assignment.gs.js.map +0 -1
- package/dist/compliance/tests/slices/slices.gs.d.ts +0 -1
- package/dist/compliance/tests/slices/slices.gs.js +0 -294
- package/dist/compliance/tests/slices/slices.gs.js.map +0 -1
- package/dist/compliance/tests/string_conversion/string_conversion.gs.d.ts +0 -1
- package/dist/compliance/tests/string_conversion/string_conversion.gs.js +0 -41
- package/dist/compliance/tests/string_conversion/string_conversion.gs.js.map +0 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.d.ts +0 -1
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js +0 -17
- package/dist/compliance/tests/string_rune_conversion/string_rune_conversion.gs.js.map +0 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js +0 -48
- package/dist/compliance/tests/struct_embedding/struct_embedding.gs.js.map +0 -1
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js +0 -19
- package/dist/compliance/tests/struct_field_access/struct_field_access.gs.js.map +0 -1
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js +0 -26
- package/dist/compliance/tests/struct_pointer_interface_fields/struct_pointer_interface_fields.gs.js.map +0 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.d.ts +0 -1
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js +0 -30
- package/dist/compliance/tests/struct_value_init_clone/struct_value_init_clone.gs.js.map +0 -1
- package/dist/compliance/tests/switch_statement/switch_statement.gs.d.ts +0 -1
- package/dist/compliance/tests/switch_statement/switch_statement.gs.js +0 -76
- package/dist/compliance/tests/switch_statement/switch_statement.gs.js.map +0 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.d.ts +0 -1
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js +0 -31
- package/dist/compliance/tests/value_type_copy_behavior/value_type_copy_behavior.gs.js.map +0 -1
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
package compiler
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/ast"
|
|
5
|
+
"go/token"
|
|
6
|
+
"go/types"
|
|
7
|
+
|
|
8
|
+
"golang.org/x/tools/go/packages"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// fileImport tracks an import in a file.
|
|
12
|
+
type fileImport struct {
|
|
13
|
+
importPath string
|
|
14
|
+
importVars map[string]struct{}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// AssignmentType indicates how a variable's value was assigned or used.
|
|
18
|
+
type AssignmentType int
|
|
19
|
+
|
|
20
|
+
const (
|
|
21
|
+
// DirectAssignment represents a direct value copy (e.g., x = y)
|
|
22
|
+
DirectAssignment AssignmentType = iota
|
|
23
|
+
// AddressOfAssignment represents taking the address (e.g., p = &y)
|
|
24
|
+
// or assigning to a dereferenced pointer (*p = y) - indicating the pointer p is used.
|
|
25
|
+
AddressOfAssignment
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
// AssignmentInfo stores information about a single assignment source or destination.
|
|
29
|
+
type AssignmentInfo struct {
|
|
30
|
+
Object types.Object // The source or destination variable object
|
|
31
|
+
Type AssignmentType // The type of assignment involved
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// VariableUsageInfo tracks how a variable is used throughout the code.
|
|
35
|
+
type VariableUsageInfo struct {
|
|
36
|
+
// Sources lists variables whose values (or addresses) are assigned TO this variable.
|
|
37
|
+
// Example: For `x = y`, y is a source for x. For `x = &y`, y is a source for x.
|
|
38
|
+
Sources []AssignmentInfo
|
|
39
|
+
// Destinations lists variables that are assigned the value (or address) FROM this variable.
|
|
40
|
+
// Example: For `y = x`, y is a destination for x. For `p = &x`, p is a destination for x.
|
|
41
|
+
Destinations []AssignmentInfo
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Analysis holds information gathered during the analysis phase of the Go code compilation.
|
|
45
|
+
// This data is used to make decisions about how to generate TypeScript code.
|
|
46
|
+
// Analysis is read-only after being built and should not be modified during code generation.
|
|
47
|
+
type Analysis struct {
|
|
48
|
+
// VariableUsage tracks how variables are assigned and used, particularly for pointer analysis.
|
|
49
|
+
// The key is the variable's types.Object.
|
|
50
|
+
VariableUsage map[types.Object]*VariableUsageInfo
|
|
51
|
+
|
|
52
|
+
// Imports stores the imports for the file
|
|
53
|
+
Imports map[string]*fileImport
|
|
54
|
+
|
|
55
|
+
// Cmap stores the comment map for the file
|
|
56
|
+
Cmap ast.CommentMap
|
|
57
|
+
|
|
58
|
+
// AsyncFuncs tracks which functions are async using the function's types.Object
|
|
59
|
+
// as the key to avoid false-positive matches with functions having the same name
|
|
60
|
+
AsyncFuncs map[types.Object]bool
|
|
61
|
+
|
|
62
|
+
// NeedsDeferMap tracks nodes that need defer handling
|
|
63
|
+
NeedsDeferMap map[ast.Node]bool
|
|
64
|
+
|
|
65
|
+
// IsInAsyncFunctionMap tracks nodes that are inside async functions
|
|
66
|
+
IsInAsyncFunctionMap map[ast.Node]bool
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// NewAnalysis creates a new Analysis instance.
|
|
70
|
+
func NewAnalysis() *Analysis {
|
|
71
|
+
return &Analysis{
|
|
72
|
+
VariableUsage: make(map[types.Object]*VariableUsageInfo),
|
|
73
|
+
Imports: make(map[string]*fileImport),
|
|
74
|
+
AsyncFuncs: make(map[types.Object]bool),
|
|
75
|
+
NeedsDeferMap: make(map[ast.Node]bool),
|
|
76
|
+
IsInAsyncFunctionMap: make(map[ast.Node]bool),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// NeedsDefer returns whether the given node needs defer handling.
|
|
81
|
+
func (a *Analysis) NeedsDefer(node ast.Node) bool {
|
|
82
|
+
if node == nil {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
return a.NeedsDeferMap[node]
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// IsInAsyncFunction returns whether the given node is inside an async function.
|
|
89
|
+
func (a *Analysis) IsInAsyncFunction(node ast.Node) bool {
|
|
90
|
+
if node == nil {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
return a.IsInAsyncFunctionMap[node]
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// IsAsyncFunc returns whether the given object represents an async function.
|
|
97
|
+
func (a *Analysis) IsAsyncFunc(obj types.Object) bool {
|
|
98
|
+
if obj == nil {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
return a.AsyncFuncs[obj]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// IsFuncLitAsync checks if a function literal is async based on our analysis.
|
|
105
|
+
func (a *Analysis) IsFuncLitAsync(funcLit *ast.FuncLit) bool {
|
|
106
|
+
if funcLit == nil {
|
|
107
|
+
return false
|
|
108
|
+
}
|
|
109
|
+
// Function literals are marked during analysis if they contain async operations
|
|
110
|
+
return a.IsInAsyncFunctionMap[funcLit]
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// NeedsBoxed returns whether the given object needs to be boxed.
|
|
114
|
+
// According to the new logic, a variable needs boxing if its address is taken
|
|
115
|
+
// and assigned to another variable (i.e., it appears as a destination with AddressOfAssignment).
|
|
116
|
+
func (a *Analysis) NeedsBoxed(obj types.Object) bool {
|
|
117
|
+
if obj == nil {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
usageInfo, exists := a.VariableUsage[obj]
|
|
121
|
+
if !exists {
|
|
122
|
+
return false
|
|
123
|
+
}
|
|
124
|
+
// Check if any destination assignment involves taking the address of 'obj'
|
|
125
|
+
for _, destInfo := range usageInfo.Destinations {
|
|
126
|
+
if destInfo.Type == AddressOfAssignment {
|
|
127
|
+
return true
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// NeedsBoxedAccess returns whether accessing the given object requires '.value' access in TypeScript.
|
|
134
|
+
// This function is critical for correctly handling pointer dereferencing by determining when
|
|
135
|
+
// a variable is boxed and needs .value to access its content.
|
|
136
|
+
//
|
|
137
|
+
// Two distinct cases determine when a variable needs .value access:
|
|
138
|
+
//
|
|
139
|
+
// 1. The variable itself is boxed (its address is taken)
|
|
140
|
+
// Example: let x = $.box(10) => x.value
|
|
141
|
+
//
|
|
142
|
+
// 2. For pointer variables: it points to a boxed struct variable rather than a direct struct literal
|
|
143
|
+
// Example: let ptrToVal = val (where val is boxed) => ptrToVal.value
|
|
144
|
+
// vs. let ptr = new MyStruct() => ptr (no .value needed)
|
|
145
|
+
//
|
|
146
|
+
// This distinction is crucial for avoiding over-dereferencing or under-dereferencing,
|
|
147
|
+
// which was the root cause of several bugs in our pointer handling.
|
|
148
|
+
func (a *Analysis) NeedsBoxedAccess(obj types.Object) bool {
|
|
149
|
+
if obj == nil {
|
|
150
|
+
return false
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// First, check if the variable itself is boxed - this always requires .value
|
|
154
|
+
// A variable is boxed if its address is taken elsewhere in the code
|
|
155
|
+
if a.NeedsBoxed(obj) {
|
|
156
|
+
return true
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Check if this is a pointer variable pointing to a boxed struct value
|
|
160
|
+
objType := obj.Type()
|
|
161
|
+
if ptrType, isPointer := objType.Underlying().(*types.Pointer); isPointer {
|
|
162
|
+
// Check if it's a pointer to a struct
|
|
163
|
+
if elemType := ptrType.Elem(); elemType != nil {
|
|
164
|
+
if _, isStructType := elemType.Underlying().(*types.Struct); isStructType {
|
|
165
|
+
// For struct pointers, check if it points to a boxed struct variable
|
|
166
|
+
if usageInfo, exists := a.VariableUsage[obj]; exists {
|
|
167
|
+
for _, src := range usageInfo.Sources {
|
|
168
|
+
// Check if this pointer was assigned the address of another variable
|
|
169
|
+
// (e.g., ptr = &someVar) rather than a direct literal (ptr = &Struct{})
|
|
170
|
+
if src.Type == AddressOfAssignment && src.Object != nil {
|
|
171
|
+
// Bug fix: If the source variable is boxed, the pointer needs .value to access it
|
|
172
|
+
// This distinguishes between:
|
|
173
|
+
// - ptrToVal := &val (val is boxed, so we need ptrToVal.value)
|
|
174
|
+
// - ptr := &MyStruct{} (direct literal, no boxing needed)
|
|
175
|
+
return a.NeedsBoxed(src.Object)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return false
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// NeedsBoxedDeref determines whether a pointer dereference operation (*ptr) needs
|
|
187
|
+
// the .value suffix in TypeScript when used in a direct dereference expression.
|
|
188
|
+
//
|
|
189
|
+
// Critical distinction (source of bugs):
|
|
190
|
+
//
|
|
191
|
+
// 1. For primitive types and pointers-to-primitive: Need .value
|
|
192
|
+
// *p => p!.value
|
|
193
|
+
// **p => p!.value!.value
|
|
194
|
+
//
|
|
195
|
+
// 2. For pointers to structs: No .value needed because structs are references
|
|
196
|
+
// *p => p!
|
|
197
|
+
// Where p is a pointer to a struct
|
|
198
|
+
//
|
|
199
|
+
// This distinction is essential because in TypeScript:
|
|
200
|
+
// - Primitives are stored inside $.Box with a .value property
|
|
201
|
+
// - Structs are reference types, so dereferencing just removes the null possibility
|
|
202
|
+
func (a *Analysis) NeedsBoxedDeref(ptrType types.Type) bool {
|
|
203
|
+
// If we don't have a valid pointer type, default to true (safer)
|
|
204
|
+
if ptrType == nil {
|
|
205
|
+
return true
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Unwrap the pointer to get the element type
|
|
209
|
+
ptrTypeUnwrapped, ok := ptrType.(*types.Pointer)
|
|
210
|
+
if !ok {
|
|
211
|
+
return true // Not a pointer type, default to true
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Get the underlying element type
|
|
215
|
+
elemType := ptrTypeUnwrapped.Elem()
|
|
216
|
+
if elemType == nil {
|
|
217
|
+
return true
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Check if the element is another pointer - if so, we always need .value
|
|
221
|
+
// This fixes the bug with multi-level pointer dereferencing like **p
|
|
222
|
+
if _, isPointer := elemType.(*types.Pointer); isPointer {
|
|
223
|
+
return true
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check if the element is a struct (directly or via a named type)
|
|
227
|
+
// Bug fix: Struct pointers in TS don't need .value when dereferenced
|
|
228
|
+
// because structs are already references in JavaScript/TypeScript
|
|
229
|
+
if _, isStruct := elemType.Underlying().(*types.Struct); isStruct {
|
|
230
|
+
return false // Pointers to structs don't need .value suffix in direct dereference (*p)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// For all other cases (primitives, pointers-to-pointers, etc.) need .value
|
|
234
|
+
// This ensures primitives and nested pointers are correctly dereferenced
|
|
235
|
+
return true
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// NeedsBoxedFieldAccess determines whether a pointer variable needs the .value
|
|
239
|
+
// suffix when accessing fields (e.g., ptr.field).
|
|
240
|
+
//
|
|
241
|
+
// Bug fix: This function was a major source of issues with struct field access.
|
|
242
|
+
// The critical discovery was that field access through a pointer depends not on the
|
|
243
|
+
// field itself, but on whether the pointer variable is boxed:
|
|
244
|
+
//
|
|
245
|
+
// 1. For normal struct pointers (unboxed): No .value needed
|
|
246
|
+
// Example: let ptr = new MyStruct() => ptr.field
|
|
247
|
+
// (Common case from &MyStruct{} literals)
|
|
248
|
+
//
|
|
249
|
+
// 2. For boxed struct pointers: Need .value to access the pointed-to struct
|
|
250
|
+
// Example: let ptrToVal = val (where val is boxed) => ptrToVal.value.field
|
|
251
|
+
//
|
|
252
|
+
// We ultimately delegated this decision to WriteSelectorExpr which examines
|
|
253
|
+
// the actual variable to determine if it's boxed, rather than just the type.
|
|
254
|
+
func (a *Analysis) NeedsBoxedFieldAccess(ptrType types.Type) bool {
|
|
255
|
+
// If we don't have a valid pointer type, default to false
|
|
256
|
+
if ptrType == nil {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Unwrap the pointer to get the element type
|
|
261
|
+
ptrTypeUnwrapped, ok := ptrType.(*types.Pointer)
|
|
262
|
+
if !ok {
|
|
263
|
+
return false // Not a pointer type, no dereference needed for field access
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Check if the element is a struct (directly or via a named type)
|
|
267
|
+
elemType := ptrTypeUnwrapped.Elem()
|
|
268
|
+
if elemType == nil {
|
|
269
|
+
return false // Not pointing to anything
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// For pointers to structs, check if it's a struct type first
|
|
273
|
+
_, isStruct := elemType.Underlying().(*types.Struct)
|
|
274
|
+
if !isStruct {
|
|
275
|
+
return false // Not a pointer to a struct
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// The critical decision: We'll determine if .value is needed in the WriteSelectorExpr function
|
|
279
|
+
// by checking if the pointer variable itself is boxed.
|
|
280
|
+
// This allows us to handle both:
|
|
281
|
+
// - ptr := &MyStruct{} (unboxed, direct access)
|
|
282
|
+
// - ptrToVal := &val (boxed, needs .value)
|
|
283
|
+
return false
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// analysisVisitor implements ast.Visitor and is used to traverse the AST during analysis.
|
|
287
|
+
type analysisVisitor struct {
|
|
288
|
+
// analysis stores information gathered during the traversal
|
|
289
|
+
analysis *Analysis
|
|
290
|
+
|
|
291
|
+
// pkg provides type information and other package details
|
|
292
|
+
pkg *packages.Package
|
|
293
|
+
|
|
294
|
+
// inAsyncFunction tracks if we're currently inside an async function
|
|
295
|
+
inAsyncFunction bool
|
|
296
|
+
|
|
297
|
+
// currentFuncName tracks the name of the function we're currently analyzing
|
|
298
|
+
currentFuncName string
|
|
299
|
+
|
|
300
|
+
// currentReceiver tracks the object of the receiver if inside a method
|
|
301
|
+
currentReceiver *types.Var
|
|
302
|
+
|
|
303
|
+
// currentFuncObj tracks the object of the function declaration we're currently analyzing
|
|
304
|
+
currentFuncObj types.Object
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// getOrCreateUsageInfo retrieves or creates the VariableUsageInfo for a given object.
|
|
308
|
+
func (v *analysisVisitor) getOrCreateUsageInfo(obj types.Object) *VariableUsageInfo {
|
|
309
|
+
if obj == nil {
|
|
310
|
+
return nil // Should not happen with valid objects
|
|
311
|
+
}
|
|
312
|
+
info, exists := v.analysis.VariableUsage[obj]
|
|
313
|
+
if !exists {
|
|
314
|
+
info = &VariableUsageInfo{}
|
|
315
|
+
v.analysis.VariableUsage[obj] = info
|
|
316
|
+
}
|
|
317
|
+
return info
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Visit implements the ast.Visitor interface.
|
|
321
|
+
// It analyzes each node in the AST to gather information needed for code generation.
|
|
322
|
+
func (v *analysisVisitor) Visit(node ast.Node) ast.Visitor {
|
|
323
|
+
if node == nil {
|
|
324
|
+
return nil
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Store async state for the current node
|
|
328
|
+
v.analysis.IsInAsyncFunctionMap[node] = v.inAsyncFunction
|
|
329
|
+
|
|
330
|
+
switch n := node.(type) {
|
|
331
|
+
case *ast.GenDecl:
|
|
332
|
+
// Handle general declarations (var, const, type, import)
|
|
333
|
+
if n.Tok == token.VAR {
|
|
334
|
+
for _, spec := range n.Specs {
|
|
335
|
+
if valueSpec, ok := spec.(*ast.ValueSpec); ok {
|
|
336
|
+
// Process each declared variable (LHS)
|
|
337
|
+
for i, lhsIdent := range valueSpec.Names {
|
|
338
|
+
if lhsIdent.Name == "_" {
|
|
339
|
+
continue
|
|
340
|
+
}
|
|
341
|
+
lhsObj := v.pkg.TypesInfo.ObjectOf(lhsIdent)
|
|
342
|
+
if lhsObj == nil {
|
|
343
|
+
continue
|
|
344
|
+
}
|
|
345
|
+
// Ensure usage info exists for LHS
|
|
346
|
+
lhsUsageInfo := v.getOrCreateUsageInfo(lhsObj)
|
|
347
|
+
|
|
348
|
+
// Check if there's a corresponding initial value (RHS)
|
|
349
|
+
if valueSpec.Values != nil && i < len(valueSpec.Values) {
|
|
350
|
+
rhsExpr := valueSpec.Values[i]
|
|
351
|
+
|
|
352
|
+
// --- Analyze RHS and Update Usage Info (similar to AssignStmt) ---
|
|
353
|
+
assignmentType := DirectAssignment
|
|
354
|
+
var sourceObj types.Object
|
|
355
|
+
|
|
356
|
+
if unaryExpr, ok := rhsExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
357
|
+
// Case: var lhs = &rhs_ident
|
|
358
|
+
assignmentType = AddressOfAssignment
|
|
359
|
+
if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
360
|
+
sourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
361
|
+
}
|
|
362
|
+
} else if rhsIdent, ok := rhsExpr.(*ast.Ident); ok {
|
|
363
|
+
// Case: var lhs = rhs_ident
|
|
364
|
+
assignmentType = DirectAssignment
|
|
365
|
+
sourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// --- Record Usage ---
|
|
369
|
+
if sourceObj != nil {
|
|
370
|
+
// Record source for LHS
|
|
371
|
+
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
372
|
+
Object: sourceObj,
|
|
373
|
+
Type: assignmentType,
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
// Record destination for RHS source
|
|
377
|
+
sourceUsageInfo := v.getOrCreateUsageInfo(sourceObj)
|
|
378
|
+
sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
|
|
379
|
+
Object: lhsObj,
|
|
380
|
+
Type: assignmentType,
|
|
381
|
+
})
|
|
382
|
+
}
|
|
383
|
+
// Note: We might need to handle var lhs = &T{} cases later if necessary.
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Continue traversal AFTER processing the declaration itself
|
|
390
|
+
// to handle expressions within initial values if needed.
|
|
391
|
+
// However, the core usage tracking is done above.
|
|
392
|
+
// Let standard traversal handle children.
|
|
393
|
+
return v
|
|
394
|
+
|
|
395
|
+
case *ast.FuncDecl:
|
|
396
|
+
// Determine if this function declaration is async based on its body
|
|
397
|
+
v.currentFuncName = n.Name.Name
|
|
398
|
+
isAsync := false
|
|
399
|
+
if n.Body != nil {
|
|
400
|
+
containsAsyncOps := v.containsAsyncOperations(n.Body)
|
|
401
|
+
if containsAsyncOps {
|
|
402
|
+
// Get the object for this function declaration
|
|
403
|
+
if obj := v.pkg.TypesInfo.ObjectOf(n.Name); obj != nil {
|
|
404
|
+
v.analysis.AsyncFuncs[obj] = true
|
|
405
|
+
}
|
|
406
|
+
isAsync = true
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
v.analysis.IsInAsyncFunctionMap[n] = isAsync
|
|
410
|
+
|
|
411
|
+
// Set current receiver if this is a method
|
|
412
|
+
originalReceiver := v.currentReceiver
|
|
413
|
+
v.currentReceiver = nil // Reset for current function
|
|
414
|
+
if n.Recv != nil && len(n.Recv.List) > 0 {
|
|
415
|
+
// Assuming a single receiver for simplicity for now
|
|
416
|
+
if len(n.Recv.List[0].Names) > 0 {
|
|
417
|
+
if ident := n.Recv.List[0].Names[0]; ident != nil && ident.Name != "_" {
|
|
418
|
+
if def := v.pkg.TypesInfo.Defs[ident]; def != nil {
|
|
419
|
+
if vr, ok := def.(*types.Var); ok {
|
|
420
|
+
v.currentReceiver = vr
|
|
421
|
+
// Add the receiver variable to the VariableUsage map
|
|
422
|
+
// to ensure it is properly analyzed for boxing
|
|
423
|
+
v.getOrCreateUsageInfo(v.currentReceiver)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Save original states to restore after visiting
|
|
431
|
+
originalInAsync := v.inAsyncFunction
|
|
432
|
+
originalFuncObj := v.currentFuncObj
|
|
433
|
+
|
|
434
|
+
// Update visitor state for this function
|
|
435
|
+
v.inAsyncFunction = isAsync
|
|
436
|
+
v.currentFuncObj = v.pkg.TypesInfo.ObjectOf(n.Name)
|
|
437
|
+
v.analysis.IsInAsyncFunctionMap[n] = isAsync // Ensure FuncDecl node itself is marked
|
|
438
|
+
|
|
439
|
+
// Check if the body contains any defer statements
|
|
440
|
+
if n.Body != nil && v.containsDefer(n.Body) {
|
|
441
|
+
v.analysis.NeedsDeferMap[n.Body] = true
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Visit the body with updated state
|
|
445
|
+
ast.Walk(v, n.Body)
|
|
446
|
+
|
|
447
|
+
// Restore states after visiting
|
|
448
|
+
defer func() {
|
|
449
|
+
v.currentFuncName = ""
|
|
450
|
+
v.inAsyncFunction = originalInAsync
|
|
451
|
+
v.currentReceiver = originalReceiver
|
|
452
|
+
v.currentFuncObj = originalFuncObj
|
|
453
|
+
}()
|
|
454
|
+
return nil // Stop traversal here, ast.Walk handled the body
|
|
455
|
+
|
|
456
|
+
case *ast.FuncLit:
|
|
457
|
+
// Determine if this function literal is async based on its body
|
|
458
|
+
isAsync := v.containsAsyncOperations(n.Body)
|
|
459
|
+
v.analysis.IsInAsyncFunctionMap[n] = isAsync
|
|
460
|
+
|
|
461
|
+
// Save original inAsyncFunction state to restore after visiting
|
|
462
|
+
originalInAsync := v.inAsyncFunction
|
|
463
|
+
v.inAsyncFunction = isAsync
|
|
464
|
+
|
|
465
|
+
// Check if the body contains any defer statements
|
|
466
|
+
if n.Body != nil && v.containsDefer(n.Body) {
|
|
467
|
+
v.analysis.NeedsDeferMap[n.Body] = true
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Visit the body with updated state
|
|
471
|
+
ast.Walk(v, n.Body)
|
|
472
|
+
|
|
473
|
+
// Restore inAsyncFunction state after visiting
|
|
474
|
+
v.inAsyncFunction = originalInAsync
|
|
475
|
+
return nil // Stop traversal here, ast.Walk handled the body
|
|
476
|
+
|
|
477
|
+
case *ast.BlockStmt:
|
|
478
|
+
// Check for defer statements in this block
|
|
479
|
+
if v.containsDefer(n) {
|
|
480
|
+
v.analysis.NeedsDeferMap[n] = true
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Store async state for this block
|
|
484
|
+
v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
|
|
485
|
+
|
|
486
|
+
return v
|
|
487
|
+
|
|
488
|
+
case *ast.UnaryExpr:
|
|
489
|
+
// We handle address-of (&) within AssignStmt where it's actually used.
|
|
490
|
+
// Standalone &x doesn't directly assign, but its usage in assignments
|
|
491
|
+
// or function calls determines boxing. Assignments are handled below.
|
|
492
|
+
// Function calls like foo(&x) would require different tracking if needed.
|
|
493
|
+
// For now, we focus on assignments as per the request.
|
|
494
|
+
return v
|
|
495
|
+
|
|
496
|
+
case *ast.CallExpr:
|
|
497
|
+
// Check if this is a function call that might be async
|
|
498
|
+
if funcIdent, ok := n.Fun.(*ast.Ident); ok {
|
|
499
|
+
// Get the object for this function call
|
|
500
|
+
if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
|
|
501
|
+
// We're calling an async function, so mark current function as async if we're in one
|
|
502
|
+
if v.currentFuncObj != nil {
|
|
503
|
+
v.analysis.AsyncFuncs[v.currentFuncObj] = true
|
|
504
|
+
v.inAsyncFunction = true // Update visitor state
|
|
505
|
+
// Mark the FuncDecl node itself if possible (might need to store the node too)
|
|
506
|
+
for nodeAst := range v.analysis.IsInAsyncFunctionMap { // Find the node to update
|
|
507
|
+
if fd, ok := nodeAst.(*ast.FuncDecl); ok && v.pkg.TypesInfo.ObjectOf(fd.Name) == v.currentFuncObj {
|
|
508
|
+
v.analysis.IsInAsyncFunctionMap[nodeAst] = true
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Store async state for this call expression
|
|
516
|
+
v.analysis.IsInAsyncFunctionMap[n] = v.inAsyncFunction
|
|
517
|
+
|
|
518
|
+
return v
|
|
519
|
+
|
|
520
|
+
case *ast.SelectorExpr:
|
|
521
|
+
// No need to track private field access since all fields are public
|
|
522
|
+
return v
|
|
523
|
+
|
|
524
|
+
case *ast.AssignStmt:
|
|
525
|
+
for i, currentLHSExpr := range n.Lhs {
|
|
526
|
+
if i >= len(n.Rhs) {
|
|
527
|
+
break // Should not happen in valid Go
|
|
528
|
+
}
|
|
529
|
+
currentRHSExpr := n.Rhs[i]
|
|
530
|
+
|
|
531
|
+
// --- Analyze RHS to determine assignment type and source object (if any) ---
|
|
532
|
+
rhsAssignmentType := DirectAssignment
|
|
533
|
+
var rhsSourceObj types.Object // The variable object on the RHS (e.g., 'y' in x = y or x = &y)
|
|
534
|
+
|
|
535
|
+
if unaryExpr, ok := currentRHSExpr.(*ast.UnaryExpr); ok && unaryExpr.Op == token.AND {
|
|
536
|
+
// RHS is &some_expr
|
|
537
|
+
rhsAssignmentType = AddressOfAssignment
|
|
538
|
+
if rhsIdent, ok := unaryExpr.X.(*ast.Ident); ok {
|
|
539
|
+
// RHS is &variable
|
|
540
|
+
rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
541
|
+
}
|
|
542
|
+
// If RHS is &structLit{} or &array[0], rhsSourceObj remains nil.
|
|
543
|
+
// _, ok := unaryExpr.X.(*ast.CompositeLit); ok
|
|
544
|
+
} else if rhsIdent, ok := currentRHSExpr.(*ast.Ident); ok {
|
|
545
|
+
// RHS is variable
|
|
546
|
+
rhsAssignmentType = DirectAssignment
|
|
547
|
+
rhsSourceObj = v.pkg.TypesInfo.ObjectOf(rhsIdent)
|
|
548
|
+
}
|
|
549
|
+
// If RHS is a literal, function call, etc., rhsSourceObj remains nil.
|
|
550
|
+
|
|
551
|
+
// --- Determine the LHS object (if it's a simple variable or a known field) ---
|
|
552
|
+
var lhsTrackedObj types.Object // The object on the LHS we might record info *for* (e.g. its sources)
|
|
553
|
+
|
|
554
|
+
if lhsIdent, ok := currentLHSExpr.(*ast.Ident); ok {
|
|
555
|
+
if lhsIdent.Name == "_" {
|
|
556
|
+
continue // Skip blank identifier assignments
|
|
557
|
+
}
|
|
558
|
+
lhsTrackedObj = v.pkg.TypesInfo.ObjectOf(lhsIdent)
|
|
559
|
+
} else if selExpr, ok := currentLHSExpr.(*ast.SelectorExpr); ok {
|
|
560
|
+
// LHS is struct.field or package.Var
|
|
561
|
+
if selection := v.pkg.TypesInfo.Selections[selExpr]; selection != nil {
|
|
562
|
+
lhsTrackedObj = selection.Obj() // This is the field or selected var object
|
|
563
|
+
}
|
|
564
|
+
} else if _, ok := currentLHSExpr.(*ast.StarExpr); ok {
|
|
565
|
+
// LHS is *pointer.
|
|
566
|
+
// We don't try to get a types.Object for the dereferenced entity itself to store in VariableUsage.
|
|
567
|
+
// lhsTrackedObj remains nil. The effect on rhsSourceObj (if its address is taken) is handled below.
|
|
568
|
+
}
|
|
569
|
+
// For other complex LHS (e.g., map_expr[key_expr]), lhsTrackedObj remains nil.
|
|
570
|
+
|
|
571
|
+
// --- Record Usage Information ---
|
|
572
|
+
|
|
573
|
+
// 1. If LHS is a trackable variable/field, record what's assigned to it (its sources).
|
|
574
|
+
// We only want to create VariableUsage entries for actual variables/fields.
|
|
575
|
+
if _, isVar := lhsTrackedObj.(*types.Var); isVar {
|
|
576
|
+
lhsUsageInfo := v.getOrCreateUsageInfo(lhsTrackedObj)
|
|
577
|
+
if rhsSourceObj != nil {
|
|
578
|
+
// Case: var1 = var2 OR var1 = &var2 OR field1 = var2 OR field1 = &var2
|
|
579
|
+
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
580
|
+
Object: rhsSourceObj,
|
|
581
|
+
Type: rhsAssignmentType,
|
|
582
|
+
})
|
|
583
|
+
} else if rhsAssignmentType == AddressOfAssignment {
|
|
584
|
+
// Case: var1 = &non_ident_expr (e.g., &T{}) OR field1 = &non_ident_expr
|
|
585
|
+
// lhsTrackedObj is assigned an address, but not of a named variable.
|
|
586
|
+
lhsUsageInfo.Sources = append(lhsUsageInfo.Sources, AssignmentInfo{
|
|
587
|
+
Object: nil, // No specific source variable object
|
|
588
|
+
Type: rhsAssignmentType,
|
|
589
|
+
})
|
|
590
|
+
}
|
|
591
|
+
// If rhsSourceObj is nil and rhsAssignmentType is DirectAssignment (e.g. var1 = 10),
|
|
592
|
+
// no source object to record for LHS sources.
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// 2. If RHS involved a source variable (rhsSourceObj is not nil),
|
|
596
|
+
// record that this source variable was used (its destinations).
|
|
597
|
+
// This is CRITICAL for boxing analysis (e.g., if &rhsSourceObj was assigned).
|
|
598
|
+
if rhsSourceObj != nil {
|
|
599
|
+
sourceUsageInfo := v.getOrCreateUsageInfo(rhsSourceObj)
|
|
600
|
+
// The 'Object' in DestinationInfo is what/where rhsSourceObj (or its address) was assigned TO.
|
|
601
|
+
// This can be lhsTrackedObj (if LHS was an ident or field).
|
|
602
|
+
// If LHS was complex (e.g., *ptr, map[k]), lhsTrackedObj might be nil for that DestinationInfo.Object.
|
|
603
|
+
// Even if lhsTrackedObj is nil for the DestinationInfo.Object, if rhsAssignmentType is AddressOfAssignment,
|
|
604
|
+
// it's important to record that rhsSourceObj's address was taken.
|
|
605
|
+
sourceUsageInfo.Destinations = append(sourceUsageInfo.Destinations, AssignmentInfo{
|
|
606
|
+
Object: lhsTrackedObj, // This can be nil if LHS is complex (*p, map[k])
|
|
607
|
+
Type: rhsAssignmentType,
|
|
608
|
+
})
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
return v // Continue traversal
|
|
612
|
+
|
|
613
|
+
case *ast.CompositeLit:
|
|
614
|
+
// No need to track private field access in composite literals since all fields are public
|
|
615
|
+
return v
|
|
616
|
+
|
|
617
|
+
default:
|
|
618
|
+
// For all other nodes, continue traversal
|
|
619
|
+
return v
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// containsAsyncOperations checks if a node contains any async operations like channel operations.
|
|
624
|
+
func (v *analysisVisitor) containsAsyncOperations(node ast.Node) bool {
|
|
625
|
+
var hasAsync bool
|
|
626
|
+
|
|
627
|
+
ast.Inspect(node, func(n ast.Node) bool {
|
|
628
|
+
if n == nil {
|
|
629
|
+
return false
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
switch s := n.(type) {
|
|
633
|
+
case *ast.SendStmt:
|
|
634
|
+
// Channel send operation (ch <- value)
|
|
635
|
+
hasAsync = true
|
|
636
|
+
return false
|
|
637
|
+
|
|
638
|
+
case *ast.UnaryExpr:
|
|
639
|
+
// Channel receive operation (<-ch)
|
|
640
|
+
if s.Op == token.ARROW {
|
|
641
|
+
hasAsync = true
|
|
642
|
+
return false
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
case *ast.CallExpr:
|
|
646
|
+
// Check if we're calling a function known to be async
|
|
647
|
+
if funcIdent, ok := s.Fun.(*ast.Ident); ok {
|
|
648
|
+
// Get the object for this function call
|
|
649
|
+
if obj := v.pkg.TypesInfo.Uses[funcIdent]; obj != nil && v.analysis.IsAsyncFunc(obj) {
|
|
650
|
+
hasAsync = true
|
|
651
|
+
return false
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// TODO: Add detection of method calls on async types
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return true
|
|
659
|
+
})
|
|
660
|
+
|
|
661
|
+
return hasAsync
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// containsDefer checks if a block contains any defer statements.
|
|
665
|
+
func (v *analysisVisitor) containsDefer(block *ast.BlockStmt) bool {
|
|
666
|
+
hasDefer := false
|
|
667
|
+
|
|
668
|
+
ast.Inspect(block, func(n ast.Node) bool {
|
|
669
|
+
if _, ok := n.(*ast.DeferStmt); ok {
|
|
670
|
+
hasDefer = true
|
|
671
|
+
return false
|
|
672
|
+
}
|
|
673
|
+
return true
|
|
674
|
+
})
|
|
675
|
+
|
|
676
|
+
return hasDefer
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// AnalyzeFile analyzes a Go source file AST and populates the Analysis struct with information
|
|
680
|
+
// that will be used during code generation to properly handle pointers, variables that need boxing, etc.
|
|
681
|
+
func AnalyzeFile(file *ast.File, pkg *packages.Package, analysis *Analysis, cmap ast.CommentMap) {
|
|
682
|
+
// Store the comment map in the analysis object
|
|
683
|
+
analysis.Cmap = cmap
|
|
684
|
+
|
|
685
|
+
// Process imports from the file
|
|
686
|
+
for _, imp := range file.Imports {
|
|
687
|
+
path := ""
|
|
688
|
+
if imp.Path != nil {
|
|
689
|
+
path = imp.Path.Value
|
|
690
|
+
// Remove quotes from the import path string
|
|
691
|
+
path = path[1 : len(path)-1]
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// Store the import in the analysis
|
|
695
|
+
if path != "" {
|
|
696
|
+
name := ""
|
|
697
|
+
if imp.Name != nil {
|
|
698
|
+
name = imp.Name.Name
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
fileImp := &fileImport{
|
|
702
|
+
importPath: path,
|
|
703
|
+
importVars: make(map[string]struct{}),
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Use the import name or path as the key
|
|
707
|
+
key := path
|
|
708
|
+
if name != "" {
|
|
709
|
+
key = name
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
analysis.Imports[key] = fileImp
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Create an analysis visitor to traverse the AST
|
|
717
|
+
visitor := &analysisVisitor{
|
|
718
|
+
analysis: analysis,
|
|
719
|
+
pkg: pkg,
|
|
720
|
+
inAsyncFunction: false,
|
|
721
|
+
currentReceiver: nil, // Initialize currentReceiver
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Walk the AST with our visitor
|
|
725
|
+
ast.Walk(visitor, file)
|
|
726
|
+
}
|