diffgenome 0.1.0__py3-none-any.whl

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. diffgenome/__init__.py +7 -0
  2. diffgenome/__main__.py +240 -0
  3. diffgenome/_collectors/go/dg/dg.go +623 -0
  4. diffgenome/_collectors/go/go.mod +3 -0
  5. diffgenome/_collectors/go/instrument/facts.go +346 -0
  6. diffgenome/_collectors/go/instrument/main.go +484 -0
  7. diffgenome/_collectors/node/instrument.js +289 -0
  8. diffgenome/_collectors/node/jest-setup.js +40 -0
  9. diffgenome/_collectors/node/package-lock.json +35 -0
  10. diffgenome/_collectors/node/package.json +11 -0
  11. diffgenome/_collectors/node/runtime.js +426 -0
  12. diffgenome/ambiguity.py +122 -0
  13. diffgenome/api.py +67 -0
  14. diffgenome/change.py +86 -0
  15. diffgenome/change_artifact.py +310 -0
  16. diffgenome/collect/__init__.py +2 -0
  17. diffgenome/collect/go_test.py +271 -0
  18. diffgenome/collect/node_jest.py +319 -0
  19. diffgenome/collect/py_monitoring.py +985 -0
  20. diffgenome/collect/py_runtime.py +116 -0
  21. diffgenome/collect/py_symbols.py +238 -0
  22. diffgenome/collect/pytest_plugin.py +130 -0
  23. diffgenome/compose.py +469 -0
  24. diffgenome/dependence.py +264 -0
  25. diffgenome/evaluate.py +669 -0
  26. diffgenome/frontends/__init__.py +0 -0
  27. diffgenome/frontends/python_ir.py +335 -0
  28. diffgenome/genome.py +1016 -0
  29. diffgenome/genome_pipeline.py +674 -0
  30. diffgenome/genome_prompt.py +33 -0
  31. diffgenome/genome_state.py +2118 -0
  32. diffgenome/graph.py +426 -0
  33. diffgenome/llm.py +189 -0
  34. diffgenome/model.py +364 -0
  35. diffgenome/mvp.py +398 -0
  36. diffgenome/probe.py +509 -0
  37. diffgenome/projection.py +308 -0
  38. diffgenome/py.typed +0 -0
  39. diffgenome/render.py +118 -0
  40. diffgenome/report.py +363 -0
  41. diffgenome/resolve.py +37 -0
  42. diffgenome/runtime.py +74 -0
  43. diffgenome/runtime_evidence.py +261 -0
  44. diffgenome/sandbox.py +166 -0
  45. diffgenome/serialize.py +96 -0
  46. diffgenome/sites.py +19 -0
  47. diffgenome/static_types.py +69 -0
  48. diffgenome/structure.py +462 -0
  49. diffgenome-0.1.0.dist-info/METADATA +139 -0
  50. diffgenome-0.1.0.dist-info/RECORD +53 -0
  51. diffgenome-0.1.0.dist-info/WHEEL +4 -0
  52. diffgenome-0.1.0.dist-info/entry_points.txt +2 -0
  53. diffgenome-0.1.0.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,484 @@
1
+ // diffgenome Go source instrumenter.
2
+ //
3
+ // go run ./instrument -root <repo copy> -module <module path> -src <dir>[,dir] -tests <dir>[,dir] -index <out.json>
4
+ //
5
+ // Rewrites every .go file under the roots IN PLACE (the copy is disposable): each function
6
+ // or method body reports entry (dg.Enter with its parameters), and a deferred function
7
+ // reports the results, a trailing error, or a panic (dg.Exit / dg.Panic). Unnamed results
8
+ // are named so the defer can read them. Test functions and t.Run subtests become stimuli
9
+ // (dg.Begin / dg.End). Files under a test root, *_test.go files and generated mock packages
10
+ // are "test" origin; a MockGen file's "Source: <pkg> (interfaces: X)" header gives each
11
+ // mock method a deterministic claim on the in-repo definer of that interface method.
12
+ //
13
+ // Symbol naming: go:<package dir relative to root>.<Recv.Func>; closures are <anon>@line.
14
+ // It also writes the definition index the Python side uses for diff mapping and context.
15
+ package main
16
+
17
+ import (
18
+ "bytes"
19
+ "encoding/json"
20
+ "flag"
21
+ "fmt"
22
+ "go/ast"
23
+ "go/format"
24
+ "go/parser"
25
+ "go/token"
26
+ "os"
27
+ "path/filepath"
28
+ "regexp"
29
+ "sort"
30
+ "strconv"
31
+ "strings"
32
+ )
33
+
34
+ type definition struct {
35
+ Symbol string `json:"symbol"`
36
+ Path string `json:"path"`
37
+ Start int `json:"start"`
38
+ End int `json:"end"`
39
+ Kind string `json:"kind"`
40
+ }
41
+
42
+ type root struct {
43
+ dir string
44
+ origin string
45
+ }
46
+
47
+ var mockSource = regexp.MustCompile(`Source: (\S+) \(interfaces: ([^)]+)\)`)
48
+
49
+ func main() {
50
+ rootFlag := flag.String("root", "", "repository copy")
51
+ module := flag.String("module", "", "module path of the repository")
52
+ srcFlag := flag.String("src", ".", "comma-separated source dirs (repo origin)")
53
+ testFlag := flag.String("tests", "", "comma-separated test dirs (test origin), e.g. db/mock")
54
+ indexFlag := flag.String("index", "", "definition index output")
55
+ factsFlag := flag.String("facts", "", "write the neutral IR of repo-origin functions to this file (from the original source)")
56
+ flag.Parse()
57
+ if *rootFlag == "" || *module == "" {
58
+ fmt.Fprintln(os.Stderr, "-root and -module are required")
59
+ os.Exit(2)
60
+ }
61
+ absRoot, _ := filepath.Abs(*rootFlag)
62
+ var roots []root
63
+ for _, d := range strings.Split(*testFlag, ",") {
64
+ if d != "" {
65
+ roots = append(roots, root{filepath.Join(absRoot, d), "test"})
66
+ }
67
+ }
68
+ for _, d := range strings.Split(*srcFlag, ",") {
69
+ if d != "" {
70
+ roots = append(roots, root{filepath.Join(absRoot, d), "repo"})
71
+ }
72
+ }
73
+ // method definers per package dir: pkgdir -> method name -> []type
74
+ definers := collectDefiners(absRoot)
75
+ var index []definition
76
+ seen := map[string]bool{}
77
+ count := 0
78
+ for _, r := range roots {
79
+ filepath.Walk(r.dir, func(path string, info os.FileInfo, err error) error {
80
+ if err != nil {
81
+ return nil
82
+ }
83
+ name := info.Name()
84
+ if info.IsDir() {
85
+ if strings.HasPrefix(name, ".") || name == "vendor" || name == "testdata" || name == "node_modules" || path == filepath.Join(absRoot, "internal", "diffgenome") {
86
+ return filepath.SkipDir
87
+ }
88
+ return nil
89
+ }
90
+ if !strings.HasSuffix(name, ".go") || seen[path] {
91
+ return nil
92
+ }
93
+ seen[path] = true
94
+ origin := r.origin
95
+ if strings.HasSuffix(name, "_test.go") {
96
+ origin = "test"
97
+ }
98
+ changed, defs, err := instrumentFile(absRoot, *module, path, origin, definers, *factsFlag != "")
99
+ if err != nil {
100
+ fmt.Fprintf(os.Stderr, "diffgenome: %s: %v\n", path, err)
101
+ return nil
102
+ }
103
+ index = append(index, defs...)
104
+ if changed {
105
+ count++
106
+ }
107
+ return nil
108
+ })
109
+ }
110
+ if *factsFlag != "" {
111
+ data, _ := json.MarshalIndent(map[string]any{"functions": irFunctions}, "", " ")
112
+ _ = os.WriteFile(*factsFlag, append(data, '\n'), 0o644)
113
+ }
114
+ if *indexFlag != "" {
115
+ sort.Slice(index, func(i, j int) bool {
116
+ return index[i].Path < index[j].Path || (index[i].Path == index[j].Path && index[i].Start < index[j].Start)
117
+ })
118
+ data, _ := json.MarshalIndent(map[string]any{"root": absRoot, "definitions": index}, "", " ")
119
+ _ = os.WriteFile(*indexFlag, append(data, '\n'), 0o644)
120
+ }
121
+ fmt.Printf("instrumented %d files, indexed %d definitions\n", count, len(index))
122
+ }
123
+
124
+ // collectDefiners maps, per package directory, each method name to the receiver types
125
+ // defining it, so a mocked interface method can be claimed on its sole in-repo definer.
126
+ func collectDefiners(absRoot string) map[string]map[string][]string {
127
+ out := map[string]map[string][]string{}
128
+ filepath.Walk(absRoot, func(path string, info os.FileInfo, err error) error {
129
+ if err != nil || info.IsDir() || !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
130
+ if info != nil && info.IsDir() && (strings.HasPrefix(info.Name(), ".") || info.Name() == "vendor" || info.Name() == "node_modules") {
131
+ return filepath.SkipDir
132
+ }
133
+ return nil
134
+ }
135
+ fset := token.NewFileSet()
136
+ f, perr := parser.ParseFile(fset, path, nil, 0)
137
+ if perr != nil {
138
+ return nil
139
+ }
140
+ pkgDir, _ := filepath.Rel(absRoot, filepath.Dir(path))
141
+ pkgDir = filepath.ToSlash(pkgDir)
142
+ if _, ok := out[pkgDir]; !ok {
143
+ out[pkgDir] = map[string][]string{}
144
+ }
145
+ for _, d := range f.Decls {
146
+ fd, ok := d.(*ast.FuncDecl)
147
+ if !ok || fd.Recv == nil || len(fd.Recv.List) == 0 {
148
+ continue
149
+ }
150
+ rt := receiverType(fd.Recv.List[0].Type)
151
+ if rt == "" || strings.HasPrefix(rt, "Mock") {
152
+ continue
153
+ }
154
+ out[pkgDir][fd.Name.Name] = appendUnique(out[pkgDir][fd.Name.Name], rt)
155
+ }
156
+ return nil
157
+ })
158
+ return out
159
+ }
160
+
161
+ func appendUnique(xs []string, x string) []string {
162
+ for _, y := range xs {
163
+ if y == x {
164
+ return xs
165
+ }
166
+ }
167
+ return append(xs, x)
168
+ }
169
+
170
+ func receiverType(e ast.Expr) string {
171
+ switch t := e.(type) {
172
+ case *ast.StarExpr:
173
+ return receiverType(t.X)
174
+ case *ast.Ident:
175
+ return t.Name
176
+ case *ast.IndexExpr:
177
+ return receiverType(t.X)
178
+ case *ast.IndexListExpr:
179
+ return receiverType(t.X)
180
+ }
181
+ return ""
182
+ }
183
+
184
+ type fileCtx struct {
185
+ fset *token.FileSet
186
+ rel string
187
+ pkgDir string
188
+ origin string
189
+ module string
190
+ mockPkgDir string // package dir of the mocked interfaces, if this is a MockGen file
191
+ mockIfaces map[string]bool
192
+ definers map[string]map[string][]string
193
+ index *[]definition
194
+ changed bool
195
+ }
196
+
197
+ var irFunctions []irFn
198
+
199
+ func instrumentFile(absRoot, module, path, origin string, definers map[string]map[string][]string, facts bool) (bool, []definition, error) {
200
+ src, err := os.ReadFile(path)
201
+ if err != nil {
202
+ return false, nil, err
203
+ }
204
+ if bytes.Contains(src, []byte("internal/diffgenome/dg\"")) {
205
+ return false, nil, nil // already instrumented (re-runs after adding a probe file)
206
+ }
207
+ fset := token.NewFileSet()
208
+ f, err := parser.ParseFile(fset, path, src, parser.ParseComments)
209
+ if err != nil {
210
+ return false, nil, err
211
+ }
212
+ rel, _ := filepath.Rel(absRoot, path)
213
+ rel = filepath.ToSlash(rel)
214
+ pkgDir := filepath.ToSlash(filepath.Dir(rel))
215
+ if pkgDir == "." {
216
+ pkgDir = ""
217
+ }
218
+ fc := &fileCtx{fset: fset, rel: rel, pkgDir: pkgDir, origin: origin, module: module, definers: definers}
219
+ // MockGen header -> claims for mock methods
220
+ if bytes.Contains(src, []byte("Code generated by MockGen")) {
221
+ if m := mockSource.FindSubmatch(src); m != nil {
222
+ srcPkg := string(m[1])
223
+ fc.mockPkgDir = strings.TrimPrefix(strings.TrimPrefix(srcPkg, module), "/")
224
+ fc.mockIfaces = map[string]bool{}
225
+ for _, name := range strings.Split(string(m[2]), ",") {
226
+ fc.mockIfaces[strings.TrimSpace(name)] = true
227
+ }
228
+ }
229
+ origin = "test"
230
+ fc.origin = "test"
231
+ }
232
+ var defs []definition
233
+ fc.index = &defs
234
+ // facts and branch sites come from the ORIGINAL tree, before any rewriting
235
+ if origin == "repo" {
236
+ if facts {
237
+ irFunctions = append(irFunctions, fc.lowerFuncs(f)...)
238
+ }
239
+ fc.wrapBranches(f)
240
+ }
241
+ // type declarations for the index (enclosing "class" lookups)
242
+ for _, d := range f.Decls {
243
+ if gd, ok := d.(*ast.GenDecl); ok && gd.Tok == token.TYPE {
244
+ for _, s := range gd.Specs {
245
+ if ts, ok := s.(*ast.TypeSpec); ok {
246
+ defs = append(defs, definition{Symbol: fc.symbolFor(ts.Name.Name), Path: rel, Start: fset.Position(ts.Pos()).Line, End: fset.Position(ts.End()).Line, Kind: "class"})
247
+ }
248
+ }
249
+ }
250
+ }
251
+ for _, d := range f.Decls {
252
+ if fd, ok := d.(*ast.FuncDecl); ok && fd.Body != nil {
253
+ fc.instrumentFunc(fd)
254
+ }
255
+ }
256
+ // closures anywhere (including inside already-instrumented bodies)
257
+ ast.Inspect(f, func(n ast.Node) bool {
258
+ if fl, ok := n.(*ast.FuncLit); ok && fl.Body != nil {
259
+ fc.instrumentLit(fl)
260
+ }
261
+ return true
262
+ })
263
+ if !fc.changed {
264
+ return false, defs, nil
265
+ }
266
+ var buf bytes.Buffer
267
+ if err := format.Node(&buf, fset, f); err != nil {
268
+ return false, defs, err
269
+ }
270
+ out := addImport(buf.String(), module+"/internal/diffgenome/dg")
271
+ if err := os.WriteFile(path, []byte(out), 0o644); err != nil {
272
+ return false, defs, err
273
+ }
274
+ return true, defs, nil
275
+ }
276
+
277
+ func (fc *fileCtx) symbolFor(qual string) string {
278
+ if fc.pkgDir == "" {
279
+ return "go:" + qual
280
+ }
281
+ return "go:" + fc.pkgDir + "." + qual
282
+ }
283
+
284
+ var anonStack []string // qualname stack for closures
285
+
286
+ func (fc *fileCtx) instrumentFunc(fd *ast.FuncDecl) {
287
+ qual := fd.Name.Name
288
+ if fd.Recv != nil && len(fd.Recv.List) > 0 {
289
+ if rt := receiverType(fd.Recv.List[0].Type); rt != "" {
290
+ qual = rt + "." + fd.Name.Name
291
+ }
292
+ }
293
+ sym := fc.symbolFor(qual)
294
+ line := fc.fset.Position(fd.Pos()).Line
295
+ *fc.index = append(*fc.index, definition{Symbol: sym, Path: fc.rel, Start: line, End: fc.fset.Position(fd.End()).Line, Kind: "function"})
296
+ claim, relation := "", ""
297
+ if fc.mockIfaces != nil && fd.Recv != nil {
298
+ rt := receiverType(fd.Recv.List[0].Type)
299
+ iface := strings.TrimPrefix(rt, "Mock")
300
+ if fc.mockIfaces[iface] && !strings.HasSuffix(rt, "MockRecorder") {
301
+ if defs := fc.definers[fc.mockPkgDir][fd.Name.Name]; len(defs) == 1 {
302
+ claim = "go:" + fc.mockPkgDir + "." + defs[0] + "." + fd.Name.Name
303
+ relation = "mocks-interface:" + iface
304
+ } else if len(defs) > 1 {
305
+ claim = "go:" + fc.mockPkgDir + "." + iface + "." + fd.Name.Name
306
+ relation = "mocks-interface:" + iface + ";candidates=" + strings.Join(defs, "|")
307
+ }
308
+ }
309
+ }
310
+ recv := ""
311
+ if fd.Recv != nil && len(fd.Recv.List) > 0 && len(fd.Recv.List[0].Names) > 0 && fd.Recv.List[0].Names[0].Name != "_" {
312
+ recv = fd.Recv.List[0].Names[0].Name
313
+ }
314
+ fc.wrapBody(fd.Type, fd.Body, sym, line, claim, relation, isTestFunc(fd), recv)
315
+ fc.markLits(fd.Body, qual)
316
+ }
317
+
318
+ func isTestFunc(fd *ast.FuncDecl) bool {
319
+ if fd.Recv != nil || !strings.HasPrefix(fd.Name.Name, "Test") || fd.Type.Params == nil || len(fd.Type.Params.List) != 1 {
320
+ return false
321
+ }
322
+ return exprString(fd.Type.Params.List[0].Type) == "*testing.T"
323
+ }
324
+
325
+ func isSubtestLit(fl *ast.FuncLit) bool {
326
+ if fl.Type.Params == nil || len(fl.Type.Params.List) != 1 {
327
+ return false
328
+ }
329
+ return exprString(fl.Type.Params.List[0].Type) == "*testing.T"
330
+ }
331
+
332
+ func exprString(e ast.Expr) string {
333
+ var b bytes.Buffer
334
+ _ = format.Node(&b, token.NewFileSet(), e)
335
+ return b.String()
336
+ }
337
+
338
+ var litQual = map[*ast.FuncLit]string{}
339
+
340
+ func (fc *fileCtx) markLits(body *ast.BlockStmt, qual string) {
341
+ ast.Inspect(body, func(n ast.Node) bool {
342
+ if fl, ok := n.(*ast.FuncLit); ok {
343
+ if _, done := litQual[fl]; !done {
344
+ litQual[fl] = qual + ".<anon>@" + strconv.Itoa(fc.fset.Position(fl.Pos()).Line)
345
+ }
346
+ }
347
+ return true
348
+ })
349
+ }
350
+
351
+ var litDone = map[*ast.FuncLit]bool{}
352
+
353
+ func (fc *fileCtx) instrumentLit(fl *ast.FuncLit) {
354
+ if litDone[fl] {
355
+ return
356
+ }
357
+ litDone[fl] = true
358
+ line := fc.fset.Position(fl.Pos()).Line
359
+ qual, ok := litQual[fl]
360
+ if !ok {
361
+ qual = "<anon>@" + strconv.Itoa(line)
362
+ }
363
+ sym := fc.symbolFor(qual)
364
+ *fc.index = append(*fc.index, definition{Symbol: sym, Path: fc.rel, Start: line, End: fc.fset.Position(fl.End()).Line, Kind: "function"})
365
+ fc.wrapBody(fl.Type, fl.Body, sym, line, "", "", isSubtestLit(fl) && fc.origin == "test", "")
366
+ fc.markLits(fl.Body, qual)
367
+ }
368
+
369
+ // wrapBody prepends the entry call and the deferred exit to a function body.
370
+ func (fc *fileCtx) wrapBody(ft *ast.FuncType, body *ast.BlockStmt, sym string, line int, claim, relation string, stimulus bool, recv string) {
371
+ // name unnamed results so the defer can read them
372
+ var resultNames []ast.Expr
373
+ if ft.Results != nil {
374
+ idx := 0
375
+ for _, field := range ft.Results.List {
376
+ if len(field.Names) == 0 {
377
+ name := ast.NewIdent(fmt.Sprintf("__dgr%d", idx))
378
+ field.Names = []*ast.Ident{name}
379
+ resultNames = append(resultNames, ast.NewIdent(name.Name))
380
+ idx++
381
+ continue
382
+ }
383
+ for _, n := range field.Names {
384
+ if n.Name == "_" {
385
+ n.Name = fmt.Sprintf("__dgr%d", idx)
386
+ }
387
+ resultNames = append(resultNames, ast.NewIdent(n.Name))
388
+ idx++
389
+ }
390
+ }
391
+ }
392
+ // parameters
393
+ var paramNames []string
394
+ var paramValues []ast.Expr
395
+ if ft.Params != nil {
396
+ for _, field := range ft.Params.List {
397
+ for _, n := range field.Names {
398
+ if n.Name == "_" {
399
+ continue
400
+ }
401
+ paramNames = append(paramNames, n.Name)
402
+ paramValues = append(paramValues, ast.NewIdent(n.Name))
403
+ }
404
+ }
405
+ }
406
+ metaElts := []ast.Expr{
407
+ kv("Sym", strLit(sym)), kv("Origin", strLit(fc.origin)), kv("File", strLit(fc.rel)),
408
+ kv("Line", &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(line)}),
409
+ kv("Params", &ast.CompositeLit{Type: &ast.ArrayType{Elt: ast.NewIdent("string")}, Elts: strLits(paramNames)}),
410
+ }
411
+ if claim != "" {
412
+ metaElts = append(metaElts, kv("Claim", strLit(claim)), kv("Relation", strLit(relation)))
413
+ }
414
+ if recv != "" {
415
+ metaElts = append(metaElts, kv("Recv", ast.NewIdent(recv)))
416
+ }
417
+ meta := &ast.CompositeLit{Type: sel("dg", "Meta"), Elts: metaElts}
418
+ enterArgs := append([]ast.Expr{meta}, paramValues...)
419
+ enter := &ast.AssignStmt{
420
+ Lhs: []ast.Expr{ast.NewIdent("__dgc")}, Tok: token.DEFINE,
421
+ Rhs: []ast.Expr{&ast.CallExpr{Fun: sel("dg", "Enter"), Args: enterArgs}},
422
+ }
423
+ exitArgs := append([]ast.Expr{ast.NewIdent("__dgc")}, resultNames...)
424
+ deferBody := &ast.BlockStmt{List: []ast.Stmt{
425
+ &ast.IfStmt{
426
+ Init: &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent("__dgp")}, Tok: token.DEFINE, Rhs: []ast.Expr{&ast.CallExpr{Fun: ast.NewIdent("recover")}}},
427
+ Cond: &ast.BinaryExpr{X: ast.NewIdent("__dgp"), Op: token.NEQ, Y: ast.NewIdent("nil")},
428
+ Body: &ast.BlockStmt{List: []ast.Stmt{
429
+ &ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Panic"), Args: []ast.Expr{ast.NewIdent("__dgc"), ast.NewIdent("__dgp")}}},
430
+ &ast.ExprStmt{X: &ast.CallExpr{Fun: ast.NewIdent("panic"), Args: []ast.Expr{ast.NewIdent("__dgp")}}},
431
+ }},
432
+ Else: &ast.BlockStmt{List: []ast.Stmt{
433
+ &ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Exit"), Args: exitArgs}},
434
+ }},
435
+ },
436
+ }}
437
+ deferStmt := &ast.DeferStmt{Call: &ast.CallExpr{Fun: &ast.FuncLit{Type: &ast.FuncType{Params: &ast.FieldList{}}, Body: deferBody}}}
438
+ litDone[deferStmt.Call.Fun.(*ast.FuncLit)] = true
439
+ stmts := []ast.Stmt{enter, deferStmt}
440
+ if stimulus {
441
+ // dg.Begin(t.Name()); defer func() { dg.End(t.Failed()) }()
442
+ tname := ft.Params.List[0].Names[0].Name
443
+ begin := &ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "Begin"), Args: []ast.Expr{&ast.CallExpr{Fun: sel(tname, "Name")}}}}
444
+ endLit := &ast.FuncLit{Type: &ast.FuncType{Params: &ast.FieldList{}}, Body: &ast.BlockStmt{List: []ast.Stmt{
445
+ &ast.ExprStmt{X: &ast.CallExpr{Fun: sel("dg", "End"), Args: []ast.Expr{&ast.CallExpr{Fun: sel(tname, "Failed")}}}},
446
+ }}}
447
+ litDone[endLit] = true
448
+ end := &ast.DeferStmt{Call: &ast.CallExpr{Fun: endLit}}
449
+ stmts = []ast.Stmt{begin, end, enter, deferStmt}
450
+ }
451
+ body.List = append(stmts, body.List...)
452
+ fc.changed = true
453
+ }
454
+
455
+ func kv(k string, v ast.Expr) ast.Expr { return &ast.KeyValueExpr{Key: ast.NewIdent(k), Value: v} }
456
+ func strLit(s string) ast.Expr { return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(s)} }
457
+ func sel(x, name string) ast.Expr {
458
+ return &ast.SelectorExpr{X: ast.NewIdent(x), Sel: ast.NewIdent(name)}
459
+ }
460
+ func strLits(xs []string) []ast.Expr {
461
+ out := make([]ast.Expr, 0, len(xs))
462
+ for _, x := range xs {
463
+ out = append(out, strLit(x))
464
+ }
465
+ return out
466
+ }
467
+
468
+ // addImport inserts the dg import after the package clause (gofmt-compatible enough).
469
+ func addImport(src, path string) string {
470
+ idx := strings.Index(src, "\npackage ")
471
+ if idx < 0 && strings.HasPrefix(src, "package ") {
472
+ idx = 0
473
+ } else if idx >= 0 {
474
+ idx++
475
+ } else {
476
+ return src
477
+ }
478
+ end := strings.Index(src[idx:], "\n")
479
+ if end < 0 {
480
+ return src
481
+ }
482
+ insert := "\n\nimport dg " + strconv.Quote(path) + "\n"
483
+ return src[:idx+end] + insert + src[idx+end:]
484
+ }