cmd/viewer,types/views: add support for views of maps

Updates #4635

Signed-off-by: Maisem Ali <maisem@tailscale.com>
pull/4651/head
Maisem Ali 3 years ago committed by Maisem Ali
parent 5cd56fe8d5
commit d04afc697c

@ -19,7 +19,18 @@ type StructWithoutPtrs struct {
} }
type Map struct { type Map struct {
M map[string]int Int map[string]int
SliceInt map[string][]int
StructWithPtr map[string]*StructWithPtrs
StructWithoutPtr map[string]*StructWithoutPtrs
SlicesWithPtrs map[string][]*StructWithPtrs
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
StructWithoutPtrKey map[StructWithoutPtrs]int `json:"-"`
// Unsupported.
SliceIntPtr map[string][]*int
PointerKey map[*string]int `json:"-"`
StructWithPtrKey map[StructWithPtrs]int `json:"-"`
} }
type StructWithPtrs struct { type StructWithPtrs struct {

@ -61,10 +61,64 @@ func (src *Map) Clone() *Map {
} }
dst := new(Map) dst := new(Map)
*dst = *src *dst = *src
if dst.M != nil { if dst.Int != nil {
dst.M = map[string]int{} dst.Int = map[string]int{}
for k, v := range src.M { for k, v := range src.Int {
dst.M[k] = v dst.Int[k] = v
}
}
if dst.SliceInt != nil {
dst.SliceInt = map[string][]int{}
for k := range src.SliceInt {
dst.SliceInt[k] = append([]int{}, src.SliceInt[k]...)
}
}
if dst.StructWithPtr != nil {
dst.StructWithPtr = map[string]*StructWithPtrs{}
for k, v := range src.StructWithPtr {
dst.StructWithPtr[k] = v.Clone()
}
}
if dst.StructWithoutPtr != nil {
dst.StructWithoutPtr = map[string]*StructWithoutPtrs{}
for k, v := range src.StructWithoutPtr {
dst.StructWithoutPtr[k] = v.Clone()
}
}
if dst.SlicesWithPtrs != nil {
dst.SlicesWithPtrs = map[string][]*StructWithPtrs{}
for k := range src.SlicesWithPtrs {
dst.SlicesWithPtrs[k] = append([]*StructWithPtrs{}, src.SlicesWithPtrs[k]...)
}
}
if dst.SlicesWithoutPtrs != nil {
dst.SlicesWithoutPtrs = map[string][]*StructWithoutPtrs{}
for k := range src.SlicesWithoutPtrs {
dst.SlicesWithoutPtrs[k] = append([]*StructWithoutPtrs{}, src.SlicesWithoutPtrs[k]...)
}
}
if dst.StructWithoutPtrKey != nil {
dst.StructWithoutPtrKey = map[StructWithoutPtrs]int{}
for k, v := range src.StructWithoutPtrKey {
dst.StructWithoutPtrKey[k] = v
}
}
if dst.SliceIntPtr != nil {
dst.SliceIntPtr = map[string][]*int{}
for k := range src.SliceIntPtr {
dst.SliceIntPtr[k] = append([]*int{}, src.SliceIntPtr[k]...)
}
}
if dst.PointerKey != nil {
dst.PointerKey = map[*string]int{}
for k, v := range src.PointerKey {
dst.PointerKey[k] = v
}
}
if dst.StructWithPtrKey != nil {
dst.StructWithPtrKey = map[StructWithPtrs]int{}
for k, v := range src.StructWithPtrKey {
dst.StructWithPtrKey[k] = v
} }
} }
return dst return dst
@ -72,7 +126,16 @@ func (src *Map) Clone() *Map {
// 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 _MapCloneNeedsRegeneration = Map(struct { var _MapCloneNeedsRegeneration = Map(struct {
M map[string]int Int map[string]int
SliceInt map[string][]int
StructWithPtr map[string]*StructWithPtrs
StructWithoutPtr map[string]*StructWithoutPtrs
SlicesWithPtrs map[string][]*StructWithPtrs
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
StructWithoutPtrKey map[StructWithoutPtrs]int
SliceIntPtr map[string][]*int
PointerKey map[*string]int
StructWithPtrKey map[StructWithPtrs]int
}{}) }{})
// Clone makes a deep copy of StructWithSlices. // Clone makes a deep copy of StructWithSlices.

@ -188,9 +188,57 @@ func (v *MapView) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (v MapView) Int() views.Map[string, int] { return views.MapOf(v.ж.Int) }
func (v MapView) SliceInt() views.MapFn[string, []int, views.Slice[int]] {
return views.MapFnOf(v.ж.SliceInt, func(t []int) views.Slice[int] {
return views.SliceOf(t)
})
}
func (v MapView) StructWithPtr() views.MapFn[string, *StructWithPtrs, StructWithPtrsView] {
return views.MapFnOf(v.ж.StructWithPtr, func(t *StructWithPtrs) StructWithPtrsView {
return t.View()
})
}
func (v MapView) StructWithoutPtr() views.MapFn[string, *StructWithoutPtrs, StructWithoutPtrsView] {
return views.MapFnOf(v.ж.StructWithoutPtr, func(t *StructWithoutPtrs) StructWithoutPtrsView {
return t.View()
})
}
func (v MapView) SlicesWithPtrs() views.MapFn[string, []*StructWithPtrs, views.SliceView[*StructWithPtrs, StructWithPtrsView]] {
return views.MapFnOf(v.ж.SlicesWithPtrs, func(t []*StructWithPtrs) views.SliceView[*StructWithPtrs, StructWithPtrsView] {
return views.SliceOfViews[*StructWithPtrs, StructWithPtrsView](t)
})
}
func (v MapView) SlicesWithoutPtrs() views.MapFn[string, []*StructWithoutPtrs, views.SliceView[*StructWithoutPtrs, StructWithoutPtrsView]] {
return views.MapFnOf(v.ж.SlicesWithoutPtrs, func(t []*StructWithoutPtrs) views.SliceView[*StructWithoutPtrs, StructWithoutPtrsView] {
return views.SliceOfViews[*StructWithoutPtrs, StructWithoutPtrsView](t)
})
}
func (v MapView) StructWithoutPtrKey() views.Map[StructWithoutPtrs, int] {
return views.MapOf(v.ж.StructWithoutPtrKey)
}
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") }
// 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 _MapViewNeedsRegeneration = Map(struct { var _MapViewNeedsRegeneration = Map(struct {
M map[string]int Int map[string]int
SliceInt map[string][]int
StructWithPtr map[string]*StructWithPtrs
StructWithoutPtr map[string]*StructWithoutPtrs
SlicesWithPtrs map[string][]*StructWithPtrs
SlicesWithoutPtrs map[string][]*StructWithoutPtrs
StructWithoutPtrKey map[StructWithoutPtrs]int
SliceIntPtr map[string][]*int
PointerKey map[*string]int
StructWithPtrKey map[StructWithPtrs]int
}{}) }{})
// View returns a readonly view of StructWithSlices. // View returns a readonly view of StructWithSlices.

@ -88,8 +88,12 @@ func (v *{{.ViewName}}) UnmarshalJSON(b []byte) error {
{{end}} {{end}}
{{define "mapField"}} {{define "mapField"}}
// Unsupported, panics. func(v {{.ViewName}}) {{.FieldName}}() views.Map[{{.MapKeyType}},{{.MapValueType}}] { return views.MapOf(v.ж.{{.FieldName}})}
func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")} {{end}}
{{define "mapFnField"}}
func(v {{.ViewName}}) {{.FieldName}}() views.MapFn[{{.MapKeyType}},{{.MapValueType}},{{.MapValueView}}] { return views.MapFnOf(v.ж.{{.FieldName}}, func (t {{.MapValueType}}) {{.MapValueView}} {
return {{.MapFn}}
})}
{{end}} {{end}}
{{define "unsupportedField"}}func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")} {{define "unsupportedField"}}func(v {{.ViewName}}) {{.FieldName}}() {{.FieldType}} {panic("unsupported")}
{{end}} {{end}}
@ -132,6 +136,11 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
FieldName string FieldName string
FieldType string FieldType string
FieldViewName string FieldViewName string
MapKeyType string
MapValueType string
MapValueView string
MapFn string
}{ }{
StructName: typ.Obj().Name(), StructName: typ.Obj().Name(),
ViewName: typ.Obj().Name() + "View", ViewName: typ.Obj().Name() + "View",
@ -194,7 +203,7 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
writeTemplate("sliceField") writeTemplate("sliceField")
} }
continue continue
case *types.Struct: case *types.Struct, *types.Named:
strucT := underlying strucT := underlying
args.FieldType = it.QualifiedName(fieldType) args.FieldType = it.QualifiedName(fieldType)
if codegen.ContainsPointers(strucT) { if codegen.ContainsPointers(strucT) {
@ -204,9 +213,62 @@ func genView(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named, thi
writeTemplate("valueField") writeTemplate("valueField")
continue continue
case *types.Map: case *types.Map:
// TODO(maisem): support this. m := underlying
// args.FieldType = importedName(ft) args.FieldType = it.QualifiedName(fieldType)
// writeTemplate("mapField") shallow, deep, key := requiresCloning(m.Key())
if shallow || deep {
writeTemplate("unsupportedField")
continue
}
args.MapKeyType = it.QualifiedName(key)
mElem := m.Elem()
var template string
switch u := mElem.(type) {
case *types.Basic:
template = "mapField"
args.MapValueType = it.QualifiedName(mElem)
case *types.Slice:
slice := u
sElem := slice.Elem()
switch x := sElem.(type) {
case *types.Basic:
args.MapValueView = fmt.Sprintf("views.Slice[%v]", sElem)
args.MapValueType = "[]" + sElem.String()
args.MapFn = "views.SliceOf(t)"
template = "mapFnField"
case *types.Pointer:
ptr := x
pElem := ptr.Elem()
switch pElem.(type) {
case *types.Struct, *types.Named:
ptrType := it.QualifiedName(ptr)
viewType := it.QualifiedName(pElem) + "View"
args.MapFn = fmt.Sprintf("views.SliceOfViews[%v,%v](t)", ptrType, viewType)
args.MapValueView = fmt.Sprintf("views.SliceView[%v,%v]", ptrType, viewType)
args.MapValueType = "[]" + ptrType
template = "mapFnField"
default:
template = "unsupportedField"
}
default:
template = "unsupportedField"
}
case *types.Pointer:
ptr := u
pElem := ptr.Elem()
switch pElem.(type) {
case *types.Struct, *types.Named:
args.MapValueType = it.QualifiedName(ptr)
args.MapValueView = it.QualifiedName(pElem) + "View"
args.MapFn = "t.View()"
template = "mapFnField"
default:
template = "unsupportedField"
}
default:
template = "unsupportedField"
}
writeTemplate(template)
continue continue
case *types.Pointer: case *types.Pointer:
ptr := underlying ptr := underlying

@ -531,13 +531,6 @@ type NetInfo struct {
// Update BasicallyEqual when adding fields. // Update BasicallyEqual when adding fields.
} }
// DERPLatencyForEach calls fn for each value in the DERPLatency map.
func (v NetInfoView) DERPLatencyForEach(fn func(k string, v float64)) {
for k, v := range v.ж.DERPLatency {
fn(k, v)
}
}
func (ni *NetInfo) String() string { func (ni *NetInfo) String() string {
if ni == nil { if ni == nil {
return "NetInfo(nil)" return "NetInfo(nil)"

@ -343,6 +343,8 @@ func (v NetInfoView) PMP() opt.Bool { return v.ж.PMP }
func (v NetInfoView) PCP() opt.Bool { return v.ж.PCP } func (v NetInfoView) PCP() opt.Bool { return v.ж.PCP }
func (v NetInfoView) PreferredDERP() int { return v.ж.PreferredDERP } func (v NetInfoView) PreferredDERP() int { return v.ж.PreferredDERP }
func (v NetInfoView) LinkType() string { return v.ж.LinkType } func (v NetInfoView) LinkType() string { return v.ж.LinkType }
func (v NetInfoView) DERPLatency() views.Map[string, float64] { return views.MapOf(v.ж.DERPLatency) }
func (v NetInfoView) String() string { return v.ж.String() } func (v NetInfoView) String() string { return v.ж.String() }
// 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.
@ -471,6 +473,12 @@ func (v *DNSConfigView) UnmarshalJSON(b []byte) error {
func (v DNSConfigView) Resolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] { func (v DNSConfigView) Resolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.Resolvers) return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.Resolvers)
} }
func (v DNSConfigView) Routes() views.MapFn[string, []*dnstype.Resolver, views.SliceView[*dnstype.Resolver, dnstype.ResolverView]] {
return views.MapFnOf(v.ж.Routes, func(t []*dnstype.Resolver) views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](t)
})
}
func (v DNSConfigView) FallbackResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] { func (v DNSConfigView) FallbackResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.FallbackResolvers) return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.FallbackResolvers)
} }
@ -667,6 +675,11 @@ func (v *DERPMapView) UnmarshalJSON(b []byte) error {
return nil return nil
} }
func (v DERPMapView) Regions() views.MapFn[int, *DERPRegion, DERPRegionView] {
return views.MapFnOf(v.ж.Regions, func(t *DERPRegion) DERPRegionView {
return t.View()
})
}
func (v DERPMapView) OmitDefaultRegions() bool { return v.ж.OmitDefaultRegions } func (v DERPMapView) OmitDefaultRegions() bool { return v.ж.OmitDefaultRegions }
// 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.

@ -53,6 +53,8 @@ func SliceOfViews[T ViewCloner[T, V], V StructView[T]](x []T) SliceView[T, V] {
// SliceView is a read-only wrapper around a struct which should only be exposed // SliceView is a read-only wrapper around a struct which should only be exposed
// as a View. // as a View.
type SliceView[T ViewCloner[T, V], V StructView[T]] struct { type SliceView[T ViewCloner[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 // 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. // to callers. You must not let callers be able to mutate it.
ж []T ж []T
@ -88,6 +90,8 @@ func (v SliceView[T, V]) AsSlice() []V {
// Slice is a read-only accessor for a slice. // Slice is a read-only accessor for a slice.
type Slice[T any] struct { type Slice[T any] 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 // 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. // to callers. You must not let callers be able to mutate it.
ж []T ж []T
@ -181,3 +185,102 @@ func (v IPPrefixSlice) MarshalJSON() ([]byte, error) {
func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error { func (v *IPPrefixSlice) UnmarshalJSON(b []byte) error {
return v.ж.UnmarshalJSON(b) return v.ж.UnmarshalJSON(b)
} }
// MapOf returns a read-only view over m.
func MapOf[K comparable, V comparable](m map[K]V) Map[K, V] {
return Map[K, V]{m}
}
// Map is a read-only accessor over a map whose values are immutable.
type Map[K comparable, V any] 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.
ж map[K]V
}
// Has reports whether k has an entry in the map.
func (m Map[K, V]) Has(k K) bool {
_, ok := m.ж[k]
return ok
}
// IsNil reports whether the underlying map is nil.
func (m Map[K, V]) IsNil() bool {
return m.ж == nil
}
// Len returns the number of elements in the map.
func (m Map[K, V]) Len() int { return len(m.ж) }
// Get returns the element with key k.
func (m Map[K, V]) Get(k K) V {
return m.ж[k]
}
// GetOk returns the element with key k and a bool representing whether the key
// is in map.
func (m Map[K, V]) GetOk(k K) (V, bool) {
v, ok := m.ж[k]
return v, ok
}
// ForEach calls f for every k,v pair in the underlying map.
func (m Map[K, V]) ForEach(f func(k K, v V)) {
for k, v := range m.ж {
f(k, v)
}
}
// MapFnOf returns a MapFn for m.
func MapFnOf[K comparable, T any, V any](m map[K]T, f func(T) V) MapFn[K, T, V] {
return MapFn[K, T, V]{
ж: m,
wrapv: f,
}
}
// MapFn is like Map but with a func to convert values from T to V.
// It is used to provide map of slices and views.
type MapFn[K comparable, T any, V any] 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.
ж map[K]T
wrapv func(T) V
}
// Has reports whether k has an entry in the map.
func (m MapFn[K, T, V]) Has(k K) bool {
_, ok := m.ж[k]
return ok
}
// Get returns the element with key k.
func (m MapFn[K, T, V]) Get(k K) V {
return m.wrapv(m.ж[k])
}
// IsNil reports whether the underlying map is nil.
func (m MapFn[K, T, V]) IsNil() bool {
return m.ж == nil
}
// Len returns the number of elements in the map.
func (m MapFn[K, T, V]) Len() int { return len(m.ж) }
// GetOk returns the element with key k and a bool representing whether the key
// is in map.
func (m MapFn[K, T, V]) GetOk(k K) (V, bool) {
v, ok := m.ж[k]
return m.wrapv(v), ok
}
// ForEach calls f for every k,v pair in the underlying map.
func (m MapFn[K, T, V]) ForEach(f func(k K, v V)) {
for k, v := range m.ж {
f(k, m.wrapv(v))
}
}

Loading…
Cancel
Save