cmd/cloner: handle named slices as map values (#20387)

Previously cloner only handled literal slices for values, like
`map[string][]int`. This adds support for named types with an underlying
type of slice, like `map[string]IntSlice` with `type IntSlice []int`.

Updates tailscale/corp#44077

Signed-off-by: Andrew Lytvynov <awly@tailscale.com>
This commit is contained in:
Andrew Lytvynov 2026-07-10 08:56:49 -07:00 committed by GitHub
parent 0e79b322a9
commit 3872880617
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 53 additions and 3 deletions

View File

@ -169,7 +169,7 @@ func gen(buf *bytes.Buffer, it *codegen.ImportTracker, typ *types.Named) {
writef("}")
case *types.Map:
elem := ft.Elem()
if sliceType, isSlice := elem.(*types.Slice); isSlice {
if sliceType, isSlice := elem.Underlying().(*types.Slice); isSlice {
n := it.QualifiedName(sliceType.Elem())
writef("if dst.%s != nil {", fname)
writef("\tdst.%s = map[%s]%s{}", fname, it.QualifiedName(ft.Key()), it.QualifiedName(elem))

View File

@ -283,3 +283,13 @@ func TestDeeplyNestedMap(t *testing.T) {
t.Errorf("Clone() aliased FourLevels map: new nested key appeared in original")
}
}
func TestMapWithNamedSliceValues(t *testing.T) {
orig := &clonerex.MapWithNamedSliceValues{
M: map[string]clonerex.NamedSlice{"k": {"foo", "bar"}},
}
cloned := orig.Clone()
if diff := cmp.Diff(orig, cloned); diff != "" {
t.Errorf("Clone() mismatch (-orig +cloned):\n%s", diff)
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap,NamedMapContainer,MapSlicePointerContainer
//go:generate go run tailscale.com/cmd/cloner -clonefunc=true -type SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap,NamedMapContainer,MapSlicePointerContainer,MapWithNamedSliceValues
// Package clonerex is an example package for the cloner tool.
package clonerex
@ -72,3 +72,12 @@ type DeeplyNestedMap struct {
ThreeLevels map[string]map[string]map[string]int
FourLevels map[string]map[string]map[string]map[string]*SliceContainer
}
// MapWithNamedSliceValues has a map with a named slice type for values. This
// tests that the generator treats these values like any other slice and not a
// struct.
type MapWithNamedSliceValues struct {
M map[string]NamedSlice
}
type NamedSlice []string

View File

@ -209,9 +209,31 @@ func (src *MapSlicePointerContainer) Clone() *MapSlicePointerContainer {
Routes map[string][]*SliceContainer
}{})
// Clone makes a deep copy of MapWithNamedSliceValues.
// The result aliases no memory with the original.
func (src *MapWithNamedSliceValues) Clone() *MapWithNamedSliceValues {
if src == nil {
return nil
}
dst := new(MapWithNamedSliceValues)
*dst = *src
if dst.M != nil {
dst.M = map[string]NamedSlice{}
for k := range src.M {
dst.M[k] = append([]string{}, src.M[k]...)
}
}
return dst
}
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _MapWithNamedSliceValuesCloneNeedsRegeneration = MapWithNamedSliceValues(struct {
M map[string]NamedSlice
}{})
// Clone duplicates src into dst and reports whether it succeeded.
// To succeed, <src, dst> must be of types <*T, *T> or <*T, **T>,
// where T is one of SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap,NamedMapContainer,MapSlicePointerContainer.
// where T is one of SliceContainer,InterfaceContainer,MapWithPointers,DeeplyNestedMap,NamedMapContainer,MapSlicePointerContainer,MapWithNamedSliceValues.
func Clone(dst, src any) bool {
switch src := src.(type) {
case *SliceContainer:
@ -268,6 +290,15 @@ func Clone(dst, src any) bool {
*dst = src.Clone()
return true
}
case *MapWithNamedSliceValues:
switch dst := dst.(type) {
case *MapWithNamedSliceValues:
*dst = *src.Clone()
return true
case **MapWithNamedSliceValues:
*dst = src.Clone()
return true
}
}
return false
}