Codebase list golang-github-sanity-io-litter / de6befe
New upstream version 1.3.0 Anthony Fok 3 years ago
16 changed file(s) with 307 addition(s) and 169 deletion(s). Raw diff Collapse all Expand all
00 language: go
11 go:
2 - 1.9.x
2 - 1.14.x
1212 "strings"
1313 )
1414
15 var packageNameStripperRegexp = regexp.MustCompile("\\b[a-zA-Z_]+[a-zA-Z_0-9]+\\.")
16 var compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`)
15 var (
16 packageNameStripperRegexp = regexp.MustCompile(`\b[a-zA-Z_]+[a-zA-Z_0-9]+\.`)
17 compactTypeRegexp = regexp.MustCompile(`\s*([,;{}()])\s*`)
18 )
1719
1820 // Dumper is the interface for implementing custom dumper for your types.
1921 type Dumper interface {
3032 FieldFilter func(reflect.StructField, reflect.Value) bool
3133 HomePackage string
3234 Separator string
35 StrictGo bool
36
37 // DisablePointerReplacement, if true, disables the replacing of pointer data with variable names
38 // when it's safe. This is useful for diffing two structures, where pointer variables would cause
39 // false changes. However, circular graphs are still detected and elided to avoid infinite output.
40 DisablePointerReplacement bool
3341 }
3442
3543 // Config is the default config used when calling Dump
4452 w io.Writer
4553 depth int
4654 config *Options
47 pointers []uintptr
48 visitedPointers []uintptr
55 pointers ptrmap
56 visitedPointers ptrmap
57 parentPointers ptrmap
4958 currentPointerName string
5059 homePackageRegexp *regexp.Regexp
5160 }
5261
62 func (s *dumpState) write(b []byte) {
63 if _, err := s.w.Write(b); err != nil {
64 panic(err)
65 }
66 }
67
68 func (s *dumpState) writeString(str string) {
69 s.write([]byte(str))
70 }
71
5372 func (s *dumpState) indent() {
5473 if !s.config.Compact {
55 s.w.Write(bytes.Repeat([]byte(" "), s.depth))
74 s.write(bytes.Repeat([]byte(" "), s.depth))
5675 }
5776 }
5877
5978 func (s *dumpState) newlineWithPointerNameComment() {
60 if s.currentPointerName != "" {
79 if name := s.currentPointerName; name != "" {
6180 if s.config.Compact {
62 s.w.Write([]byte(fmt.Sprintf("/*%s*/", s.currentPointerName)))
81 s.write([]byte(fmt.Sprintf("/*%s*/", name)))
6382 } else {
64 s.w.Write([]byte(fmt.Sprintf(" // %s\n", s.currentPointerName)))
83 s.write([]byte(fmt.Sprintf(" // %s\n", name)))
6584 }
6685 s.currentPointerName = ""
6786 return
6887 }
6988 if !s.config.Compact {
70 s.w.Write([]byte("\n"))
89 s.write([]byte("\n"))
7190 }
7291 }
7392
81100 if s.config.Compact {
82101 typeName = compactTypeRegexp.ReplaceAllString(typeName, "$1")
83102 }
84 s.w.Write([]byte(typeName))
103 s.write([]byte(typeName))
85104 }
86105
87106 func (s *dumpState) dumpSlice(v reflect.Value) {
88107 s.dumpType(v)
89108 numEntries := v.Len()
90109 if numEntries == 0 {
91 s.w.Write([]byte("{}"))
110 s.write([]byte("{}"))
92111 if s.config.Compact {
93 s.w.Write([]byte(";"))
112 s.write([]byte(";"))
94113 }
95114 s.newlineWithPointerNameComment()
96115 return
97116 }
98 s.w.Write([]byte("{"))
117 s.write([]byte("{"))
99118 s.newlineWithPointerNameComment()
100119 s.depth++
101120 for i := 0; i < numEntries; i++ {
102121 s.indent()
103122 s.dumpVal(v.Index(i))
104123 if !s.config.Compact || i < numEntries-1 {
105 s.w.Write([]byte(","))
124 s.write([]byte(","))
106125 }
107126 s.newlineWithPointerNameComment()
108127 }
109128 s.depth--
110129 s.indent()
111 s.w.Write([]byte("}"))
130 s.write([]byte("}"))
112131 }
113132
114133 func (s *dumpState) dumpStruct(v reflect.Value) {
115134 dumpPreamble := func() {
116135 s.dumpType(v)
117 s.w.Write([]byte("{"))
136 s.write([]byte("{"))
118137 s.newlineWithPointerNameComment()
119138 s.depth++
120139 }
137156 preambleDumped = true
138157 }
139158 s.indent()
140 s.w.Write([]byte(vtf.Name))
159 s.write([]byte(vtf.Name))
141160 if s.config.Compact {
142 s.w.Write([]byte(":"))
161 s.write([]byte(":"))
143162 } else {
144 s.w.Write([]byte(": "))
163 s.write([]byte(": "))
145164 }
146165 s.dumpVal(v.Field(i))
147166 if !s.config.Compact || i < numFields-1 {
148 s.w.Write([]byte(","))
167 s.write([]byte(","))
149168 }
150169 s.newlineWithPointerNameComment()
151170 }
152171 if preambleDumped {
153172 s.depth--
154173 s.indent()
155 s.w.Write([]byte("}"))
174 s.write([]byte("}"))
156175 } else {
157176 // There were no fields dumped
158177 s.dumpType(v)
159 s.w.Write([]byte("{}"))
178 s.write([]byte("{}"))
160179 }
161180 }
162181
163182 func (s *dumpState) dumpMap(v reflect.Value) {
164183 s.dumpType(v)
165 s.w.Write([]byte("{"))
184 s.write([]byte("{"))
166185 s.newlineWithPointerNameComment()
167186 s.depth++
168187 keys := v.MapKeys()
175194 s.indent()
176195 s.dumpVal(key)
177196 if s.config.Compact {
178 s.w.Write([]byte(":"))
197 s.write([]byte(":"))
179198 } else {
180 s.w.Write([]byte(": "))
199 s.write([]byte(": "))
181200 }
182201 s.dumpVal(v.MapIndex(key))
183202 if !s.config.Compact || i < numKeys-1 {
184 s.w.Write([]byte(","))
203 s.write([]byte(","))
185204 }
186205 s.newlineWithPointerNameComment()
187206 }
188207 s.depth--
189208 s.indent()
190 s.w.Write([]byte("}"))
209 s.write([]byte("}"))
191210 }
192211
193212 func (s *dumpState) dumpFunc(v reflect.Value) {
206225 if s.config.Compact {
207226 name = compactTypeRegexp.ReplaceAllString(name, "$1")
208227 }
209 s.w.Write([]byte(name))
228 s.write([]byte(name))
210229 }
211230 }
212231
220239 s.dumpType(v)
221240
222241 if s.config.Compact {
223 s.w.Write(buf.Bytes())
242 s.write(buf.Bytes())
224243 return
225244 }
226245
242261 } else {
243262 s.indent()
244263 }
245 s.w.Write([]byte(line))
264 s.write([]byte(line))
246265
247266 // At EOF we're done
248267 if err == io.EOF {
262281 s.dumpVal(v)
263282 }
264283
265 func (s *dumpState) handlePointerAliasingAndCheckIfShouldDescend(value reflect.Value) bool {
284 func (s *dumpState) descendIntoPossiblePointer(value reflect.Value, f func()) {
285 canonicalize := true
286 if isPointerValue(value) {
287 ptr := value.Pointer()
288
289 // If elision disabled, and this is not a circular reference, don't canonicalize
290 if s.config.DisablePointerReplacement && s.parentPointers.add(ptr) {
291 canonicalize = false
292 }
293
294 // Add to stack of pointers we're recursively descending into
295 s.parentPointers.add(ptr)
296 defer s.parentPointers.remove(ptr)
297 }
298
299 if !canonicalize {
300 pointerName, _ := s.pointerNameFor(value)
301 s.currentPointerName = pointerName
302 f()
303 return
304 }
305
266306 pointerName, firstVisit := s.pointerNameFor(value)
267307 if pointerName == "" {
268 return true
308 f()
309 return
269310 }
270311 if firstVisit {
271312 s.currentPointerName = pointerName
272 return true
273 }
274 s.w.Write([]byte(pointerName))
275 return false
313 f()
314 return
315 }
316 s.write([]byte(pointerName))
276317 }
277318
278319 func (s *dumpState) dumpVal(value reflect.Value) {
279320 if value.Kind() == reflect.Ptr && value.IsNil() {
280 s.w.Write([]byte("nil"))
321 s.write([]byte("nil"))
281322 return
282323 }
283324
287328 // Handle custom dumpers
288329 dumperType := reflect.TypeOf((*Dumper)(nil)).Elem()
289330 if v.Type().Implements(dumperType) {
290 if s.handlePointerAliasingAndCheckIfShouldDescend(v) {
331 s.descendIntoPossiblePointer(v, func() {
291332 s.dumpCustom(v)
292 }
333 })
293334 return
294335 }
295336
297338 case reflect.Invalid:
298339 // Do nothing. We should never get here since invalid has already
299340 // been handled above.
300 s.w.Write([]byte("<invalid>"))
341 s.write([]byte("<invalid>"))
301342
302343 case reflect.Bool:
303344 printBool(s.w, v.Bool())
321362 printComplex(s.w, v.Complex(), 64)
322363
323364 case reflect.String:
324 s.w.Write([]byte(strconv.Quote(v.String())))
365 s.write([]byte(strconv.Quote(v.String())))
325366
326367 case reflect.Slice:
327368 if v.IsNil() {
331372 fallthrough
332373
333374 case reflect.Array:
334 if s.handlePointerAliasingAndCheckIfShouldDescend(v) {
375 s.descendIntoPossiblePointer(v, func() {
335376 s.dumpSlice(v)
336 }
377 })
337378
338379 case reflect.Interface:
339380 // The only time we should get here is for nil interfaces due to
343384 }
344385
345386 case reflect.Ptr:
346 if s.handlePointerAliasingAndCheckIfShouldDescend(v) {
347 s.w.Write([]byte("&"))
348 s.dumpVal(v.Elem())
349 }
387 s.descendIntoPossiblePointer(v, func() {
388 if s.config.StrictGo {
389 s.writeString(fmt.Sprintf("(func(v %s) *%s { return &v })(", v.Elem().Type(), v.Elem().Type()))
390 s.dumpVal(v.Elem())
391 s.writeString(")")
392 } else {
393 s.writeString("&")
394 s.dumpVal(v.Elem())
395 }
396 })
350397
351398 case reflect.Map:
352 if s.handlePointerAliasingAndCheckIfShouldDescend(v) {
399 s.descendIntoPossiblePointer(v, func() {
353400 s.dumpMap(v)
354 }
401 })
355402
356403 case reflect.Struct:
357404 s.dumpStruct(v)
361408
362409 default:
363410 if v.CanInterface() {
364 fmt.Fprintf(s.w, "%v", v.Interface())
411 s.writeString(fmt.Sprintf("%v", v.Interface()))
365412 } else {
366 fmt.Fprintf(s.w, "%v", v.String())
367 }
368 }
369 }
370
371 // call to signal that the pointer is being visited, returns true if this is the
372 // first visit to that pointer. Used to detect when to output the entire contents
373 // behind a pointer (the first time), and when to just emit a name (all other times)
374 func (s *dumpState) visitPointerAndCheckIfItIsTheFirstTime(ptr uintptr) bool {
375 for _, addr := range s.visitedPointers {
376 if addr == ptr {
377 return false
378 }
379 }
380 s.visitedPointers = append(s.visitedPointers, ptr)
381 return true
413 s.writeString(fmt.Sprintf("%v", v.String()))
414 }
415 }
382416 }
383417
384418 // registers that the value has been visited and checks to see if it is one of the
389423 func (s *dumpState) pointerNameFor(v reflect.Value) (string, bool) {
390424 if isPointerValue(v) {
391425 ptr := v.Pointer()
392 for i, addr := range s.pointers {
393 if ptr == addr {
394 firstVisit := s.visitPointerAndCheckIfItIsTheFirstTime(ptr)
395 return fmt.Sprintf("p%d", i), firstVisit
396 }
426 if info, ok := s.pointers[ptr]; ok {
427 firstVisit := s.visitedPointers.add(ptr)
428 return fmt.Sprintf("p%d", info.order), firstVisit
397429 }
398430 }
399431 return "", false
403435 func newDumpState(value interface{}, options *Options, writer io.Writer) *dumpState {
404436 result := &dumpState{
405437 config: options,
406 pointers: MapReusedPointers(reflect.ValueOf(value)),
438 pointers: mapReusedPointers(reflect.ValueOf(value)),
407439 w: writer,
408440 }
409441
429461 for i, value := range values {
430462 state := newDumpState(value, &o, os.Stdout)
431463 if i > 0 {
432 state.w.Write([]byte(o.Separator))
464 state.write([]byte(o.Separator))
433465 }
434466 state.dump(value)
435467 }
436 os.Stdout.Write([]byte("\n"))
468 _, _ = os.Stdout.Write([]byte("\n"))
437469 }
438470
439471 // Sdump dumps a value to a string according to the options
441473 buf := new(bytes.Buffer)
442474 for i, value := range values {
443475 if i > 0 {
444 buf.Write([]byte(o.Separator))
476 _, _ = buf.Write([]byte(o.Separator))
445477 }
446478 state := newDumpState(value, &o, buf)
447479 state.dump(value)
2424 Public int
2525 private int
2626 }
27
28 type IntAlias int
2729
2830 type InterfaceStruct struct {
2931 Ifc interface{}
6567 float64(12.3),
6668 complex64(12 + 10.5i),
6769 complex128(-1.2 - 0.1i),
70 (func(v int) *int { return &v })(10),
6871 "string with \"quote\"",
6972 []int{1, 2, 3},
7073 interface{}("hello from interface"),
7174 BlankStruct{},
7275 &BlankStruct{},
7376 BasicStruct{1, 2},
77 IntAlias(10),
78 (func(v IntAlias) *IntAlias { return &v })(10),
7479 Function,
7580 func(arg string) (bool, error) { return false, nil },
7681 nil,
127132 HidePrivateFields bool
128133 HomePackage string
129134 Separator string
135 StrictGo bool
130136 }
131137
132138 opts := options{
139145 opts,
140146 &BasicStruct{1, 2},
141147 Function,
148 (func(v int) *int { return &v })(20),
149 (func(v IntAlias) *IntAlias { return &v })(20),
142150 litter.Dump,
143151 func(s string, i int) (bool, error) { return false, nil },
144152 }
153
145154 runTestWithCfg(t, "config_Compact", &litter.Options{
146155 Compact: true,
147156 }, data)
162171 return f.Type.Kind() == reflect.String
163172 },
164173 }, data)
174 runTestWithCfg(t, "config_StrictGo", &litter.Options{
175 StrictGo: true,
176 }, data)
177
178 basic := &BasicStruct{1, 2}
179 runTestWithCfg(t, "config_DisablePointerReplacement_simpleReusedStruct", &litter.Options{
180 DisablePointerReplacement: true,
181 }, []interface{}{basic, basic})
182 circular := &RecursiveStruct{}
183 circular.Ptr = circular
184 runTestWithCfg(t, "config_DisablePointerReplacement_circular", &litter.Options{
185 DisablePointerReplacement: true,
186 }, circular)
165187 }
166188
167189 func TestSdump_multipleArgs(t *testing.T) {
00 module github.com/sanity-io/litter
1
2 go 1.14
13
24 require (
35 github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b // indirect
+0
-92
mapper.go less more
0 package litter
1
2 import (
3 "reflect"
4 "sort"
5 )
6
7 type pointerMap struct {
8 pointers []uintptr
9 reusedPointers []uintptr
10 }
11
12 // MapReusedPointers : Given a structure, it will recurively map all pointers mentioned in the tree, breaking
13 // circular references and provide a list of all pointers that was referenced at least twice
14 // by the provided structure.
15 func MapReusedPointers(v reflect.Value) []uintptr {
16 pm := &pointerMap{
17 reusedPointers: []uintptr{},
18 }
19 pm.consider(v)
20 return pm.reusedPointers
21 }
22
23 // Recursively consider v and each of its children, updating the map according to the
24 // semantics of MapReusedPointers
25 func (pm *pointerMap) consider(v reflect.Value) {
26 if v.Kind() == reflect.Invalid {
27 return
28 }
29 // fmt.Printf("Considering [%s] %#v\n\r", v.Type().String(), v.Interface())
30 if isPointerValue(v) && v.Pointer() != 0 { // pointer is 0 for unexported fields
31 // fmt.Printf("Ptr is %d\n\r", v.Pointer())
32 reused := pm.addPointerReturnTrueIfWasReused(v.Pointer())
33 if reused {
34 // No use descending inside this value, since it have been seen before and all its descendants
35 // have been considered
36 return
37 }
38 }
39
40 // Now descend into any children of this value
41 switch v.Kind() {
42 case reflect.Slice, reflect.Array:
43 numEntries := v.Len()
44 for i := 0; i < numEntries; i++ {
45 pm.consider(v.Index(i))
46 }
47
48 case reflect.Interface:
49 pm.consider(v.Elem())
50
51 case reflect.Ptr:
52 pm.consider(v.Elem())
53
54 case reflect.Map:
55 keys := v.MapKeys()
56 sort.Sort(mapKeySorter{
57 keys: keys,
58 options: &Config,
59 })
60 for _, key := range keys {
61 pm.consider(v.MapIndex(key))
62 }
63
64 case reflect.Struct:
65 numFields := v.NumField()
66 for i := 0; i < numFields; i++ {
67 pm.consider(v.Field(i))
68 }
69 }
70 }
71
72 // addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused
73 func (pm *pointerMap) addPointerReturnTrueIfWasReused(ptr uintptr) bool {
74 // Is this allready known to be reused?
75 for _, have := range pm.reusedPointers {
76 if ptr == have {
77 return true
78 }
79 }
80 // Have we seen it once before?
81 for _, seen := range pm.pointers {
82 if ptr == seen {
83 // Add it to the register of pointers we have seen more than once
84 pm.reusedPointers = append(pm.reusedPointers, ptr)
85 return true
86 }
87 }
88 // This pointer was new to us
89 pm.pointers = append(pm.pointers, ptr)
90 return false
91 }
0 package litter
1
2 import (
3 "reflect"
4 "sort"
5 )
6
7 // mapReusedPointers takes a structure, and recursively maps all pointers mentioned in the tree,
8 // detecting circular references, and providing a list of all pointers that was referenced at
9 // least twice by the provided structure.
10 func mapReusedPointers(v reflect.Value) ptrmap {
11 pm := &pointerVisitor{}
12 pm.consider(v)
13 return pm.reused
14 }
15
16 // A map of pointers.
17 type (
18 ptrinfo struct {
19 order int
20 }
21 ptrmap map[uintptr]ptrinfo
22 )
23
24 // Returns true if contains a pointer.
25 func (pm *ptrmap) contains(p uintptr) bool {
26 if *pm != nil {
27 _, ok := (*pm)[p]
28 return ok
29 }
30 return false
31 }
32
33 // Removes a pointer.
34 func (pm *ptrmap) remove(p uintptr) {
35 if *pm != nil {
36 delete(*pm, p)
37 }
38 }
39
40 // Adds a pointer.
41 func (pm *ptrmap) add(p uintptr) bool {
42 if pm.contains(p) {
43 return false
44 }
45 pm.put(p)
46 return true
47 }
48
49 // Adds a pointer (slow path).
50 func (pm *ptrmap) put(p uintptr) {
51 if *pm == nil {
52 *pm = make(map[uintptr]ptrinfo, 31)
53 }
54 (*pm)[p] = ptrinfo{order: len(*pm)}
55 }
56
57 type pointerVisitor struct {
58 pointers ptrmap
59 reused ptrmap
60 }
61
62 // Recursively consider v and each of its children, updating the map according to the
63 // semantics of MapReusedPointers
64 func (pv *pointerVisitor) consider(v reflect.Value) {
65 if v.Kind() == reflect.Invalid {
66 return
67 }
68 if isPointerValue(v) && v.Pointer() != 0 { // pointer is 0 for unexported fields
69 if pv.tryAddPointer(v.Pointer()) {
70 // No use descending inside this value, since it have been seen before and all its descendants
71 // have been considered
72 return
73 }
74 }
75
76 // Now descend into any children of this value
77 switch v.Kind() {
78 case reflect.Slice, reflect.Array:
79 numEntries := v.Len()
80 for i := 0; i < numEntries; i++ {
81 pv.consider(v.Index(i))
82 }
83
84 case reflect.Interface:
85 pv.consider(v.Elem())
86
87 case reflect.Ptr:
88 pv.consider(v.Elem())
89
90 case reflect.Map:
91 keys := v.MapKeys()
92 sort.Sort(mapKeySorter{
93 keys: keys,
94 options: &Config,
95 })
96 for _, key := range keys {
97 pv.consider(v.MapIndex(key))
98 }
99
100 case reflect.Struct:
101 numFields := v.NumField()
102 for i := 0; i < numFields; i++ {
103 pv.consider(v.Field(i))
104 }
105 }
106 }
107
108 // addPointer to the pointerMap, update reusedPointers. Returns true if pointer was reused
109 func (pv *pointerVisitor) tryAddPointer(p uintptr) bool {
110 // Is this allready known to be reused?
111 if pv.reused.contains(p) {
112 return true
113 }
114
115 // Have we seen it once before?
116 if pv.pointers.contains(p) {
117 // Add it to the register of pointers we have seen more than once
118 pv.reused.add(p)
119 return true
120 }
121
122 // This pointer was new to us
123 pv.pointers.add(p)
124 return false
125 }
0 []interface{}{litter_test.options{Compact:false,StripPackageNames:false,HidePrivateFields:true,HomePackage:"",Separator:" "},&litter_test.BasicStruct{Public:1,private:2},litter_test.Function,litter.Dump,func(string,int)(bool,error)}
0 []interface{}{litter_test.options{Compact:false,StripPackageNames:false,HidePrivateFields:true,HomePackage:"",Separator:" ",StrictGo:false},&litter_test.BasicStruct{Public:1,private:2},litter_test.Function,&20,&20,litter.Dump,func(string,int)(bool,error)}
0 &litter_test.RecursiveStruct{ // p0
1 Ptr: p0,
2 }
0 []interface {}{
1 &litter_test.BasicStruct{ // p0
2 Public: 1,
3 private: 2,
4 },
5 &litter_test.BasicStruct{ // p0
6 Public: 1,
7 private: 2,
8 },
9 }
44 },
55 &litter_test.BasicStruct{},
66 litter_test.Function,
7 &20,
8 &20,
79 litter.Dump,
810 func(string, int) (bool, error),
911 }
44 HidePrivateFields: true,
55 HomePackage: "",
66 Separator: " ",
7 StrictGo: false,
78 },
89 &litter_test.BasicStruct{
910 Public: 1,
1011 },
1112 litter_test.Function,
13 &20,
14 &20,
1215 litter.Dump,
1316 func(string, int) (bool, error),
1417 }
77 private: 2,
88 },
99 litter_test.Function,
10 &20,
11 &20,
1012 litter.Dump,
1113 func(string, int) (bool, error),
1214 }
44 HidePrivateFields: true,
55 HomePackage: "",
66 Separator: " ",
7 StrictGo: false,
78 },
89 &BasicStruct{
910 Public: 1,
1011 private: 2,
1112 },
1213 Function,
14 &20,
15 &20,
1316 litter.Dump,
1417 func(string, int) (bool, error),
1518 }
0 []interface {}{
1 litter_test.options{
2 Compact: false,
3 StripPackageNames: false,
4 HidePrivateFields: true,
5 HomePackage: "",
6 Separator: " ",
7 StrictGo: false,
8 },
9 (func(v litter_test.BasicStruct) *litter_test.BasicStruct { return &v })(litter_test.BasicStruct{
10 Public: 1,
11 private: 2,
12 }),
13 litter_test.Function,
14 (func(v int) *int { return &v })(20),
15 (func(v litter_test.IntAlias) *litter_test.IntAlias { return &v })(20),
16 litter.Dump,
17 func(string, int) (bool, error),
18 }
44 HidePrivateFields: true,
55 HomePackage: "",
66 Separator: " ",
7 StrictGo: false,
78 },
89 &BasicStruct{
910 Public: 1,
1011 private: 2,
1112 },
1213 Function,
14 &20,
15 &20,
1316 Dump,
1417 func(string, int) (bool, error),
1518 }
1414 12.3,
1515 complex64(12+10.5i),
1616 complex128(-1.2-0.1i),
17 &10,
1718 "string with \"quote\"",
1819 []int{
1920 1,
2728 Public: 1,
2829 private: 2,
2930 },
31 10,
32 &10,
3033 litter_test.Function,
3134 func(string) (bool, error),
3235 nil,