takes // in iterators for recursive data structures. // // # Analyzer recursiveiter // // recursiveiter: check for inefficient recursive iterators // // This analyzer reports when a function that returns an iterator // (iter.Seq or iter.Seq2) calls itself as the operand of a range // statement, as this is inefficient. // // When implementing an iterator (e.g. iter.Seq[T]) for a recursive // data type such as a tree or linked list, it is tempting to // recursively range over the iterator for each child element. // // Here's an example of a naive iterator over a binary tree: // // type tree struct { // value int // left, right *tree // } // // func (t *tree) All() iter.Seq[int] { // return func(yield func(int) bool) { // if t != nil { // for elem := range t.left.All() { // "inefficient recursive iterator" // if !yield(elem) { // return // } // } // if !yield(t.value) { // return // } // for elem := range t.right.All() { // "inefficient recursive iterator" // if !yield(elem) { // return // } // } // } // } // } // // Though it correctly enumerates the elements of the tree, it hides a // significant performance problem--two, in fact. Consider a balanced // tree of N nodes. Iterating the root node will cause All to be // called once on every node of the tree. This results in a chain of // nested active range-over-func statements when yield(t.value) is // called on a leaf node. // // The first performance problem is that each range-over-func // statement must typically heap-allocate a variable, so iteration of // the tree allocates as many variables as there are elements in the // tree, for a total of O(N) allocations, all unnecessary. // // The second problem is that each call to yield for a leaf of the // tree causes each of the enclosing range loops to receive a value, // which they then immediately pass on to their respective yield // function. This results in a chain of log(N) dynamic yield calls per // element, a total of O(N*log N) dynamic calls overall, when only // O(N) are necessary. // // A better implementation strategy for recursive iterators is to // first define the "every" operator for your recursive data type, // where every(f) reports whether an arbitrary predicate f(x) is true // for every element x in the data type. For our tree, the every // function would be: // // func (t *tree) every(f func(int) bool) bool { // return t == nil || // t.left.every(f) && f(t.value) && t.right.every(f) // } // // For example, this use of the every operator prints whether every // element in the tree is an even number: // // even := func(x int) bool { return x&1 == 0 } // println(t.every(even)) // // Then the iterator can be simply expressed as a trivial wrapper // around the every operator: // // func (t *tree) All() iter.Seq[int] { // return func(yield func(int) bool) { // _ = t.every(yield) // } // } // // In effect, tree.All computes whether yield returns true for each // element, short-circuiting if it ever returns false, then discards // the final boolean result. // // This has much better performance characteristics: it makes one // dynamic call per element of the tree, and it doesn't heap-allocate // anything. It is also clearer. package recursiveiter func {{.TestFuncName}}(t *{{.TestingPackageName}}.T) { {{- /* Test cases struct declaration and empty initialization. */}} tests := []struct { name string // description of this test case {{- $commentPrinted := false }} {{- if and .Receiver .Receiver.Constructor}} {{- range .Receiver.Constructor.Args}} {{- if .Name}} {{- if not $commentPrinted}} // Named input parameters for receiver constructor. {{- $commentPrinted = true }} {{- end}} {{.Name}} {{.Type}} {{- end}} {{- end}} {{- end}} {{- $commentPrinted := false }} {{- range .Func.Args}} {{- if .Name}} {{- if not $commentPrinted}} // Named input parameters for target function. {{- $commentPrinted = true }} {{- end}} {{.Name}} {{.Type}} {{- end}} {{- end}} {{- range $index, $res := .Func.Results}} {{- if eq $res.Name "gotErr"}} wantErr bool {{- else if eq $index 0}} want {{$res.Type}} {{- else}} want{{add $index 1}} {{$res.Type}} {{- end}} {{- end}} }{ // TODO: Add test cases. } {{- /* Loop over all the test cases. */}} for _, tt := range tests { t.Run(tt.name, func(t *{{.TestingPackageName}}.T) { {{- /* Constructor or empty initialization. */}} {{- if .Receiver}} {{- if .Receiver.Constructor}} {{- /* Receiver variable by calling constructor. */}} {{fieldNames .Receiver.Constructor.Results ""}} := {{if .PackageName}}{{.PackageName}}.{{end}} {{- .Receiver.Constructor.Name}} {{- /* Constructor input parameters. */ -}} ( {{- range $index, $arg := .Receiver.Constructor.Args}} {{- if ne $index 0}}, {{end}} {{- if .Name}}tt.{{.Name}}{{else}}{{.Value}}{{end}} {{- end -}} ) {{- /* Handles the error return from constructor. */}} {{- $last := last .Receiver.Constructor.Results}} {{- if eq $last.Type "error"}} if err != nil { t.Fatalf("could not construct receiver type: %v", err) } {{- end}} {{- else}} {{- /* Receiver variable declaration. */}} // TODO: construct the receiver type. var {{.Receiver.Var.Name}} {{.Receiver.Var.Type}} {{- end}} {{- end}} {{- /* Got variables. */}} {{if .Func.Results}}{{fieldNames .Func.Results ""}} := {{end}} {{- /* Call expression. */}} {{- if .Receiver}}{{/* Call method by VAR.METHOD. */}} {{- .Receiver.Var.Name}}. {{- else if .PackageName}}{{/* Call function by PACKAGE.FUNC. */}} {{- .PackageName}}. {{- end}}{{.Func.Name}} {{- /* Input parameters. */ -}} ( {{- range $index, $arg := .Func.Args}} {{- if ne $index 0}}, {{end}} {{- if .Name}}tt.{{.Name}}{{else}}{{.Value}}{{end}} {{- end -}} ) {{- /* Handles the returned error before the rest of return value. */}} {{- $last := last .Func.Results}} {{- if eq $last.Type "error"}} if gotErr != nil { if !tt.wantErr { t.Errorf("{{$.Func.Name}}() failed: %v", gotErr) } return } if tt.wantErr { t.Fatal("{{$.Func.Name}}() succeeded unexpectedly") } {{- end}} {{- /* Compare the returned values except for the last returned error. */}} {{- if or (and .Func.Results (ne $last.Type "error")) (and (gt (len .Func.Results) 1) (eq $last.Type "error"))}} // TODO: update the condition below to compare got with tt.want. {{- range $index, $res := .Func.Results}} {{- if ne $res.Name "gotErr"}} if true { t.Errorf("{{$.Func.Name}}() = %v, want %v", {{.Name}}, tt.{{if eq $index 0}}want{{else}}want{{add $index 1}}{{end}}) } {{- end}} {{- end}} {{- end}} }) } }