From bbd65862d7747af0b2f24bab1633efadadc2d3a2 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Tue, 23 Jan 2024 13:23:23 +0100 Subject: [PATCH] cmd/viewer: add view-only option This PR adds a --view-only-type flag to viewer to allow a type to be generated without a cloner. This is made available to make the few cases where writing a custom cloner is more practical, for example if the field is an interface. Updates corp/#16789 Signed-off-by: Kristoffer Dalby --- cmd/viewer/tests/tests.go | 13 ++++++++- cmd/viewer/tests/tests_view.go | 52 ++++++++++++++++++++++++++++++++++ cmd/viewer/viewer.go | 18 ++++++++++-- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go index 55413403b..b5dd83f61 100644 --- a/cmd/viewer/tests/tests.go +++ b/cmd/viewer/tests/tests.go @@ -9,7 +9,7 @@ import ( "net/netip" ) -//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded --clone-only-type=OnlyGetClone +//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,OnlyGetView,StructWithEmbedded --clone-only-type=OnlyGetClone --view-only-type=OnlyGetView type StructWithoutPtrs struct { Int int @@ -62,6 +62,17 @@ type OnlyGetClone struct { SinViewerPorFavor bool } +type OnlyGetView struct { + SinClonerPorFavor bool +} + +// Custom cloner func +func (ogv *OnlyGetView) Clone() *OnlyGetView { + return &OnlyGetView{ + SinClonerPorFavor: ogv.SinClonerPorFavor, + } +} + type StructWithEmbedded struct { A *StructWithPtrs StructWithSlices diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go index 0c6c9e287..685daf309 100644 --- a/cmd/viewer/tests/tests_view.go +++ b/cmd/viewer/tests/tests_view.go @@ -325,6 +325,58 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct { Data []byte }{}) +// View returns a readonly view of OnlyGetView. +func (p *OnlyGetView) View() OnlyGetViewView { + return OnlyGetViewView{ж: p} +} + +// OnlyGetViewView provides a read-only view over OnlyGetView. +// +// Its methods should only be called if `Valid()` returns true. +type OnlyGetViewView struct { + // ж is the underlying mutable value, named with a hard-to-type + // character that looks pointy like a pointer. + // It is named distinctively to make you think of how dangerous it is to escape + // to callers. You must not let callers be able to mutate it. + ж *OnlyGetView +} + +// Valid reports whether underlying value is non-nil. +func (v OnlyGetViewView) Valid() bool { return v.ж != nil } + +// AsStruct returns a clone of the underlying value which aliases no memory with +// the original. +func (v OnlyGetViewView) AsStruct() *OnlyGetView { + if v.ж == nil { + return nil + } + return v.ж.Clone() +} + +func (v OnlyGetViewView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *OnlyGetViewView) UnmarshalJSON(b []byte) error { + if v.ж != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + var x OnlyGetView + if err := json.Unmarshal(b, &x); err != nil { + return err + } + v.ж = &x + return nil +} + +func (v OnlyGetViewView) SinClonerPorFavor() bool { return v.ж.SinClonerPorFavor } + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _OnlyGetViewViewNeedsRegeneration = OnlyGetView(struct { + SinClonerPorFavor bool +}{}) + // View returns a readonly view of StructWithEmbedded. func (p *StructWithEmbedded) View() StructWithEmbeddedView { return StructWithEmbeddedView{ж: p} diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go index 56b5e2bd2..0b66a13ea 100644 --- a/cmd/viewer/viewer.go +++ b/cmd/viewer/viewer.go @@ -40,7 +40,7 @@ func (v {{.ViewName}}) Valid() bool { return v.ж != nil } // AsStruct returns a clone of the underlying value which aliases no memory with // the original. -func (v {{.ViewName}}) AsStruct() *{{.StructName}}{ +func (v {{.ViewName}}) AsStruct() *{{.StructName}}{ if v.ж == nil { return nil } @@ -324,6 +324,7 @@ var ( flagCloneFunc = flag.Bool("clonefunc", false, "add a top-level Clone func") flagCloneOnlyTypes = flag.String("clone-only-type", "", "comma-separated list of types (a subset of --type) that should only generate a go:generate clone line and not actual views") + flagViewOnlyTypes = flag.String("view-only-type", "", "comma-separated list of types (a subset of --type) that should only generate views and not be added to go:generate for cloner") ) func main() { @@ -336,10 +337,21 @@ func main() { } typeNames := strings.Split(*flagTypes, ",") + viewOnlyTypes := map[string]bool{} + for _, t := range strings.Split(*flagViewOnlyTypes, ",") { + viewOnlyTypes[t] = true + } + var flagArgs []string flagArgs = append(flagArgs, fmt.Sprintf("-clonefunc=%v", *flagCloneFunc)) if *flagTypes != "" { - flagArgs = append(flagArgs, "-type="+*flagTypes) + var cloneTypes []string + for _, t := range strings.Split(*flagTypes, ",") { + if _, ok := viewOnlyTypes[t]; !ok { + cloneTypes = append(cloneTypes, t) + } + } + flagArgs = append(flagArgs, "-type="+strings.Join(cloneTypes, ",")) } if *flagBuildTags != "" { flagArgs = append(flagArgs, "-tags="+*flagBuildTags) @@ -373,7 +385,7 @@ func main() { break } } - if !hasClone { + if !hasClone && !viewOnlyTypes[typeName] { runCloner = true } genView(buf, it, typ, pkg.Types)