mirror of
https://github.com/tailscale/tailscale.git
synced 2025-08-11 21:27:31 +00:00
cmd/viewer, types/views, util/codegen: add viewer support for custom container types
This adds support for container-like types such as Container[T] that don't explicitly specify a view type for T. Instead, a package implementing a container type should also implement and export a ContainerView[T, V] type and a ContainerViewOf(*Container[T]) ContainerView[T, V] function, which returns a view for the specified container, inferring the element view type V from the element type T. Updates #12736 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
@@ -9,10 +9,11 @@ import (
|
||||
"net/netip"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
"tailscale.com/types/ptr"
|
||||
"tailscale.com/types/views"
|
||||
)
|
||||
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct --clone-only-type=OnlyGetClone
|
||||
//go:generate go run tailscale.com/cmd/viewer --type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct,StructWithContainers --clone-only-type=OnlyGetClone
|
||||
|
||||
type StructWithoutPtrs struct {
|
||||
Int int
|
||||
@@ -114,3 +115,50 @@ type GenericCloneableStruct[T views.ViewCloner[T, V], V views.StructView[T]] str
|
||||
PtrValueMap map[string]*T
|
||||
SliceMap map[string][]T
|
||||
}
|
||||
|
||||
// Container is a pre-defined container type, such as a collection, an optional
|
||||
// value or a generic wrapper.
|
||||
type Container[T any] struct {
|
||||
Item T
|
||||
}
|
||||
|
||||
func (c *Container[T]) Clone() *Container[T] {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
if cloner, ok := any(c.Item).(views.Cloner[T]); ok {
|
||||
return &Container[T]{cloner.Clone()}
|
||||
}
|
||||
if !views.ContainsPointers[T]() {
|
||||
return ptr.To(*c)
|
||||
}
|
||||
panic(fmt.Errorf("%T contains pointers, but is not cloneable", c.Item))
|
||||
}
|
||||
|
||||
// ContainerView is a pre-defined readonly view of a Container[T].
|
||||
type ContainerView[T views.ViewCloner[T, V], V views.StructView[T]] 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.
|
||||
ж *Container[T]
|
||||
}
|
||||
|
||||
func (cv ContainerView[T, V]) Item() V {
|
||||
return cv.ж.Item.View()
|
||||
}
|
||||
|
||||
func ContainerViewOf[T views.ViewCloner[T, V], V views.StructView[T]](c *Container[T]) ContainerView[T, V] {
|
||||
return ContainerView[T, V]{c}
|
||||
}
|
||||
|
||||
type GenericBasicStruct[T BasicType] struct {
|
||||
Value T
|
||||
}
|
||||
|
||||
type StructWithContainers struct {
|
||||
IntContainer Container[int]
|
||||
CloneableContainer Container[*StructWithPtrs]
|
||||
BasicGenericContainer Container[GenericBasicStruct[int]]
|
||||
ClonableGenericContainer Container[*GenericNoPtrsStruct[int]]
|
||||
}
|
||||
|
@@ -416,3 +416,24 @@ func _GenericCloneableStructCloneNeedsRegeneration[T views.ViewCloner[T, V], V v
|
||||
SliceMap map[string][]T
|
||||
}{})
|
||||
}
|
||||
|
||||
// Clone makes a deep copy of StructWithContainers.
|
||||
// The result aliases no memory with the original.
|
||||
func (src *StructWithContainers) Clone() *StructWithContainers {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
dst := new(StructWithContainers)
|
||||
*dst = *src
|
||||
dst.CloneableContainer = *src.CloneableContainer.Clone()
|
||||
dst.ClonableGenericContainer = *src.ClonableGenericContainer.Clone()
|
||||
return dst
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _StructWithContainersCloneNeedsRegeneration = StructWithContainers(struct {
|
||||
IntContainer Container[int]
|
||||
CloneableContainer Container[*StructWithPtrs]
|
||||
BasicGenericContainer Container[GenericBasicStruct[int]]
|
||||
ClonableGenericContainer Container[*GenericNoPtrsStruct[int]]
|
||||
}{})
|
||||
|
@@ -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,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct
|
||||
//go:generate go run tailscale.com/cmd/cloner -clonefunc=false -type=StructWithPtrs,StructWithoutPtrs,Map,StructWithSlices,OnlyGetClone,StructWithEmbedded,GenericIntStruct,GenericNoPtrsStruct,GenericCloneableStruct,StructWithContainers
|
||||
|
||||
// View returns a readonly view of StructWithPtrs.
|
||||
func (p *StructWithPtrs) View() StructWithPtrsView {
|
||||
@@ -604,3 +604,67 @@ func _GenericCloneableStructViewNeedsRegeneration[T views.ViewCloner[T, V], V vi
|
||||
SliceMap map[string][]T
|
||||
}{})
|
||||
}
|
||||
|
||||
// View returns a readonly view of StructWithContainers.
|
||||
func (p *StructWithContainers) View() StructWithContainersView {
|
||||
return StructWithContainersView{ж: p}
|
||||
}
|
||||
|
||||
// StructWithContainersView provides a read-only view over StructWithContainers.
|
||||
//
|
||||
// Its methods should only be called if `Valid()` returns true.
|
||||
type StructWithContainersView 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.
|
||||
ж *StructWithContainers
|
||||
}
|
||||
|
||||
// Valid reports whether underlying value is non-nil.
|
||||
func (v StructWithContainersView) Valid() bool { return v.ж != nil }
|
||||
|
||||
// AsStruct returns a clone of the underlying value which aliases no memory with
|
||||
// the original.
|
||||
func (v StructWithContainersView) AsStruct() *StructWithContainers {
|
||||
if v.ж == nil {
|
||||
return nil
|
||||
}
|
||||
return v.ж.Clone()
|
||||
}
|
||||
|
||||
func (v StructWithContainersView) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
func (v *StructWithContainersView) UnmarshalJSON(b []byte) error {
|
||||
if v.ж != nil {
|
||||
return errors.New("already initialized")
|
||||
}
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
var x StructWithContainers
|
||||
if err := json.Unmarshal(b, &x); err != nil {
|
||||
return err
|
||||
}
|
||||
v.ж = &x
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v StructWithContainersView) IntContainer() Container[int] { return v.ж.IntContainer }
|
||||
func (v StructWithContainersView) CloneableContainer() ContainerView[*StructWithPtrs, StructWithPtrsView] {
|
||||
return ContainerViewOf(&v.ж.CloneableContainer)
|
||||
}
|
||||
func (v StructWithContainersView) BasicGenericContainer() Container[GenericBasicStruct[int]] {
|
||||
return v.ж.BasicGenericContainer
|
||||
}
|
||||
func (v StructWithContainersView) ClonableGenericContainer() ContainerView[*GenericNoPtrsStruct[int], GenericNoPtrsStructView[int]] {
|
||||
return ContainerViewOf(&v.ж.ClonableGenericContainer)
|
||||
}
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _StructWithContainersViewNeedsRegeneration = StructWithContainers(struct {
|
||||
IntContainer Container[int]
|
||||
CloneableContainer Container[*StructWithPtrs]
|
||||
BasicGenericContainer Container[GenericBasicStruct[int]]
|
||||
ClonableGenericContainer Container[*GenericNoPtrsStruct[int]]
|
||||
}{})
|
||||
|
Reference in New Issue
Block a user