Skip to content

Commit f8684f4

Browse files
committed
Take union of all filters when typechecking
This takes the union of all node filters when type-checking, so that different generators can't interfere with each other. Previously, if you ran a generator with a narrower type filter before one with a broad filter, you'd get weird results because the narrower filter is the one that's actually applied to all the shared packages. Now, we gather all filters ahead of time, and then use their union: if any filter thinks a reference is important, we consider that reference important for the whole context. Concretely, this manifested if you ran the CRD generator before deepcopy -- the CRD generator has a much narrower node filter, so you'd get weird deepcopy results. If you ran them the other way around, it would be fine. Now, either way works.
1 parent 50950e4 commit f8684f4

File tree

6 files changed

+104
-43
lines changed

6 files changed

+104
-43
lines changed

pkg/crd/gen.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package crd
1818

1919
import (
2020
"fmt"
21+
"go/ast"
2122
"go/types"
2223

2324
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -84,6 +85,9 @@ type Generator struct {
8485
CRDVersions []string `marker:"crdVersions,optional"`
8586
}
8687

88+
func (Generator) CheckFilter() loader.NodeFilter {
89+
return filterTypesForCRDs
90+
}
8791
func (Generator) RegisterMarkers(into *markers.Registry) error {
8892
return crdmarkers.Register(into)
8993
}
@@ -277,3 +281,22 @@ func FindKubeKinds(parser *Parser, metav1Pkg *loader.Package) map[schema.GroupKi
277281

278282
return kubeKinds
279283
}
284+
285+
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,
286+
// like interfaces and struct fields without JSON tag.
287+
func filterTypesForCRDs(node ast.Node) bool {
288+
switch node := node.(type) {
289+
case *ast.InterfaceType:
290+
// skip interfaces, we never care about references in them
291+
return false
292+
case *ast.StructType:
293+
return true
294+
case *ast.Field:
295+
_, hasTag := loader.ParseAstTag(node.Tag).Lookup("json")
296+
// fields without JSON tags mean we have custom serialization,
297+
// so only visit fields with tags.
298+
return hasTag
299+
default:
300+
return true
301+
}
302+
}

pkg/crd/parser.go

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ package crd
1818

1919
import (
2020
"fmt"
21-
"go/ast"
2221

2322
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2423
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -216,7 +215,7 @@ func (p *Parser) AddPackage(pkg *loader.Package) {
216215
return
217216
}
218217
p.indexTypes(pkg)
219-
p.Checker.Check(pkg, filterTypesForCRDs)
218+
p.Checker.Check(pkg)
220219
p.packages[pkg] = struct{}{}
221220
}
222221

@@ -236,22 +235,3 @@ func (p *Parser) NeedPackage(pkg *loader.Package) {
236235
}
237236
p.AddPackage(pkg)
238237
}
239-
240-
// filterTypesForCRDs filters out all nodes that aren't used in CRD generation,
241-
// like interfaces and struct fields without JSON tag.
242-
func filterTypesForCRDs(node ast.Node) bool {
243-
switch node := node.(type) {
244-
case *ast.InterfaceType:
245-
// skip interfaces, we never care about references in them
246-
return false
247-
case *ast.StructType:
248-
return true
249-
case *ast.Field:
250-
_, hasTag := loader.ParseAstTag(node.Tag).Lookup("json")
251-
// fields without JSON tags mean we have custom serialization,
252-
// so only visit fields with tags.
253-
return hasTag
254-
default:
255-
return true
256-
}
257-
}

pkg/deepcopy/gen.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ type Generator struct {
5858
Year string `marker:",optional"`
5959
}
6060

61+
func (Generator) CheckFilter() loader.NodeFilter {
62+
return func(node ast.Node) bool {
63+
// ignore interfaces
64+
_, isIface := node.(*ast.InterfaceType)
65+
return !isIface
66+
}
67+
}
68+
6169
func (Generator) RegisterMarkers(into *markers.Registry) error {
6270
if err := markers.RegisterAll(into,
6371
enablePkgMarker, legacyEnablePkgMarker, enableTypeMarker,
@@ -196,11 +204,7 @@ func (ctx *ObjectGenCtx) generateForPackage(root *loader.Package) []byte {
196204
return nil
197205
}
198206

199-
ctx.Checker.Check(root, func(node ast.Node) bool {
200-
// ignore interfaces
201-
_, isIface := node.(*ast.InterfaceType)
202-
return !isIface
203-
})
207+
ctx.Checker.Check(root)
204208

205209
root.NeedTypesInfo()
206210

pkg/genall/genall.go

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,32 @@ func (g Generators) RegisterMarkers(reg *markers.Registry) error {
4646
return nil
4747
}
4848

49+
// CheckFilters returns the set of NodeFilters for all Generators that
50+
// implement NeedsTypeChecking.
51+
func (g Generators) CheckFilters() []loader.NodeFilter {
52+
var filters []loader.NodeFilter
53+
for _, gen := range g {
54+
withFilter, needsChecking := (*gen).(NeedsTypeChecking)
55+
if !needsChecking {
56+
continue
57+
}
58+
filters = append(filters, withFilter.CheckFilter())
59+
}
60+
return filters
61+
}
62+
63+
// NeedsTypeChecking indicates that a particular generator needs & has opinions
64+
// on typechecking. If this is not implemented, a generator will be given a
65+
// context with a nil typechecker.
66+
type NeedsTypeChecking interface {
67+
// CheckFilter indicates the loader.NodeFilter (if any) that should be used
68+
// to prune out unused types/packages when type-checking (nodes for which
69+
// the filter returns true are considered "interesting"). This filter acts
70+
// as a baseline -- all types the pass through this filter will be checked,
71+
// but more than that may also be checked due to other generators' filters.
72+
CheckFilter() loader.NodeFilter
73+
}
74+
4975
// Generator knows how to register some set of markers, and then produce
5076
// output artifacts based on loaded code containing those markers,
5177
// sharing common loaded data.
@@ -144,7 +170,9 @@ func (g Generators) ForRoots(rootPaths ...string) (*Runtime, error) {
144170
},
145171
Roots: roots,
146172
InputRule: InputFromFileSystem,
147-
Checker: &loader.TypeChecker{},
173+
Checker: &loader.TypeChecker{
174+
NodeFilters: g.CheckFilters(),
175+
},
148176
},
149177
OutputRules: OutputRules{Default: OutputToNothing},
150178
}
@@ -169,6 +197,13 @@ func (r *Runtime) Run() bool {
169197
for _, gen := range r.Generators {
170198
ctx := r.GenerationContext // make a shallow copy
171199
ctx.OutputRule = r.OutputRules.ForGenerator(gen)
200+
201+
// don't pass a typechecker to generators that don't provide a filter
202+
// to avoid accidents
203+
if _, needsChecking := (*gen).(NeedsTypeChecking); !needsChecking {
204+
ctx.Checker = nil
205+
}
206+
172207
if err := (*gen).Generate(&ctx); err != nil {
173208
fmt.Fprintln(os.Stderr, err)
174209
hadErrs = true

pkg/loader/refs.go

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -185,37 +185,51 @@ func allReferencedPackages(pkg *Package, filterNodes NodeFilter) []*Package {
185185
// checking each package's types' externally-referenced types, and only
186186
// type-checking those packages.
187187
type TypeChecker struct {
188+
// NodeFilters are used to filter the set of references that are followed
189+
// when typechecking. If any of the filters returns true for a given node,
190+
// its package will be added to the set of packages to check.
191+
//
192+
// If no filters are specified, all references are followed (this may be slow).
193+
//
194+
// Modifying this after the first call to check may yield strange/invalid
195+
// results.
196+
NodeFilters []NodeFilter
197+
188198
checkedPackages map[*Package]struct{}
189-
filterNodes NodeFilter
190199
sync.Mutex
191200
}
192201

193-
// Check type-checks the given package and all packages referenced
194-
// by types that pass through (have true returned by) filterNodes.
195-
func (c *TypeChecker) Check(root *Package, filterNodes NodeFilter) {
202+
// Check type-checks the given package and all packages referenced by types
203+
// that pass through (have true returned by) any of the NodeFilters.
204+
func (c *TypeChecker) Check(root *Package) {
196205
c.init()
197206

198-
if filterNodes == nil {
199-
filterNodes = c.filterNodes
200-
}
201-
202207
// use a sub-checker with the appropriate settings
203208
(&TypeChecker{
204-
filterNodes: filterNodes,
209+
NodeFilters: c.NodeFilters,
205210
checkedPackages: c.checkedPackages,
206211
}).check(root)
207212
}
208213

209-
func (c *TypeChecker) init() {
210-
if c.checkedPackages == nil {
211-
c.checkedPackages = make(map[*Package]struct{})
214+
func (c *TypeChecker) isNodeInteresting(node ast.Node) bool {
215+
// no filters --> everything is important
216+
if len(c.NodeFilters) == 0 {
217+
return true
212218
}
213-
if c.filterNodes == nil {
214-
// check every type by default
215-
c.filterNodes = func(_ ast.Node) bool {
219+
220+
// otherwise, passing through any one filter means this node is important
221+
for _, filter := range c.NodeFilters {
222+
if filter(node) {
216223
return true
217224
}
218225
}
226+
return false
227+
}
228+
229+
func (c *TypeChecker) init() {
230+
if c.checkedPackages == nil {
231+
c.checkedPackages = make(map[*Package]struct{})
232+
}
219233
}
220234

221235
// check recursively type-checks the given package, only loading packages that
@@ -232,7 +246,7 @@ func (c *TypeChecker) check(root *Package) {
232246
return
233247
}
234248

235-
refedPackages := allReferencedPackages(root, c.filterNodes)
249+
refedPackages := allReferencedPackages(root, c.isNodeInteresting)
236250

237251
// first, resolve imports for all leaf packages...
238252
var wg sync.WaitGroup

pkg/schemapatcher/gen.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
crdgen "sigs.k8s.io/controller-tools/pkg/crd"
3333
crdmarkers "sigs.k8s.io/controller-tools/pkg/crd/markers"
3434
"sigs.k8s.io/controller-tools/pkg/genall"
35+
"sigs.k8s.io/controller-tools/pkg/loader"
3536
"sigs.k8s.io/controller-tools/pkg/markers"
3637
yamlop "sigs.k8s.io/controller-tools/pkg/schemapatcher/internal/yaml"
3738
)
@@ -87,6 +88,10 @@ type Generator struct {
8788

8889
var _ genall.Generator = &Generator{}
8990

91+
func (Generator) CheckFilter() loader.NodeFilter {
92+
return crdgen.Generator{}.CheckFilter()
93+
}
94+
9095
func (Generator) RegisterMarkers(into *markers.Registry) error {
9196
return crdmarkers.Register(into)
9297
}

0 commit comments

Comments
 (0)