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:
Nick Khyl
2024-07-14 11:45:55 -05:00
committed by Nick Khyl
parent e7bf6e716b
commit 20562a4fb9
7 changed files with 544 additions and 5 deletions

View File

@@ -11,6 +11,7 @@ import (
"errors"
"fmt"
"maps"
"reflect"
"slices"
"go4.org/mem"
@@ -111,6 +112,13 @@ type StructView[T any] interface {
AsStruct() T
}
// Cloner is any type that has a Clone function returning a deep-clone of the receiver.
type Cloner[T any] interface {
// Clone returns a deep-clone of the receiver.
// It returns nil, when the receiver is nil.
Clone() T
}
// ViewCloner is any type that has had View and Clone funcs generated using
// tailscale.com/cmd/viewer.
type ViewCloner[T any, V StructView[T]] interface {
@@ -555,3 +563,46 @@ func (m MapFn[K, T, V]) Range(f MapRangeFn[K, V]) {
}
}
}
// ContainsPointers reports whether T contains any pointers,
// either explicitly or implicitly.
// It has special handling for some types that contain pointers
// that we know are free from memory aliasing/mutation concerns.
func ContainsPointers[T any]() bool {
return containsPointers(reflect.TypeFor[T]())
}
func containsPointers(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.Pointer, reflect.UnsafePointer:
return true
case reflect.Chan, reflect.Map, reflect.Slice:
return true
case reflect.Array:
return containsPointers(typ.Elem())
case reflect.Interface, reflect.Func:
return true // err on the safe side.
case reflect.Struct:
if isWellKnownImmutableStruct(typ) {
return false
}
for i := range typ.NumField() {
if containsPointers(typ.Field(i).Type) {
return true
}
}
}
return false
}
func isWellKnownImmutableStruct(typ reflect.Type) bool {
switch typ.String() {
case "time.Time":
// time.Time contains a pointer that does not need copying
return true
case "netip.Addr", "netip.Prefix", "netip.AddrPort":
return true
default:
return false
}
}