slog: support go 1.19

To allow slog to work on Go 1.19, restore the 1.19-compatible code we
were using previously for string and slice headers.

slogtest never supported 1.19, but slog depends on it. So replace
errors.Join with a simple workaround.

Change-Id: I30ca95b9b023a839a8a2e566c3566418ebd07392
Reviewed-on: https://go-review.googlesource.com/c/exp/+/495975
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/slog/slogtest/slogtest.go b/slog/slogtest/slogtest.go
index e2f09e9..24b3513 100644
--- a/slog/slogtest/slogtest.go
+++ b/slog/slogtest/slogtest.go
@@ -2,7 +2,6 @@
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"reflect"
 	"runtime"
@@ -225,7 +224,7 @@
 			}
 		}
 	}
-	return errors.Join(errs...)
+	return errorsJoin(errs...)
 }
 
 type check func(map[string]any) string
diff --git a/slog/slogtest/slogtest_119.go b/slog/slogtest/slogtest_119.go
new file mode 100644
index 0000000..21cd8b9
--- /dev/null
+++ b/slog/slogtest/slogtest_119.go
@@ -0,0 +1,27 @@
+// Copyright 2023 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.
+
+//go:build go1.19 && !go1.20
+
+package slogtest
+
+import (
+	"errors"
+	"strings"
+)
+
+func errorsJoin(errs ...error) error {
+	var b strings.Builder
+	for _, err := range errs {
+		if err != nil {
+			b.WriteString(err.Error())
+			b.WriteByte('\n')
+		}
+	}
+	s := b.String()
+	if len(s) == 0 {
+		return nil
+	}
+	return errors.New(s)
+}
diff --git a/slog/slogtest/slogtest_120.go b/slog/slogtest/slogtest_120.go
new file mode 100644
index 0000000..dfb3e41
--- /dev/null
+++ b/slog/slogtest/slogtest_120.go
@@ -0,0 +1,11 @@
+// Copyright 2023 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.
+
+//go:build go1.20
+
+package slogtest
+
+import "errors"
+
+var errorsJoin = errors.Join
diff --git a/slog/value.go b/slog/value.go
index 0903a7c..df9b047 100644
--- a/slog/value.go
+++ b/slog/value.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-//go:build go1.20
-
 package slog
 
 import (
@@ -38,11 +36,6 @@
 	any any
 }
 
-type (
-	stringptr *byte // used in Value.any when the Value is a string
-	groupptr  *Attr // used in Value.any when the Value is a []Attr
-)
-
 // Kind is the kind of a Value.
 type Kind int
 
@@ -108,11 +101,6 @@
 
 //////////////// Constructors
 
-// StringValue returns a new Value for a string.
-func StringValue(value string) Value {
-	return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
-}
-
 // IntValue returns a Value for an int.
 func IntValue(v int) Value {
 	return Int64Value(int64(v))
@@ -164,12 +152,6 @@
 	return Value{num: uint64(v.Nanoseconds()), any: KindDuration}
 }
 
-// GroupValue returns a new Value for a list of Attrs.
-// The caller must not subsequently mutate the argument slice.
-func GroupValue(as ...Attr) Value {
-	return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
-}
-
 // AnyValue returns a Value for the supplied value.
 //
 // If the supplied value is of type Value, it is returned
@@ -265,21 +247,6 @@
 	}
 }
 
-// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
-// the methods Int64, Float64, and so on, which panic if v is of the
-// wrong kind, String never panics.
-func (v Value) String() string {
-	if sp, ok := v.any.(stringptr); ok {
-		return unsafe.String(sp, v.num)
-	}
-	var buf []byte
-	return string(v.append(buf))
-}
-
-func (v Value) str() string {
-	return unsafe.String(v.any.(stringptr), v.num)
-}
-
 // Int64 returns v's value as an int64. It panics
 // if v is not a signed integer.
 func (v Value) Int64() int64 {
diff --git a/slog/value_119.go b/slog/value_119.go
new file mode 100644
index 0000000..29b0d73
--- /dev/null
+++ b/slog/value_119.go
@@ -0,0 +1,53 @@
+// Copyright 2022 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.
+
+//go:build go1.19 && !go1.20
+
+package slog
+
+import (
+	"reflect"
+	"unsafe"
+)
+
+type (
+	stringptr unsafe.Pointer // used in Value.any when the Value is a string
+	groupptr  unsafe.Pointer // used in Value.any when the Value is a []Attr
+)
+
+// StringValue returns a new Value for a string.
+func StringValue(value string) Value {
+	hdr := (*reflect.StringHeader)(unsafe.Pointer(&value))
+	return Value{num: uint64(hdr.Len), any: stringptr(hdr.Data)}
+}
+
+func (v Value) str() string {
+	var s string
+	hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+	hdr.Data = uintptr(v.any.(stringptr))
+	hdr.Len = int(v.num)
+	return s
+}
+
+// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
+// the methods Int64, Float64, and so on, which panic if v is of the
+// wrong kind, String never panics.
+func (v Value) String() string {
+	if sp, ok := v.any.(stringptr); ok {
+		// Inlining this code makes a huge difference.
+		var s string
+		hdr := (*reflect.StringHeader)(unsafe.Pointer(&s))
+		hdr.Data = uintptr(sp)
+		hdr.Len = int(v.num)
+		return s
+	}
+	return string(v.append(nil))
+}
+
+// GroupValue returns a new Value for a list of Attrs.
+// The caller must not subsequently mutate the argument slice.
+func GroupValue(as ...Attr) Value {
+	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&as))
+	return Value{num: uint64(hdr.Len), any: groupptr(hdr.Data)}
+}
diff --git a/slog/value_120.go b/slog/value_120.go
new file mode 100644
index 0000000..f7d4c09
--- /dev/null
+++ b/slog/value_120.go
@@ -0,0 +1,39 @@
+// Copyright 2022 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.
+
+//go:build go1.20
+
+package slog
+
+import "unsafe"
+
+type (
+	stringptr *byte // used in Value.any when the Value is a string
+	groupptr  *Attr // used in Value.any when the Value is a []Attr
+)
+
+// StringValue returns a new Value for a string.
+func StringValue(value string) Value {
+	return Value{num: uint64(len(value)), any: stringptr(unsafe.StringData(value))}
+}
+
+// GroupValue returns a new Value for a list of Attrs.
+// The caller must not subsequently mutate the argument slice.
+func GroupValue(as ...Attr) Value {
+	return Value{num: uint64(len(as)), any: groupptr(unsafe.SliceData(as))}
+}
+
+// String returns Value's value as a string, formatted like fmt.Sprint. Unlike
+// the methods Int64, Float64, and so on, which panic if v is of the
+// wrong kind, String never panics.
+func (v Value) String() string {
+	if sp, ok := v.any.(stringptr); ok {
+		return unsafe.String(sp, v.num)
+	}
+	return string(v.append(nil))
+}
+
+func (v Value) str() string {
+	return unsafe.String(v.any.(stringptr), v.num)
+}