internal: fill struct with initialized values instead of nil values

The previous implementation filled map, slice, channel, signature, and
pointer types with nil values rather than initializing these types.

Change-Id: I558446af31fdd8877db5eb3cd92b4004f5d5ab22
Reviewed-on: https://go-review.googlesource.com/c/tools/+/239039
Run-TryBot: Josh Baum <joshbaum@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/analysisinternal/analysis.go b/internal/analysisinternal/analysis.go
index 2658681..14b96a7 100644
--- a/internal/analysisinternal/analysis.go
+++ b/internal/analysisinternal/analysis.go
@@ -48,7 +48,7 @@
 	case *types.Chan, *types.Interface, *types.Map, *types.Pointer, *types.Signature, *types.Slice:
 		return ast.NewIdent("nil")
 	case *types.Struct:
-		texpr := typeExpr(fset, f, pkg, typ) // typ because we want the name here.
+		texpr := TypeExpr(fset, f, pkg, typ) // typ because we want the name here.
 		if texpr == nil {
 			return nil
 		}
@@ -56,7 +56,7 @@
 			Type: texpr,
 		}
 	case *types.Array:
-		texpr := typeExpr(fset, f, pkg, u.Elem())
+		texpr := TypeExpr(fset, f, pkg, u.Elem())
 		if texpr == nil {
 			return nil
 		}
@@ -70,7 +70,7 @@
 	return nil
 }
 
-func typeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
+func TypeExpr(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
 	switch t := typ.(type) {
 	case *types.Basic:
 		switch t.Kind() {
@@ -101,6 +101,11 @@
 			X:   ast.NewIdent(pkgName),
 			Sel: ast.NewIdent(t.Obj().Name()),
 		}
+	case *types.Pointer:
+		return &ast.UnaryExpr{
+			Op: token.MUL,
+			X:  TypeExpr(fset, f, pkg, t.Elem()),
+		}
 	default:
 		return nil // TODO: anonymous structs, but who does that
 	}
diff --git a/internal/lsp/analysis/fillstruct/fillstruct.go b/internal/lsp/analysis/fillstruct/fillstruct.go
index 23e1662..8fb4585 100644
--- a/internal/lsp/analysis/fillstruct/fillstruct.go
+++ b/internal/lsp/analysis/fillstruct/fillstruct.go
@@ -111,7 +111,7 @@
 				continue
 			}
 
-			value := analysisinternal.ZeroValue(pass.Fset, file, pass.Pkg, field.Type())
+			value := populateValue(pass.Fset, file, pass.Pkg, field.Type())
 			if value == nil {
 				continue
 			}
@@ -144,9 +144,9 @@
 			return
 		}
 
-		msg := "Fill struct with default values"
+		msg := "Fill struct"
 		if name, ok := expr.Type.(*ast.Ident); ok {
-			msg = fmt.Sprintf("Fill %s with default values", name)
+			msg = fmt.Sprintf("Fill %s", name)
 		}
 
 		pass.Report(analysis.Diagnostic{
@@ -172,3 +172,136 @@
 	}
 	return tok.LineStart(line + 1)
 }
+
+// populateValue constructs an expression to fill the value of a struct field.
+//
+// When the type of a struct field is a basic literal or interface, we return
+// default values. For other types, such as maps, slices, and channels, we create
+// expressions rather than using default values.
+//
+// The reasoning here is that users will call fillstruct with the intention of
+// initializing the struct, in which case setting these fields to nil has no effect.
+func populateValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
+	under := typ
+	if n, ok := typ.(*types.Named); ok {
+		under = n.Underlying()
+	}
+	switch u := under.(type) {
+	case *types.Basic:
+		switch {
+		case u.Info()&types.IsNumeric != 0:
+			return &ast.BasicLit{Kind: token.INT, Value: "0"}
+		case u.Info()&types.IsBoolean != 0:
+			return &ast.Ident{Name: "false"}
+		case u.Info()&types.IsString != 0:
+			return &ast.BasicLit{Kind: token.STRING, Value: `""`}
+		default:
+			panic("unknown basic type")
+		}
+	case *types.Map:
+		k := analysisinternal.TypeExpr(fset, f, pkg, u.Key())
+		v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
+		if k == nil || v == nil {
+			return nil
+		}
+		return &ast.CompositeLit{
+			Type: &ast.MapType{
+				Key:   k,
+				Value: v,
+			},
+		}
+	case *types.Slice:
+		s := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
+		if s == nil {
+			return nil
+		}
+		return &ast.CompositeLit{
+			Type: &ast.ArrayType{
+				Elt: s,
+			},
+		}
+	case *types.Array:
+		a := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
+		if a == nil {
+			return nil
+		}
+		return &ast.CompositeLit{
+			Type: &ast.ArrayType{
+				Elt: a,
+				Len: &ast.BasicLit{
+					Kind: token.INT, Value: fmt.Sprintf("%v", u.Len())},
+			},
+		}
+	case *types.Chan:
+		v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
+		if v == nil {
+			return nil
+		}
+		dir := ast.ChanDir(u.Dir())
+		if u.Dir() == types.SendRecv {
+			dir = ast.SEND | ast.RECV
+		}
+		return &ast.CallExpr{
+			Fun: ast.NewIdent("make"),
+			Args: []ast.Expr{
+				&ast.ChanType{
+					Dir:   dir,
+					Value: v,
+				},
+			},
+		}
+	case *types.Struct:
+		s := analysisinternal.TypeExpr(fset, f, pkg, typ)
+		if s == nil {
+			return nil
+		}
+		return &ast.CompositeLit{
+			Type: s,
+		}
+	case *types.Signature:
+		var params []*ast.Field
+		for i := 0; i < u.Params().Len(); i++ {
+			p := analysisinternal.TypeExpr(fset, f, pkg, u.Params().At(i).Type())
+			if p == nil {
+				return nil
+			}
+			params = append(params, &ast.Field{
+				Type: p,
+				Names: []*ast.Ident{
+					{
+						Name: u.Params().At(i).Name(),
+					},
+				},
+			})
+		}
+		var returns []*ast.Field
+		for i := 0; i < u.Results().Len(); i++ {
+			r := analysisinternal.TypeExpr(fset, f, pkg, u.Results().At(i).Type())
+			if r == nil {
+				return nil
+			}
+			returns = append(returns, &ast.Field{
+				Type: r,
+			})
+		}
+		return &ast.FuncLit{
+			Type: &ast.FuncType{
+				Params: &ast.FieldList{
+					List: params,
+				},
+				Results: &ast.FieldList{
+					List: returns,
+				},
+			},
+			Body: &ast.BlockStmt{},
+		}
+	case *types.Pointer:
+		return &ast.UnaryExpr{
+			Op: token.AND,
+			X:  populateValue(fset, f, pkg, u.Elem()),
+		}
+	case *types.Interface:
+		return ast.NewIdent("nil")
+	}
+	return nil
+}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
index 447ed15..8358383 100644
--- a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
+++ b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
@@ -6,6 +6,8 @@
 
 import (
 	data "b"
+	"go/ast"
+	"go/token"
 )
 
 type emptyStruct struct{}
@@ -37,3 +39,53 @@
 var _ = nestedStruct{} // want ""
 
 var _ = data.B{} // want ""
+
+type typedStruct struct {
+	m  map[string]int
+	s  []int
+	c  chan int
+	c1 <-chan int
+	a  [2]string
+}
+
+var _ = typedStruct{} // want ""
+
+type funStruct struct {
+	fn func(i int) int
+}
+
+var _ = funStruct{} // want ""
+
+type funStructCompex struct {
+	fn func(i int, s string) (string, int)
+}
+
+var _ = funStructCompex{} // want ""
+
+type funStructEmpty struct {
+	fn func()
+}
+
+var _ = funStructEmpty{} // want ""
+
+type Foo struct {
+	A int
+}
+
+type Bar struct {
+	X *Foo
+	Y *Foo
+}
+
+var _ = Bar{} // want ""
+
+type importedStruct struct {
+	m  map[*ast.CompositeLit]ast.Field
+	s  []ast.BadExpr
+	a  [3]token.Token
+	c  chan ast.EmptyStmt
+	fn func(ast_decl ast.DeclStmt) ast.Ellipsis
+	st ast.CompositeLit
+}
+
+var _ = importedStruct{} // want ""
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
index 3fc564b..2f52bf5 100644
--- a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
+++ b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
@@ -6,6 +6,8 @@
 
 import (
 	data "b"
+	"go/ast"
+	"go/token"
 )
 
 type emptyStruct struct{}
@@ -47,3 +49,79 @@
 var _ = data.B{
 	ExportedInt: 0,
 } // want ""
+
+type typedStruct struct {
+	m  map[string]int
+	s  []int
+	c  chan int
+	c1 <-chan int
+	a  [2]string
+}
+
+var _ = typedStruct{
+	m:  map[string]int{},
+	s:  []int{},
+	c:  make(chan int),
+	c1: make(<-chan int),
+	a:  [2]string{},
+} // want ""
+
+type funStruct struct {
+	fn func(i int) int
+}
+
+var _ = funStruct{
+	fn: func(i int) int {
+	},
+} // want ""
+
+type funStructCompex struct {
+	fn func(i int, s string) (string, int)
+}
+
+var _ = funStructCompex{
+	fn: func(i int, s string) (string, int) {
+	},
+} // want ""
+
+type funStructEmpty struct {
+	fn func()
+}
+
+var _ = funStructEmpty{
+	fn: func() {
+	},
+} // want ""
+
+type Foo struct {
+	A int
+}
+
+type Bar struct {
+	X *Foo
+	Y *Foo
+}
+
+var _ = Bar{
+	X: &Foo{},
+	Y: &Foo{},
+} // want ""
+
+type importedStruct struct {
+	m  map[*ast.CompositeLit]ast.Field
+	s  []ast.BadExpr
+	a  [3]token.Token
+	c  chan ast.EmptyStmt
+	fn func(ast_decl ast.DeclStmt) ast.Ellipsis
+	st ast.CompositeLit
+}
+
+var _ = importedStruct{
+	m: map[*ast.CompositeLit]ast.Field{},
+	s: []ast.BadExpr{},
+	a: [3]token.Token{},
+	c: make(chan ast.EmptyStmt),
+	fn: func(ast_decl ast.DeclStmt) ast.Ellipsis {
+	},
+	st: ast.CompositeLit{},
+} // want ""
diff --git a/internal/lsp/testdata/lsp/primarymod/fillstruct/fill_struct.go.golden b/internal/lsp/testdata/lsp/primarymod/fillstruct/fill_struct.go.golden
index 162b4c4..7fb2752 100644
--- a/internal/lsp/testdata/lsp/primarymod/fillstruct/fill_struct.go.golden
+++ b/internal/lsp/testdata/lsp/primarymod/fillstruct/fill_struct.go.golden
@@ -21,8 +21,8 @@
 	a := StructA{
 	unexportedIntField: 0,
 	ExportedIntField:   0,
-	MapA:               nil,
-	Array:              nil,
+	MapA:               map[int]string{},
+	Array:              []int{},
 	StructB:            StructB{},
 }  //@suggestedfix("}", "refactor.rewrite")
 	b := StructA2{} //@suggestedfix("}", "refactor.rewrite")
@@ -51,7 +51,7 @@
 func fill() {
 	a := StructA{}  //@suggestedfix("}", "refactor.rewrite")
 	b := StructA2{
-	B: nil,
+	B: &StructB{},
 } //@suggestedfix("}", "refactor.rewrite")
 	c := StructA3{} //@suggestedfix("}", "refactor.rewrite")
 }