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