1// Copyright 2020, The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package value
6
7import (
8	"reflect"
9	"strconv"
10)
11
12// TypeString is nearly identical to reflect.Type.String,
13// but has an additional option to specify that full type names be used.
14func TypeString(t reflect.Type, qualified bool) string {
15	return string(appendTypeName(nil, t, qualified, false))
16}
17
18func appendTypeName(b []byte, t reflect.Type, qualified, elideFunc bool) []byte {
19	// BUG: Go reflection provides no way to disambiguate two named types
20	// of the same name and within the same package,
21	// but declared within the namespace of different functions.
22
23	// Named type.
24	if t.Name() != "" {
25		if qualified && t.PkgPath() != "" {
26			b = append(b, '"')
27			b = append(b, t.PkgPath()...)
28			b = append(b, '"')
29			b = append(b, '.')
30			b = append(b, t.Name()...)
31		} else {
32			b = append(b, t.String()...)
33		}
34		return b
35	}
36
37	// Unnamed type.
38	switch k := t.Kind(); k {
39	case reflect.Bool, reflect.String, reflect.UnsafePointer,
40		reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
41		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
42		reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128:
43		b = append(b, k.String()...)
44	case reflect.Chan:
45		if t.ChanDir() == reflect.RecvDir {
46			b = append(b, "<-"...)
47		}
48		b = append(b, "chan"...)
49		if t.ChanDir() == reflect.SendDir {
50			b = append(b, "<-"...)
51		}
52		b = append(b, ' ')
53		b = appendTypeName(b, t.Elem(), qualified, false)
54	case reflect.Func:
55		if !elideFunc {
56			b = append(b, "func"...)
57		}
58		b = append(b, '(')
59		for i := 0; i < t.NumIn(); i++ {
60			if i > 0 {
61				b = append(b, ", "...)
62			}
63			if i == t.NumIn()-1 && t.IsVariadic() {
64				b = append(b, "..."...)
65				b = appendTypeName(b, t.In(i).Elem(), qualified, false)
66			} else {
67				b = appendTypeName(b, t.In(i), qualified, false)
68			}
69		}
70		b = append(b, ')')
71		switch t.NumOut() {
72		case 0:
73			// Do nothing
74		case 1:
75			b = append(b, ' ')
76			b = appendTypeName(b, t.Out(0), qualified, false)
77		default:
78			b = append(b, " ("...)
79			for i := 0; i < t.NumOut(); i++ {
80				if i > 0 {
81					b = append(b, ", "...)
82				}
83				b = appendTypeName(b, t.Out(i), qualified, false)
84			}
85			b = append(b, ')')
86		}
87	case reflect.Struct:
88		b = append(b, "struct{ "...)
89		for i := 0; i < t.NumField(); i++ {
90			if i > 0 {
91				b = append(b, "; "...)
92			}
93			sf := t.Field(i)
94			if !sf.Anonymous {
95				if qualified && sf.PkgPath != "" {
96					b = append(b, '"')
97					b = append(b, sf.PkgPath...)
98					b = append(b, '"')
99					b = append(b, '.')
100				}
101				b = append(b, sf.Name...)
102				b = append(b, ' ')
103			}
104			b = appendTypeName(b, sf.Type, qualified, false)
105			if sf.Tag != "" {
106				b = append(b, ' ')
107				b = strconv.AppendQuote(b, string(sf.Tag))
108			}
109		}
110		if b[len(b)-1] == ' ' {
111			b = b[:len(b)-1]
112		} else {
113			b = append(b, ' ')
114		}
115		b = append(b, '}')
116	case reflect.Slice, reflect.Array:
117		b = append(b, '[')
118		if k == reflect.Array {
119			b = strconv.AppendUint(b, uint64(t.Len()), 10)
120		}
121		b = append(b, ']')
122		b = appendTypeName(b, t.Elem(), qualified, false)
123	case reflect.Map:
124		b = append(b, "map["...)
125		b = appendTypeName(b, t.Key(), qualified, false)
126		b = append(b, ']')
127		b = appendTypeName(b, t.Elem(), qualified, false)
128	case reflect.Ptr:
129		b = append(b, '*')
130		b = appendTypeName(b, t.Elem(), qualified, false)
131	case reflect.Interface:
132		b = append(b, "interface{ "...)
133		for i := 0; i < t.NumMethod(); i++ {
134			if i > 0 {
135				b = append(b, "; "...)
136			}
137			m := t.Method(i)
138			if qualified && m.PkgPath != "" {
139				b = append(b, '"')
140				b = append(b, m.PkgPath...)
141				b = append(b, '"')
142				b = append(b, '.')
143			}
144			b = append(b, m.Name...)
145			b = appendTypeName(b, m.Type, qualified, true)
146		}
147		if b[len(b)-1] == ' ' {
148			b = b[:len(b)-1]
149		} else {
150			b = append(b, ' ')
151		}
152		b = append(b, '}')
153	default:
154		panic("invalid kind: " + k.String())
155	}
156	return b
157}
158