diff --git a/duration_slice.go b/duration_slice.go index ee987274..ea7e2b08 100644 --- a/duration_slice.go +++ b/duration_slice.go @@ -19,6 +19,13 @@ func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSli } func (s *durationSliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []time.Duration{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]time.Duration, len(ss)) for i, d := range ss { diff --git a/float32_slice.go b/float32_slice.go index caa35274..763b3809 100644 --- a/float32_slice.go +++ b/float32_slice.go @@ -20,6 +20,13 @@ func newFloat32SliceValue(val []float32, p *[]float32) *float32SliceValue { } func (s *float32SliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []float32{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]float32, len(ss)) for i, d := range ss { diff --git a/float64_slice.go b/float64_slice.go index 85bf3073..ba7fc75c 100644 --- a/float64_slice.go +++ b/float64_slice.go @@ -20,6 +20,13 @@ func newFloat64SliceValue(val []float64, p *[]float64) *float64SliceValue { } func (s *float64SliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []float64{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]float64, len(ss)) for i, d := range ss { diff --git a/int32_slice.go b/int32_slice.go index ff128ff0..3ff7eb45 100644 --- a/int32_slice.go +++ b/int32_slice.go @@ -20,6 +20,13 @@ func newInt32SliceValue(val []int32, p *[]int32) *int32SliceValue { } func (s *int32SliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []int32{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]int32, len(ss)) for i, d := range ss { diff --git a/int64_slice.go b/int64_slice.go index 25464638..a4aa96a6 100644 --- a/int64_slice.go +++ b/int64_slice.go @@ -20,6 +20,13 @@ func newInt64SliceValue(val []int64, p *[]int64) *int64SliceValue { } func (s *int64SliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []int64{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]int64, len(ss)) for i, d := range ss { diff --git a/int_slice.go b/int_slice.go index e71c39d9..bd1fab58 100644 --- a/int_slice.go +++ b/int_slice.go @@ -20,6 +20,13 @@ func newIntSliceValue(val []int, p *[]int) *intSliceValue { } func (s *intSliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []int{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]int, len(ss)) for i, d := range ss { diff --git a/slice_empty_test.go b/slice_empty_test.go new file mode 100644 index 00000000..e63e0615 --- /dev/null +++ b/slice_empty_test.go @@ -0,0 +1,78 @@ +// Copyright 2026 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package pflag + +import ( + "net" + "reflect" + "testing" + "time" +) + +func TestSliceFlagsAcceptExplicitEmptyValue(t *testing.T) { + _, defaultNetwork, err := net.ParseCIDR("192.0.2.0/24") + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + add func(*FlagSet) interface{} + }{ + {"bool", func(f *FlagSet) interface{} { return f.BoolSlice("values", []bool{true}, "") }}, + {"duration", func(f *FlagSet) interface{} { return f.DurationSlice("values", []time.Duration{time.Second}, "") }}, + {"float32", func(f *FlagSet) interface{} { return f.Float32Slice("values", []float32{1}, "") }}, + {"float64", func(f *FlagSet) interface{} { return f.Float64Slice("values", []float64{1}, "") }}, + {"int", func(f *FlagSet) interface{} { return f.IntSlice("values", []int{1}, "") }}, + {"int32", func(f *FlagSet) interface{} { return f.Int32Slice("values", []int32{1}, "") }}, + {"int64", func(f *FlagSet) interface{} { return f.Int64Slice("values", []int64{1}, "") }}, + {"ip", func(f *FlagSet) interface{} { return f.IPSlice("values", []net.IP{net.ParseIP("192.0.2.1")}, "") }}, + {"ipnet", func(f *FlagSet) interface{} { + return f.IPNetSlice("values", []net.IPNet{*defaultNetwork}, "") + }}, + {"string", func(f *FlagSet) interface{} { return f.StringSlice("values", []string{"value"}, "") }}, + {"uint", func(f *FlagSet) interface{} { return f.UintSlice("values", []uint{1}, "") }}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + value := reflect.ValueOf(test.add(f)).Elem() + if err := f.Parse([]string{"--values="}); err != nil { + t.Fatalf("expected no error; got %v", err) + } + if !f.Changed("values") { + t.Fatal("expected --values= to mark the flag as changed") + } + if value.IsNil() || value.Len() != 0 { + t.Fatalf("got %v, want a non-nil empty slice", value.Interface()) + } + }) + } +} + +func TestSliceDefaultPreservedWithoutFlag(t *testing.T) { + tests := []struct { + name string + defaultValue []int + }{ + {"nil", nil}, + {"empty", []int{}}, + {"populated", []int{1, 2}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + f := NewFlagSet("test", ContinueOnError) + value := f.IntSlice("values", test.defaultValue, "") + if err := f.Parse(nil); err != nil { + t.Fatalf("expected no error; got %v", err) + } + if !reflect.DeepEqual(*value, test.defaultValue) { + t.Fatalf("got %v, want preserved default %v", *value, test.defaultValue) + } + }) + } +} diff --git a/uint_slice.go b/uint_slice.go index d38e124e..9f2ad2ad 100644 --- a/uint_slice.go +++ b/uint_slice.go @@ -20,6 +20,13 @@ func newUintSliceValue(val []uint, p *[]uint) *uintSliceValue { } func (s *uintSliceValue) Set(val string) error { + if val == "" { + if !s.changed { + *s.value = []uint{} + } + s.changed = true + return nil + } ss := strings.Split(val, ",") out := make([]uint, len(ss)) for i, d := range ss {