diff --git a/cmd/cloner/cloner.go b/cmd/cloner/cloner.go index a1ffc30fe..6c2091148 100644 --- a/cmd/cloner/cloner.go +++ b/cmd/cloner/cloner.go @@ -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) diff --git a/cmd/viewer/tests/tests.go b/cmd/viewer/tests/tests.go index 4020e5651..0180ddb4c 100644 --- a/cmd/viewer/tests/tests.go +++ b/cmd/viewer/tests/tests.go @@ -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 { diff --git a/cmd/viewer/tests/tests_clone.go b/cmd/viewer/tests/tests_clone.go index 106a9b684..f286cc9e0 100644 --- a/cmd/viewer/tests/tests_clone.go +++ b/cmd/viewer/tests/tests_clone.go @@ -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 }{}) diff --git a/cmd/viewer/tests/tests_view.go b/cmd/viewer/tests/tests_view.go index f1d8f424f..0bf58a2f0 100644 --- a/cmd/viewer/tests/tests_view.go +++ b/cmd/viewer/tests/tests_view.go @@ -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 }{}) diff --git a/cmd/viewer/viewer.go b/cmd/viewer/viewer.go index 2d30cc2eb..258de29a7 100644 --- a/cmd/viewer/viewer.go +++ b/cmd/viewer/viewer.go @@ -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) diff --git a/types/views/views.go b/types/views/views.go index 3911f1112..ee6d7fe10 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -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