mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-29 15:23:45 +00:00
cmd/{cloner,viewer},types/views: add viewer and cloner support for slices and maps of both structs (rather than pointers to structs) and views
In this PR we add viewer/cloner codegen support for the following field types, where View is an existing, likely generated, view type and T is a struct (rather than a pointer to struct). - []T - []View - map[K]View To support []T, we introduce the generic view type view.ValueSliceView[T, P, V]. Here, P (i.e. *T) implements ViewCloner[*T, V], and V is the concrete view type for T. For the slices and maps of views we use existing views.Slice and view.Map view types. This is mostly done in a preparation for generating netmap.NetworkMapView. Updates #12614 Updates tailscale/corp#27502 Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
parent
dda2c0d2c2
commit
20d6058e7b
@ -150,6 +150,8 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
|
||||
writef("\tdst.%s[i] = append(src.%s[i][:0:0], src.%s[i]...)", fname, fname, fname)
|
||||
} else if _, isIface := ft.Elem().Underlying().(*types.Interface); isIface {
|
||||
writef("\tdst.%s[i] = src.%s[i].Clone()", fname, fname)
|
||||
} else if codegen.IsViewType(ft.Elem()) {
|
||||
writef("\tdst.%s[i] = src.%s[i]", fname, fname)
|
||||
} else {
|
||||
writef("\tdst.%s[i] = *src.%s[i].Clone()", fname, fname)
|
||||
}
|
||||
@ -187,7 +189,7 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
|
||||
writef("\t\tdst.%s[k] = append([]%s{}, src.%s[k]...)", fname, n, fname)
|
||||
writef("\t}")
|
||||
writef("}")
|
||||
} else if codegen.ContainsPointers(elem) {
|
||||
} else if codegen.ContainsPointers(elem) && !codegen.IsViewType(elem) {
|
||||
writef("if dst.%s != nil {", fname)
|
||||
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(elem))
|
||||
writef("\tfor k, v := range src.%s {", fname)
|
||||
|
@ -30,6 +30,7 @@ type Map struct {
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
StructWithView map[string]StructWithPtrsView
|
||||
|
||||
// Unsupported views.
|
||||
SliceIntPtr map[string][]*int
|
||||
@ -63,10 +64,11 @@ type StructWithSlices struct {
|
||||
Slice []string
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
Structs []StructWithPtrs
|
||||
Views []StructWithPtrsView
|
||||
|
||||
// Unsupported views.
|
||||
Structs []StructWithPtrs
|
||||
Ints []*int
|
||||
Ints []*int
|
||||
}
|
||||
|
||||
type OnlyGetClone struct {
|
||||
|
@ -114,6 +114,7 @@ func (src *Map) Clone() *Map {
|
||||
dst.StructWithPtr[k] = *(v.Clone())
|
||||
}
|
||||
}
|
||||
dst.StructWithView = maps.Clone(src.StructWithView)
|
||||
if dst.SliceIntPtr != nil {
|
||||
dst.SliceIntPtr = map[string][]*int{}
|
||||
for k := range src.SliceIntPtr {
|
||||
@ -136,6 +137,7 @@ var _MapCloneNeedsRegeneration = Map(struct {
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
StructWithView map[string]StructWithPtrsView
|
||||
SliceIntPtr map[string][]*int
|
||||
PointerKey map[*string]int
|
||||
StructWithPtrKey map[StructWithPtrs]int
|
||||
@ -179,6 +181,12 @@ func (src *StructWithSlices) Clone() *StructWithSlices {
|
||||
dst.Structs[i] = *src.Structs[i].Clone()
|
||||
}
|
||||
}
|
||||
if src.Views != nil {
|
||||
dst.Views = make([]StructWithPtrsView, len(src.Views))
|
||||
for i := range dst.Views {
|
||||
dst.Views[i] = src.Views[i]
|
||||
}
|
||||
}
|
||||
if src.Ints != nil {
|
||||
dst.Ints = make([]*int, len(src.Ints))
|
||||
for i := range dst.Ints {
|
||||
@ -201,6 +209,7 @@ var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
Structs []StructWithPtrs
|
||||
Views []StructWithPtrsView
|
||||
Ints []*int
|
||||
}{})
|
||||
|
||||
|
@ -220,6 +220,10 @@ func (v MapView) StructWithPtr() views.MapFn[string, StructWithPtrs, StructWithP
|
||||
return t.View()
|
||||
})
|
||||
}
|
||||
|
||||
func (v MapView) StructWithView() views.Map[string, StructWithPtrsView] {
|
||||
return views.MapOf(v.ж.StructWithView)
|
||||
}
|
||||
func (v MapView) SliceIntPtr() map[string][]*int { panic("unsupported") }
|
||||
func (v MapView) PointerKey() map[*string]int { panic("unsupported") }
|
||||
func (v MapView) StructWithPtrKey() map[StructWithPtrs]int { panic("unsupported") }
|
||||
@ -235,6 +239,7 @@ var _MapViewNeedsRegeneration = Map(struct {
|
||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||
StructWithPtr map[string]StructWithPtrs
|
||||
StructWithView map[string]StructWithPtrsView
|
||||
SliceIntPtr map[string][]*int
|
||||
PointerKey map[*string]int
|
||||
StructWithPtrKey map[StructWithPtrs]int
|
||||
@ -299,8 +304,13 @@ func (v StructWithSlicesView) Prefixes() views.Slice[netip.Prefix] {
|
||||
return views.SliceOf(v.ж.Prefixes)
|
||||
}
|
||||
func (v StructWithSlicesView) Data() views.ByteSlice[[]byte] { return views.ByteSliceOf(v.ж.Data) }
|
||||
func (v StructWithSlicesView) Structs() StructWithPtrs { panic("unsupported") }
|
||||
func (v StructWithSlicesView) Ints() *int { panic("unsupported") }
|
||||
func (v StructWithSlicesView) Structs() views.ValueSliceView[StructWithPtrs, *StructWithPtrs, StructWithPtrsView] {
|
||||
return views.SliceOfValueViews[StructWithPtrs, *StructWithPtrs](v.ж.Structs)
|
||||
}
|
||||
func (v StructWithSlicesView) Views() views.Slice[StructWithPtrsView] {
|
||||
return views.SliceOf(v.ж.Views)
|
||||
}
|
||||
func (v StructWithSlicesView) Ints() *int { panic("unsupported") }
|
||||
|
||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||
var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
||||
@ -311,6 +321,7 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
||||
Prefixes []netip.Prefix
|
||||
Data []byte
|
||||
Structs []StructWithPtrs
|
||||
Views []StructWithPtrsView
|
||||
Ints []*int
|
||||
}{})
|
||||
|
||||
|
@ -75,6 +75,8 @@ func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
|
||||
{{end}}
|
||||
{{define "viewSliceField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() views.SliceView[{{.FieldType}},{{.FieldViewName}}] { return views.SliceOfViews[{{.FieldType}},{{.FieldViewName}}](v.ж.{{.FieldName}}) }
|
||||
{{end}}
|
||||
{{define "viewValueSliceField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() views.ValueSliceView[{{.FieldType}},*{{.FieldType}},{{.FieldViewName}}] { return views.SliceOfValueViews[{{.FieldType}},*{{.FieldType}}](v.ж.{{.FieldName}}) }
|
||||
{{end}}
|
||||
{{define "viewField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldViewName}} { return v.ж.{{.FieldName}}.View() }
|
||||
{{end}}
|
||||
{{define "makeViewField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldViewName}} { return {{.MakeViewFnName}}(&v.ж.{{.FieldName}}) }
|
||||
@ -108,6 +110,9 @@ func init() {
|
||||
}
|
||||
|
||||
func requiresCloning(t types.Type) (shallow, deep bool, base types.Type) {
|
||||
if codegen.IsViewType(t) {
|
||||
return false, false, t
|
||||
}
|
||||
switch v := t.(type) {
|
||||
case *types.Pointer:
|
||||
_, deep, base = requiresCloning(v.Elem())
|
||||
@ -198,6 +203,10 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
|
||||
writeTemplate("unsupportedField")
|
||||
}
|
||||
continue
|
||||
case *types.Struct:
|
||||
args.FieldViewName = appendNameSuffix(it.QualifiedName(elem), "View")
|
||||
writeTemplate("viewValueSliceField")
|
||||
continue
|
||||
case *types.Interface:
|
||||
if viewType := viewTypeForValueType(elem); viewType != nil {
|
||||
args.FieldViewName = it.QualifiedName(viewType)
|
||||
@ -260,7 +269,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
|
||||
case *types.Struct, *types.Named, *types.Alias:
|
||||
strucT := u
|
||||
args.FieldType = it.QualifiedName(fieldType)
|
||||
if codegen.ContainsPointers(strucT) {
|
||||
if codegen.ContainsPointers(strucT) && !codegen.IsViewType(strucT) {
|
||||
args.MapFn = "t.View()"
|
||||
template = "mapFnField"
|
||||
args.MapValueType = it.QualifiedName(mElem)
|
||||
|
@ -822,6 +822,78 @@ func (p *ValuePointer[T]) UnmarshalJSON(b []byte) error {
|
||||
return json.Unmarshal(b, &p.ж)
|
||||
}
|
||||
|
||||
// ViewClonerPointer is a constraint that permits pointer types
|
||||
// implementing the [ViewCloner] interface.
|
||||
type ViewClonerPointer[T any, V StructView[*T]] interface {
|
||||
ViewCloner[*T, V]
|
||||
*T
|
||||
}
|
||||
|
||||
// SliceOfValueViews returns a [ValueSliceView] for x.
|
||||
// It is like [SliceOfViews], but x is a slice of values whose pointers
|
||||
// implement the [ViewCloner] interface rather than a slice of pointers.
|
||||
func SliceOfValueViews[T any, P ViewClonerPointer[T, V], V StructView[*T]](x []T) ValueSliceView[T, P, V] {
|
||||
return ValueSliceView[T, P, V]{x}
|
||||
}
|
||||
|
||||
// ValueSliceView is like [SliceView], but wraps a slice of values
|
||||
// (whose pointers implement the [ViewCloner] interface) instead of a slice of pointers.
|
||||
// In other words, the [ViewCloner] interface must be implemented by *T rather than T.
|
||||
type ValueSliceView[
|
||||
T any,
|
||||
P ViewClonerPointer[T, V],
|
||||
V 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.
|
||||
ж []T
|
||||
}
|
||||
|
||||
// All returns an iterator over v.
|
||||
func (v ValueSliceView[T, P, V]) All() iter.Seq2[int, V] {
|
||||
return func(yield func(int, V) bool) {
|
||||
for i := range v.ж {
|
||||
if !yield(i, P(&v.ж[i]).View()) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler.
|
||||
func (v ValueSliceView[T, P, V]) MarshalJSON() ([]byte, error) { return json.Marshal(v.ж) }
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler.
|
||||
func (v *ValueSliceView[T, P, V]) UnmarshalJSON(b []byte) error {
|
||||
return unmarshalSliceFromJSON(b, &v.ж)
|
||||
}
|
||||
|
||||
// IsNil reports whether the underlying slice is nil.
|
||||
func (v ValueSliceView[T, P, V]) IsNil() bool { return v.ж == nil }
|
||||
|
||||
// Len returns the length of the slice.
|
||||
func (v ValueSliceView[T, P, V]) Len() int { return len(v.ж) }
|
||||
|
||||
// At returns a View of the element at index `i` of the slice.
|
||||
func (v ValueSliceView[T, P, V]) At(i int) V { return P(&v.ж[i]).View() }
|
||||
|
||||
// SliceFrom returns v[i:].
|
||||
func (v ValueSliceView[T, P, V]) SliceFrom(i int) ValueSliceView[T, P, V] {
|
||||
return ValueSliceView[T, P, V]{v.ж[i:]}
|
||||
}
|
||||
|
||||
// SliceTo returns v[:i].
|
||||
func (v ValueSliceView[T, P, V]) SliceTo(i int) ValueSliceView[T, P, V] {
|
||||
return ValueSliceView[T, P, V]{v.ж[:i]}
|
||||
}
|
||||
|
||||
// Slice returns v[i:j].
|
||||
func (v ValueSliceView[T, P, V]) Slice(i, j int) ValueSliceView[T, P, V] {
|
||||
return ValueSliceView[T, P, V]{v.ж[i:j]}
|
||||
}
|
||||
|
||||
// ContainsPointers reports whether T contains any pointers,
|
||||
// either explicitly or implicitly.
|
||||
// It has special handling for some types that contain pointers
|
||||
|
Loading…
x
Reference in New Issue
Block a user