1// Copyright 2011 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 main
6
7import (
8	"fmt"
9	"go/ast"
10	"go/parser"
11	"go/token"
12	"os"
13	"os/exec"
14	"path/filepath"
15	"reflect"
16	"runtime"
17	"strings"
18)
19
20// Partial type checker.
21//
22// The fact that it is partial is very important: the input is
23// an AST and a description of some type information to
24// assume about one or more packages, but not all the
25// packages that the program imports. The checker is
26// expected to do as much as it can with what it has been
27// given. There is not enough information supplied to do
28// a full type check, but the type checker is expected to
29// apply information that can be derived from variable
30// declarations, function and method returns, and type switches
31// as far as it can, so that the caller can still tell the types
32// of expression relevant to a particular fix.
33//
34// TODO(rsc,gri): Replace with go/typechecker.
35// Doing that could be an interesting test case for go/typechecker:
36// the constraints about working with partial information will
37// likely exercise it in interesting ways. The ideal interface would
38// be to pass typecheck a map from importpath to package API text
39// (Go source code), but for now we use data structures (TypeConfig, Type).
40//
41// The strings mostly use gofmt form.
42//
43// A Field or FieldList has as its type a comma-separated list
44// of the types of the fields. For example, the field list
45//	x, y, z int
46// has type "int, int, int".
47
48// The prefix "type " is the type of a type.
49// For example, given
50//	var x int
51//	type T int
52// x's type is "int" but T's type is "type int".
53// mkType inserts the "type " prefix.
54// getType removes it.
55// isType tests for it.
56
57func mkType(t string) string {
58	return "type " + t
59}
60
61func getType(t string) string {
62	if !isType(t) {
63		return ""
64	}
65	return t[len("type "):]
66}
67
68func isType(t string) bool {
69	return strings.HasPrefix(t, "type ")
70}
71
72// TypeConfig describes the universe of relevant types.
73// For ease of creation, the types are all referred to by string
74// name (e.g., "reflect.Value").  TypeByName is the only place
75// where the strings are resolved.
76
77type TypeConfig struct {
78	Type map[string]*Type
79	Var  map[string]string
80	Func map[string]string
81
82	// External maps from a name to its type.
83	// It provides additional typings not present in the Go source itself.
84	// For now, the only additional typings are those generated by cgo.
85	External map[string]string
86}
87
88// typeof returns the type of the given name, which may be of
89// the form "x" or "p.X".
90func (cfg *TypeConfig) typeof(name string) string {
91	if cfg.Var != nil {
92		if t := cfg.Var[name]; t != "" {
93			return t
94		}
95	}
96	if cfg.Func != nil {
97		if t := cfg.Func[name]; t != "" {
98			return "func()" + t
99		}
100	}
101	return ""
102}
103
104// Type describes the Fields and Methods of a type.
105// If the field or method cannot be found there, it is next
106// looked for in the Embed list.
107type Type struct {
108	Field  map[string]string // map field name to type
109	Method map[string]string // map method name to comma-separated return types (should start with "func ")
110	Embed  []string          // list of types this type embeds (for extra methods)
111	Def    string            // definition of named type
112}
113
114// dot returns the type of "typ.name", making its decision
115// using the type information in cfg.
116func (typ *Type) dot(cfg *TypeConfig, name string) string {
117	if typ.Field != nil {
118		if t := typ.Field[name]; t != "" {
119			return t
120		}
121	}
122	if typ.Method != nil {
123		if t := typ.Method[name]; t != "" {
124			return t
125		}
126	}
127
128	for _, e := range typ.Embed {
129		etyp := cfg.Type[e]
130		if etyp != nil {
131			if t := etyp.dot(cfg, name); t != "" {
132				return t
133			}
134		}
135	}
136
137	return ""
138}
139
140// typecheck type checks the AST f assuming the information in cfg.
141// It returns two maps with type information:
142// typeof maps AST nodes to type information in gofmt string form.
143// assign maps type strings to lists of expressions that were assigned
144// to values of another type that were assigned to that type.
145func typecheck(cfg *TypeConfig, f *ast.File) (typeof map[any]string, assign map[string][]any) {
146	typeof = make(map[any]string)
147	assign = make(map[string][]any)
148	cfg1 := &TypeConfig{}
149	*cfg1 = *cfg // make copy so we can add locally
150	copied := false
151
152	// If we import "C", add types of cgo objects.
153	cfg.External = map[string]string{}
154	cfg1.External = cfg.External
155	if imports(f, "C") {
156		// Run cgo on gofmtFile(f)
157		// Parse, extract decls from _cgo_gotypes.go
158		// Map _Ctype_* types to C.* types.
159		err := func() error {
160			txt, err := gofmtFile(f)
161			if err != nil {
162				return err
163			}
164			dir, err := os.MkdirTemp(os.TempDir(), "fix_cgo_typecheck")
165			if err != nil {
166				return err
167			}
168			defer os.RemoveAll(dir)
169			err = os.WriteFile(filepath.Join(dir, "in.go"), txt, 0600)
170			if err != nil {
171				return err
172			}
173			goCmd := "go"
174			if goroot := runtime.GOROOT(); goroot != "" {
175				goCmd = filepath.Join(goroot, "bin", "go")
176			}
177			cmd := exec.Command(goCmd, "tool", "cgo", "-objdir", dir, "-srcdir", dir, "in.go")
178			if reportCgoError != nil {
179				// Since cgo command errors will be reported, also forward the error
180				// output from the command for debugging.
181				cmd.Stderr = os.Stderr
182			}
183			err = cmd.Run()
184			if err != nil {
185				return err
186			}
187			out, err := os.ReadFile(filepath.Join(dir, "_cgo_gotypes.go"))
188			if err != nil {
189				return err
190			}
191			cgo, err := parser.ParseFile(token.NewFileSet(), "cgo.go", out, 0)
192			if err != nil {
193				return err
194			}
195			for _, decl := range cgo.Decls {
196				fn, ok := decl.(*ast.FuncDecl)
197				if !ok {
198					continue
199				}
200				if strings.HasPrefix(fn.Name.Name, "_Cfunc_") {
201					var params, results []string
202					for _, p := range fn.Type.Params.List {
203						t := gofmt(p.Type)
204						t = strings.ReplaceAll(t, "_Ctype_", "C.")
205						params = append(params, t)
206					}
207					for _, r := range fn.Type.Results.List {
208						t := gofmt(r.Type)
209						t = strings.ReplaceAll(t, "_Ctype_", "C.")
210						results = append(results, t)
211					}
212					cfg.External["C."+fn.Name.Name[7:]] = joinFunc(params, results)
213				}
214			}
215			return nil
216		}()
217		if err != nil {
218			if reportCgoError == nil {
219				fmt.Fprintf(os.Stderr, "go fix: warning: no cgo types: %s\n", err)
220			} else {
221				reportCgoError(err)
222			}
223		}
224	}
225
226	// gather function declarations
227	for _, decl := range f.Decls {
228		fn, ok := decl.(*ast.FuncDecl)
229		if !ok {
230			continue
231		}
232		typecheck1(cfg, fn.Type, typeof, assign)
233		t := typeof[fn.Type]
234		if fn.Recv != nil {
235			// The receiver must be a type.
236			rcvr := typeof[fn.Recv]
237			if !isType(rcvr) {
238				if len(fn.Recv.List) != 1 {
239					continue
240				}
241				rcvr = mkType(gofmt(fn.Recv.List[0].Type))
242				typeof[fn.Recv.List[0].Type] = rcvr
243			}
244			rcvr = getType(rcvr)
245			if rcvr != "" && rcvr[0] == '*' {
246				rcvr = rcvr[1:]
247			}
248			typeof[rcvr+"."+fn.Name.Name] = t
249		} else {
250			if isType(t) {
251				t = getType(t)
252			} else {
253				t = gofmt(fn.Type)
254			}
255			typeof[fn.Name] = t
256
257			// Record typeof[fn.Name.Obj] for future references to fn.Name.
258			typeof[fn.Name.Obj] = t
259		}
260	}
261
262	// gather struct declarations
263	for _, decl := range f.Decls {
264		d, ok := decl.(*ast.GenDecl)
265		if ok {
266			for _, s := range d.Specs {
267				switch s := s.(type) {
268				case *ast.TypeSpec:
269					if cfg1.Type[s.Name.Name] != nil {
270						break
271					}
272					if !copied {
273						copied = true
274						// Copy map lazily: it's time.
275						cfg1.Type = make(map[string]*Type)
276						for k, v := range cfg.Type {
277							cfg1.Type[k] = v
278						}
279					}
280					t := &Type{Field: map[string]string{}}
281					cfg1.Type[s.Name.Name] = t
282					switch st := s.Type.(type) {
283					case *ast.StructType:
284						for _, f := range st.Fields.List {
285							for _, n := range f.Names {
286								t.Field[n.Name] = gofmt(f.Type)
287							}
288						}
289					case *ast.ArrayType, *ast.StarExpr, *ast.MapType:
290						t.Def = gofmt(st)
291					}
292				}
293			}
294		}
295	}
296
297	typecheck1(cfg1, f, typeof, assign)
298	return typeof, assign
299}
300
301// reportCgoError, if non-nil, reports a non-nil error from running the "cgo"
302// tool. (Set to a non-nil hook during testing if cgo is expected to work.)
303var reportCgoError func(err error)
304
305func makeExprList(a []*ast.Ident) []ast.Expr {
306	var b []ast.Expr
307	for _, x := range a {
308		b = append(b, x)
309	}
310	return b
311}
312
313// typecheck1 is the recursive form of typecheck.
314// It is like typecheck but adds to the information in typeof
315// instead of allocating a new map.
316func typecheck1(cfg *TypeConfig, f any, typeof map[any]string, assign map[string][]any) {
317	// set sets the type of n to typ.
318	// If isDecl is true, n is being declared.
319	set := func(n ast.Expr, typ string, isDecl bool) {
320		if typeof[n] != "" || typ == "" {
321			if typeof[n] != typ {
322				assign[typ] = append(assign[typ], n)
323			}
324			return
325		}
326		typeof[n] = typ
327
328		// If we obtained typ from the declaration of x
329		// propagate the type to all the uses.
330		// The !isDecl case is a cheat here, but it makes
331		// up in some cases for not paying attention to
332		// struct fields. The real type checker will be
333		// more accurate so we won't need the cheat.
334		if id, ok := n.(*ast.Ident); ok && id.Obj != nil && (isDecl || typeof[id.Obj] == "") {
335			typeof[id.Obj] = typ
336		}
337	}
338
339	// Type-check an assignment lhs = rhs.
340	// If isDecl is true, this is := so we can update
341	// the types of the objects that lhs refers to.
342	typecheckAssign := func(lhs, rhs []ast.Expr, isDecl bool) {
343		if len(lhs) > 1 && len(rhs) == 1 {
344			if _, ok := rhs[0].(*ast.CallExpr); ok {
345				t := split(typeof[rhs[0]])
346				// Lists should have same length but may not; pair what can be paired.
347				for i := 0; i < len(lhs) && i < len(t); i++ {
348					set(lhs[i], t[i], isDecl)
349				}
350				return
351			}
352		}
353		if len(lhs) == 1 && len(rhs) == 2 {
354			// x = y, ok
355			rhs = rhs[:1]
356		} else if len(lhs) == 2 && len(rhs) == 1 {
357			// x, ok = y
358			lhs = lhs[:1]
359		}
360
361		// Match as much as we can.
362		for i := 0; i < len(lhs) && i < len(rhs); i++ {
363			x, y := lhs[i], rhs[i]
364			if typeof[y] != "" {
365				set(x, typeof[y], isDecl)
366			} else {
367				set(y, typeof[x], false)
368			}
369		}
370	}
371
372	expand := func(s string) string {
373		typ := cfg.Type[s]
374		if typ != nil && typ.Def != "" {
375			return typ.Def
376		}
377		return s
378	}
379
380	// The main type check is a recursive algorithm implemented
381	// by walkBeforeAfter(n, before, after).
382	// Most of it is bottom-up, but in a few places we need
383	// to know the type of the function we are checking.
384	// The before function records that information on
385	// the curfn stack.
386	var curfn []*ast.FuncType
387
388	before := func(n any) {
389		// push function type on stack
390		switch n := n.(type) {
391		case *ast.FuncDecl:
392			curfn = append(curfn, n.Type)
393		case *ast.FuncLit:
394			curfn = append(curfn, n.Type)
395		}
396	}
397
398	// After is the real type checker.
399	after := func(n any) {
400		if n == nil {
401			return
402		}
403		if false && reflect.TypeOf(n).Kind() == reflect.Pointer { // debugging trace
404			defer func() {
405				if t := typeof[n]; t != "" {
406					pos := fset.Position(n.(ast.Node).Pos())
407					fmt.Fprintf(os.Stderr, "%s: typeof[%s] = %s\n", pos, gofmt(n), t)
408				}
409			}()
410		}
411
412		switch n := n.(type) {
413		case *ast.FuncDecl, *ast.FuncLit:
414			// pop function type off stack
415			curfn = curfn[:len(curfn)-1]
416
417		case *ast.FuncType:
418			typeof[n] = mkType(joinFunc(split(typeof[n.Params]), split(typeof[n.Results])))
419
420		case *ast.FieldList:
421			// Field list is concatenation of sub-lists.
422			t := ""
423			for _, field := range n.List {
424				if t != "" {
425					t += ", "
426				}
427				t += typeof[field]
428			}
429			typeof[n] = t
430
431		case *ast.Field:
432			// Field is one instance of the type per name.
433			all := ""
434			t := typeof[n.Type]
435			if !isType(t) {
436				// Create a type, because it is typically *T or *p.T
437				// and we might care about that type.
438				t = mkType(gofmt(n.Type))
439				typeof[n.Type] = t
440			}
441			t = getType(t)
442			if len(n.Names) == 0 {
443				all = t
444			} else {
445				for _, id := range n.Names {
446					if all != "" {
447						all += ", "
448					}
449					all += t
450					typeof[id.Obj] = t
451					typeof[id] = t
452				}
453			}
454			typeof[n] = all
455
456		case *ast.ValueSpec:
457			// var declaration. Use type if present.
458			if n.Type != nil {
459				t := typeof[n.Type]
460				if !isType(t) {
461					t = mkType(gofmt(n.Type))
462					typeof[n.Type] = t
463				}
464				t = getType(t)
465				for _, id := range n.Names {
466					set(id, t, true)
467				}
468			}
469			// Now treat same as assignment.
470			typecheckAssign(makeExprList(n.Names), n.Values, true)
471
472		case *ast.AssignStmt:
473			typecheckAssign(n.Lhs, n.Rhs, n.Tok == token.DEFINE)
474
475		case *ast.Ident:
476			// Identifier can take its type from underlying object.
477			if t := typeof[n.Obj]; t != "" {
478				typeof[n] = t
479			}
480
481		case *ast.SelectorExpr:
482			// Field or method.
483			name := n.Sel.Name
484			if t := typeof[n.X]; t != "" {
485				t = strings.TrimPrefix(t, "*") // implicit *
486				if typ := cfg.Type[t]; typ != nil {
487					if t := typ.dot(cfg, name); t != "" {
488						typeof[n] = t
489						return
490					}
491				}
492				tt := typeof[t+"."+name]
493				if isType(tt) {
494					typeof[n] = getType(tt)
495					return
496				}
497			}
498			// Package selector.
499			if x, ok := n.X.(*ast.Ident); ok && x.Obj == nil {
500				str := x.Name + "." + name
501				if cfg.Type[str] != nil {
502					typeof[n] = mkType(str)
503					return
504				}
505				if t := cfg.typeof(x.Name + "." + name); t != "" {
506					typeof[n] = t
507					return
508				}
509			}
510
511		case *ast.CallExpr:
512			// make(T) has type T.
513			if isTopName(n.Fun, "make") && len(n.Args) >= 1 {
514				typeof[n] = gofmt(n.Args[0])
515				return
516			}
517			// new(T) has type *T
518			if isTopName(n.Fun, "new") && len(n.Args) == 1 {
519				typeof[n] = "*" + gofmt(n.Args[0])
520				return
521			}
522			// Otherwise, use type of function to determine arguments.
523			t := typeof[n.Fun]
524			if t == "" {
525				t = cfg.External[gofmt(n.Fun)]
526			}
527			in, out := splitFunc(t)
528			if in == nil && out == nil {
529				return
530			}
531			typeof[n] = join(out)
532			for i, arg := range n.Args {
533				if i >= len(in) {
534					break
535				}
536				if typeof[arg] == "" {
537					typeof[arg] = in[i]
538				}
539			}
540
541		case *ast.TypeAssertExpr:
542			// x.(type) has type of x.
543			if n.Type == nil {
544				typeof[n] = typeof[n.X]
545				return
546			}
547			// x.(T) has type T.
548			if t := typeof[n.Type]; isType(t) {
549				typeof[n] = getType(t)
550			} else {
551				typeof[n] = gofmt(n.Type)
552			}
553
554		case *ast.SliceExpr:
555			// x[i:j] has type of x.
556			typeof[n] = typeof[n.X]
557
558		case *ast.IndexExpr:
559			// x[i] has key type of x's type.
560			t := expand(typeof[n.X])
561			if strings.HasPrefix(t, "[") || strings.HasPrefix(t, "map[") {
562				// Lazy: assume there are no nested [] in the array
563				// length or map key type.
564				if _, elem, ok := strings.Cut(t, "]"); ok {
565					typeof[n] = elem
566				}
567			}
568
569		case *ast.StarExpr:
570			// *x for x of type *T has type T when x is an expr.
571			// We don't use the result when *x is a type, but
572			// compute it anyway.
573			t := expand(typeof[n.X])
574			if isType(t) {
575				typeof[n] = "type *" + getType(t)
576			} else if strings.HasPrefix(t, "*") {
577				typeof[n] = t[len("*"):]
578			}
579
580		case *ast.UnaryExpr:
581			// &x for x of type T has type *T.
582			t := typeof[n.X]
583			if t != "" && n.Op == token.AND {
584				typeof[n] = "*" + t
585			}
586
587		case *ast.CompositeLit:
588			// T{...} has type T.
589			typeof[n] = gofmt(n.Type)
590
591			// Propagate types down to values used in the composite literal.
592			t := expand(typeof[n])
593			if strings.HasPrefix(t, "[") { // array or slice
594				// Lazy: assume there are no nested [] in the array length.
595				if _, et, ok := strings.Cut(t, "]"); ok {
596					for _, e := range n.Elts {
597						if kv, ok := e.(*ast.KeyValueExpr); ok {
598							e = kv.Value
599						}
600						if typeof[e] == "" {
601							typeof[e] = et
602						}
603					}
604				}
605			}
606			if strings.HasPrefix(t, "map[") { // map
607				// Lazy: assume there are no nested [] in the map key type.
608				if kt, vt, ok := strings.Cut(t[len("map["):], "]"); ok {
609					for _, e := range n.Elts {
610						if kv, ok := e.(*ast.KeyValueExpr); ok {
611							if typeof[kv.Key] == "" {
612								typeof[kv.Key] = kt
613							}
614							if typeof[kv.Value] == "" {
615								typeof[kv.Value] = vt
616							}
617						}
618					}
619				}
620			}
621			if typ := cfg.Type[t]; typ != nil && len(typ.Field) > 0 { // struct
622				for _, e := range n.Elts {
623					if kv, ok := e.(*ast.KeyValueExpr); ok {
624						if ft := typ.Field[fmt.Sprintf("%s", kv.Key)]; ft != "" {
625							if typeof[kv.Value] == "" {
626								typeof[kv.Value] = ft
627							}
628						}
629					}
630				}
631			}
632
633		case *ast.ParenExpr:
634			// (x) has type of x.
635			typeof[n] = typeof[n.X]
636
637		case *ast.RangeStmt:
638			t := expand(typeof[n.X])
639			if t == "" {
640				return
641			}
642			var key, value string
643			if t == "string" {
644				key, value = "int", "rune"
645			} else if strings.HasPrefix(t, "[") {
646				key = "int"
647				_, value, _ = strings.Cut(t, "]")
648			} else if strings.HasPrefix(t, "map[") {
649				if k, v, ok := strings.Cut(t[len("map["):], "]"); ok {
650					key, value = k, v
651				}
652			}
653			changed := false
654			if n.Key != nil && key != "" {
655				changed = true
656				set(n.Key, key, n.Tok == token.DEFINE)
657			}
658			if n.Value != nil && value != "" {
659				changed = true
660				set(n.Value, value, n.Tok == token.DEFINE)
661			}
662			// Ugly failure of vision: already type-checked body.
663			// Do it again now that we have that type info.
664			if changed {
665				typecheck1(cfg, n.Body, typeof, assign)
666			}
667
668		case *ast.TypeSwitchStmt:
669			// Type of variable changes for each case in type switch,
670			// but go/parser generates just one variable.
671			// Repeat type check for each case with more precise
672			// type information.
673			as, ok := n.Assign.(*ast.AssignStmt)
674			if !ok {
675				return
676			}
677			varx, ok := as.Lhs[0].(*ast.Ident)
678			if !ok {
679				return
680			}
681			t := typeof[varx]
682			for _, cas := range n.Body.List {
683				cas := cas.(*ast.CaseClause)
684				if len(cas.List) == 1 {
685					// Variable has specific type only when there is
686					// exactly one type in the case list.
687					if tt := typeof[cas.List[0]]; isType(tt) {
688						tt = getType(tt)
689						typeof[varx] = tt
690						typeof[varx.Obj] = tt
691						typecheck1(cfg, cas.Body, typeof, assign)
692					}
693				}
694			}
695			// Restore t.
696			typeof[varx] = t
697			typeof[varx.Obj] = t
698
699		case *ast.ReturnStmt:
700			if len(curfn) == 0 {
701				// Probably can't happen.
702				return
703			}
704			f := curfn[len(curfn)-1]
705			res := n.Results
706			if f.Results != nil {
707				t := split(typeof[f.Results])
708				for i := 0; i < len(res) && i < len(t); i++ {
709					set(res[i], t[i], false)
710				}
711			}
712
713		case *ast.BinaryExpr:
714			// Propagate types across binary ops that require two args of the same type.
715			switch n.Op {
716			case token.EQL, token.NEQ: // TODO: more cases. This is enough for the cftype fix.
717				if typeof[n.X] != "" && typeof[n.Y] == "" {
718					typeof[n.Y] = typeof[n.X]
719				}
720				if typeof[n.X] == "" && typeof[n.Y] != "" {
721					typeof[n.X] = typeof[n.Y]
722				}
723			}
724		}
725	}
726	walkBeforeAfter(f, before, after)
727}
728
729// Convert between function type strings and lists of types.
730// Using strings makes this a little harder, but it makes
731// a lot of the rest of the code easier. This will all go away
732// when we can use go/typechecker directly.
733
734// splitFunc splits "func(x,y,z) (a,b,c)" into ["x", "y", "z"] and ["a", "b", "c"].
735func splitFunc(s string) (in, out []string) {
736	if !strings.HasPrefix(s, "func(") {
737		return nil, nil
738	}
739
740	i := len("func(") // index of beginning of 'in' arguments
741	nparen := 0
742	for j := i; j < len(s); j++ {
743		switch s[j] {
744		case '(':
745			nparen++
746		case ')':
747			nparen--
748			if nparen < 0 {
749				// found end of parameter list
750				out := strings.TrimSpace(s[j+1:])
751				if len(out) >= 2 && out[0] == '(' && out[len(out)-1] == ')' {
752					out = out[1 : len(out)-1]
753				}
754				return split(s[i:j]), split(out)
755			}
756		}
757	}
758	return nil, nil
759}
760
761// joinFunc is the inverse of splitFunc.
762func joinFunc(in, out []string) string {
763	outs := ""
764	if len(out) == 1 {
765		outs = " " + out[0]
766	} else if len(out) > 1 {
767		outs = " (" + join(out) + ")"
768	}
769	return "func(" + join(in) + ")" + outs
770}
771
772// split splits "int, float" into ["int", "float"] and splits "" into [].
773func split(s string) []string {
774	out := []string{}
775	i := 0 // current type being scanned is s[i:j].
776	nparen := 0
777	for j := 0; j < len(s); j++ {
778		switch s[j] {
779		case ' ':
780			if i == j {
781				i++
782			}
783		case '(':
784			nparen++
785		case ')':
786			nparen--
787			if nparen < 0 {
788				// probably can't happen
789				return nil
790			}
791		case ',':
792			if nparen == 0 {
793				if i < j {
794					out = append(out, s[i:j])
795				}
796				i = j + 1
797			}
798		}
799	}
800	if nparen != 0 {
801		// probably can't happen
802		return nil
803	}
804	if i < len(s) {
805		out = append(out, s[i:])
806	}
807	return out
808}
809
810// join is the inverse of split.
811func join(x []string) string {
812	return strings.Join(x, ", ")
813}
814