github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/internal/cmd/gtrace/writer.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"container/list"
     6  	"fmt"
     7  	"go/build"
     8  	"go/token"
     9  	"go/types"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"runtime"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"sync"
    18  	"unicode"
    19  	"unicode/utf8"
    20  )
    21  
    22  //nolint:maligned
    23  type Writer struct {
    24  	Output  io.Writer
    25  	Context build.Context
    26  
    27  	once sync.Once
    28  	bw   *bufio.Writer
    29  
    30  	atEOL bool
    31  	depth int
    32  	scope *list.List
    33  
    34  	pkg *types.Package
    35  	std map[string]bool
    36  }
    37  
    38  func (w *Writer) Write(p Package) error {
    39  	w.pkg = p.Package
    40  
    41  	w.init()
    42  	w.line(`// Code generated by gtrace. DO NOT EDIT.`)
    43  
    44  	for i, line := range p.BuildConstraints {
    45  		if i == 0 {
    46  			w.line()
    47  		}
    48  		w.line(line)
    49  	}
    50  	w.line()
    51  	w.line(`package `, p.Name())
    52  	w.line()
    53  
    54  	var deps []dep
    55  	for _, trace := range p.Traces {
    56  		deps = w.traceImports(deps, trace)
    57  	}
    58  	w.importDeps(deps)
    59  
    60  	w.newScope(func() {
    61  		for _, trace := range p.Traces {
    62  			w.options(trace)
    63  			w.compose(trace)
    64  			if trace.Nested {
    65  				w.isZero(trace)
    66  			}
    67  			for _, hook := range trace.Hooks {
    68  				w.hook(trace, hook)
    69  			}
    70  		}
    71  		for _, trace := range p.Traces {
    72  			for _, hook := range trace.Hooks {
    73  				w.hookShortcut(trace, hook)
    74  			}
    75  		}
    76  	})
    77  
    78  	return w.bw.Flush()
    79  }
    80  
    81  func (w *Writer) init() {
    82  	w.once.Do(func() {
    83  		w.bw = bufio.NewWriter(w.Output)
    84  		w.scope = list.New()
    85  	})
    86  }
    87  
    88  func (w *Writer) mustDeclare(name string) {
    89  	s, ok := w.scope.Back().Value.(*scope)
    90  	if !ok {
    91  		panic(fmt.Sprintf("unsupported type conversion from %T to *scope", s))
    92  	}
    93  	if !s.set(name) {
    94  		where := s.where(name)
    95  		panic(fmt.Sprintf(
    96  			"gtrace: can't declare identifier: %q: already defined at %q",
    97  			name, where,
    98  		))
    99  	}
   100  }
   101  
   102  func (w *Writer) declare(name string) string {
   103  	if isPredeclared(name) {
   104  		name = firstChar(name)
   105  	}
   106  	s, ok := w.scope.Back().Value.(*scope)
   107  	if !ok {
   108  		panic(fmt.Sprintf("unsupported type conversion from %T to *scope", s))
   109  	}
   110  	for i := 0; ; i++ {
   111  		v := name
   112  		if i > 0 {
   113  			v += strconv.Itoa(i)
   114  		}
   115  		if token.IsKeyword(v) {
   116  			continue
   117  		}
   118  		if w.isGlobalScope() && w.pkg.Scope().Lookup(v) != nil {
   119  			continue
   120  		}
   121  		if s.set(v) {
   122  			return v
   123  		}
   124  	}
   125  }
   126  
   127  func isPredeclared(name string) bool {
   128  	return types.Universe.Lookup(name) != nil
   129  }
   130  
   131  func (w *Writer) isGlobalScope() bool {
   132  	return w.scope.Back().Prev() == nil
   133  }
   134  
   135  func (w *Writer) capture(vars ...string) {
   136  	s, ok := w.scope.Back().Value.(*scope)
   137  	if !ok {
   138  		panic(fmt.Sprintf("unsupported type conversion from %T to *scope", s))
   139  	}
   140  	for _, v := range vars {
   141  		if !s.set(v) {
   142  			panic(fmt.Sprintf("can't capture variable %q", v))
   143  		}
   144  	}
   145  }
   146  
   147  type dep struct {
   148  	pkgPath string
   149  	pkgName string
   150  	typName string
   151  }
   152  
   153  func (w *Writer) typeImports(dst []dep, t types.Type) []dep {
   154  	if p, ok := t.(*types.Pointer); ok {
   155  		return w.typeImports(dst, p.Elem())
   156  	}
   157  	n, ok := t.(*types.Named)
   158  	if !ok {
   159  		return dst
   160  	}
   161  	var (
   162  		obj = n.Obj()
   163  		pkg = obj.Pkg()
   164  	)
   165  	if pkg != nil && pkg.Path() != w.pkg.Path() {
   166  		return append(dst, dep{
   167  			pkgPath: pkg.Path(),
   168  			pkgName: pkg.Name(),
   169  			typName: obj.Name(),
   170  		})
   171  	}
   172  
   173  	return dst
   174  }
   175  
   176  func forEachField(s *types.Struct, fn func(*types.Var)) {
   177  	for i := 0; i < s.NumFields(); i++ {
   178  		fn(s.Field(i))
   179  	}
   180  }
   181  
   182  func unwrapStruct(t types.Type) (n *types.Named, s *types.Struct) {
   183  	var ok bool
   184  	n, ok = t.(*types.Named)
   185  	if ok {
   186  		s, _ = n.Underlying().(*types.Struct)
   187  	}
   188  
   189  	return
   190  }
   191  
   192  func (w *Writer) funcImports(dst []dep, fn *Func) []dep {
   193  	for i := range fn.Params {
   194  		dst = w.typeImports(dst, fn.Params[i].Type)
   195  		if _, s := unwrapStruct(fn.Params[i].Type); s != nil {
   196  			forEachField(s, func(v *types.Var) {
   197  				if v.Exported() {
   198  					dst = w.typeImports(dst, v.Type())
   199  				}
   200  			})
   201  		}
   202  	}
   203  	for _, x := range fn.Result {
   204  		if fn, ok := x.(*Func); ok {
   205  			dst = w.funcImports(dst, fn)
   206  		}
   207  	}
   208  
   209  	return dst
   210  }
   211  
   212  func (w *Writer) traceImports(dst []dep, t *Trace) []dep {
   213  	for _, h := range t.Hooks {
   214  		dst = w.funcImports(dst, h.Func)
   215  	}
   216  
   217  	return dst
   218  }
   219  
   220  func (w *Writer) importDeps(deps []dep) {
   221  	seen := map[string]bool{}
   222  	for i := 0; i < len(deps); {
   223  		d := deps[i]
   224  		if seen[d.pkgPath] {
   225  			n := len(deps)
   226  			deps[i], deps[n-1] = deps[n-1], deps[i]
   227  			deps = deps[:n-1]
   228  
   229  			continue
   230  		}
   231  		seen[d.pkgPath] = true
   232  		i++
   233  	}
   234  	if len(deps) == 0 {
   235  		return
   236  	}
   237  	sort.Slice(deps, func(i, j int) bool {
   238  		var (
   239  			d0   = deps[i]
   240  			d1   = deps[j]
   241  			std0 = w.isStdLib(d0.pkgPath)
   242  			std1 = w.isStdLib(d1.pkgPath)
   243  		)
   244  		if std0 != std1 {
   245  			return std0
   246  		}
   247  
   248  		return d0.pkgPath < d1.pkgPath
   249  	})
   250  	w.line(`import (`)
   251  	var lastStd bool
   252  	for i := range deps {
   253  		if w.isStdLib(deps[i].pkgPath) {
   254  			lastStd = true
   255  		} else if lastStd {
   256  			lastStd = false
   257  			w.line()
   258  		}
   259  		w.line("\t", `"`, deps[i].pkgPath, `"`)
   260  	}
   261  	w.line(`)`)
   262  	w.line()
   263  }
   264  
   265  func (w *Writer) isStdLib(pkg string) bool {
   266  	w.ensureStdLibMapping()
   267  	s := strings.Split(pkg, "/")[0]
   268  
   269  	return w.std[s]
   270  }
   271  
   272  func (w *Writer) ensureStdLibMapping() {
   273  	if w.std != nil {
   274  		return
   275  	}
   276  	w.std = make(map[string]bool)
   277  
   278  	src := filepath.Join(w.Context.GOROOT, "src")
   279  	files, err := os.ReadDir(src)
   280  	if err != nil {
   281  		panic(fmt.Sprintf("can't list GOROOT's src: %v", err))
   282  	}
   283  	for _, file := range files {
   284  		if !file.IsDir() {
   285  			continue
   286  		}
   287  		name := filepath.Base(file.Name())
   288  		switch name {
   289  		case "cmd", "internal":
   290  			// Ignored.
   291  
   292  		default:
   293  			w.std[name] = true
   294  		}
   295  	}
   296  }
   297  
   298  func (w *Writer) call(args []string) {
   299  	w.code(`(`)
   300  	for i, name := range args {
   301  		if i > 0 {
   302  			w.code(`, `)
   303  		}
   304  		w.code(name)
   305  	}
   306  	w.line(`)`)
   307  }
   308  
   309  func (w *Writer) isZero(trace *Trace) {
   310  	w.newScope(func() {
   311  		t := w.declare("t")
   312  		w.line(`// isZero checks whether `, t, ` is empty`)
   313  		w.line(`func (`, t, ` `, trace.Name, `) isZero() bool {`)
   314  		w.block(func() {
   315  			for _, hook := range trace.Hooks {
   316  				w.line(`if `, t, `.`, hook.Name, ` != nil {`)
   317  				w.block(func() {
   318  					w.line(`return false`)
   319  				})
   320  				w.line(`}`)
   321  			}
   322  			w.line(`return true`)
   323  		})
   324  		w.line(`}`)
   325  	})
   326  }
   327  
   328  func (w *Writer) compose(trace *Trace) {
   329  	w.newScope(func() {
   330  		t := w.declare("t")
   331  		x := w.declare("x")
   332  		ret := w.declare("ret")
   333  		w.line(`// Compose returns a new `, trace.Name, ` which has functional fields composed both from `,
   334  			t, ` and `, x, `.`,
   335  		)
   336  		w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`)
   337  		w.code(`func (`, t, ` *`, trace.Name, `) Compose(`, x, ` *`, trace.Name, `, opts ...`+trace.Name+`ComposeOption) `)
   338  		w.line(`*`, trace.Name, ` {`)
   339  		w.block(func() {
   340  			w.line(`if `, t, ` == nil {`)
   341  			w.block(func() {
   342  				w.line(`return x`)
   343  			})
   344  			w.line(`}`)
   345  			w.line(`var `, ret, ` `, trace.Name, ``)
   346  			if len(trace.Hooks) > 0 {
   347  				w.line(`options := `, unexported(trace.Name), `ComposeOptions{}`)
   348  				w.line(`for _, opt := range opts {`)
   349  				w.block(func() {
   350  					w.line(`if opt != nil {`)
   351  					w.block(func() {
   352  						w.line(`opt(&options)`)
   353  					})
   354  					w.line(`}`)
   355  				})
   356  				w.line(`}`)
   357  			}
   358  			for _, hook := range trace.Hooks {
   359  				w.composeHook(hook, t, x, ret+"."+hook.Name)
   360  			}
   361  			w.line(`return &`, ret)
   362  		})
   363  		w.line(`}`)
   364  	})
   365  }
   366  
   367  func (w *Writer) composeHook(hook Hook, t1, t2, dst string) {
   368  	w.line(`{`)
   369  	w.block(func() {
   370  		h1 := w.declare("h1")
   371  		h2 := w.declare("h2")
   372  		w.line(h1, ` := `, t1, `.`, hook.Name)
   373  		w.line(h2, ` := `, t2, `.`, hook.Name)
   374  		w.code(dst, ` = `)
   375  		w.composeHookCall(hook.Func, h1, h2)
   376  	})
   377  	w.line(`}`)
   378  }
   379  
   380  //nolint:funlen
   381  func (w *Writer) composeHookCall(fn *Func, h1, h2 string) {
   382  	w.newScope(func() {
   383  		w.capture(h1, h2)
   384  		w.block(func() {
   385  			w.capture(h1, h2)
   386  			w.code(`func`)
   387  			args := w.funcParams(fn.Params)
   388  			if fn.HasResult() {
   389  				w.code(` `)
   390  			}
   391  			w.funcResults(fn)
   392  			w.line(` {`)
   393  			w.line(`if options.panicCallback != nil {`)
   394  			w.block(func() {
   395  				w.line("defer func() {")
   396  				w.block(func() {
   397  					w.line("if e := recover(); e != nil {")
   398  					w.block(func() {
   399  						w.line(`options.panicCallback(e)`)
   400  					})
   401  					w.line("}")
   402  				})
   403  				w.line("}()")
   404  			})
   405  			w.line("}")
   406  			var (
   407  				r1 string
   408  				r2 string
   409  				rs []string
   410  			)
   411  			if fn.HasResult() {
   412  				r1 = w.declare("r")
   413  				r2 = w.declare("r")
   414  				rs = []string{r1, r2}
   415  				w.code("var " + r1 + ", " + r2 + " ")
   416  				w.funcResults(fn)
   417  				_ = w.bw.WriteByte('\n')
   418  				w.atEOL = true
   419  			}
   420  			for i, h := range []string{h1, h2} {
   421  				w.line("if " + h + " != nil {")
   422  				w.block(func() {
   423  					if fn.HasResult() {
   424  						w.code(rs[i], ` = `) //nolint:scopelint
   425  					}
   426  					w.code(h) //nolint:scopelint
   427  					w.call(args)
   428  				})
   429  				w.line("}")
   430  			}
   431  			if fn.HasResult() {
   432  				w.code(`return `)
   433  				switch x := fn.Result[0].(type) {
   434  				case *Func:
   435  					w.composeHookCall(x, r1, r2)
   436  				case *Trace:
   437  					w.line(r1, `.Compose(`, r2, `)`)
   438  				default:
   439  					panic("unknown result type")
   440  				}
   441  			}
   442  		})
   443  		w.line(`}`)
   444  	})
   445  }
   446  
   447  func (w *Writer) options(trace *Trace) {
   448  	w.newScope(func() {
   449  		w.line(fmt.Sprintf(`// %sComposeOptions is a holder of options`, unexported(trace.Name)))
   450  		w.line(fmt.Sprintf(`type %sComposeOptions struct {`, unexported(trace.Name)))
   451  		w.block(func() {
   452  			w.line(`panicCallback func(e interface{})`)
   453  		})
   454  		w.line(`}`)
   455  		_ = w.bw.WriteByte('\n')
   456  	})
   457  	w.newScope(func() {
   458  		w.line(fmt.Sprintf(`// %sOption specified %s compose option`, trace.Name, trace.Name))
   459  		w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`)
   460  		w.line(fmt.Sprintf(`type %sComposeOption func(o *%sComposeOptions)`, trace.Name, unexported(trace.Name)))
   461  		_ = w.bw.WriteByte('\n')
   462  	})
   463  	w.newScope(func() {
   464  		w.line(fmt.Sprintf(`// With%sPanicCallback specified behavior on panic`, trace.Name))
   465  		w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`)
   466  		w.line(fmt.Sprintf(`func With%sPanicCallback(cb func(e interface{})) %sComposeOption {`, trace.Name, trace.Name))
   467  		w.block(func() {
   468  			w.line(fmt.Sprintf(`return func(o *%sComposeOptions) {`, unexported(trace.Name)))
   469  			w.block(func() {
   470  				w.line(`o.panicCallback = cb`)
   471  			})
   472  			w.line(`}`)
   473  		})
   474  		w.line(`}`)
   475  		_ = w.bw.WriteByte('\n')
   476  	})
   477  }
   478  
   479  func (w *Writer) hook(trace *Trace, hook Hook) {
   480  	w.newScope(func() {
   481  		t := w.declare("t")
   482  		fn := w.declare("fn")
   483  
   484  		w.code(`func (`, t, ` *`, trace.Name, `) `, unexported(hook.Name))
   485  
   486  		w.code(`(`)
   487  		var args []string
   488  		for i := range hook.Func.Params {
   489  			if i > 0 {
   490  				w.code(`, `)
   491  			}
   492  			args = append(args, w.funcParam(&hook.Func.Params[i]))
   493  		}
   494  		w.code(`)`)
   495  		if hook.Func.HasResult() {
   496  			w.code(` `)
   497  		}
   498  		w.funcResultsFlags(hook.Func, docs)
   499  		w.line(` {`)
   500  		w.block(func() {
   501  			w.line(fn, ` := `, t, `.`, hook.Name)
   502  			w.line(`if `, fn, ` == nil {`)
   503  			w.block(func() {
   504  				w.zeroReturn(hook.Func)
   505  			})
   506  			w.line(`}`)
   507  
   508  			w.hookFuncCall(hook.Func, fn, args)
   509  		})
   510  		w.line(`}`)
   511  	})
   512  }
   513  
   514  func (w *Writer) hookFuncCall(fn *Func, name string, args []string) {
   515  	var res string
   516  	if fn.HasResult() {
   517  		res = w.declare("res")
   518  		w.code(res, ` := `)
   519  	}
   520  
   521  	w.code(name)
   522  	w.call(args)
   523  
   524  	if !fn.HasResult() {
   525  		return
   526  	}
   527  
   528  	r, isFunc := fn.Result[0].(*Func)
   529  	if isFunc {
   530  		w.line(`if `, res, ` == nil {`)
   531  		w.block(func() {
   532  			w.zeroReturn(fn)
   533  		})
   534  		w.line(`}`)
   535  
   536  		if r.HasResult() {
   537  			w.newScope(func() {
   538  				w.code(`return func`)
   539  				args := w.funcParams(r.Params)
   540  				w.code(` `)
   541  				w.funcResults(r)
   542  				w.line(` {`)
   543  				w.block(func() {
   544  					w.hookFuncCall(r, res, args)
   545  				})
   546  				w.line(`}`)
   547  			})
   548  
   549  			return
   550  		}
   551  	}
   552  
   553  	w.line(`return `, res)
   554  }
   555  
   556  func nameParam(p *Param) (s string) {
   557  	s = p.Name
   558  	if s == "" {
   559  		s = firstChar(ident(typeBasename(p.Type)))
   560  	}
   561  
   562  	return unexported(s)
   563  }
   564  
   565  func (w *Writer) declareParams(src []Param) (names []string) {
   566  	names = make([]string, len(src))
   567  	for i := range src {
   568  		names[i] = w.declare(nameParam(&src[i]))
   569  	}
   570  
   571  	return names
   572  }
   573  
   574  func flattenParams(params []Param) (dst []Param) {
   575  	for i := range params {
   576  		_, s := unwrapStruct(params[i].Type)
   577  		if s != nil {
   578  			dst = flattenStruct(dst, s)
   579  
   580  			continue
   581  		}
   582  		dst = append(dst, params[i])
   583  	}
   584  
   585  	return dst
   586  }
   587  
   588  func typeBasename(t types.Type) (name string) {
   589  	lo, name := rsplit(t.String(), '.')
   590  	if name == "" {
   591  		name = lo
   592  	}
   593  
   594  	return name
   595  }
   596  
   597  func flattenStruct(dst []Param, s *types.Struct) []Param {
   598  	forEachField(s, func(f *types.Var) {
   599  		if !f.Exported() {
   600  			return
   601  		}
   602  		var (
   603  			name = f.Name()
   604  			typ  = f.Type()
   605  		)
   606  		if name == typeBasename(typ) {
   607  			// NOTE: field name essentially be empty for embedded structs or
   608  			// fields called exactly as type.
   609  			name = ""
   610  		}
   611  		dst = append(dst, Param{
   612  			Name: name,
   613  			Type: typ,
   614  		})
   615  	})
   616  
   617  	return dst
   618  }
   619  
   620  func (w *Writer) constructParams(params []Param, names []string) (res []string) {
   621  	for i := range params {
   622  		n, s := unwrapStruct(params[i].Type)
   623  		if s != nil {
   624  			var v string
   625  			v, names = w.constructStruct(n, s, names)
   626  			res = append(res, v)
   627  
   628  			continue
   629  		}
   630  		name := names[0]
   631  		names = names[1:]
   632  		res = append(res, name)
   633  	}
   634  
   635  	return res
   636  }
   637  
   638  func (w *Writer) constructStruct(n types.Type, s *types.Struct, vars []string) (string, []string) {
   639  	p := w.declare("p")
   640  	// maybe skip pointers from flattening to not allocate anyhing during trace.
   641  	w.line(`var `, p, ` `, w.typeString(n))
   642  	for i := 0; i < s.NumFields(); i++ {
   643  		v := s.Field(i)
   644  		if !v.Exported() {
   645  			continue
   646  		}
   647  		name := vars[0]
   648  		vars = vars[1:]
   649  		w.line(p, `.`, v.Name(), ` = `, name)
   650  	}
   651  
   652  	return p, vars
   653  }
   654  
   655  func (w *Writer) hookShortcut(trace *Trace, hook Hook) {
   656  	name := exported(tempName(trace.Name, hook.Name))
   657  
   658  	w.mustDeclare(name)
   659  
   660  	w.newScope(func() {
   661  		t := w.declare("t")
   662  		w.line(`// Internals: https://github.com/ydb-platform/ydb-go-sdk/blob/master/VERSIONING.md#internals`)
   663  		w.code(`func `, name)
   664  		w.code(`(`)
   665  		var ctx string
   666  		w.code(t, ` *`, trace.Name)
   667  
   668  		var (
   669  			params = flattenParams(hook.Func.Params)
   670  			names  = w.declareParams(params)
   671  		)
   672  		for i := range params {
   673  			w.code(`, `)
   674  			w.code(names[i], ` `, w.typeString(params[i].Type))
   675  		}
   676  		w.code(`)`)
   677  		if hook.Func.HasResult() {
   678  			w.code(` `)
   679  		}
   680  		w.shortcutFuncResultsFlags(hook.Func, docs)
   681  		w.line(` {`)
   682  		w.block(func() {
   683  			for _, name := range names {
   684  				w.capture(name)
   685  			}
   686  			vars := w.constructParams(hook.Func.Params, names)
   687  			var res string
   688  			if hook.Func.HasResult() {
   689  				res = w.declare("res")
   690  				w.code(res, ` := `)
   691  			}
   692  			w.code(t, `.`, unexported(hook.Name))
   693  			if ctx != "" {
   694  				vars = append([]string{ctx}, vars...)
   695  			}
   696  			w.call(vars)
   697  			if hook.Func.HasResult() {
   698  				w.code(`return `)
   699  				r := hook.Func.Result[0]
   700  				switch x := r.(type) {
   701  				case *Func:
   702  					w.hookFuncShortcut(x, res)
   703  				case *Trace:
   704  					w.line(res)
   705  				default:
   706  					panic("unexpected result type")
   707  				}
   708  			}
   709  		})
   710  		w.line(`}`)
   711  	})
   712  }
   713  
   714  func (w *Writer) hookFuncShortcut(fn *Func, name string) {
   715  	w.newScope(func() {
   716  		w.code(`func(`)
   717  		var (
   718  			params = flattenParams(fn.Params)
   719  			names  = w.declareParams(params)
   720  		)
   721  		for i := range params {
   722  			if i > 0 {
   723  				w.code(`, `)
   724  			}
   725  			w.code(names[i], ` `, w.typeString(params[i].Type))
   726  		}
   727  		w.code(`)`)
   728  		if fn.HasResult() {
   729  			w.code(` `)
   730  		}
   731  		w.shortcutFuncResults(fn)
   732  		w.line(` {`)
   733  		w.block(func() {
   734  			for _, name := range names {
   735  				w.capture(name)
   736  			}
   737  			params := w.constructParams(fn.Params, names)
   738  			var res string
   739  			if fn.HasResult() {
   740  				res = w.declare("res")
   741  				w.code(res, ` := `)
   742  			}
   743  			w.code(name)
   744  			w.call(params)
   745  			if fn.HasResult() {
   746  				r := fn.Result[0]
   747  				w.code(`return `)
   748  				switch x := r.(type) {
   749  				case *Func:
   750  					w.hookFuncShortcut(x, res)
   751  				case *Trace:
   752  					w.line(res)
   753  				default:
   754  					panic("unexpected result type")
   755  				}
   756  			}
   757  		})
   758  		w.line(`}`)
   759  	})
   760  }
   761  
   762  func (w *Writer) zeroReturn(fn *Func) {
   763  	if !fn.HasResult() {
   764  		w.line(`return`)
   765  
   766  		return
   767  	}
   768  	w.code(`return `)
   769  	switch x := fn.Result[0].(type) {
   770  	case *Func:
   771  		w.funcSignature(x)
   772  		w.line(` {`)
   773  		w.block(func() {
   774  			w.zeroReturn(x)
   775  		})
   776  		w.line(`}`)
   777  	case *Trace:
   778  		w.line(x.Name, `{}`)
   779  	default:
   780  		panic("unexpected result type")
   781  	}
   782  }
   783  
   784  func (w *Writer) funcParams(params []Param) (vars []string) {
   785  	w.code(`(`)
   786  	for i := range params {
   787  		if i > 0 {
   788  			w.code(`, `)
   789  		}
   790  		vars = append(vars, w.funcParam(&params[i]))
   791  	}
   792  	w.code(`)`)
   793  
   794  	return
   795  }
   796  
   797  func (w *Writer) funcParam(p *Param) (name string) {
   798  	name = w.declare(nameParam(p))
   799  	w.code(name, ` `)
   800  	w.code(w.typeString(p.Type))
   801  
   802  	return name
   803  }
   804  
   805  func (w *Writer) funcParamSign(p *Param) {
   806  	name := nameParam(p)
   807  	if len(name) == 1 || isPredeclared(name) {
   808  		name = "_"
   809  	}
   810  	w.code(name, ` `)
   811  	w.code(w.typeString(p.Type))
   812  }
   813  
   814  type flags uint8
   815  
   816  func (f flags) has(x flags) bool {
   817  	return f&x != 0
   818  }
   819  
   820  const (
   821  	_ flags = 1 << iota >> 1
   822  	docs
   823  )
   824  
   825  func (w *Writer) funcResultsFlags(fn *Func, flags flags) {
   826  	for _, r := range fn.Result {
   827  		switch x := r.(type) {
   828  		case *Func:
   829  			w.funcSignatureFlags(x, flags)
   830  		case *Trace:
   831  			w.code(x.Name, ` `)
   832  		default:
   833  			panic("unexpected result type")
   834  		}
   835  	}
   836  }
   837  
   838  func (w *Writer) funcResults(fn *Func) {
   839  	w.funcResultsFlags(fn, 0)
   840  }
   841  
   842  func (w *Writer) funcSignatureFlags(fn *Func, flags flags) {
   843  	haveNames := haveNames(fn.Params)
   844  	w.code(`func(`)
   845  	for i := range fn.Params {
   846  		if i > 0 {
   847  			w.code(`, `)
   848  		}
   849  		if flags.has(docs) && haveNames {
   850  			w.funcParamSign(&fn.Params[i])
   851  		} else {
   852  			w.code(w.typeString(fn.Params[i].Type))
   853  		}
   854  	}
   855  	w.code(`)`)
   856  	if fn.HasResult() {
   857  		if fn.isFuncResult() {
   858  			w.code(` `)
   859  		}
   860  		w.funcResultsFlags(fn, flags)
   861  	}
   862  }
   863  
   864  func (w *Writer) funcSignature(fn *Func) {
   865  	w.funcSignatureFlags(fn, 0)
   866  }
   867  
   868  func (w *Writer) shortcutFuncSignFlags(fn *Func, flags flags) {
   869  	var (
   870  		params    = flattenParams(fn.Params)
   871  		haveNames = haveNames(params)
   872  	)
   873  	w.code(`func(`)
   874  	for i := range params {
   875  		if i > 0 {
   876  			w.code(`, `)
   877  		}
   878  		if flags.has(docs) && haveNames {
   879  			w.funcParamSign(&params[i])
   880  		} else {
   881  			w.code(w.typeString(params[i].Type))
   882  		}
   883  	}
   884  	w.code(`)`)
   885  	if fn.HasResult() {
   886  		if fn.isFuncResult() {
   887  			w.code(` `)
   888  		}
   889  		w.shortcutFuncResultsFlags(fn, flags)
   890  	}
   891  }
   892  
   893  func (w *Writer) shortcutFuncResultsFlags(fn *Func, flags flags) {
   894  	for _, r := range fn.Result {
   895  		switch x := r.(type) {
   896  		case *Func:
   897  			w.shortcutFuncSignFlags(x, flags)
   898  		case *Trace:
   899  			w.code(x.Name, ` `)
   900  		default:
   901  			panic("unexpected result type")
   902  		}
   903  	}
   904  }
   905  
   906  func (w *Writer) shortcutFuncResults(fn *Func) {
   907  	w.shortcutFuncResultsFlags(fn, 0)
   908  }
   909  
   910  func haveNames(params []Param) bool {
   911  	for i := range params {
   912  		name := nameParam(&params[i])
   913  		if len(name) > 1 && !isPredeclared(name) {
   914  			return true
   915  		}
   916  	}
   917  
   918  	return false
   919  }
   920  
   921  func (w *Writer) typeString(t types.Type) string {
   922  	return types.TypeString(t, func(pkg *types.Package) string {
   923  		if pkg.Path() == w.pkg.Path() {
   924  			return "" // same package; unqualified
   925  		}
   926  
   927  		return pkg.Name()
   928  	})
   929  }
   930  
   931  func (w *Writer) block(fn func()) {
   932  	w.depth++
   933  	w.newScope(fn)
   934  	w.depth--
   935  }
   936  
   937  func (w *Writer) newScope(fn func()) {
   938  	w.scope.PushBack(new(scope))
   939  	fn()
   940  	w.scope.Remove(w.scope.Back())
   941  }
   942  
   943  func (w *Writer) line(args ...string) {
   944  	w.code(args...)
   945  	_ = w.bw.WriteByte('\n')
   946  	w.atEOL = true
   947  }
   948  
   949  func (w *Writer) code(args ...string) {
   950  	if w.atEOL {
   951  		for i := 0; i < w.depth; i++ {
   952  			_ = w.bw.WriteByte('\t')
   953  		}
   954  		w.atEOL = false
   955  	}
   956  	for _, arg := range args {
   957  		_, _ = w.bw.WriteString(arg)
   958  	}
   959  }
   960  
   961  func exported(s string) string {
   962  	r, size := utf8.DecodeRuneInString(s)
   963  	if r == utf8.RuneError {
   964  		panic("invalid string")
   965  	}
   966  
   967  	return string(unicode.ToUpper(r)) + s[size:]
   968  }
   969  
   970  func unexported(s string) string {
   971  	r, size := utf8.DecodeRuneInString(s)
   972  	if r == utf8.RuneError {
   973  		panic("invalid string")
   974  	}
   975  
   976  	return string(unicode.ToLower(r)) + s[size:]
   977  }
   978  
   979  func firstChar(s string) string {
   980  	r, _ := utf8.DecodeRuneInString(s)
   981  	if r == utf8.RuneError {
   982  		panic("invalid string")
   983  	}
   984  
   985  	return string(r)
   986  }
   987  
   988  func ident(s string) string {
   989  	// Identifier must not begin with number.
   990  	for len(s) > 0 {
   991  		r, size := utf8.DecodeRuneInString(s)
   992  		if r == utf8.RuneError {
   993  			panic("invalid string")
   994  		}
   995  		if !unicode.IsNumber(r) {
   996  			break
   997  		}
   998  		s = s[size:]
   999  	}
  1000  
  1001  	// Filter out non letter/number/underscore characters.
  1002  	s = strings.Map(func(r rune) rune {
  1003  		switch {
  1004  		case r == '_' ||
  1005  			unicode.IsLetter(r) ||
  1006  			unicode.IsNumber(r):
  1007  
  1008  			return r
  1009  		default:
  1010  			return -1
  1011  		}
  1012  	}, s)
  1013  
  1014  	if !token.IsIdentifier(s) {
  1015  		s = "_" + s
  1016  	}
  1017  
  1018  	return s
  1019  }
  1020  
  1021  func tempName(names ...string) string {
  1022  	var sb strings.Builder
  1023  	for i, name := range names {
  1024  		if i == 0 {
  1025  			name = unexported(name)
  1026  		} else {
  1027  			name = exported(name)
  1028  		}
  1029  		sb.WriteString(name)
  1030  	}
  1031  
  1032  	return sb.String()
  1033  }
  1034  
  1035  type decl struct {
  1036  	where string
  1037  }
  1038  
  1039  type scope struct {
  1040  	vars map[string]decl
  1041  }
  1042  
  1043  func (s *scope) set(v string) bool {
  1044  	if s.vars == nil {
  1045  		s.vars = make(map[string]decl)
  1046  	}
  1047  	if _, has := s.vars[v]; has {
  1048  		return false
  1049  	}
  1050  	_, file, line, _ := runtime.Caller(2) //nolint:gomnd
  1051  	s.vars[v] = decl{
  1052  		where: fmt.Sprintf("%s:%d", file, line),
  1053  	}
  1054  
  1055  	return true
  1056  }
  1057  
  1058  func (s *scope) where(v string) string {
  1059  	d := s.vars[v]
  1060  
  1061  	return d.where
  1062  }