mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-30 07:43:42 +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)
|
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 {
|
} else if _, isIface := ft.Elem().Underlying().(*types.Interface); isIface {
|
||||||
writef("\tdst.%s[i] = src.%s[i].Clone()", fname, fname)
|
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 {
|
} else {
|
||||||
writef("\tdst.%s[i] = *src.%s[i].Clone()", fname, fname)
|
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\tdst.%s[k] = append([]%s{}, src.%s[k]...)", fname, n, fname)
|
||||||
writef("\t}")
|
writef("\t}")
|
||||||
writef("}")
|
writef("}")
|
||||||
} else if codegen.ContainsPointers(elem) {
|
} else if codegen.ContainsPointers(elem) && !codegen.IsViewType(elem) {
|
||||||
writef("if dst.%s != nil {", fname)
|
writef("if dst.%s != nil {", fname)
|
||||||
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(elem))
|
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(elem))
|
||||||
writef("\tfor k, v := range src.%s {", fname)
|
writef("\tfor k, v := range src.%s {", fname)
|
||||||
|
@ -30,6 +30,7 @@ type Map struct {
|
|||||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||||
StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
|
StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
|
||||||
StructWithPtr map[string]StructWithPtrs
|
StructWithPtr map[string]StructWithPtrs
|
||||||
|
StructWithView map[string]StructWithPtrsView
|
||||||
|
|
||||||
// Unsupported views.
|
// Unsupported views.
|
||||||
SliceIntPtr map[string][]*int
|
SliceIntPtr map[string][]*int
|
||||||
@ -63,10 +64,11 @@ type StructWithSlices struct {
|
|||||||
Slice []string
|
Slice []string
|
||||||
Prefixes []netip.Prefix
|
Prefixes []netip.Prefix
|
||||||
Data []byte
|
Data []byte
|
||||||
|
Structs []StructWithPtrs
|
||||||
|
Views []StructWithPtrsView
|
||||||
|
|
||||||
// Unsupported views.
|
// Unsupported views.
|
||||||
Structs []StructWithPtrs
|
Ints []*int
|
||||||
Ints []*int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnlyGetClone struct {
|
type OnlyGetClone struct {
|
||||||
|
@ -114,6 +114,7 @@ func (src *Map) Clone() *Map {
|
|||||||
dst.StructWithPtr[k] = *(v.Clone())
|
dst.StructWithPtr[k] = *(v.Clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dst.StructWithView = maps.Clone(src.StructWithView)
|
||||||
if dst.SliceIntPtr != nil {
|
if dst.SliceIntPtr != nil {
|
||||||
dst.SliceIntPtr = map[string][]*int{}
|
dst.SliceIntPtr = map[string][]*int{}
|
||||||
for k := range src.SliceIntPtr {
|
for k := range src.SliceIntPtr {
|
||||||
@ -136,6 +137,7 @@ var _MapCloneNeedsRegeneration = Map(struct {
|
|||||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||||
StructWithPtr map[string]StructWithPtrs
|
StructWithPtr map[string]StructWithPtrs
|
||||||
|
StructWithView map[string]StructWithPtrsView
|
||||||
SliceIntPtr map[string][]*int
|
SliceIntPtr map[string][]*int
|
||||||
PointerKey map[*string]int
|
PointerKey map[*string]int
|
||||||
StructWithPtrKey map[StructWithPtrs]int
|
StructWithPtrKey map[StructWithPtrs]int
|
||||||
@ -179,6 +181,12 @@ func (src *StructWithSlices) Clone() *StructWithSlices {
|
|||||||
dst.Structs[i] = *src.Structs[i].Clone()
|
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 {
|
if src.Ints != nil {
|
||||||
dst.Ints = make([]*int, len(src.Ints))
|
dst.Ints = make([]*int, len(src.Ints))
|
||||||
for i := range dst.Ints {
|
for i := range dst.Ints {
|
||||||
@ -201,6 +209,7 @@ var _StructWithSlicesCloneNeedsRegeneration = StructWithSlices(struct {
|
|||||||
Prefixes []netip.Prefix
|
Prefixes []netip.Prefix
|
||||||
Data []byte
|
Data []byte
|
||||||
Structs []StructWithPtrs
|
Structs []StructWithPtrs
|
||||||
|
Views []StructWithPtrsView
|
||||||
Ints []*int
|
Ints []*int
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
|
@ -220,6 +220,10 @@ func (v MapView) StructWithPtr() views.MapFn[string, StructWithPtrs, StructWithP
|
|||||||
return t.View()
|
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) SliceIntPtr() map[string][]*int { panic("unsupported") }
|
||||||
func (v MapView) PointerKey() map[*string]int { panic("unsupported") }
|
func (v MapView) PointerKey() map[*string]int { panic("unsupported") }
|
||||||
func (v MapView) StructWithPtrKey() map[StructWithPtrs]int { panic("unsupported") }
|
func (v MapView) StructWithPtrKey() map[StructWithPtrs]int { panic("unsupported") }
|
||||||
@ -235,6 +239,7 @@ var _MapViewNeedsRegeneration = Map(struct {
|
|||||||
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
|
||||||
StructWithoutPtrKey map[StructWithoutPtrs]int
|
StructWithoutPtrKey map[StructWithoutPtrs]int
|
||||||
StructWithPtr map[string]StructWithPtrs
|
StructWithPtr map[string]StructWithPtrs
|
||||||
|
StructWithView map[string]StructWithPtrsView
|
||||||
SliceIntPtr map[string][]*int
|
SliceIntPtr map[string][]*int
|
||||||
PointerKey map[*string]int
|
PointerKey map[*string]int
|
||||||
StructWithPtrKey map[StructWithPtrs]int
|
StructWithPtrKey map[StructWithPtrs]int
|
||||||
@ -299,8 +304,13 @@ func (v StructWithSlicesView) Prefixes() views.Slice[netip.Prefix] {
|
|||||||
return views.SliceOf(v.ж.Prefixes)
|
return views.SliceOf(v.ж.Prefixes)
|
||||||
}
|
}
|
||||||
func (v StructWithSlicesView) Data() views.ByteSlice[[]byte] { return views.ByteSliceOf(v.ж.Data) }
|
func (v StructWithSlicesView) Data() views.ByteSlice[[]byte] { return views.ByteSliceOf(v.ж.Data) }
|
||||||
func (v StructWithSlicesView) Structs() StructWithPtrs { panic("unsupported") }
|
func (v StructWithSlicesView) Structs() views.ValueSliceView[StructWithPtrs, *StructWithPtrs, StructWithPtrsView] {
|
||||||
func (v StructWithSlicesView) Ints() *int { panic("unsupported") }
|
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.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
||||||
@ -311,6 +321,7 @@ var _StructWithSlicesViewNeedsRegeneration = StructWithSlices(struct {
|
|||||||
Prefixes []netip.Prefix
|
Prefixes []netip.Prefix
|
||||||
Data []byte
|
Data []byte
|
||||||
Structs []StructWithPtrs
|
Structs []StructWithPtrs
|
||||||
|
Views []StructWithPtrsView
|
||||||
Ints []*int
|
Ints []*int
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
|
@ -75,6 +75,8 @@ func (v *{{.ViewName}}{{.TypeParamNames}}) UnmarshalJSON(b []byte) error {
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{define "viewSliceField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() views.SliceView[{{.FieldType}},{{.FieldViewName}}] { return views.SliceOfViews[{{.FieldType}},{{.FieldViewName}}](v.ж.{{.FieldName}}) }
|
{{define "viewSliceField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() views.SliceView[{{.FieldType}},{{.FieldViewName}}] { return views.SliceOfViews[{{.FieldType}},{{.FieldViewName}}](v.ж.{{.FieldName}}) }
|
||||||
{{end}}
|
{{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() }
|
{{define "viewField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldViewName}} { return v.ж.{{.FieldName}}.View() }
|
||||||
{{end}}
|
{{end}}
|
||||||
{{define "makeViewField"}}func (v {{.ViewName}}{{.TypeParamNames}}) {{.FieldName}}() {{.FieldViewName}} { return {{.MakeViewFnName}}(&v.ж.{{.FieldName}}) }
|
{{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) {
|
func requiresCloning(t types.Type) (shallow, deep bool, base types.Type) {
|
||||||
|
if codegen.IsViewType(t) {
|
||||||
|
return false, false, t
|
||||||
|
}
|
||||||
switch v := t.(type) {
|
switch v := t.(type) {
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
_, deep, base = requiresCloning(v.Elem())
|
_, deep, base = requiresCloning(v.Elem())
|
||||||
@ -198,6 +203,10 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, _ *
|
|||||||
writeTemplate("unsupportedField")
|
writeTemplate("unsupportedField")
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
case *types.Struct:
|
||||||
|
args.FieldViewName = appendNameSuffix(it.QualifiedName(elem), "View")
|
||||||
|
writeTemplate("viewValueSliceField")
|
||||||
|
continue
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
if viewType := viewTypeForValueType(elem); viewType != nil {
|
if viewType := viewTypeForValueType(elem); viewType != nil {
|
||||||
args.FieldViewName = it.QualifiedName(viewType)
|
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:
|
case *types.Struct, *types.Named, *types.Alias:
|
||||||
strucT := u
|
strucT := u
|
||||||
args.FieldType = it.QualifiedName(fieldType)
|
args.FieldType = it.QualifiedName(fieldType)
|
||||||
if codegen.ContainsPointers(strucT) {
|
if codegen.ContainsPointers(strucT) && !codegen.IsViewType(strucT) {
|
||||||
args.MapFn = "t.View()"
|
args.MapFn = "t.View()"
|
||||||
template = "mapFnField"
|
template = "mapFnField"
|
||||||
args.MapValueType = it.QualifiedName(mElem)
|
args.MapValueType = it.QualifiedName(mElem)
|
||||||
|
@ -822,6 +822,78 @@ func (p *ValuePointer[T]) UnmarshalJSON(b []byte) error {
|
|||||||
return json.Unmarshal(b, &p.ж)
|
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,
|
// ContainsPointers reports whether T contains any pointers,
|
||||||
// either explicitly or implicitly.
|
// either explicitly or implicitly.
|
||||||
// It has special handling for some types that contain pointers
|
// It has special handling for some types that contain pointers
|
||||||
|
Loading…
x
Reference in New Issue
Block a user