1
0
mirror of https://github.com/golang/go synced 2024-11-23 14:50:07 -07:00

go/types: record types for union subexpressions

Prior to unions, unary and binary expressions always had a recorded
type. Preserve this by recording a type for all unary and binary
expressions encountered while parsing a union type.

Updates #50093

Change-Id: I5ba20f37854760596350d91ea325dc98e67e115a
Reviewed-on: https://go-review.googlesource.com/c/go/+/371757
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Robert Findley 2021-12-14 12:48:31 -05:00
parent af05064f97
commit 8108444eaa
3 changed files with 51 additions and 23 deletions

View File

@ -197,12 +197,6 @@ type Info struct {
// identifier z in a variable declaration 'var z int' is found
// only in the Defs map, and identifiers denoting packages in
// qualified identifiers are collected in the Uses map.
//
// For binary expressions representing unions in constraint
// position or type elements in interfaces, a union type is
// recorded for the top-level expression only. For instance,
// given the constraint a|b|c, the union type for (a|b)|c
// is recorded, but not the union type for a|b.
Types map[ast.Expr]TypeAndValue
// Instances maps identifiers denoting parameterized types or functions to

View File

@ -379,10 +379,23 @@ func TestTypesInfo(t *testing.T) {
{genericPkg + `u1a; func _[_ interface{~int}]() {}`, `~int`, `~int`},
{genericPkg + `u2a; func _[_ interface{int|string}]() {}`, `int | string`, `int|string`},
{genericPkg + `u3a; func _[_ interface{int|string|~bool}]() {}`, `int | string | ~bool`, `int|string|~bool`},
{genericPkg + `u3b; func _[_ interface{int|string|~bool}]() {}`, `int | string`, `int|string`},
{genericPkg + `u3b; func _[_ interface{int|string|~bool}]() {}`, `~bool`, `~bool`},
{genericPkg + `u3b; func _[_ interface{int|string|~float64|~bool}]() {}`, `int | string | ~float64`, `int|string|~float64`},
{genericPkg + `u0b; func _[_ int]() {}`, `int`, `int`},
{genericPkg + `u1b; func _[_ ~int]() {}`, `~int`, `~int`},
{genericPkg + `u2b; func _[_ int|string]() {}`, `int | string`, `int|string`},
{genericPkg + `u3b; func _[_ int|string|~bool]() {}`, `int | string | ~bool`, `int|string|~bool`},
{genericPkg + `u3b; func _[_ int|string|~bool]() {}`, `int | string`, `int|string`},
{genericPkg + `u3b; func _[_ int|string|~bool]() {}`, `~bool`, `~bool`},
{genericPkg + `u3b; func _[_ int|string|~float64|~bool]() {}`, `int | string | ~float64`, `int|string|~float64`},
{genericPkg + `u0b; type _ interface{int}`, `int`, `int`},
{genericPkg + `u1b; type _ interface{~int}`, `~int`, `~int`},
{genericPkg + `u2b; type _ interface{int|string}`, `int | string`, `int|string`},
{genericPkg + `u3b; type _ interface{int|string|~bool}`, `int | string | ~bool`, `int|string|~bool`},
{genericPkg + `u3b; type _ interface{int|string|~bool}`, `int | string`, `int|string`},
{genericPkg + `u3b; type _ interface{int|string|~bool}`, `~bool`, `~bool`},
{genericPkg + `u3b; type _ interface{int|string|~float64|~bool}`, `int | string | ~float64`, `int|string|~float64`},
}
for _, test := range tests {

View File

@ -51,23 +51,37 @@ const maxTermCount = 100
// parseUnion parses uexpr as a union of expressions.
// The result is a Union type, or Typ[Invalid] for some errors.
func parseUnion(check *Checker, uexpr ast.Expr) Type {
tlist := flattenUnion(nil, uexpr)
blist, tlist := flattenUnion(nil, uexpr)
assert(len(blist) == len(tlist)-1)
var terms []*Term
for _, x := range tlist {
tilde, typ := parseTilde(check, x)
if len(tlist) == 1 && !tilde {
var u Type
for i, x := range tlist {
term := parseTilde(check, x)
if len(tlist) == 1 && !term.tilde {
// Single type. Ok to return early because all relevant
// checks have been performed in parseTilde (no need to
// run through term validity check below).
return typ // typ already recorded through check.typ in parseTilde
return term.typ // typ already recorded through check.typ in parseTilde
}
if len(terms) >= maxTermCount {
check.errorf(x, _InvalidUnion, "cannot handle more than %d union terms (implementation limitation)", maxTermCount)
check.recordTypeAndValue(uexpr, typexpr, Typ[Invalid], nil)
return Typ[Invalid]
if u != Typ[Invalid] {
check.errorf(x, _InvalidUnion, "cannot handle more than %d union terms (implementation limitation)", maxTermCount)
u = Typ[Invalid]
}
} else {
terms = append(terms, term)
u = &Union{terms}
}
terms = append(terms, NewTerm(tilde, typ))
if i > 0 {
check.recordTypeAndValue(blist[i-1], typexpr, u, nil)
}
}
if u == Typ[Invalid] {
return u
}
// Check validity of terms.
@ -109,17 +123,17 @@ func parseUnion(check *Checker, uexpr ast.Expr) Type {
}
})
u := &Union{terms}
check.recordTypeAndValue(uexpr, typexpr, u, nil)
return u
}
func parseTilde(check *Checker, x ast.Expr) (tilde bool, typ Type) {
func parseTilde(check *Checker, tx ast.Expr) *Term {
x := tx
var tilde bool
if op, _ := x.(*ast.UnaryExpr); op != nil && op.Op == token.TILDE {
x = op.X
tilde = true
}
typ = check.typ(x)
typ := check.typ(x)
// Embedding stand-alone type parameters is not permitted (issue #47127).
// We don't need this restriction anymore if we make the underlying type of a type
// parameter its constraint interface: if we embed a lone type parameter, we will
@ -129,7 +143,11 @@ func parseTilde(check *Checker, x ast.Expr) (tilde bool, typ Type) {
check.error(x, _MisplacedTypeParam, "cannot embed a type parameter")
typ = Typ[Invalid]
}
return
term := NewTerm(tilde, typ)
if tilde {
check.recordTypeAndValue(tx, typexpr, &Union{[]*Term{term}}, nil)
}
return term
}
// overlappingTerm reports the index of the term x in terms which is
@ -150,10 +168,13 @@ func overlappingTerm(terms []*Term, y *Term) int {
return -1
}
func flattenUnion(list []ast.Expr, x ast.Expr) []ast.Expr {
// flattenUnion walks a union type expression of the form A | B | C | ...,
// extracting both the binary exprs (blist) and leaf types (tlist).
func flattenUnion(list []ast.Expr, x ast.Expr) (blist, tlist []ast.Expr) {
if o, _ := x.(*ast.BinaryExpr); o != nil && o.Op == token.OR {
list = flattenUnion(list, o.X)
blist, tlist = flattenUnion(list, o.X)
blist = append(blist, o)
x = o.Y
}
return append(list, x)
return blist, append(tlist, x)
}