Skip to content

Commit a304b37

Browse files
h9jianggopherbot
authored andcommitted
internal/typesinternal: support union types in func TypeExpr
- Updates "typesinternal.TypeExpr" to support the representation of union types. Union types are now expressed as nested binary expressions connected by `|`, mirroring the structure used in "go/ast". The last term in the union corresponds to the topmost level of the binary expression. - Updates "typesinternal.ZeroValue" & "ZeroString" function returnning both the zero value and a boolean indicating whether the input type is valid or not. - Removes the possibility of func TypeExpr returning nil, eliminating the need for nil checks. - Updates "typesinternal.ZeroValue" to support alias type param. - Adds marker tests (type parameter, named type, alias type) for fillStruct. Fixes: - Fill struct code action returns &*new(T) when input type is pointer to type parameter. - TypeExpr return expression without type params when input type is instantiated type. - ZeroString and ZeroExpr now return new(T) for type parameter aliases. Previously, t.Underlying() on an alias returned the underlying interface, resulting in nil. Change-Id: Ib433d9ddca3dd329f950ca328455c4397d8c68bb Reviewed-on: https://go-review.googlesource.com/c/tools/+/631995 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Hongxiang Jiang <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 412b5e9 commit a304b37

File tree

15 files changed

+630
-245
lines changed

15 files changed

+630
-245
lines changed

go/ssa/const.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func zeroConst(t types.Type) *Const {
7878
func (c *Const) RelString(from *types.Package) string {
7979
var s string
8080
if c.Value == nil {
81-
s = typesinternal.ZeroString(c.typ, types.RelativeTo(from))
81+
s, _ = typesinternal.ZeroString(c.typ, types.RelativeTo(from))
8282
} else if c.Value.Kind() == constant.String {
8383
s = constant.StringVal(c.Value)
8484
const max = 20

go/ssa/const_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ func TestConstString(t *testing.T) {
5858
{"interface{string}", nil, `"":interface{string}`},
5959
{"interface{int|int64}", nil, "0:interface{int|int64}"},
6060
{"interface{bool}", nil, "false:interface{bool}"},
61-
{"interface{bool|int}", nil, "nil:interface{bool|int}"},
62-
{"interface{int|string}", nil, "nil:interface{int|string}"},
63-
{"interface{bool|string}", nil, "nil:interface{bool|string}"},
64-
{"interface{struct{x string}}", nil, "nil:interface{struct{x string}}"},
61+
{"interface{bool|int}", nil, "invalid:interface{bool|int}"},
62+
{"interface{int|string}", nil, "invalid:interface{int|string}"},
63+
{"interface{bool|string}", nil, "invalid:interface{bool|string}"},
64+
{"interface{struct{x string}}", nil, "invalid:interface{struct{x string}}"},
6565
{"interface{int|int64}", int64(1), "1:interface{int|int64}"},
6666
{"interface{~bool}", true, "true:interface{~bool}"},
6767
{"interface{Named}", "lorem ipsum", `"lorem ipsum":interface{P.Named}`},

gopls/internal/analysis/fillreturns/fillreturns.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ outer:
184184
// If no identifier matches the pattern, generate a zero value.
185185
if best := fuzzy.BestMatch(retTyp.String(), names); best != "" {
186186
fixed[i] = ast.NewIdent(best)
187-
} else if zero := typesinternal.ZeroExpr(file, pass.Pkg, retTyp); zero != nil {
187+
} else if zero, isValid := typesinternal.ZeroExpr(file, pass.Pkg, retTyp); isValid {
188188
fixed[i] = zero
189189
} else {
190190
return nil, nil

gopls/internal/analysis/fillstruct/fillstruct.go

Lines changed: 64 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ func SuggestedFix(fset *token.FileSet, start, end token.Pos, content []byte, fil
232232
// NOTE: We currently match on the name of the field key rather than the field type.
233233
if best := fuzzy.BestMatch(fieldName, names); best != "" {
234234
kv.Value = ast.NewIdent(best)
235-
} else if v := populateValue(file, pkg, fieldTyp); v != nil {
236-
kv.Value = v
235+
} else if expr, isValid := populateValue(file, pkg, fieldTyp); isValid {
236+
kv.Value = expr
237237
} else {
238238
return nil, nil, nil // no fix to suggest
239239
}
@@ -328,170 +328,109 @@ func indent(str, ind []byte) []byte {
328328
//
329329
// The reasoning here is that users will call fillstruct with the intention of
330330
// initializing the struct, in which case setting these fields to nil has no effect.
331-
//
332-
// populateValue returns nil if the value cannot be filled.
333-
func populateValue(f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
334-
switch u := typ.Underlying().(type) {
335-
case *types.Basic:
336-
switch {
337-
case u.Info()&types.IsNumeric != 0:
338-
return &ast.BasicLit{Kind: token.INT, Value: "0"}
339-
case u.Info()&types.IsBoolean != 0:
340-
return &ast.Ident{Name: "false"}
341-
case u.Info()&types.IsString != 0:
342-
return &ast.BasicLit{Kind: token.STRING, Value: `""`}
343-
case u.Kind() == types.UnsafePointer:
344-
return ast.NewIdent("nil")
345-
case u.Kind() == types.Invalid:
346-
return nil
331+
func populateValue(f *ast.File, pkg *types.Package, typ types.Type) (_ ast.Expr, isValid bool) {
332+
switch t := typ.(type) {
333+
case *types.TypeParam, *types.Interface, *types.Struct, *types.Basic:
334+
return typesinternal.ZeroExpr(f, pkg, t)
335+
336+
case *types.Alias, *types.Named:
337+
switch t.Underlying().(type) {
338+
// Avoid typesinternal.ZeroExpr here as we don't want to return nil.
339+
case *types.Map, *types.Slice:
340+
return &ast.CompositeLit{
341+
Type: typesinternal.TypeExpr(f, pkg, t),
342+
}, true
347343
default:
348-
panic(fmt.Sprintf("unknown basic type %v", u))
344+
return typesinternal.ZeroExpr(f, pkg, t)
349345
}
350346

351-
case *types.Map:
352-
k := typesinternal.TypeExpr(f, pkg, u.Key())
353-
v := typesinternal.TypeExpr(f, pkg, u.Elem())
354-
if k == nil || v == nil {
355-
return nil
356-
}
357-
return &ast.CompositeLit{
358-
Type: &ast.MapType{
359-
Key: k,
360-
Value: v,
361-
},
362-
}
363-
case *types.Slice:
364-
s := typesinternal.TypeExpr(f, pkg, u.Elem())
365-
if s == nil {
366-
return nil
367-
}
347+
// Avoid typesinternal.ZeroExpr here as we don't want to return nil.
348+
case *types.Map, *types.Slice:
368349
return &ast.CompositeLit{
369-
Type: &ast.ArrayType{
370-
Elt: s,
371-
},
372-
}
350+
Type: typesinternal.TypeExpr(f, pkg, t),
351+
}, true
373352

374353
case *types.Array:
375-
a := typesinternal.TypeExpr(f, pkg, u.Elem())
376-
if a == nil {
377-
return nil
378-
}
379354
return &ast.CompositeLit{
380355
Type: &ast.ArrayType{
381-
Elt: a,
356+
Elt: typesinternal.TypeExpr(f, pkg, t.Elem()),
382357
Len: &ast.BasicLit{
383-
Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
358+
Kind: token.INT, Value: fmt.Sprintf("%v", t.Len()),
384359
},
385360
},
386-
}
361+
}, true
387362

388363
case *types.Chan:
389-
v := typesinternal.TypeExpr(f, pkg, u.Elem())
390-
if v == nil {
391-
return nil
392-
}
393-
dir := ast.ChanDir(u.Dir())
394-
if u.Dir() == types.SendRecv {
364+
dir := ast.ChanDir(t.Dir())
365+
if t.Dir() == types.SendRecv {
395366
dir = ast.SEND | ast.RECV
396367
}
397368
return &ast.CallExpr{
398369
Fun: ast.NewIdent("make"),
399370
Args: []ast.Expr{
400371
&ast.ChanType{
401372
Dir: dir,
402-
Value: v,
373+
Value: typesinternal.TypeExpr(f, pkg, t.Elem()),
403374
},
404375
},
405-
}
406-
407-
case *types.Struct:
408-
s := typesinternal.TypeExpr(f, pkg, typ)
409-
if s == nil {
410-
return nil
411-
}
412-
return &ast.CompositeLit{
413-
Type: s,
414-
}
376+
}, true
415377

416378
case *types.Signature:
417-
var params []*ast.Field
418-
for i := 0; i < u.Params().Len(); i++ {
419-
p := typesinternal.TypeExpr(f, pkg, u.Params().At(i).Type())
420-
if p == nil {
421-
return nil
422-
}
423-
params = append(params, &ast.Field{
424-
Type: p,
425-
Names: []*ast.Ident{
426-
{
427-
Name: u.Params().At(i).Name(),
428-
},
429-
},
430-
})
431-
}
432-
var returns []*ast.Field
433-
for i := 0; i < u.Results().Len(); i++ {
434-
r := typesinternal.TypeExpr(f, pkg, u.Results().At(i).Type())
435-
if r == nil {
436-
return nil
437-
}
438-
returns = append(returns, &ast.Field{
439-
Type: r,
440-
})
441-
}
442379
return &ast.FuncLit{
443-
Type: &ast.FuncType{
444-
Params: &ast.FieldList{
445-
List: params,
446-
},
447-
Results: &ast.FieldList{
448-
List: returns,
380+
Type: typesinternal.TypeExpr(f, pkg, t).(*ast.FuncType),
381+
// The body of the function literal contains a panic statement to
382+
// avoid type errors.
383+
Body: &ast.BlockStmt{
384+
List: []ast.Stmt{
385+
&ast.ExprStmt{
386+
X: &ast.CallExpr{
387+
Fun: ast.NewIdent("panic"),
388+
Args: []ast.Expr{
389+
&ast.BasicLit{
390+
Kind: token.STRING,
391+
Value: `"TODO"`,
392+
},
393+
},
394+
},
395+
},
449396
},
450397
},
451-
Body: &ast.BlockStmt{},
452-
}
398+
}, true
453399

454400
case *types.Pointer:
455-
switch types.Unalias(u.Elem()).(type) {
401+
switch tt := types.Unalias(t.Elem()).(type) {
456402
case *types.Basic:
457403
return &ast.CallExpr{
458404
Fun: &ast.Ident{
459405
Name: "new",
460406
},
461407
Args: []ast.Expr{
462408
&ast.Ident{
463-
Name: u.Elem().String(),
409+
Name: t.Elem().String(),
464410
},
465411
},
466-
}
412+
}, true
413+
// Pointer to type parameter should return new(T) instead of &*new(T).
414+
case *types.TypeParam:
415+
return &ast.CallExpr{
416+
Fun: &ast.Ident{
417+
Name: "new",
418+
},
419+
Args: []ast.Expr{
420+
&ast.Ident{
421+
Name: tt.Obj().Name(),
422+
},
423+
},
424+
}, true
467425
default:
468-
x := populateValue(f, pkg, u.Elem())
469-
if x == nil {
470-
return nil
471-
}
426+
// TODO(hxjiang): & prefix only works if populateValue returns a
427+
// composite literal T{} or the expression new(T).
428+
expr, isValid := populateValue(f, pkg, t.Elem())
472429
return &ast.UnaryExpr{
473430
Op: token.AND,
474-
X: x,
475-
}
476-
}
477-
478-
case *types.Interface:
479-
if param, ok := types.Unalias(typ).(*types.TypeParam); ok {
480-
// *new(T) is the zero value of a type parameter T.
481-
// TODO(adonovan): one could give a more specific zero
482-
// value if the type has a core type that is, say,
483-
// always a number or a pointer. See go/ssa for details.
484-
return &ast.StarExpr{
485-
X: &ast.CallExpr{
486-
Fun: ast.NewIdent("new"),
487-
Args: []ast.Expr{
488-
ast.NewIdent(param.Obj().Name()),
489-
},
490-
},
491-
}
431+
X: expr,
432+
}, isValid
492433
}
493-
494-
return ast.NewIdent("nil")
495434
}
496-
return nil
435+
return nil, false
497436
}

gopls/internal/golang/addtest.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,7 +497,7 @@ func AddTestForFunc(ctx context.Context, snapshot *cache.Snapshot, loc protocol.
497497
if i == 0 && isContextType(typ) {
498498
f.Value = qf(types.NewPackage("context", "context")) + ".Background()"
499499
} else if name == "" || name == "_" {
500-
f.Value = typesinternal.ZeroString(typ, qf)
500+
f.Value, _ = typesinternal.ZeroString(typ, qf)
501501
} else {
502502
f.Name = name
503503
}
@@ -631,7 +631,7 @@ func AddTestForFunc(ctx context.Context, snapshot *cache.Snapshot, loc protocol.
631631
if i == 0 && isContextType(typ) {
632632
f.Value = qf(types.NewPackage("context", "context")) + ".Background()"
633633
} else if name == "" || name == "_" {
634-
f.Value = typesinternal.ZeroString(typ, qf)
634+
f.Value, _ = typesinternal.ZeroString(typ, qf)
635635
} else {
636636
f.Name = name
637637
}

gopls/internal/golang/completion/postfix_snippets.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,8 +442,8 @@ func (a *postfixTmplArgs) TypeName(t types.Type) (string, error) {
442442

443443
// Zero return the zero value representation of type t
444444
func (a *postfixTmplArgs) Zero(t types.Type) string {
445-
// TODO: use typesinternal.ZeroString, once it supports invalid types.
446-
return formatZeroValue(t, a.qf)
445+
zero, _ := typesinternal.ZeroString(t, a.qf)
446+
return zero
447447
}
448448

449449
func (a *postfixTmplArgs) IsIdent() bool {

gopls/internal/golang/completion/statements.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"golang.org/x/tools/gopls/internal/golang"
1616
"golang.org/x/tools/gopls/internal/golang/completion/snippet"
1717
"golang.org/x/tools/gopls/internal/protocol"
18+
"golang.org/x/tools/internal/typesinternal"
1819
)
1920

2021
// addStatementCandidates adds full statement completion candidates
@@ -294,7 +295,9 @@ func (c *completer) addErrCheck() {
294295
} else {
295296
snip.WriteText("return ")
296297
for i := 0; i < result.Len()-1; i++ {
297-
snip.WriteText(formatZeroValue(result.At(i).Type(), c.qf))
298+
if zero, isValid := typesinternal.ZeroString(result.At(i).Type(), c.qf); isValid {
299+
snip.WriteText(zero)
300+
}
298301
snip.WriteText(", ")
299302
}
300303
snip.WritePlaceholder(func(b *snippet.Builder) {
@@ -404,7 +407,10 @@ func (c *completer) addReturnZeroValues() {
404407
fmt.Fprintf(&label, ", ")
405408
}
406409

407-
zero := formatZeroValue(result.At(i).Type(), c.qf)
410+
zero, isValid := typesinternal.ZeroString(result.At(i).Type(), c.qf)
411+
if !isValid {
412+
zero = ""
413+
}
408414
snip.WritePlaceholder(func(b *snippet.Builder) {
409415
b.WriteText(zero)
410416
})

gopls/internal/golang/completion/util.go

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -277,31 +277,6 @@ func prevStmt(pos token.Pos, path []ast.Node) ast.Stmt {
277277
return nil
278278
}
279279

280-
// formatZeroValue produces Go code representing the zero value of T. It
281-
// returns the empty string if T is invalid.
282-
//
283-
// TODO(rfindley): use typesinternal.ZeroString once we can sort out how to
284-
// propagate invalid types (see golang/go#70744).
285-
func formatZeroValue(T types.Type, qf types.Qualifier) string {
286-
switch u := T.Underlying().(type) {
287-
case *types.Basic:
288-
switch {
289-
case u.Info()&types.IsNumeric > 0:
290-
return "0"
291-
case u.Info()&types.IsString > 0:
292-
return `""`
293-
case u.Info()&types.IsBoolean > 0:
294-
return "false"
295-
default:
296-
return ""
297-
}
298-
case *types.Pointer, *types.Interface, *types.Chan, *types.Map, *types.Slice, *types.Signature:
299-
return "nil"
300-
default:
301-
return types.TypeString(T, qf) + "{}"
302-
}
303-
}
304-
305280
// isBasicKind returns whether t is a basic type of kind k.
306281
func isBasicKind(t types.Type, k types.BasicInfo) bool {
307282
b, _ := t.Underlying().(*types.Basic)

0 commit comments

Comments
 (0)