diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go index e281ccb6d..55413403b 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 --clone-only-type=OnlyGetClone +//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded --clone-only-type=OnlyGetClone type StructWithoutPtrs struct { Int int @@ -61,3 +61,8 @@ type StructWithSlices struct { type OnlyGetClone struct { SinViewerPorFavor bool } + +type StructWithEmbedded struct { + A *StructWithPtrs + StructWithSlices +} diff --git a/cmd/viewer/tests/tests_clone.go b/cmd/viewer/tests/tests_clone.go index 2558d9ae2..3ff914126 100644 --- a/cmd/viewer/tests/tests_clone.go +++ b/cmd/viewer/tests/tests_clone.go @@ -211,3 +211,22 @@ func (src *OnlyGetClone) Clone() *OnlyGetClone { var _OnlyGetCloneCloneNeedsRegeneration = OnlyGetClone(struct { SinViewerPorFavor bool }{}) + +// Clone makes a deep copy of StructWithEmbedded. +// The result aliases no memory with the original. +func (src *StructWithEmbedded) Clone() *StructWithEmbedded { + if src == nil { + return nil + } + dst := new(StructWithEmbedded) + *dst = *src + dst.A = src.A.Clone() + dst.StructWithSlices = *src.StructWithSlices.Clone() + return dst +} + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _StructWithEmbeddedCloneNeedsRegeneration = StructWithEmbedded(struct { + A *StructWithPtrs + StructWithSlices +}{}) diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go index 7f8d31e01..042f10829 100644 --- a/cmd/viewer/tests/tests_view.go +++ b/cmd/viewer/tests/tests_view.go @@ -14,7 +14,7 @@ import ( "tailscale.com/types/views" ) -//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone +//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded // View returns a readonly view of StructWithPtrs. func (p *StructWithPtrs) View() StructWithPtrsView { @@ -325,3 +325,59 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct { Prefixes []netip.Prefix Data []byte }{}) + +// View returns a readonly view of StructWithEmbedded. +func (p *StructWithEmbedded) View() StructWithEmbeddedView { + return StructWithEmbeddedView{ж: p} +} + +// StructWithEmbeddedView provides a read-only view over StructWithEmbedded. +// +// Its methods should only be called if `Valid()` returns true. +type StructWithEmbeddedView 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. + ж *StructWithEmbedded +} + +// Valid reports whether underlying value is non-nil. +func (v StructWithEmbeddedView) Valid() bool { return v.ж != nil } + +// AsStruct returns a clone of the underlying value which aliases no memory with +// the original. +func (v StructWithEmbeddedView) AsStruct() *StructWithEmbedded { + if v.ж == nil { + return nil + } + return v.ж.Clone() +} + +func (v StructWithEmbeddedView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) } + +func (v *StructWithEmbeddedView) UnmarshalJSON(b []byte) error { + if v.ж != nil { + return errors.New("already initialized") + } + if len(b) == 0 { + return nil + } + var x StructWithEmbedded + if err := json.Unmarshal(b, &x); err != nil { + return err + } + v.ж = &x + return nil +} + +func (v StructWithEmbeddedView) A() StructWithPtrsView { return v.ж.A.View() } +func (v StructWithEmbeddedView) StructWithSlices() StructWithSlicesView { + return v.ж.StructWithSlices.View() +} + +// A compilation failure here means this code must be regenerated, with the command at the top of this file. +var _StructWithEmbeddedViewNeedsRegeneration = StructWithEmbedded(struct { + A *StructWithPtrs + StructWithSlices +}{}) diff --git a/util/codegen/codegen.go b/util/codegen/codegen.go index b1e012fad..cf848b1d2 100644 --- a/util/codegen/codegen.go +++ b/util/codegen/codegen.go @@ -202,13 +202,18 @@ func AssertStructUnchanged(t *types.Struct, tname, ctx string, it *ImportTracker w("var _%s%sNeedsRegeneration = %s(struct {", tname, ctx, tname) for i := 0; i < t.NumFields(); i++ { - fname := t.Field(i).Name() + st := t.Field(i) + fname := st.Name() ft := t.Field(i).Type() if IsInvalid(ft) { continue } qname := it.QualifiedName(ft) - w("\t%s %s", fname, qname) + if st.Anonymous() { + w("\t%s ", fname) + } else { + w("\t%s %s", fname, qname) + } } w("}{})\n")