Skip to content

Commit 59b0eab

Browse files
committed
slog: remove restrictions on types stored in Value
Previously, Go values of type Kind and *time.Location had special meaning in the Value.any field, so we could not allow Values to hold Go values of those types. Define unexported types for those exported ones and wrap values in them. This allows a Value to hold any Go value. Change-Id: Iaab01ebad5d16e9f3f7df808b077e28f06539b97 Reviewed-on: https://go-review.googlesource.com/c/exp/+/442360 Reviewed-by: Alan Donovan <adonovan@google.com> Run-TryBot: Jonathan Amsterdam <jba@google.com>
1 parent df71a2c commit 59b0eab

File tree

4 files changed

+50
-19
lines changed

4 files changed

+50
-19
lines changed

slog/value.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919
// Kind is the kind of a Value.
2020
type Kind int
2121

22+
// Unexported version of Kind, just so we can store Kinds in Values.
23+
// (No user-provided value has this type.)
24+
type kind Kind
25+
2226
// The following list is sorted alphabetically, but it's also important that
2327
// AnyKind is 0 so that a zero Value represents nil.
2428

@@ -86,10 +90,14 @@ func BoolValue(v bool) Value {
8690
return Value{num: u, any: BoolKind}
8791
}
8892

93+
// Unexported version of *time.Location, just so we can store *time.Locations in
94+
// Values. (No user-provided value has this type.)
95+
type timeLocation *time.Location
96+
8997
// TimeValue returns a Value for a time.Time.
9098
// It discards the monotonic portion.
9199
func TimeValue(v time.Time) Value {
92-
return Value{num: uint64(v.UnixNano()), any: v.Location()}
100+
return Value{num: uint64(v.UnixNano()), any: timeLocation(v.Location())}
93101
}
94102

95103
// DurationValue returns a Value for a time.Duration.
@@ -152,9 +160,7 @@ func AnyValue(v any) Value {
152160
case []Attr:
153161
return GroupValue(v...)
154162
case Kind:
155-
panic("cannot store a slog.Kind in a slog.Value")
156-
case *time.Location:
157-
panic("cannot store a *time.Location in a slog.Value")
163+
return Value{any: kind(v)}
158164
default:
159165
return Value{any: v}
160166
}
@@ -166,6 +172,9 @@ func AnyValue(v any) Value {
166172
func (v Value) Any() any {
167173
switch v.Kind() {
168174
case AnyKind, GroupKind, LogValuerKind:
175+
if k, ok := v.any.(kind); ok {
176+
return Kind(k)
177+
}
169178
return v.any
170179
case Int64Kind:
171180
return int64(v.num)
@@ -255,7 +264,7 @@ func (v Value) Time() time.Time {
255264
}
256265

257266
func (v Value) time() time.Time {
258-
return time.Unix(0, int64(v.num)).In(v.any.(*time.Location))
267+
return time.Unix(0, int64(v.num)).In(v.any.(timeLocation))
259268
}
260269

261270
// LogValuer returns the Value's value as a LogValuer. It panics

slog/value_safe.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@
66

77
package slog
88

9-
import "time"
10-
119
// This file defines the most portable representation of Value.
1210

1311
// A Value can represent (almost) any Go value, but unlike type any,
@@ -33,12 +31,14 @@ func (v Value) Kind() Kind {
3331
switch k := v.any.(type) {
3432
case Kind:
3533
return k
36-
case *time.Location:
34+
case timeLocation:
3735
return TimeKind
3836
case []Attr:
3937
return GroupKind
4038
case LogValuer:
4139
return LogValuerKind
40+
case kind: // a kind is just a wrapper for a Kind
41+
return AnyKind
4242
default:
4343
return AnyKind
4444
}

slog/value_test.go

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func TestValueEqual(t *testing.T) {
2121
Float64Value(3.7),
2222
BoolValue(true),
2323
BoolValue(false),
24+
TimeValue(testTime),
2425
AnyValue(&x),
2526
AnyValue(&y),
2627
GroupValue(Bool("b", true), Int("i", 3)),
@@ -62,25 +63,27 @@ func TestValueString(t *testing.T) {
6263
{Float64Value(.15), "0.15"},
6364
{BoolValue(true), "true"},
6465
{StringValue("foo"), "foo"},
66+
{TimeValue(testTime), "2000-01-02 03:04:05 +0000 UTC"},
6567
{AnyValue(time.Duration(3 * time.Second)), "3s"},
6668
} {
6769
if got := test.v.String(); got != test.want {
68-
t.Errorf("%#v: got %q, want %q", test.v, got, test.want)
70+
t.Errorf("%#v:\ngot %q\nwant %q", test.v, got, test.want)
6971
}
7072
}
7173
}
7274

7375
func TestValueNoAlloc(t *testing.T) {
7476
// Assign values just to make sure the compiler doesn't optimize away the statements.
7577
var (
76-
i int64
77-
u uint64
78-
f float64
79-
b bool
80-
s string
81-
x any
82-
p = &i
83-
d time.Duration
78+
i int64
79+
u uint64
80+
f float64
81+
b bool
82+
s string
83+
x any
84+
p = &i
85+
d time.Duration
86+
tm time.Time
8487
)
8588
a := int(testing.AllocsPerRun(5, func() {
8689
i = Int64Value(1).Int64()
@@ -89,6 +92,7 @@ func TestValueNoAlloc(t *testing.T) {
8992
b = BoolValue(true).Bool()
9093
s = StringValue("foo").String()
9194
d = DurationValue(d).Duration()
95+
tm = TimeValue(testTime).Time()
9296
x = AnyValue(p).Any()
9397
}))
9498
if a != 0 {
@@ -99,6 +103,7 @@ func TestValueNoAlloc(t *testing.T) {
99103
_ = b
100104
_ = s
101105
_ = x
106+
_ = tm
102107
}
103108

104109
func TestAnyLevelAlloc(t *testing.T) {
@@ -119,6 +124,22 @@ func TestAnyLevel(t *testing.T) {
119124
}
120125
}
121126

127+
func TestSpecialValueTypes(t *testing.T) {
128+
t.Run("time.Location", func(t *testing.T) {
129+
want := time.UTC
130+
got := AnyValue(want).Any()
131+
if got != want {
132+
t.Errorf("got %v, want %v", got, want)
133+
}
134+
})
135+
t.Run("Kind", func(t *testing.T) {
136+
want := BoolKind
137+
got := AnyValue(want).Any()
138+
if got != want {
139+
t.Errorf("got %v, want %v", got, want)
140+
}
141+
})
142+
}
122143
func TestLogValue(t *testing.T) {
123144
want := "replaced"
124145
r := &replace{StringValue(want)}

slog/value_unsafe.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ package slog
1010

1111
import (
1212
"reflect"
13-
"time"
1413
"unsafe"
1514
)
1615

@@ -45,12 +44,14 @@ func (v Value) Kind() Kind {
4544
return x
4645
case stringptr:
4746
return StringKind
48-
case *time.Location:
47+
case timeLocation:
4948
return TimeKind
5049
case groupptr:
5150
return GroupKind
5251
case LogValuer:
5352
return LogValuerKind
53+
case kind: // a kind is just a wrapper for a Kind
54+
return AnyKind
5455
default:
5556
return AnyKind
5657
}

0 commit comments

Comments
 (0)