goscript 0.0.20 → 0.0.22
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/builtin/builtin.ts +390 -119
- package/compiler/assignment.go +407 -0
- package/compiler/compiler.go +203 -5879
- package/compiler/compiler_test.go +4 -0
- package/compiler/composite-lit.go +537 -0
- package/compiler/config.go +3 -0
- package/compiler/decl.go +223 -0
- package/compiler/expr-call.go +423 -0
- package/compiler/expr-selector.go +105 -0
- package/compiler/expr-star.go +90 -0
- package/compiler/expr-type.go +182 -0
- package/compiler/expr-value.go +119 -0
- package/compiler/expr.go +356 -0
- package/compiler/field.go +169 -0
- package/compiler/lit.go +99 -0
- package/compiler/output.go +1 -1
- package/compiler/primitive.go +142 -0
- package/compiler/{write-type-spec.go → spec-struct.go} +79 -203
- package/compiler/spec-value.go +194 -0
- package/compiler/spec.go +263 -0
- package/compiler/stmt-assign.go +411 -0
- package/compiler/stmt-for.go +178 -0
- package/compiler/stmt-range.go +237 -0
- package/compiler/stmt.go +907 -0
- package/compiler/type-assert.go +463 -0
- package/compiler/type-info.go +141 -0
- package/compiler/type.go +556 -0
- package/dist/builtin/builtin.d.ts +24 -6
- package/dist/builtin/builtin.js +215 -19
- package/dist/builtin/builtin.js.map +1 -1
- package/go.mod +2 -1
- package/go.sum +4 -2
- package/package.json +2 -2
- /package/compiler/{writer.go → code-writer.go} +0 -0
|
@@ -0,0 +1,407 @@
|
|
|
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
|
+
// writeAssignmentCore handles the central logic for translating Go assignment
|
|
12
|
+
// operations (LHS op RHS) into TypeScript. It's called by `WriteStmtAssign`
|
|
13
|
+
// and other functions that need to generate assignment code.
|
|
14
|
+
//
|
|
15
|
+
// Key behaviors:
|
|
16
|
+
// - Multi-variable assignment (e.g., `a, b = b, a`): Translates to TypeScript
|
|
17
|
+
// array destructuring: `[a_ts, b_ts] = [b_ts, a_ts]`. It correctly handles
|
|
18
|
+
// non-null assertions for array index expressions on both LHS and RHS if
|
|
19
|
+
// all expressions involved are index expressions (common in swaps).
|
|
20
|
+
// - Single-variable assignment to a map index (`myMap[key] = value`): Translates
|
|
21
|
+
// to `$.mapSet(myMap_ts, key_ts, value_ts)`.
|
|
22
|
+
// - Other single-variable assignments (`variable = value`):
|
|
23
|
+
// - The LHS expression is written (caller typically ensures `.value` is appended
|
|
24
|
+
// if assigning to a boxed variable's content).
|
|
25
|
+
// - The Go assignment token (`tok`, e.g., `=`, `+=`) is translated to its
|
|
26
|
+
// TypeScript equivalent using `TokenToTs`.
|
|
27
|
+
// - The RHS expression(s) are written. If `shouldApplyClone` indicates the RHS
|
|
28
|
+
// is a struct value, `.clone()` is appended to the translated RHS to emulate
|
|
29
|
+
// Go's value semantics for struct assignment.
|
|
30
|
+
// - Blank identifiers (`_`) on the LHS are handled by omitting them in TypeScript
|
|
31
|
+
// destructuring patterns or by skipping the assignment for single assignments.
|
|
32
|
+
//
|
|
33
|
+
// This function handles all assignment types including:
|
|
34
|
+
// - Pointer dereference assignments (*p = v)
|
|
35
|
+
// - Blank identifier assignments (_ = v)
|
|
36
|
+
func (c *GoToTSCompiler) writeAssignmentCore(lhs, rhs []ast.Expr, tok token.Token, addDeclaration bool) error {
|
|
37
|
+
// Handle blank identifier (_) on the LHS for single assignments
|
|
38
|
+
if len(lhs) == 1 && len(rhs) == 1 {
|
|
39
|
+
if ident, ok := lhs[0].(*ast.Ident); ok && ident.Name == "_" {
|
|
40
|
+
// Evaluate the RHS expression for side effects, but don't assign it
|
|
41
|
+
c.tsw.WriteLiterally("/* _ = */ ")
|
|
42
|
+
if err := c.WriteValueExpr(rhs[0]); err != nil {
|
|
43
|
+
return err
|
|
44
|
+
}
|
|
45
|
+
return nil
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Handle the special case of "*p = val" (assignment to dereferenced pointer)
|
|
49
|
+
if starExpr, ok := lhs[0].(*ast.StarExpr); ok {
|
|
50
|
+
// For *p = val, we need to set p's .value property
|
|
51
|
+
// Write "p!.value = " for the underlying value
|
|
52
|
+
if err := c.WriteValueExpr(starExpr.X); err != nil { // p in *p
|
|
53
|
+
return err
|
|
54
|
+
}
|
|
55
|
+
c.tsw.WriteLiterally("!.value = ") // Add non-null assertion for TS safety
|
|
56
|
+
|
|
57
|
+
// Handle the RHS expression (potentially adding .clone() for structs)
|
|
58
|
+
if shouldApplyClone(c.pkg, rhs[0]) {
|
|
59
|
+
if err := c.WriteValueExpr(rhs[0]); err != nil {
|
|
60
|
+
return err
|
|
61
|
+
}
|
|
62
|
+
c.tsw.WriteLiterally(".clone()")
|
|
63
|
+
} else {
|
|
64
|
+
if err := c.WriteValueExpr(rhs[0]); err != nil {
|
|
65
|
+
return err
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return nil
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Special handling for boxed variables in declarations
|
|
72
|
+
if addDeclaration && tok == token.DEFINE {
|
|
73
|
+
// Determine if LHS is boxed
|
|
74
|
+
isLHSBoxed := false
|
|
75
|
+
var lhsIdent *ast.Ident
|
|
76
|
+
var lhsObj types.Object
|
|
77
|
+
|
|
78
|
+
if ident, ok := lhs[0].(*ast.Ident); ok {
|
|
79
|
+
lhsIdent = ident
|
|
80
|
+
// Get the types.Object from the identifier
|
|
81
|
+
if use, ok := c.pkg.TypesInfo.Uses[ident]; ok {
|
|
82
|
+
lhsObj = use
|
|
83
|
+
} else if def, ok := c.pkg.TypesInfo.Defs[ident]; ok {
|
|
84
|
+
lhsObj = def
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if this variable needs to be boxed
|
|
88
|
+
if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
|
|
89
|
+
isLHSBoxed = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Special handling for short declaration of boxed variables
|
|
94
|
+
if isLHSBoxed && lhsIdent != nil {
|
|
95
|
+
c.tsw.WriteLiterally("let ")
|
|
96
|
+
// Just write the identifier name without .value
|
|
97
|
+
c.tsw.WriteLiterally(lhsIdent.Name)
|
|
98
|
+
|
|
99
|
+
// Add type annotation for boxed variables in declarations
|
|
100
|
+
if lhsObj != nil {
|
|
101
|
+
c.tsw.WriteLiterally(": ")
|
|
102
|
+
c.tsw.WriteLiterally("$.Box<")
|
|
103
|
+
c.WriteGoType(lhsObj.Type())
|
|
104
|
+
c.tsw.WriteLiterally(">")
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
c.tsw.WriteLiterally(" = ")
|
|
108
|
+
|
|
109
|
+
// Box the initializer
|
|
110
|
+
c.tsw.WriteLiterally("$.box(")
|
|
111
|
+
if err := c.WriteValueExpr(rhs[0]); err != nil {
|
|
112
|
+
return err
|
|
113
|
+
}
|
|
114
|
+
c.tsw.WriteLiterally(")")
|
|
115
|
+
return nil
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
c.tsw.WriteLiterally("let ")
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Special case for multi-variable assignment to handle array element swaps
|
|
123
|
+
if len(lhs) > 1 && len(rhs) > 1 {
|
|
124
|
+
// Check if this is an array element swap pattern (common pattern a[i], a[j] = a[j], a[i])
|
|
125
|
+
// Identify if we're dealing with array index expressions that might need null assertions
|
|
126
|
+
allIndexExprs := true
|
|
127
|
+
for _, expr := range append(lhs, rhs...) {
|
|
128
|
+
_, isIndexExpr := expr.(*ast.IndexExpr)
|
|
129
|
+
if !isIndexExpr {
|
|
130
|
+
allIndexExprs = false
|
|
131
|
+
break
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Use array destructuring for multi-variable assignments
|
|
136
|
+
c.tsw.WriteLiterally("[")
|
|
137
|
+
for i, l := range lhs {
|
|
138
|
+
if i != 0 {
|
|
139
|
+
c.tsw.WriteLiterally(", ")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle blank identifier
|
|
143
|
+
if ident, ok := l.(*ast.Ident); ok && ident.Name == "_" {
|
|
144
|
+
// If it's a blank identifier, we write nothing,
|
|
145
|
+
// leaving an empty slot in the destructuring array.
|
|
146
|
+
} else if indexExpr, ok := l.(*ast.IndexExpr); ok && allIndexExprs { // MODIFICATION: Added 'else if'
|
|
147
|
+
// Note: We don't use WriteIndexExpr here because we need direct array access for swapping
|
|
148
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
149
|
+
return err
|
|
150
|
+
}
|
|
151
|
+
c.tsw.WriteLiterally("!") // non-null assertion
|
|
152
|
+
c.tsw.WriteLiterally("[")
|
|
153
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
154
|
+
return err
|
|
155
|
+
}
|
|
156
|
+
c.tsw.WriteLiterally("]")
|
|
157
|
+
} else {
|
|
158
|
+
// Normal case - write the entire expression
|
|
159
|
+
if err := c.WriteValueExpr(l); err != nil {
|
|
160
|
+
return err
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
c.tsw.WriteLiterally("] = [")
|
|
165
|
+
for i, r := range rhs {
|
|
166
|
+
if i != 0 {
|
|
167
|
+
c.tsw.WriteLiterally(", ")
|
|
168
|
+
}
|
|
169
|
+
if indexExpr, ok := r.(*ast.IndexExpr); ok && allIndexExprs {
|
|
170
|
+
// Note: We don't use WriteIndexExpr here because we need direct array access for swapping
|
|
171
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil {
|
|
172
|
+
return err
|
|
173
|
+
}
|
|
174
|
+
c.tsw.WriteLiterally("!")
|
|
175
|
+
c.tsw.WriteLiterally("[")
|
|
176
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil {
|
|
177
|
+
return err
|
|
178
|
+
}
|
|
179
|
+
c.tsw.WriteLiterally("]")
|
|
180
|
+
} else {
|
|
181
|
+
// Normal case - write the entire expression
|
|
182
|
+
if err := c.WriteValueExpr(r); err != nil {
|
|
183
|
+
return err
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
c.tsw.WriteLiterally("]")
|
|
188
|
+
return nil
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --- Logic for assignments ---
|
|
192
|
+
isMapIndexLHS := false // Track if the first LHS is a map index
|
|
193
|
+
for i, l := range lhs {
|
|
194
|
+
if i != 0 {
|
|
195
|
+
c.tsw.WriteLiterally(", ")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Handle map indexing assignment specially
|
|
199
|
+
// Note: We don't use WriteIndexExpr here because we need to use $.mapSet instead of .get
|
|
200
|
+
currentIsMapIndex := false
|
|
201
|
+
if indexExpr, ok := l.(*ast.IndexExpr); ok {
|
|
202
|
+
if tv, ok := c.pkg.TypesInfo.Types[indexExpr.X]; ok {
|
|
203
|
+
if _, isMap := tv.Type.Underlying().(*types.Map); isMap {
|
|
204
|
+
currentIsMapIndex = true
|
|
205
|
+
if i == 0 {
|
|
206
|
+
isMapIndexLHS = true
|
|
207
|
+
}
|
|
208
|
+
// Use mapSet helper
|
|
209
|
+
c.tsw.WriteLiterally("$.mapSet(")
|
|
210
|
+
if err := c.WriteValueExpr(indexExpr.X); err != nil { // Map
|
|
211
|
+
return err
|
|
212
|
+
}
|
|
213
|
+
c.tsw.WriteLiterally(", ")
|
|
214
|
+
if err := c.WriteValueExpr(indexExpr.Index); err != nil { // Key
|
|
215
|
+
return err
|
|
216
|
+
}
|
|
217
|
+
c.tsw.WriteLiterally(", ")
|
|
218
|
+
// Value will be added after operator and RHS
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if !currentIsMapIndex {
|
|
224
|
+
// For single assignments, handle boxed variables specially
|
|
225
|
+
if len(lhs) == 1 && len(rhs) == 1 {
|
|
226
|
+
lhsExprIdent, lhsExprIsIdent := l.(*ast.Ident)
|
|
227
|
+
if lhsExprIsIdent {
|
|
228
|
+
// Determine if LHS is boxed
|
|
229
|
+
isLHSBoxed := false
|
|
230
|
+
var lhsObj types.Object
|
|
231
|
+
|
|
232
|
+
// Get the types.Object from the identifier
|
|
233
|
+
if use, ok := c.pkg.TypesInfo.Uses[lhsExprIdent]; ok {
|
|
234
|
+
lhsObj = use
|
|
235
|
+
} else if def, ok := c.pkg.TypesInfo.Defs[lhsExprIdent]; ok {
|
|
236
|
+
lhsObj = def
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Check if this variable needs to be boxed
|
|
240
|
+
if lhsObj != nil && c.analysis.NeedsBoxed(lhsObj) {
|
|
241
|
+
isLHSBoxed = true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// prevent writing .value unless lhs is boxed
|
|
245
|
+
c.WriteIdent(lhsExprIdent, isLHSBoxed)
|
|
246
|
+
continue
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Write the LHS expression normally
|
|
251
|
+
if err := c.WriteValueExpr(l); err != nil {
|
|
252
|
+
return err
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Only write the assignment operator for regular variables, not for map assignments handled by mapSet
|
|
258
|
+
if isMapIndexLHS && len(lhs) == 1 { // Only skip operator if it's a single map assignment
|
|
259
|
+
// Continue, we've already written part of the mapSet() function call
|
|
260
|
+
} else {
|
|
261
|
+
c.tsw.WriteLiterally(" ")
|
|
262
|
+
tokStr, ok := TokenToTs(tok) // Use explicit gstypes alias
|
|
263
|
+
if !ok {
|
|
264
|
+
c.tsw.WriteLiterally("?= ")
|
|
265
|
+
c.tsw.WriteCommentLine("Unknown token " + tok.String())
|
|
266
|
+
} else {
|
|
267
|
+
c.tsw.WriteLiterally(tokStr)
|
|
268
|
+
}
|
|
269
|
+
c.tsw.WriteLiterally(" ")
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Write RHS
|
|
273
|
+
for i, r := range rhs {
|
|
274
|
+
if i != 0 {
|
|
275
|
+
c.tsw.WriteLiterally(", ")
|
|
276
|
+
}
|
|
277
|
+
// Check if we need to access a boxed source value and apply clone
|
|
278
|
+
// For struct value assignments, we need to handle:
|
|
279
|
+
// 1. Unboxed source, unboxed target: source.clone()
|
|
280
|
+
// 2. Boxed source, unboxed target: source.value.clone()
|
|
281
|
+
// 3. Unboxed source, boxed target: $.box(source)
|
|
282
|
+
// 4. Boxed source, boxed target: source (straight assignment of the box)
|
|
283
|
+
|
|
284
|
+
// Determine if RHS is a boxed variable (could be a struct or other variable)
|
|
285
|
+
needsBoxedAccessRHS := false
|
|
286
|
+
var rhsObj types.Object
|
|
287
|
+
|
|
288
|
+
// Check if RHS is an identifier (variable name)
|
|
289
|
+
rhsIdent, rhsIsIdent := r.(*ast.Ident)
|
|
290
|
+
if rhsIsIdent {
|
|
291
|
+
rhsObj = c.pkg.TypesInfo.Uses[rhsIdent]
|
|
292
|
+
if rhsObj == nil {
|
|
293
|
+
rhsObj = c.pkg.TypesInfo.Defs[rhsIdent]
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Important: For struct copying, we need to check if the variable itself is boxed
|
|
297
|
+
// Important: For struct copying, we need to check if the variable needs boxed access
|
|
298
|
+
// This is more comprehensive than just checking if it's boxed
|
|
299
|
+
if rhsObj != nil {
|
|
300
|
+
needsBoxedAccessRHS = c.analysis.NeedsBoxedAccess(rhsObj)
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle different cases for struct cloning
|
|
305
|
+
if shouldApplyClone(c.pkg, r) {
|
|
306
|
+
// For other expressions, we need to handle boxed access differently
|
|
307
|
+
if _, isIdent := r.(*ast.Ident); isIdent {
|
|
308
|
+
// For identifiers, WriteValueExpr already adds .value if needed
|
|
309
|
+
if err := c.WriteValueExpr(r); err != nil {
|
|
310
|
+
return err
|
|
311
|
+
}
|
|
312
|
+
} else {
|
|
313
|
+
// For non-identifiers, write the expression and add .value if needed
|
|
314
|
+
if err := c.WriteValueExpr(r); err != nil {
|
|
315
|
+
return err
|
|
316
|
+
}
|
|
317
|
+
// Only add .value for non-identifiers that need boxed access
|
|
318
|
+
if needsBoxedAccessRHS {
|
|
319
|
+
c.tsw.WriteLiterally(".value") // Access the boxed value
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
c.tsw.WriteLiterally(".clone()") // Always add clone for struct values
|
|
324
|
+
} else {
|
|
325
|
+
if err := c.WriteValueExpr(r); err != nil { // RHS is a non-struct value
|
|
326
|
+
return err
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// If the LHS was a single map index, close the mapSet call
|
|
332
|
+
if isMapIndexLHS && len(lhs) == 1 {
|
|
333
|
+
c.tsw.WriteLiterally(")")
|
|
334
|
+
}
|
|
335
|
+
return nil
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// shouldApplyClone determines whether a `.clone()` method call should be appended
|
|
339
|
+
// to the TypeScript translation of a Go expression `rhs` when it appears on the
|
|
340
|
+
// right-hand side of an assignment. This is primarily to emulate Go's value
|
|
341
|
+
// semantics for struct assignments, where assigning one struct variable to another
|
|
342
|
+
// creates a copy of the struct.
|
|
343
|
+
//
|
|
344
|
+
// It uses `go/types` information (`pkg.TypesInfo`) to determine the type of `rhs`.
|
|
345
|
+
// - If `rhs` is identified as a struct type (either directly, as a named type
|
|
346
|
+
// whose underlying type is a struct, or an unnamed type whose underlying type
|
|
347
|
+
// is a struct), it returns `true`.
|
|
348
|
+
// - An optimization: if `rhs` is a composite literal (`*ast.CompositeLit`),
|
|
349
|
+
// it returns `false` because a composite literal already produces a new value,
|
|
350
|
+
// so cloning is unnecessary.
|
|
351
|
+
// - If type information is unavailable or `rhs` is not a struct type, it returns `false`.
|
|
352
|
+
//
|
|
353
|
+
// This function is crucial for ensuring that assignments of struct values in
|
|
354
|
+
// TypeScript behave like copies, as they do in Go, rather than reference assignments.
|
|
355
|
+
func shouldApplyClone(pkg *packages.Package, rhs ast.Expr) bool {
|
|
356
|
+
if pkg == nil || pkg.TypesInfo == nil {
|
|
357
|
+
// Cannot determine type without type info, default to no clone
|
|
358
|
+
return false
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Get the type of the RHS expression
|
|
362
|
+
var exprType types.Type
|
|
363
|
+
|
|
364
|
+
// Handle identifiers (variables) directly - the most common case
|
|
365
|
+
if ident, ok := rhs.(*ast.Ident); ok {
|
|
366
|
+
if obj := pkg.TypesInfo.Uses[ident]; obj != nil {
|
|
367
|
+
// Get the type directly from the object
|
|
368
|
+
exprType = obj.Type()
|
|
369
|
+
} else if obj := pkg.TypesInfo.Defs[ident]; obj != nil {
|
|
370
|
+
// Also check Defs map for definitions
|
|
371
|
+
exprType = obj.Type()
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// If we couldn't get the type from Uses/Defs, try getting it from Types
|
|
376
|
+
if exprType == nil {
|
|
377
|
+
if tv, found := pkg.TypesInfo.Types[rhs]; found && tv.Type != nil {
|
|
378
|
+
exprType = tv.Type
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// No type information available
|
|
383
|
+
if exprType == nil {
|
|
384
|
+
return false
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Optimization: If it's a composite literal for a struct, no need to clone
|
|
388
|
+
// as it's already a fresh value
|
|
389
|
+
if _, isCompositeLit := rhs.(*ast.CompositeLit); isCompositeLit {
|
|
390
|
+
return false
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Check if it's a struct type (directly, through named type, or underlying)
|
|
394
|
+
if named, ok := exprType.(*types.Named); ok {
|
|
395
|
+
if _, isStruct := named.Underlying().(*types.Struct); isStruct {
|
|
396
|
+
return true // Named struct type
|
|
397
|
+
}
|
|
398
|
+
} else if _, ok := exprType.(*types.Struct); ok {
|
|
399
|
+
return true // Direct struct type
|
|
400
|
+
} else if underlying := exprType.Underlying(); underlying != nil {
|
|
401
|
+
if _, isStruct := underlying.(*types.Struct); isStruct {
|
|
402
|
+
return true // Underlying is a struct
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return false // Not a struct, do not apply clone
|
|
407
|
+
}
|