Catch panics when calling GoString like fmt %#v does (#87)

This handles a few cases (similar to how fmt %#v does):

- A GoString method on a value receiver, called with a nil pointer
- A GoString method on a pointer receiver that doesn't check for nil
- A GoString method that panics in some other way

Because Go 1.17 added a method Time.GoString with value receiver, this
broke structs that had *time.Time fields with nil values (which is
common!).

Also added a bunch of tests for these cases.

Fixes #77

Co-authored-by: Jordan Barrett <jordan.barrett@canonical.com>
diff --git a/.gitignore b/.gitignore
index 1f0a99f..b2d4781 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@
 _go*
 _test*
 _obj
+/.idea
diff --git a/formatter.go b/formatter.go
index 249f089..8e6969c 100644
--- a/formatter.go
+++ b/formatter.go
@@ -92,6 +92,24 @@
 	typ reflect.Type
 }
 
+func (p *printer) catchPanic(v reflect.Value, method string) {
+	if r := recover(); r != nil {
+		if v.Kind() == reflect.Ptr && v.IsNil() {
+			writeByte(p, '(')
+			io.WriteString(p, v.Type().String())
+			io.WriteString(p, ")(nil)")
+			return
+		}
+		writeByte(p, '(')
+		io.WriteString(p, v.Type().String())
+		io.WriteString(p, ")(PANIC=calling method ")
+		io.WriteString(p, strconv.Quote(method))
+		io.WriteString(p, ": ")
+		fmt.Fprint(p, r)
+		writeByte(p, ')')
+	}
+}
+
 func (p *printer) printValue(v reflect.Value, showType, quote bool) {
 	if p.depth > 10 {
 		io.WriteString(p, "!%v(DEPTH EXCEEDED)")
@@ -101,6 +119,7 @@
 	if v.IsValid() && v.CanInterface() {
 		i := v.Interface()
 		if goStringer, ok := i.(fmt.GoStringer); ok {
+			defer p.catchPanic(v, "GoString")
 			io.WriteString(p, goStringer.GoString())
 			return
 		}
diff --git a/formatter_test.go b/formatter_test.go
index f23c700..6e27b27 100644
--- a/formatter_test.go
+++ b/formatter_test.go
@@ -5,6 +5,7 @@
 	"io"
 	"strings"
 	"testing"
+	"time"
 	"unsafe"
 )
 
@@ -97,7 +98,7 @@
 		`[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`,
 	},
 	{F(5), "pretty.F(5)"},
-	{ NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
+	{NewStructWithPrivateFields("foo"), `NewStructWithPrivateFields("foo")`},
 	{
 		SA{&T{1, 2}, T{3, 4}},
 		`pretty.SA{
@@ -176,6 +177,41 @@
     },
 }`,
 	},
+	{(*time.Time)(nil), "(*time.Time)(nil)"},
+	{&ValueGoString{"vgs"}, `VGS vgs`},
+	{(*ValueGoString)(nil), `(*pretty.ValueGoString)(nil)`},
+	{(*VGSWrapper)(nil), `(*pretty.VGSWrapper)(nil)`},
+	{&PointerGoString{"pgs"}, `PGS pgs`},
+	{(*PointerGoString)(nil), "(*pretty.PointerGoString)(nil)"},
+	{&PanicGoString{"oops!"}, `(*pretty.PanicGoString)(PANIC=calling method "GoString": oops!)`},
+}
+
+type ValueGoString struct {
+	s string
+}
+
+func (g ValueGoString) GoString() string {
+	return "VGS " + g.s
+}
+
+type VGSWrapper struct {
+	ValueGoString
+}
+
+type PointerGoString struct {
+	s string
+}
+
+func (g *PointerGoString) GoString() string {
+	return "PGS " + g.s
+}
+
+type PanicGoString struct {
+	s string
+}
+
+func (g *PanicGoString) GoString() string {
+	panic(g.s)
 }
 
 func TestGoSyntax(t *testing.T) {